diff --git a/.github/img/ss1.png b/.github/img/ss1.png
deleted file mode 100644
index f60e74b..0000000
Binary files a/.github/img/ss1.png and /dev/null differ
diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml
index 13d346c..a797b14 100644
--- a/.github/workflows/linter.yaml
+++ b/.github/workflows/linter.yaml
@@ -15,4 +15,4 @@ jobs:
- name: Lint mta resources
run: |
echo 'ignore = {"512", "122","142","111","341","112","113","143","321","631","211","212","213","611","612","621"}' > config.lua
- luacheck --config config.lua ./
+ luacheck --exclude-files '**/*async.lua' --config config.lua ./
diff --git a/README.md b/README.md
index 847ea01..8345d3f 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,37 @@
-# `Newmodels v5 Azul 💙`
+# `Newmodels v6 Red 🍒`
This MTA resource makes use of the clientside allocated models ([engineRequestModel](https://wiki.multitheftauto.com/wiki/EngineRequestModel) and related features) to add new peds (skins), vehicles and objects:
-- place your mods (dff/txd/col files) in designated folders that are automatically detected
+- there are 3 methods to add new models:
+ - [1] place mod files (dff/txd/col files) in designated folders that are automatically scanned
+ - [2] place mod files anywhere you want and list them in the mod list lua script
+ - [3] dynamically load new models from external resources using exported functions
- the system syncs all added models with all players
-- use a simple trick in your existing scripts to work with the new model IDs
+- use a simple trick in your existing scripts to work with the new model IDs, or use the provided exported functions
- it's minimalistic, optimized and robust
-With this resource you can make scripts or change your existing scripts to add new skin, vehicle and object IDs to your server! For example, you may add all SA-MP object models!
+With this resource you can make scripts or change your existing scripts to add new skin, vehicle and object models to your server! For example, you may add all SA-MP object models!
## Docs/Tutorial
-🚀 **Are you new to this resource?** Start here: [Documentation file](/.github/doc/DOCUMENTATION.md)
+🚀 **Are you new to this resource?**
+
+Start here: [DOCUMENTATION.md file](/newmodels_red/DOCUMENTATION.md)
## Download
-⚠️ **Before you download and install this resource**, ensure you meet the requirements specified in the [Documentation](/.github/doc/DOCUMENTATION.md).
+⚠️ **Before you download and install this resource**, ensure you meet the requirements specified in the **Documentation** linked above.
Get the [Latest Version](https://github.com/Fernando-A-Rocha/mta-add-models/releases/latest) from the **Releases** section.
-## Older versions
-
-It is easy to migrate to v5 from newmodels v4 (not from v3; the architecture of that version is no longer used). The **models folder structure remains the same**, but the scripts have changed in the way models are applied to elements.
+## Migrating from Older versions
-This resource no longer uses and relies on the **MTA Element Data system** (`setElementData`) to sync the models to all clients! Instead, newmodels makes use of Lua tables and MTA events. This major change was made to **improve performance** and control the sync of models more efficiently.
+Please refer to the **Migration Guide** in the [MIGRATION.md file](/newmodels_red/MIGRATION.md).
## Community
Visit the [Thread on the MTA Forum](https://forum.mtasa.com/topic/133212-rel-add-new-models-library/) to get in touch with fellow users and developers.
-## Media
-
-
-
## Final Note
Feel free to update the documentation in this repository, and contribute to the code via pull requests.
diff --git a/[examples]/sampobj_red/README.md b/[examples]/sampobj_red/README.md
new file mode 100644
index 0000000..4d1cb73
--- /dev/null
+++ b/[examples]/sampobj_red/README.md
@@ -0,0 +1,20 @@
+# `sampobj_red`: SA-MP Objects for Newmodels v6
+
+With [`newmodels_red`](https://github.com/Fernando-A-Rocha/mta-add-models) (and `sampobj_red` resource) you can add all of [SA-MP's object models](https://dev.prineside.com/en/gtasa_samp_model_id/tag/2-sa-mp/) to your server, so you can use them to create custom maps, or spawn objects using Lua scripts.
+
+## Large Size ⚠️
+
+There are over 1,400 SA-MP object models! It is recommended to keep `download="false"` in the resource's `meta.xml` as well as `metaDownloadFalse=true` in `server.lua` to prevent clients from downloading all the models when they join your server.
+
+## How to install
+
+1. [Download](https://www.mediafire.com/file/mgqrk0rq7jrgsuc/models.zip/file) `models.zip` containing all dff/txd/col files required (total of 4,297 files; 404 MB when extracted)
+2. Extract the contents of the zip to [sampobj_red/models/]([examples]/sampobj_red/models/) folder.
+
+You're done! Newmodels will load the new models automatically when you start the `sampobj_red` resource. Their IDs are exactly the same as the IDs used in SA-MP.
+
+You may test spawning a SA-MP object using the newmodels test commands. E.g. `11686` ([is a bar counter](https://dev.prineside.com/en/gtasa_samp_model_id/model/11686-CBarSection1/)).
+
+## Media
+
+
diff --git a/[examples]/sampobj_red/meta.xml b/[examples]/sampobj_red/meta.xml
new file mode 100644
index 0000000..e0a3ed0
--- /dev/null
+++ b/[examples]/sampobj_red/meta.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/newmodels_azul/models/object/1337/SAMP/.gitignore b/[examples]/sampobj_red/models/.gitignore
similarity index 100%
rename from newmodels_azul/models/object/1337/SAMP/.gitignore
rename to [examples]/sampobj_red/models/.gitignore
index f075e1b..c22e549 100644
--- a/newmodels_azul/models/object/1337/SAMP/.gitignore
+++ b/[examples]/sampobj_red/models/.gitignore
@@ -1,3 +1,3 @@
-*.txd
*.dff
+*.txd
*.col
\ No newline at end of file
diff --git a/[examples]/sampobj_red/models/download models.zip link in documentation.txt b/[examples]/sampobj_red/models/download models.zip link in documentation.txt
new file mode 100644
index 0000000..3dc90ca
--- /dev/null
+++ b/[examples]/sampobj_red/models/download models.zip link in documentation.txt
@@ -0,0 +1 @@
+Download link in README.md, extract all dff,txd,col files here
\ No newline at end of file
diff --git a/[examples]/sampobj_red/sampobj.png b/[examples]/sampobj_red/sampobj.png
new file mode 100644
index 0000000..a4b93ea
Binary files /dev/null and b/[examples]/sampobj_red/sampobj.png differ
diff --git a/[examples]/sampobj_red/server.lua b/[examples]/sampobj_red/server.lua
new file mode 100644
index 0000000..0d06d90
--- /dev/null
+++ b/[examples]/sampobj_red/server.lua
@@ -0,0 +1,1467 @@
+--[[
+ Author: https://github.com/Fernando-A-Rocha
+
+ SA-MP Objects [Red] for Newmodels v6
+
+ Adds all SA-MP objects to the game using the newmodels system
+]]
+
+local SAMP_FILES = { -- ID => {dff and txd file names}
+ [18643] = { "LaserPointer1.dff", "LaserPointer1.txd" },
+ [18675] = { "coke_puff.dff", "MatTextures.txd" },
+ [18707] = { "prt_boatsplash.dff", "MatTextures.txd" },
+ [18739] = { "water_fountain.dff", "MatTextures.txd" },
+ [18771] = { "SpiralStair1.dff", "MatStairs.txd" },
+ [18803] = { "MBridge150m1.dff", "cs_ebridge.txd" },
+ [18835] = { "RBFunnel.dff", "MickyTextures.txd" },
+ [18867] = { "MobilePhone3.dff", "MobilePhone3.txd" },
+ [18899] = { "Bandana9.dff", "MatClothes.txd" },
+ [18931] = { "Hat6.dff", "MatClothes.txd" },
+ [18963] = { "CJElvisHead.dff", "MatClothes.txd" },
+ [18995] = { "Tube5m1.dff", "MatTextures.txd" },
+ [19027] = { "GlassesType22.dff", "MatGlasses.txd" },
+ [19059] = { "XmasOrb1.dff", "XmasOrbs.txd" },
+ [19091] = { "PomPomRed.dff", "PomPoms.txd" },
+ [19123] = { "BollardLight3.dff", "metal.txd" },
+ [19155] = { "PinSpotLight13.dff", "PinSpotLights.txd" },
+ [19187] = { "MapMarkerNew11.dff", "MapMarkers.txd" },
+ [19219] = { "MapMarker19.dff", "MapMarkers.txd" },
+ [19251] = { "MapMarker51.dff", "MapMarkers.txd" },
+ [19283] = { "PointLight3.dff", "MatLights.txd" },
+ [19315] = { "deer01.dff", "deer01.txd" },
+ [19347] = { "badge01.dff", "police_things.txd" },
+ [19379] = { "wall027.dff", "all_walls.txd" },
+ [19411] = { "wall059.dff", "all_walls.txd" },
+ [19443] = { "wall083.dff", "all_walls.txd" },
+ [19475] = { "Plane001.dff", "signsurf.txd" },
+ [19507] = { "lshouse2.dff", "ganghouse1_lax.txd" },
+ [19539] = { "Edge62_5x62_5Grass1.dff", "beach_sfs.txd" },
+ [19571] = { "PizzaBox1.dff", "cj_ss_2.txd" },
+ [19603] = { "WaterPlane1.dff", "ballyswater.txd" },
+ [19635] = { "Ramp360Degree3.dff", "landjump.txd" },
+ [19667] = { "TubeHalfLoop2a.dff", "MatTubes.txd" },
+ [19699] = { "MTubeHalf25m1.dff", "MatTubes.txd" },
+ [19731] = { "TubeHalfMtoSJoin1a.dff", "MatTubes.txd" },
+ [19763] = { "STubeHalf5Bend2b.dff", "MatTubes.txd" },
+ [19795] = { "LSPrisonGateEast.dff", "civic04_lan.txd" },
+ [19827] = { "LightSwitch2.dff", "LightSwitches.txd" },
+ [19859] = { "MIHouse1Door3.dff", "vegashse3.txd" },
+ [19891] = { "WSTubeJoiner1.dff", "WSSections.txd" },
+ [19923] = { "MKIslandCooker1.dff", "cswzkitch.txd" },
+ [19955] = { "SAMPRoadSign8.dff", "SAMPRoadSigns.txd" },
+ [19987] = { "SAMPRoadSign40.dff", "SAMPRoadSigns.txd" },
+ [18644] = { "Screwdriver1.dff", "MatTextures.txd" },
+ [18676] = { "coke_trail.dff", "MatTextures.txd" },
+ [18708] = { "prt_bubble.dff", "MatTextures.txd" },
+ [18740] = { "water_hydrant.dff", "MatTextures.txd" },
+ [18772] = { "TunnelSection1.dff", "TunnelSections.txd" },
+ [18804] = { "MBridge150m2.dff", "cs_ebridge.txd" },
+ [18836] = { "RBHalfpipe.dff", "MickyTextures.txd" },
+ [18868] = { "MobilePhone4.dff", "MobilePhone4.txd" },
+ [18900] = { "Bandana10.dff", "MatClothes.txd" },
+ [18932] = { "Hat7.dff", "MatClothes.txd" },
+ [18964] = { "SkullyCap1.dff", "MatClothes.txd" },
+ [18996] = { "Tube5m45Bend1.dff", "MatTextures.txd" },
+ [19028] = { "GlassesType23.dff", "MatGlasses.txd" },
+ [19060] = { "XmasOrb2.dff", "XmasOrbs.txd" },
+ [19092] = { "PomPomGreen.dff", "PomPoms.txd" },
+ [19124] = { "BollardLight4.dff", "metal.txd" },
+ [19156] = { "PinSpotLight14.dff", "PinSpotLights.txd" },
+ [19188] = { "MapMarkerNew12.dff", "MapMarkers.txd" },
+ [19220] = { "MapMarker20.dff", "MapMarkers.txd" },
+ [19252] = { "MapMarker52.dff", "MapMarkers.txd" },
+ [19284] = { "PointLight4.dff", "MatLights.txd" },
+ [19316] = { "FerrisCageBit01.dff", "FerrisWheel.txd" },
+ [19348] = { "cane01.dff", "classy.txd" },
+ [19380] = { "wall028.dff", "all_walls.txd" },
+ [19412] = { "wall060.dff", "all_walls.txd" },
+ [19444] = { "wall084.dff", "all_walls.txd" },
+ [19476] = { "Plane002.dff", "signsurf.txd" },
+ [19508] = { "lshouse2int.dff", "all_walls.txd" },
+ [19540] = { "Edge62_5x62_5Grass2.dff", "beach_sfs.txd" },
+ [19572] = { "PisshBox1.dff", "cj_ss_1.txd" },
+ [19604] = { "WaterPlane2.dff", "ballyswater.txd" },
+ [19636] = { "RedApplesCrate1.dff", "FruitCrates1.txd" },
+ [19668] = { "TubeHalfLoop2b.dff", "MatTubes.txd" },
+ [19700] = { "MTubeFlt12_5x12_5m1.dff", "MatTubes.txd" },
+ [19732] = { "TubeHalfMtoSJoin1b.dff", "MatTubes.txd" },
+ [19764] = { "STubeHalf45Bend1a.dff", "MatTubes.txd" },
+ [19796] = { "LSPrisonGateSouth.dff", "civic04_lan.txd" },
+ [19828] = { "LightSwitch3Off.dff", "LightSwitches.txd" },
+ [19860] = { "MIHouse1Door4.dff", "vegashse5.txd" },
+ [19892] = { "WSRoadJoiner1.dff", "WSSections.txd" },
+ [19924] = { "MKExtractionHood1.dff", "cswzkitch.txd" },
+ [19956] = { "SAMPRoadSign9.dff", "SAMPRoadSigns.txd" },
+ [19988] = { "SAMPRoadSign41.dff", "SAMPRoadSigns.txd" },
+ [18645] = { "MotorcycleHelmet1.dff", "MatClothes.txd" },
+ [18677] = { "exhale.dff", "MatTextures.txd" },
+ [18709] = { "prt_cardebris.dff", "MatTextures.txd" },
+ [18741] = { "water_ripples.dff", "MatTextures.txd" },
+ [18773] = { "TunnelJoinSection1.dff", "TunnelSections.txd" },
+ [18805] = { "MBridge150m3.dff", "cs_ebridge.txd" },
+ [18837] = { "RB25mBend90Tube.dff", "MickyTextures.txd" },
+ [18869] = { "MobilePhone5.dff", "MobilePhone5.txd" },
+ [18901] = { "Bandana11.dff", "MatClothes.txd" },
+ [18933] = { "Hat8.dff", "MatClothes.txd" },
+ [18965] = { "SkullyCap2.dff", "MatClothes.txd" },
+ [18997] = { "Tube1m1.dff", "MatTextures.txd" },
+ [19029] = { "GlassesType24.dff", "MatGlasses.txd" },
+ [19061] = { "XmasOrb3.dff", "XmasOrbs.txd" },
+ [19093] = { "HardHat2.dff", "NewHardHats.txd" },
+ [19125] = { "BollardLight5.dff", "metal.txd" },
+ [19157] = { "MetalLightBars1.dff", "MetalLightBars.txd" },
+ [19189] = { "MapMarkerNew13.dff", "MapMarkers.txd" },
+ [19221] = { "MapMarker21.dff", "MapMarkers.txd" },
+ [19253] = { "MapMarker53.dff", "MapMarkers.txd" },
+ [19285] = { "PointLight5.dff", "MatLights.txd" },
+ [19317] = { "bassguitar01.dff", "bassguitar01.txd" },
+ [19349] = { "monocle01.dff", "classy.txd" },
+ [19381] = { "wall029.dff", "all_walls.txd" },
+ [19413] = { "wall061.dff", "all_walls.txd" },
+ [19445] = { "wall085.dff", "all_walls.txd" },
+ [19477] = { "Plane003.dff", "signsurf.txd" },
+ [19509] = { "lshouse3.dff", "comedhos1_la.txd" },
+ [19541] = { "Edge62_5x15Grass1.dff", "beach_sfs.txd" },
+ [19573] = { "BriquettesBag1.dff", "cj_ss_1.txd" },
+ [19605] = { "EnExMarker4-2.dff", "EnExMarkers.txd" },
+ [19637] = { "GreenApplesCrate1.dff", "FruitCrates1.txd" },
+ [19669] = { "TubeHalfBowl1.dff", "MatTubes.txd" },
+ [19701] = { "MTubeHalfSpiral1a.dff", "MatTubes.txd" },
+ [19733] = { "STubeSeg5m1.dff", "MatTubes.txd" },
+ [19765] = { "STubeHalf45Bend1b.dff", "MatTubes.txd" },
+ [19797] = { "PoliceVisorStrobe1.dff", "PoliceVisorStrobe1.txd" },
+ [19829] = { "LightSwitch3On.dff", "LightSwitches.txd" },
+ [19861] = { "MIHouse1GarageDoor1.dff", "MIHouse1.txd" },
+ [19893] = { "LaptopSAMP1.dff", "LaptopSAMP1.txd" },
+ [19925] = { "MKWorkTop1.dff", "cswzkitch.txd" },
+ [19957] = { "SAMPRoadSign10.dff", "SAMPRoadSigns.txd" },
+ [19989] = { "SAMPRoadSign42.dff", "SAMPRoadSigns.txd" },
+ [18646] = { "PoliceLight1.dff", "MatColours.txd" },
+ [18678] = { "explosion_barrel.dff", "MatTextures.txd" },
+ [18710] = { "prt_collisionsmoke.dff", "MatTextures.txd" },
+ [18742] = { "water_speed.dff", "MatTextures.txd" },
+ [18774] = { "TunnelJoinSection2.dff", "TunnelSections.txd" },
+ [18806] = { "MBridge150m4.dff", "cs_ebridge.txd" },
+ [18838] = { "RB25mBend180Tube.dff", "MickyTextures.txd" },
+ [18870] = { "MobilePhone6.dff", "MobilePhone6.txd" },
+ [18902] = { "Bandana12.dff", "MatClothes.txd" },
+ [18934] = { "Hat9.dff", "MatClothes.txd" },
+ [18966] = { "SkullyCap3.dff", "MatClothes.txd" },
+ [18998] = { "Tube200m1.dff", "MatTextures.txd" },
+ [19030] = { "GlassesType25.dff", "MatGlasses.txd" },
+ [19062] = { "XmasOrb4.dff", "XmasOrbs.txd" },
+ [19094] = { "BurgerShotHat1.dff", "wfyburg.txd" },
+ [19126] = { "BollardLight6.dff", "metal.txd" },
+ [19158] = { "MetalLightBars2.dff", "MetalLightBars.txd" },
+ [19190] = { "MapMarkerNew14.dff", "MapMarkers.txd" },
+ [19222] = { "MapMarker22.dff", "MapMarkers.txd" },
+ [19254] = { "MapMarker54.dff", "MapMarkers.txd" },
+ [19286] = { "PointLight6.dff", "MatLights.txd" },
+ [19318] = { "flyingv01.dff", "flyingv01.txd" },
+ [19350] = { "moustache01.dff", "classy.txd" },
+ [19382] = { "wall030.dff", "all_walls.txd" },
+ [19414] = { "wall062.dff", "all_walls.txd" },
+ [19446] = { "wall086.dff", "all_walls.txd" },
+ [19478] = { "Plane004.dff", "signsurf.txd" },
+ [19510] = { "lshouse3int.dff", "all_walls.txd" },
+ [19542] = { "Edge62_5x125Grass1.dff", "beach_sfs.txd" },
+ [19574] = { "Orange1.dff", "SAMPFruits.txd" },
+ [19606] = { "EnExMarker4-3.dff", "EnExMarkers.txd" },
+ [19638] = { "OrangesCrate1.dff", "FruitCrates1.txd" },
+ [19670] = { "TubeSupport1.dff", "dynsigns.txd" },
+ [19702] = { "MTubeHalfSpiral1b.dff", "MatTubes.txd" },
+ [19734] = { "STubeSeg5m2a.dff", "MatTubes.txd" },
+ [19766] = { "STubeHalf15Bend1a.dff", "MatTubes.txd" },
+ [19798] = { "LSACarPark1.dff", "LSACarPark1.txd" },
+ [19830] = { "Blender1.dff", "csblender.txd" },
+ [19862] = { "MIHouse1GarageDoor2.dff", "vegashse3.txd" },
+ [19894] = { "LaptopSAMP2.dff", "LaptopSAMP1.txd" },
+ [19926] = { "MKWorkTop2.dff", "cswzkitch.txd" },
+ [19958] = { "SAMPRoadSign11.dff", "SAMPRoadSigns.txd" },
+ [19990] = { "SAMPRoadSign43.dff", "SAMPRoadSigns.txd" },
+ [18647] = { "RedNeonTube1.dff", "MatTextures.txd" },
+ [18679] = { "explosion_crate.dff", "MatTextures.txd" },
+ [18711] = { "prt_glass.dff", "MatTextures.txd" },
+ [18743] = { "water_splash.dff", "MatTextures.txd" },
+ [18775] = { "TunnelJoinSection3.dff", "TunnelSections.txd" },
+ [18807] = { "MBridge75mHalf.dff", "cs_ebridge.txd" },
+ [18839] = { "RB50mBend45Tube.dff", "MickyTextures.txd" },
+ [18871] = { "MobilePhone7.dff", "MobilePhone7.txd" },
+ [18903] = { "Bandana13.dff", "MatClothes.txd" },
+ [18935] = { "Hat10.dff", "MatClothes.txd" },
+ [18967] = { "HatMan1.dff", "MatClothes.txd" },
+ [18999] = { "Tube200mBendy1.dff", "MatTextures.txd" },
+ [19031] = { "GlassesType26.dff", "MatGlasses.txd" },
+ [19063] = { "XmasOrb5.dff", "XmasOrbs.txd" },
+ [19095] = { "CowboyHat1.dff", "CowboyHats.txd" },
+ [19127] = { "BollardLight7.dff", "metal.txd" },
+ [19159] = { "MirrorBall1.dff", "MirrorBall1.txd" },
+ [19191] = { "MapMarkerNew15.dff", "MapMarkers.txd" },
+ [19223] = { "MapMarker23.dff", "MapMarkers.txd" },
+ [19255] = { "MapMarker55.dff", "MapMarkers.txd" },
+ [19287] = { "PointLight7.dff", "MatLights.txd" },
+ [19319] = { "warlock01.dff", "warlock01.txd" },
+ [19351] = { "moustache02.dff", "classy.txd" },
+ [19383] = { "wall031.dff", "all_walls.txd" },
+ [19415] = { "wall063.dff", "all_walls.txd" },
+ [19447] = { "wall087.dff", "all_walls.txd" },
+ [19479] = { "Plane005.dff", "signsurf.txd" },
+ [19511] = { "lshouse4.dff", "glenphouse_lax.txd" },
+ [19543] = { "Plane62_5x15Grass1.dff", "beach_sfs.txd" },
+ [19575] = { "Apple1.dff", "SAMPFruits.txd" },
+ [19607] = { "EnExMarker4-4.dff", "EnExMarkers.txd" },
+ [19639] = { "EmptyCrate1.dff", "FruitCrates1.txd" },
+ [19671] = { "TubeSupport2.dff", "dynsigns.txd" },
+ [19703] = { "MTubeHalfSpiral2a.dff", "MatTubes.txd" },
+ [19735] = { "STubeSeg5m2b.dff", "MatTubes.txd" },
+ [19767] = { "STubeHalf15Bend1b.dff", "MatTubes.txd" },
+ [19799] = { "CaligulasVaultDoor.dff", "all_vault.txd" },
+ [19831] = { "Barbeque1.dff", "csbarbq.txd" },
+ [19863] = { "MIHouse1GarageDoor3.dff", "vegashse5.txd" },
+ [19895] = { "LadderFireTruckLts1.dff", "MatTextures.txd" },
+ [19927] = { "MKWorkTop3.dff", "cswzkitch.txd" },
+ [19959] = { "SAMPRoadSign12.dff", "SAMPRoadSigns.txd" },
+ [19991] = { "SAMPRoadSign44.dff", "SAMPRoadSigns.txd" },
+ [18648] = { "BlueNeonTube1.dff", "MatTextures.txd" },
+ [18680] = { "explosion_door.dff", "MatTextures.txd" },
+ [18712] = { "prt_gunshell.dff", "MatTextures.txd" },
+ [18744] = { "water_splash_big.dff", "MatTextures.txd" },
+ [18776] = { "TunnelJoinSection4.dff", "TunnelSections.txd" },
+ [18808] = { "Tube50m1.dff", "MatTextures.txd" },
+ [18840] = { "RB50mBend90Tube.dff", "MickyTextures.txd" },
+ [18872] = { "MobilePhone8.dff", "MobilePhone8.txd" },
+ [18904] = { "Bandana14.dff", "MatClothes.txd" },
+ [18936] = { "Helmet1.dff", "MatClothes.txd" },
+ [18968] = { "HatMan2.dff", "MatClothes.txd" },
+ [19000] = { "Tube200mBulge1.dff", "MatTextures.txd" },
+ [19032] = { "GlassesType27.dff", "MatGlasses.txd" },
+ [19064] = { "SantaHat1.dff", "SantaHats.txd" },
+ [19096] = { "CowboyHat3.dff", "CowboyHats.txd" },
+ [19128] = { "DanceFloor1.dff", "DanceFloors.txd" },
+ [19160] = { "HardHat3.dff", "NewHardHats.txd" },
+ [19192] = { "MapMarkerNew16.dff", "MapMarkers.txd" },
+ [19224] = { "MapMarker24.dff", "MapMarkers.txd" },
+ [19256] = { "MapMarker56.dff", "MapMarkers.txd" },
+ [19288] = { "PointLight8.dff", "MatLights.txd" },
+ [19320] = { "pumpkin01.dff", "pumpkin01.txd" },
+ [19352] = { "tophat01.dff", "classy.txd" },
+ [19384] = { "wall032.dff", "all_walls.txd" },
+ [19416] = { "wall064.dff", "all_walls.txd" },
+ [19448] = { "wall088.dff", "all_walls.txd" },
+ [19480] = { "Plane006.dff", "signsurf.txd" },
+ [19512] = { "lshouse4int.dff", "all_walls.txd" },
+ [19544] = { "Plane62_5x15Sand1.dff", "beach_sfs.txd" },
+ [19576] = { "Apple2.dff", "SAMPFruits.txd" },
+ [19608] = { "WoodenStage1.dff", "WoodenStage1.txd" },
+ [19640] = { "EmptyShopShelf1.dff", "new_cabinets3.txd" },
+ [19672] = { "TubeHalfLight1.dff", "dynsigns.txd" },
+ [19704] = { "MTubeHalfSpiral2b.dff", "MatTubes.txd" },
+ [19736] = { "STubeSeg6_25m1.dff", "MatTubes.txd" },
+ [19768] = { "STubeHalf15Bend2a.dff", "MatTubes.txd" },
+ [19800] = { "LSBCarPark1.dff", "LSACarPark1.txd" },
+ [19832] = { "AmmoBox1.dff", "csammobox.txd" },
+ [19864] = { "MIHouse1GarageDoor4.dff", "vegashse6.txd" },
+ [19896] = { "CigarettePack1.dff", "CigarettePacks.txd" },
+ [19928] = { "MKWorkTop4.dff", "cswzkitch.txd" },
+ [19960] = { "SAMPRoadSign13.dff", "SAMPRoadSigns.txd" },
+ [19992] = { "SAMPRoadSign45.dff", "SAMPRoadSigns.txd" },
+ [18649] = { "GreenNeonTube1.dff", "MatTextures.txd" },
+ [18681] = { "explosion_fuel_car.dff", "MatTextures.txd" },
+ [18713] = { "prt_sand2.dff", "MatTextures.txd" },
+ [18745] = { "water_splsh_sml.dff", "MatTextures.txd" },
+ [18777] = { "TunnelSpiral1.dff", "TunnelSections.txd" },
+ [18809] = { "Tube50mGlass1.dff", "MatTextures.txd" },
+ [18841] = { "RB50mBend180Tube.dff", "MickyTextures.txd" },
+ [18873] = { "MobilePhone9.dff", "MobilePhone9.txd" },
+ [18905] = { "Bandana15.dff", "MatClothes.txd" },
+ [18937] = { "Helmet2.dff", "MatClothes.txd" },
+ [18969] = { "HatMan3.dff", "MatClothes.txd" },
+ [19001] = { "VCWideLoop1.dff", "MatRamps.txd" },
+ [19033] = { "GlassesType28.dff", "MatGlasses.txd" },
+ [19065] = { "SantaHat2.dff", "SantaHats.txd" },
+ [19097] = { "CowboyHat4.dff", "CowboyHats.txd" },
+ [19129] = { "DanceFloor2.dff", "DanceFloors.txd" },
+ [19161] = { "PoliceHat1.dff", "NewPoliceHats.txd" },
+ [19193] = { "MapMarkerNew17.dff", "MapMarkers.txd" },
+ [19225] = { "MapMarker25.dff", "MapMarkers.txd" },
+ [19257] = { "MapMarker57.dff", "MapMarkers.txd" },
+ [19289] = { "PointLight9.dff", "MatLights.txd" },
+ [19321] = { "cuntainer.dff", "continX.txd" },
+ [19353] = { "wall001.dff", "all_walls.txd" },
+ [19385] = { "wall033.dff", "all_walls.txd" },
+ [19417] = { "wall065.dff", "all_walls.txd" },
+ [19449] = { "wall089.dff", "all_walls.txd" },
+ [19481] = { "Plane007.dff", "signsurf.txd" },
+ [19513] = { "whitephone.dff", "whitephone.txd" },
+ [19545] = { "Plane62_5x15Conc1.dff", "cs_ebridge.txd" },
+ [19577] = { "Tomato1.dff", "SAMPFruits.txd" },
+ [19609] = { "DrumKit1.dff", "dr_gsstudio.txd" },
+ [19641] = { "FenceSection1.dff", "FenceSection1.txd" },
+ [19673] = { "TubeHalf5Bend1a.dff", "MatTubes.txd" },
+ [19705] = { "MTubeHalfSpiral3a.dff", "MatTubes.txd" },
+ [19737] = { "STubeHalf10m1.dff", "MatTubes.txd" },
+ [19769] = { "STubeHalf15Bend2b.dff", "MatTubes.txd" },
+ [19801] = { "Balaclava1.dff", "Balaclava1.txd" },
+ [19833] = { "Cow1.dff", "Cow1.txd" },
+ [19865] = { "MIFenceWood1.dff", "vegashse2.txd" },
+ [19897] = { "CigarettePack2.dff", "CigarettePacks.txd" },
+ [19929] = { "MKWorkTop5.dff", "cswzkitch.txd" },
+ [19961] = { "SAMPRoadSign14.dff", "SAMPRoadSigns.txd" },
+ [19993] = { "CutsceneBowl1.dff", "csbowl.txd" },
+ [18650] = { "YellowNeonTube1.dff", "MatTextures.txd" },
+ [18682] = { "explosion_large.dff", "MatTextures.txd" },
+ [18714] = { "prt_sand.dff", "MatTextures.txd" },
+ [18746] = { "water_swim.dff", "MatTextures.txd" },
+ [18778] = { "RampT1.dff", "landjump.txd" },
+ [18810] = { "Tube50mBulge1.dff", "MatTextures.txd" },
+ [18842] = { "RB50mTube.dff", "MickyTextures.txd" },
+ [18874] = { "MobilePhone10.dff", "MobilePhone10.txd" },
+ [18906] = { "Bandana16.dff", "MatClothes.txd" },
+ [18938] = { "Helmet3.dff", "MatClothes.txd" },
+ [18970] = { "HatTiger1.dff", "MatClothes.txd" },
+ [19002] = { "FireHoop1.dff", "MatRamps.txd" },
+ [19034] = { "GlassesType29.dff", "MatGlasses.txd" },
+ [19066] = { "SantaHat3.dff", "SantaHats.txd" },
+ [19098] = { "CowboyHat5.dff", "CowboyHats.txd" },
+ [19130] = { "ArrowType1.dff", "MatArrows.txd" },
+ [19162] = { "PoliceHat2.dff", "NewPoliceHats.txd" },
+ [19194] = { "MapMarkerNew18.dff", "MapMarkers.txd" },
+ [19226] = { "MapMarker26.dff", "MapMarkers.txd" },
+ [19258] = { "MapMarker58.dff", "MapMarkers.txd" },
+ [19290] = { "PointLight10.dff", "MatLights.txd" },
+ [19322] = { "mallb_laW02.dff", "mall_law.txd" },
+ [19354] = { "wall002.dff", "all_walls.txd" },
+ [19386] = { "wall034.dff", "all_walls.txd" },
+ [19418] = { "handcuffs01.dff", "police_things.txd" },
+ [19450] = { "wall090.dff", "all_walls.txd" },
+ [19482] = { "Plane008.dff", "signsurf.txd" },
+ [19514] = { "SWATHgrey.dff", "swatgrey.txd" },
+ [19546] = { "Edge62_5x62_5Grass3.dff", "beach_sfs.txd" },
+ [19578] = { "Banana1.dff", "SAMPFruits.txd" },
+ [19610] = { "Microphone1.dff", "Microphone1.txd" },
+ [19642] = { "TubeSeg10m1.dff", "MatTubes.txd" },
+ [19674] = { "TubeHalf5Bend1b.dff", "MatTubes.txd" },
+ [19706] = { "MTubeHalfSpiral3b.dff", "MatTubes.txd" },
+ [19738] = { "STubeHalf5mJoin1a.dff", "MatTubes.txd" },
+ [19770] = { "STubeHalf45Bend3.dff", "MatTubes.txd" },
+ [19802] = { "GenDoorINT04Static.dff", "int_doors.txd" },
+ [19834] = { "PoliceLineTape1.dff", "PoliceLineTape1.txd" },
+ [19866] = { "MIFenceBlocks1.dff", "vegashse2.txd" },
+ [19898] = { "OilFloorStain1.dff", "hubprops1_sfse.txd" },
+ [19930] = { "MKWorkTop6.dff", "cswzkitch.txd" },
+ [19962] = { "SAMPRoadSign15.dff", "SAMPRoadSigns.txd" },
+ [19994] = { "CutsceneChair1.dff", "cschair.txd" },
+ [18651] = { "PinkNeonTube1.dff", "MatTextures.txd" },
+ [18683] = { "explosion_medium.dff", "MatTextures.txd" },
+ [18715] = { "prt_smoke_huge.dff", "MatTextures.txd" },
+ [18747] = { "waterfall_end.dff", "MatTextures.txd" },
+ [18779] = { "RampT2.dff", "MatRamps.txd" },
+ [18811] = { "Tube50mGlassBulge1.dff", "MatTextures.txd" },
+ [18843] = { "GlassSphere1.dff", "MatTextures.txd" },
+ [18875] = { "Pager1.dff", "Pager1.txd" },
+ [18907] = { "Bandana17.dff", "MatClothes.txd" },
+ [18939] = { "CapBack1.dff", "MatClothes.txd" },
+ [18971] = { "HatCool1.dff", "MatClothes.txd" },
+ [19003] = { "RampT5.dff", "MatRamps.txd" },
+ [19035] = { "GlassesType30.dff", "MatGlasses.txd" },
+ [19067] = { "HoodyHat1.dff", "HoodyHats.txd" },
+ [19099] = { "PoliceCap2.dff", "PoliceCaps.txd" },
+ [19131] = { "ArrowType2.dff", "MatArrows.txd" },
+ [19163] = { "GimpMask1.dff", "GimpMask1.txd" },
+ [19195] = { "MapMarkerNew19.dff", "MapMarkers.txd" },
+ [19227] = { "MapMarker27.dff", "MapMarkers.txd" },
+ [19259] = { "MapMarker59.dff", "MapMarkers.txd" },
+ [19291] = { "PointLight11.dff", "MatLights.txd" },
+ [19323] = { "lsmall_shop01.dff", "lsmall_shops.txd" },
+ [19355] = { "wall003.dff", "all_walls.txd" },
+ [19387] = { "wall035.dff", "all_walls.txd" },
+ [19419] = { "police_lights01.dff", "police_things.txd" },
+ [19451] = { "wall091.dff", "all_walls.txd" },
+ [19483] = { "Plane009.dff", "signsurf.txd" },
+ [19515] = { "SWATAgrey.dff", "swatgrey.txd" },
+ [19547] = { "Hill125x125Grass1.dff", "beach_sfs.txd" },
+ [19579] = { "BreadLoaf1.dff", "BreadLoaf1.txd" },
+ [19611] = { "MicrophoneStand1.dff", "Microphone1.txd" },
+ [19643] = { "TubeSeg10m2a.dff", "MatTubes.txd" },
+ [19675] = { "TubeHalf5Bend2a.dff", "MatTubes.txd" },
+ [19707] = { "MTubeHalfSpiral4a.dff", "MatTubes.txd" },
+ [19739] = { "STubeHalf5mJoin1b.dff", "MatTubes.txd" },
+ [19771] = { "STubeHalf45Bend4.dff", "MatTubes.txd" },
+ [19803] = { "TowTruckLights1.dff", "MatTextures.txd" },
+ [19835] = { "CoffeeCup1.dff", "d_s.txd" },
+ [19867] = { "MailBox1.dff", "break_garden.txd" },
+ [19899] = { "ToolCabinet1.dff", "hubprops2_sfse.txd" },
+ [19931] = { "MKWorkTop7.dff", "cswzkitch.txd" },
+ [19963] = { "SAMPRoadSign16.dff", "SAMPRoadSigns.txd" },
+ [19995] = { "CutsceneAmmoClip1.dff", "csclip.txd" },
+ [18652] = { "WhiteNeonTube1.dff", "MatTextures.txd" },
+ [18684] = { "explosion_molotov.dff", "MatTextures.txd" },
+ [18716] = { "prt_smoke_expand.dff", "MatTextures.txd" },
+ [18748] = { "WS_factorysmoke.dff", "MatTextures.txd" },
+ [18780] = { "RampT3.dff", "MatRamps.txd" },
+ [18812] = { "Tube50mFunnel1.dff", "MatTextures.txd" },
+ [18844] = { "WaterUVAnimSphere1.dff", "MatTextures.txd" },
+ [18876] = { "BigGreenGloop1.dff", "gloopX.txd" },
+ [18908] = { "Bandana18.dff", "MatClothes.txd" },
+ [18940] = { "CapBack2.dff", "MatClothes.txd" },
+ [18972] = { "HatCool2.dff", "MatClothes.txd" },
+ [19004] = { "RoundBuilding1.dff", "RoundBuilding1.txd" },
+ [19036] = { "HockeyMask1.dff", "MatHockey.txd" },
+ [19068] = { "HoodyHat2.dff", "HoodyHats.txd" },
+ [19100] = { "PoliceCap3.dff", "PoliceCaps.txd" },
+ [19132] = { "ArrowType3.dff", "MatArrows.txd" },
+ [19164] = { "GTASAMap1.dff", "gtamap.txd" },
+ [19196] = { "MapMarkerNew20.dff", "MapMarkers.txd" },
+ [19228] = { "MapMarker28.dff", "MapMarkers.txd" },
+ [19260] = { "MapMarker60.dff", "MapMarkers.txd" },
+ [19292] = { "PointLight12.dff", "MatLights.txd" },
+ [19324] = { "kmb_atm1_2.dff", "kmb_atmx.txd" },
+ [19356] = { "wall004.dff", "all_walls.txd" },
+ [19388] = { "wall036.dff", "all_walls.txd" },
+ [19420] = { "police_lights02.dff", "police_things.txd" },
+ [19452] = { "wall092.dff", "all_walls.txd" },
+ [19484] = { "landbit01_01.dff", "bakerybit2_sfse.txd" },
+ [19516] = { "Hair2_nc.dff", "noncolored.txd" },
+ [19548] = { "Hill125x125Sand1.dff", "beach_sfs.txd" },
+ [19580] = { "Pizza1.dff", "cj_ff_acc1.txd" },
+ [19612] = { "GuitarAmp1.dff", "dr_gsstudio.txd" },
+ [19644] = { "TubeSeg10m2b.dff", "MatTubes.txd" },
+ [19676] = { "TubeHalf5Bend2b.dff", "MatTubes.txd" },
+ [19708] = { "MTubeHalfSpiral4b.dff", "MatTubes.txd" },
+ [19740] = { "STubeHalf12_5m1.dff", "MatTubes.txd" },
+ [19772] = { "CrushedCarCube1.dff", "CrushedCarCube1.txd" },
+ [19804] = { "Padlock1.dff", "Padlock1.txd" },
+ [19836] = { "BloodPool1.dff", "particle.txd" },
+ [19868] = { "MeshFence1.dff", "break_fen_mesh2.txd" },
+ [19900] = { "ToolCabinet2.dff", "hubprops2_sfse.txd" },
+ [19932] = { "MKWallOvenCabinet1.dff", "cswzkitch.txd" },
+ [19964] = { "SAMPRoadSign17.dff", "SAMPRoadSigns.txd" },
+ [19996] = { "CutsceneFoldChair1.dff", "csfoldchair.txd" },
+ [18653] = { "DiscoLightRed.dff", "Carter_block.txd" },
+ [18685] = { "explosion_small.dff", "MatTextures.txd" },
+ [18717] = { "prt_spark.dff", "MatTextures.txd" },
+ [18749] = { "SAMPLogoSmall.dff", "MatTextures.txd" },
+ [18781] = { "MeshRampBig.dff", "MatRamps.txd" },
+ [18813] = { "Tube50mGlassFunnel1.dff", "MatTextures.txd" },
+ [18845] = { "RTexturesphere.dff", "MickyTextures.txd" },
+ [18877] = { "FerrisWheelBit.dff", "FerrisWheel.txd" },
+ [18909] = { "Bandana19.dff", "MatClothes.txd" },
+ [18941] = { "CapBack3.dff", "MatClothes.txd" },
+ [18973] = { "HatCool3.dff", "MatClothes.txd" },
+ [19005] = { "RampT4.dff", "MatRamps.txd" },
+ [19037] = { "HockeyMask2.dff", "MatHockey.txd" },
+ [19069] = { "HoodyHat3.dff", "HoodyHats.txd" },
+ [19101] = { "ArmyHelmet1.dff", "ArmyHelmets.txd" },
+ [19133] = { "ArrowType4.dff", "MatArrows.txd" },
+ [19165] = { "GTASAMap2.dff", "gtamap.txd" },
+ [19197] = { "EnExMarker2.dff", "EnExMarkers.txd" },
+ [19229] = { "MapMarker29.dff", "MapMarkers.txd" },
+ [19261] = { "MapMarker61.dff", "MapMarkers.txd" },
+ [19293] = { "PointLight13.dff", "MatLights.txd" },
+ [19325] = { "lsmall_window01.dff", "lsmall_shops.txd" },
+ [19357] = { "wall005.dff", "all_walls.txd" },
+ [19389] = { "wall037.dff", "all_walls.txd" },
+ [19421] = { "headphones01.dff", "headphones.txd" },
+ [19453] = { "wall093.dff", "all_walls.txd" },
+ [19485] = { "Groundbit84_SFS_01.dff", "skyscrap_sfse.txd" },
+ [19517] = { "Hair3_nc.dff", "noncolored.txd" },
+ [19549] = { "Edge62_5x32_5Grass1.dff", "beach_sfs.txd" },
+ [19581] = { "MarcosFryingPan1.dff", "lee_kitch.txd" },
+ [19613] = { "GuitarAmp2.dff", "dr_gsstudio.txd" },
+ [19645] = { "TubeSeg25m1.dff", "MatTubes.txd" },
+ [19677] = { "TubeHalfTwist1a.dff", "MatTubes.txd" },
+ [19709] = { "MTubeHalf180Bend1a.dff", "MatTubes.txd" },
+ [19741] = { "STubeFlat6_25m1.dff", "MatTubes.txd" },
+ [19773] = { "GunHolster1.dff", "MatCopStuff.txd" },
+ [19805] = { "Whiteboard1.dff", "Whiteboard1.txd" },
+ [19837] = { "GrassClump1.dff", "particle.txd" },
+ [19869] = { "MeshFence2.dff", "break_fen_mesh2.txd" },
+ [19933] = { "MKWallOven1.dff", "MKWallOven1.txd" },
+ [19965] = { "SAMPRoadSign18.dff", "SAMPRoadSigns.txd" },
+ [19997] = { "CutsceneGrgTable1.dff", "csgrgtable.txd" },
+ [18654] = { "DiscoLightGreen.dff", "Carter_block.txd" },
+ [18686] = { "explosion_tiny.dff", "MatTextures.txd" },
+ [18718] = { "prt_spark_2.dff", "MatTextures.txd" },
+ [18750] = { "SAMPLogoBig.dff", "MatTextures.txd" },
+ [18782] = { "CookieRamp1.dff", "CookieRamp1.txd" },
+ [18814] = { "Tube50mFunnel2.dff", "MatTextures.txd" },
+ [18846] = { "UFO.dff", "des_ufoinn.txd" },
+ [18878] = { "FerrisBaseBit.dff", "FerrisWheel.txd" },
+ [18910] = { "Bandana20.dff", "MatClothes.txd" },
+ [18942] = { "CapBack4.dff", "MatClothes.txd" },
+ [18974] = { "MaskZorro1.dff", "MatClothes.txd" },
+ [19006] = { "GlassesType1.dff", "MatGlasses.txd" },
+ [19038] = { "HockeyMask3.dff", "MatHockey.txd" },
+ [19070] = { "WSDown1.dff", "WSSections.txd" },
+ [19102] = { "ArmyHelmet2.dff", "ArmyHelmets.txd" },
+ [19134] = { "ArrowType5.dff", "MatArrows.txd" },
+ [19166] = { "GTASAMap3.dff", "gtamap.txd" },
+ [19198] = { "EnExMarker3.dff", "EnExMarkers.txd" },
+ [19230] = { "MapMarker30.dff", "MapMarkers.txd" },
+ [19262] = { "MapMarker62.dff", "MapMarkers.txd" },
+ [19294] = { "PointLight14.dff", "MatLights.txd" },
+ [19326] = { "7_11_sign01.dff", "7_11_posters.txd" },
+ [19358] = { "wall006.dff", "all_walls.txd" },
+ [19390] = { "wall038.dff", "all_walls.txd" },
+ [19422] = { "headphones02.dff", "headphones.txd" },
+ [19454] = { "wall094.dff", "all_walls.txd" },
+ [19486] = { "SFHarryPlums1.dff", "burgalrystore_sfse.txd" },
+ [19518] = { "Hair5_nc.dff", "noncolored.txd" },
+ [19550] = { "Plane125x125Grass2.dff", "beach_sfs.txd" },
+ [19582] = { "MarcosSteak1.dff", "lee_kitch.txd" },
+ [19614] = { "GuitarAmp3.dff", "dr_gsstudio.txd" },
+ [19646] = { "TubeHalf10m1.dff", "MatTubes.txd" },
+ [19678] = { "TubeHalfTwist1b.dff", "MatTubes.txd" },
+ [19710] = { "MTubeHalf180Bend1b.dff", "MatTubes.txd" },
+ [19742] = { "STubeHalfSpiral1a.dff", "MatTubes.txd" },
+ [19774] = { "PoliceBadge2.dff", "MatCopStuff.txd" },
+ [19806] = { "Chandelier1.dff", "svbits.txd" },
+ [19838] = { "GrassClump2.dff", "particle.txd" },
+ [19870] = { "MetalGate1.dff", "ctgtxx.txd" },
+ [19934] = { "MKCupboard1.dff", "cswzkitch.txd" },
+ [19966] = { "SAMPRoadSign19.dff", "SAMPRoadSigns.txd" },
+ [19998] = { "CutsceneLighterFl.dff", "cslighterfluid.txd" },
+ [18655] = { "DiscoLightBlue.dff", "Carter_block.txd" },
+ [18687] = { "extinguisher.dff", "MatTextures.txd" },
+ [18719] = { "prt_wake.dff", "MatTextures.txd" },
+ [18751] = { "IslandBase1.dff", "MatTextures.txd" },
+ [18783] = { "FunBoxTop1.dff", "MatRamps.txd" },
+ [18815] = { "Tube50mFunnel3.dff", "MatTextures.txd" },
+ [18847] = { "HugeHalfPipe1.dff", "glenpark7_lae.txd" },
+ [18879] = { "ferriscagebit.dff", "FerrisWheel.txd" },
+ [18911] = { "Mask1.dff", "MatClothes.txd" },
+ [18943] = { "CapBack5.dff", "MatClothes.txd" },
+ [18975] = { "Hair2.dff", "Hair2.txd" },
+ [19007] = { "GlassesType2.dff", "MatGlasses.txd" },
+ [19039] = { "WatchType1.dff", "MatWatches.txd" },
+ [19071] = { "WSStraight1.dff", "WSSections.txd" },
+ [19103] = { "ArmyHelmet3.dff", "ArmyHelmets.txd" },
+ [19135] = { "EnExMarker1.dff", "EnExMarkers.txd" },
+ [19167] = { "GTASAMap4.dff", "gtamap.txd" },
+ [19231] = { "MapMarker31.dff", "MapMarkers.txd" },
+ [19263] = { "MapMarker63.dff", "MapMarkers.txd" },
+ [19295] = { "PointLight15.dff", "MatLights.txd" },
+ [19327] = { "7_11_sign02.dff", "7_11_posters.txd" },
+ [19359] = { "wall007.dff", "all_walls.txd" },
+ [19391] = { "wall039.dff", "all_walls.txd" },
+ [19423] = { "headphones03.dff", "headphones.txd" },
+ [19455] = { "wall095.dff", "all_walls.txd" },
+ [19487] = { "tophat02.dff", "noncolored.txd" },
+ [19519] = { "Hair1_nc.dff", "noncolored.txd" },
+ [19551] = { "Plane125x125Sand2.dff", "beach_sfs.txd" },
+ [19583] = { "MarcosKnife1.dff", "lee_kitch.txd" },
+ [19615] = { "GuitarAmp4.dff", "dr_gsstudio.txd" },
+ [19647] = { "TubeHalf10mJoin1a.dff", "MatTubes.txd" },
+ [19679] = { "TubeHalfTwist2a.dff", "MatTubes.txd" },
+ [19711] = { "MTubeHalf90Bend1a.dff", "MatTubes.txd" },
+ [19743] = { "STubeHalfSpiral1b.dff", "MatTubes.txd" },
+ [19775] = { "PoliceBadge3.dff", "MatCopStuff.txd" },
+ [19807] = { "Telephone1.dff", "Telephone1.txd" },
+ [19839] = { "GrassClump3.dff", "particle.txd" },
+ [19871] = { "CordonStand1.dff", "airp_prop.txd" },
+ [19903] = { "MechanicComputer1.dff", "hubprops2_sfse.txd" },
+ [19935] = { "MKCupboard2.dff", "cswzkitch.txd" },
+ [19967] = { "SAMPRoadSign20.dff", "SAMPRoadSigns.txd" },
+ [19999] = { "CutsceneChair2.dff", "csrcrdchair.txd" },
+ [18656] = { "LightBeamWhite.dff", "LightBeams.txd" },
+ [18688] = { "fire.dff", "MatTextures.txd" },
+ [18720] = { "prt_watersplash.dff", "MatTextures.txd" },
+ [18752] = { "Volcano.dff", "Volcano.txd" },
+ [18784] = { "FunBoxRamp1.dff", "MatRamps.txd" },
+ [18816] = { "Tube50mFunnel4.dff", "MatTextures.txd" },
+ [18848] = { "SamSiteNonDynamic.dff", "milbase.txd" },
+ [18880] = { "SpeedCamera1.dff", "SpeedCamera1.txd" },
+ [18912] = { "Mask2.dff", "MatClothes.txd" },
+ [18944] = { "HatBoater1.dff", "MatClothes.txd" },
+ [18976] = { "MotorcycleHelmet2.dff", "MatClothes.txd" },
+ [19008] = { "GlassesType3.dff", "MatGlasses.txd" },
+ [19040] = { "WatchType2.dff", "MatWatches.txd" },
+ [19072] = { "WSBend45Deg1.dff", "WSSections.txd" },
+ [19104] = { "ArmyHelmet4.dff", "ArmyHelmets.txd" },
+ [19136] = { "Hair4.dff", "smyst2.txd" },
+ [19168] = { "GTASAMap5.dff", "gtamap.txd" },
+ [19200] = { "PoliceHelmet1.dff", "lapdm1.txd" },
+ [19232] = { "MapMarker32.dff", "MapMarkers.txd" },
+ [19264] = { "MapMarker1a.dff", "MapMarkers.txd" },
+ [19296] = { "PointLight16.dff", "MatLights.txd" },
+ [19328] = { "7_11_sign03.dff", "7_11_posters.txd" },
+ [19360] = { "wall008.dff", "all_walls.txd" },
+ [19392] = { "wall040.dff", "all_walls.txd" },
+ [19424] = { "headphones04.dff", "headphones.txd" },
+ [19456] = { "wall096.dff", "all_walls.txd" },
+ [19488] = { "HatBowler6.dff", "noncolored.txd" },
+ [19520] = { "pilotHat01.dff", "pilotPoliceHat.txd" },
+ [19552] = { "Plane125x125Conc2.dff", "cs_ebridge.txd" },
+ [19584] = { "MarcosSaucepan1.dff", "lee_kitch.txd" },
+ [19616] = { "GuitarAmp5.dff", "dr_gsstudio.txd" },
+ [19648] = { "TubeHalf10mJoin1b.dff", "MatTubes.txd" },
+ [19680] = { "TubeHalfTwist2b.dff", "MatTubes.txd" },
+ [19712] = { "MTubeHalf90Bend1b.dff", "MatTubes.txd" },
+ [19744] = { "STubeHalfSpiral2a.dff", "MatTubes.txd" },
+ [19776] = { "FBIIDCard1.dff", "MatCopStuff.txd" },
+ [19808] = { "Keyboard1.dff", "Keyboard1.txd" },
+ [19840] = { "WaterFall1.dff", "vegaswaterfall.txd" },
+ [19872] = { "CarFixerRamp2.dff", "CarFixerRamp1.txd" },
+ [19904] = { "ConstructionVest1.dff", "bmyap.txd" },
+ [19936] = { "MKCupboard3.dff", "cswzkitch.txd" },
+ [19968] = { "SAMPRoadSign21.dff", "SAMPRoadSigns.txd" },
+ [18657] = { "LightBeamRed.dff", "LightBeams.txd" },
+ [18689] = { "fire_bike.dff", "MatTextures.txd" },
+ [18721] = { "prt_wheeldirt.dff", "MatTextures.txd" },
+ [18753] = { "Base125mx125m1.dff", "BaseSections.txd" },
+ [18785] = { "FunBoxRamp2.dff", "MatRamps.txd" },
+ [18817] = { "Tube50mTSection1.dff", "MatTextures.txd" },
+ [18849] = { "ParaDropNonDynamic.dff", "kmb_chute.txd" },
+ [18881] = { "SkyDivePlatform2.dff", "MatRacing.txd" },
+ [18913] = { "Mask3.dff", "MatClothes.txd" },
+ [18945] = { "HatBoater2.dff", "MatClothes.txd" },
+ [18977] = { "MotorcycleHelmet3.dff", "MatClothes.txd" },
+ [19009] = { "GlassesType4.dff", "MatGlasses.txd" },
+ [19041] = { "WatchType3.dff", "MatWatches.txd" },
+ [19073] = { "WSRocky1.dff", "WSSections.txd" },
+ [19105] = { "ArmyHelmet5.dff", "ArmyHelmets.txd" },
+ [19137] = { "CluckinBellHat1.dff", "wmybell.txd" },
+ [19169] = { "GTASAMap6.dff", "gtamap.txd" },
+ [19201] = { "MapMarker1.dff", "MapMarkers.txd" },
+ [19233] = { "MapMarker33.dff", "MapMarkers.txd" },
+ [19265] = { "MapMarker1b.dff", "MapMarkers.txd" },
+ [19297] = { "PointLight17.dff", "MatLights.txd" },
+ [19329] = { "7_11_sign04.dff", "7_11_posters.txd" },
+ [19361] = { "wall009.dff", "all_walls.txd" },
+ [19393] = { "wall041.dff", "all_walls.txd" },
+ [19425] = { "speed_bump01.dff", "speed_bumps.txd" },
+ [19457] = { "wall097.dff", "all_walls.txd" },
+ [19489] = { "sfhouse1.dff", "boxhses_sfsx.txd" },
+ [19521] = { "policeHat01.dff", "pilotPoliceHat.txd" },
+ [19553] = { "StrawHat1.dff", "CWMOFR.txd" },
+ [19585] = { "MarcosPan1.dff", "lee_kitch.txd" },
+ [19617] = { "GoldRecord1.dff", "dr_gsstudio.txd" },
+ [19649] = { "TubeHalf50m1.dff", "MatTubes.txd" },
+ [19681] = { "TubeHalf45Bend1a.dff", "MatTubes.txd" },
+ [19713] = { "MTubeHalf25mDip1.dff", "MatTubes.txd" },
+ [19745] = { "STubeHalfSpiral2b.dff", "MatTubes.txd" },
+ [19777] = { "FBILogo1.dff", "MatCopStuff.txd" },
+ [19809] = { "MetalTray1.dff", "MetalTray1.txd" },
+ [19841] = { "WaterFall2.dff", "vegaswaterfall.txd" },
+ [19873] = { "ToiletPaperRoll1.dff", "BathroomStuff1.txd" },
+ [19905] = { "A51Building1.dff", "a51_ext.txd" },
+ [19937] = { "MKCupboard4.dff", "cswzkitch.txd" },
+ [19969] = { "SAMPRoadSign22.dff", "SAMPRoadSigns.txd" },
+ [18658] = { "LightBeamBlue.dff", "LightBeams.txd" },
+ [18690] = { "fire_car.dff", "MatTextures.txd" },
+ [18722] = { "puke.dff", "MatTextures.txd" },
+ [18754] = { "Base250mx250m1.dff", "BaseSections.txd" },
+ [18786] = { "FunBoxRamp3.dff", "MatRamps.txd" },
+ [18818] = { "Tube50mGlassT1.dff", "MatTextures.txd" },
+ [18850] = { "HeliPad1.dff", "sfe_copchop.txd" },
+ [18882] = { "HugeBowl1.dff", "HugeBowls.txd" },
+ [18914] = { "Mask4.dff", "MatClothes.txd" },
+ [18946] = { "HatBoater3.dff", "MatClothes.txd" },
+ [18978] = { "MotorcycleHelmet4.dff", "noncolored.txd" },
+ [19010] = { "GlassesType5.dff", "MatGlasses.txd" },
+ [19042] = { "WatchType4.dff", "MatWatches.txd" },
+ [19074] = { "Cage20mx20mx10mv2.dff", "MatTextures.txd" },
+ [19106] = { "ArmyHelmet6.dff", "ArmyHelmets.txd" },
+ [19138] = { "PoliceGlasses1.dff", "PoliceGlasses.txd" },
+ [19170] = { "GTASAMap7.dff", "gtamap.txd" },
+ [19202] = { "MapMarker2.dff", "MapMarkers.txd" },
+ [19234] = { "MapMarker34.dff", "MapMarkers.txd" },
+ [19266] = { "MapMarker31a.dff", "MapMarkers.txd" },
+ [19298] = { "PointLight18.dff", "MatLights.txd" },
+ [19330] = { "fire_hat01.dff", "firehats.txd" },
+ [19362] = { "wall010.dff", "all_walls.txd" },
+ [19394] = { "wall042.dff", "all_walls.txd" },
+ [19426] = { "wall066.dff", "all_walls.txd" },
+ [19458] = { "wall098.dff", "all_walls.txd" },
+ [19490] = { "sfhouse1int.dff", "all_walls.txd" },
+ [19522] = { "property_red.dff", "SAMPIcons.txd" },
+ [19554] = { "Beanie1.dff", "ogloc.txd" },
+ [19586] = { "MarcosSpatula1.dff", "lee_kitch.txd" },
+ [19618] = { "Safe1.dff", "kbmiscfrn2.txd" },
+ [19650] = { "TubeFlat25x25m1.dff", "MatTubes.txd" },
+ [19682] = { "TubeHalf45Bend1b.dff", "MatTubes.txd" },
+ [19714] = { "MTubeHalf25mBump1.dff", "MatTubes.txd" },
+ [19746] = { "STubeHalfSpiral3a.dff", "MatTubes.txd" },
+ [19778] = { "InsigniaDetective1.dff", "MatPoliceInsignias.txd" },
+ [19810] = { "StaffOnlySign1.dff", "StaffOnlySign1.txd" },
+ [19842] = { "WaterFallWater1.dff", "vegaswaterfall.txd" },
+ [19874] = { "SoapBar1.dff", "BathroomStuff1.txd" },
+ [19906] = { "A51Building1GrgDoor.dff", "a51_ext.txd" },
+ [19938] = { "MKShelf1.dff", "cswzkitch.txd" },
+ [19970] = { "SAMPRoadSign23.dff", "SAMPRoadSigns.txd" },
+ [18659] = { "SprayTag1.dff", "SprayTags.txd" },
+ [18691] = { "fire_large.dff", "MatTextures.txd" },
+ [18723] = { "riot_smoke.dff", "MatTextures.txd" },
+ [18755] = { "VCElevator1.dff", "VCInteriors.txd" },
+ [18787] = { "FunBoxRamp4.dff", "MatRamps.txd" },
+ [18819] = { "Tube50mPlus1.dff", "MatTextures.txd" },
+ [18851] = { "TubeToRoad1.dff", "MatTextures.txd" },
+ [18883] = { "HugeBowl2.dff", "HugeBowls.txd" },
+ [18915] = { "Mask5.dff", "MatClothes.txd" },
+ [18947] = { "HatBowler1.dff", "MatClothes.txd" },
+ [18979] = { "MotorcycleHelmet5.dff", "MatClothes.txd" },
+ [19011] = { "GlassesType6.dff", "MatGlasses.txd" },
+ [19043] = { "WatchType5.dff", "MatWatches.txd" },
+ [19075] = { "Cage5mx5mx3mv2.dff", "MatTextures.txd" },
+ [19107] = { "ArmyHelmet7.dff", "ArmyHelmets.txd" },
+ [19139] = { "PoliceGlasses2.dff", "PoliceGlasses.txd" },
+ [19171] = { "GTASAMap8.dff", "gtamap.txd" },
+ [19203] = { "MapMarker3.dff", "MapMarkers.txd" },
+ [19235] = { "MapMarker35.dff", "MapMarkers.txd" },
+ [19267] = { "MapMarker31b.dff", "MapMarkers.txd" },
+ [19299] = { "PointLightMoon1.dff", "MatLights.txd" },
+ [19331] = { "fire_hat02.dff", "firehats.txd" },
+ [19363] = { "wall011.dff", "all_walls.txd" },
+ [19395] = { "wall043.dff", "all_walls.txd" },
+ [19427] = { "wall067.dff", "all_walls.txd" },
+ [19459] = { "wall099.dff", "all_walls.txd" },
+ [19491] = { "sfhouse2.dff", "boxhses_sfsx.txd" },
+ [19523] = { "property_orange.dff", "SAMPIcons.txd" },
+ [19555] = { "BoxingGloveL.dff", "vbmybox.txd" },
+ [19587] = { "PlasticTray1.dff", "labins01_la.txd" },
+ [19619] = { "SafeDoor1.dff", "kbmiscfrn2.txd" },
+ [19651] = { "TubeHalfSpiral1a.dff", "MatTubes.txd" },
+ [19683] = { "TubeHalf15Bend1a.dff", "MatTubes.txd" },
+ [19715] = { "MTubeHalfBowl1.dff", "MatTubes.txd" },
+ [19747] = { "STubeHalfSpiral3b.dff", "MatTubes.txd" },
+ [19779] = { "InsigniaDetective2.dff", "MatPoliceInsignias.txd" },
+ [19811] = { "BurgerBox1.dff", "BurgerBox1.txd" },
+ [19843] = { "MetalPanel1.dff", "MetalPanels.txd" },
+ [19875] = { "CRDoor01New.dff", "sw_doors.txd" },
+ [19907] = { "A51Building2.dff", "a51_ext.txd" },
+ [19939] = { "MKShelf2.dff", "cswzkitch.txd" },
+ [19971] = { "SAMPRoadSign24.dff", "SAMPRoadSigns.txd" },
+ [18660] = { "SprayTag2.dff", "SprayTags.txd" },
+ [18692] = { "fire_med.dff", "MatTextures.txd" },
+ [18724] = { "shootlight.dff", "MatTextures.txd" },
+ [18756] = { "ElevatorDoor1.dff", "VCInteriors.txd" },
+ [18788] = { "MRoad40m.dff", "cs_ebridge.txd" },
+ [18820] = { "Tube50mGlassPlus1.dff", "MatTextures.txd" },
+ [18852] = { "Tube100m1.dff", "MatTextures.txd" },
+ [18884] = { "HugeBowl3.dff", "HugeBowls.txd" },
+ [18916] = { "Mask6.dff", "MatClothes.txd" },
+ [18948] = { "HatBowler2.dff", "MatClothes.txd" },
+ [18980] = { "Concrete1mx1mx25m.dff", "ConcreteBits.txd" },
+ [19012] = { "GlassesType7.dff", "MatGlasses.txd" },
+ [19044] = { "WatchType6.dff", "MatWatches.txd" },
+ [19076] = { "XmasTree1.dff", "XmasTree1.txd" },
+ [19108] = { "ArmyHelmet8.dff", "ArmyHelmets.txd" },
+ [19140] = { "PoliceGlasses3.dff", "PoliceGlasses.txd" },
+ [19172] = { "SAMPPicture1.dff", "SAMPPictures.txd" },
+ [19204] = { "MapMarker4.dff", "MapMarkers.txd" },
+ [19236] = { "MapMarker36.dff", "MapMarkers.txd" },
+ [19268] = { "MapMarker31c.dff", "MapMarkers.txd" },
+ [19300] = { "blankmodel.dff", "MatTextures.txd" },
+ [19332] = { "Hot_Air_Balloon01.dff", "balloon_texts.txd" },
+ [19364] = { "wall012.dff", "all_walls.txd" },
+ [19396] = { "wall044.dff", "all_walls.txd" },
+ [19428] = { "wall068.dff", "all_walls.txd" },
+ [19460] = { "wall100.dff", "all_walls.txd" },
+ [19492] = { "sfhouse2int.dff", "all_walls.txd" },
+ [19524] = { "property_yellow.dff", "SAMPIcons.txd" },
+ [19556] = { "BoxingGloveR.dff", "vbmybox.txd" },
+ [19588] = { "FootBridge1.dff", "FootBridge1.txd" },
+ [19620] = { "LightBar1.dff", "LightBar1.txd" },
+ [19652] = { "TubeHalfSpiral1b.dff", "MatTubes.txd" },
+ [19684] = { "TubeHalf15Bend1b.dff", "MatTubes.txd" },
+ [19716] = { "MTubeSupport1.dff", "dynsigns.txd" },
+ [19748] = { "STubeHalfSpiral4a.dff", "MatTubes.txd" },
+ [19780] = { "InsigniaDetective3.dff", "MatPoliceInsignias.txd" },
+ [19812] = { "BeerKeg1.dff", "ce_breweryref.txd" },
+ [19844] = { "MetalPanel2.dff", "MetalPanels.txd" },
+ [19876] = { "DillimoreGasExt1.dff", "cunte_gas01.txd" },
+ [19908] = { "A51Building2GrgDoor.dff", "a51_ext.txd" },
+ [19940] = { "MKShelf3.dff", "cswzkitch.txd" },
+ [19972] = { "SAMPRoadSign25.dff", "SAMPRoadSigns.txd" },
+ [18661] = { "SprayTag3.dff", "SprayTags.txd" },
+ [18693] = { "Flame99.dff", "MatTextures.txd" },
+ [18725] = { "smoke30lit.dff", "MatTextures.txd" },
+ [18757] = { "ElevatorDoor2.dff", "VCInteriors.txd" },
+ [18789] = { "MRoad150m.dff", "cs_ebridge.txd" },
+ [18821] = { "Tube50m45Bend1.dff", "MatTextures.txd" },
+ [18853] = { "Tube100m45Bend1.dff", "MatTextures.txd" },
+ [18885] = { "GunVendingMachine1.dff", "GunVendingMachine1.txd" },
+ [18917] = { "Mask7.dff", "MatClothes.txd" },
+ [18949] = { "HatBowler3.dff", "MatClothes.txd" },
+ [18981] = { "Concrete1mx25mx25m.dff", "ConcreteBits.txd" },
+ [19013] = { "GlassesType8.dff", "MatGlasses.txd" },
+ [19045] = { "WatchType7.dff", "MatWatches.txd" },
+ [19077] = { "Hair3.dff", "Hair3.txd" },
+ [19109] = { "ArmyHelmet9.dff", "ArmyHelmets.txd" },
+ [19141] = { "SWATHelmet1.dff", "swat.txd" },
+ [19173] = { "SAMPPicture2.dff", "SAMPPictures.txd" },
+ [19205] = { "MapMarker5.dff", "MapMarkers.txd" },
+ [19237] = { "MapMarker37.dff", "MapMarkers.txd" },
+ [19269] = { "MapMarker31d.dff", "MapMarkers.txd" },
+ [19301] = { "mp_sfpd_nocell.dff", "mp_policeSF.txd" },
+ [19333] = { "Hot_Air_Balloon02.dff", "balloon_texts.txd" },
+ [19365] = { "wall013.dff", "all_walls.txd" },
+ [19397] = { "wall045.dff", "all_walls.txd" },
+ [19429] = { "wall069.dff", "all_walls.txd" },
+ [19461] = { "wall101.dff", "all_walls.txd" },
+ [19493] = { "sfhouse3.dff", "boxhses_sfsx.txd" },
+ [19525] = { "WeddingCake1.dff", "WeddingCake1.txd" },
+ [19557] = { "SexyMask1.dff", "wfysex.txd" },
+ [19589] = { "RubbishSkipEmpty1.dff", "break_s_bins.txd" },
+ [19621] = { "OilCan1.dff", "OilCan1.txd" },
+ [19653] = { "TubeHalfSpiral2a.dff", "MatTubes.txd" },
+ [19685] = { "TubeHalf15Bend2a.dff", "MatTubes.txd" },
+ [19717] = { "MTubeSupport2.dff", "dynsigns.txd" },
+ [19749] = { "STubeHalfSpiral4b.dff", "MatTubes.txd" },
+ [19781] = { "InsigniaSergeant1.dff", "MatPoliceInsignias.txd" },
+ [19813] = { "ElectricalOutlet1.dff", "ElectricalOutlets.txd" },
+ [19845] = { "MetalPanel3.dff", "MetalPanels.txd" },
+ [19877] = { "DillimoreGasInt1.dff", "cunte_gas01.txd" },
+ [19909] = { "A51Building3.dff", "a51_ext.txd" },
+ [19941] = { "GoldBar1.dff", "GoldBar1.txd" },
+ [19973] = { "SAMPRoadSign26.dff", "SAMPRoadSigns.txd" },
+ [18662] = { "SprayTag4.dff", "SprayTags.txd" },
+ [18694] = { "flamethrowerp.dff", "MatTextures.txd" },
+ [18726] = { "smoke30m.dff", "MatTextures.txd" },
+ [18758] = { "VCElevatorFront1.dff", "VCInteriors.txd" },
+ [18790] = { "MRoadBend180Deg1.dff", "cs_ebridge.txd" },
+ [18822] = { "Tube50mGlass45Bend1.dff", "MatTextures.txd" },
+ [18854] = { "Tube100m90Bend1.dff", "MatTextures.txd" },
+ [18886] = { "ElectroMagnet1.dff", "ElectroMagnet1.txd" },
+ [18918] = { "Mask8.dff", "MatClothes.txd" },
+ [18950] = { "HatBowler4.dff", "MatClothes.txd" },
+ [18982] = { "Tube100m3.dff", "MatTextures.txd" },
+ [19014] = { "GlassesType9.dff", "MatGlasses.txd" },
+ [19046] = { "WatchType8.dff", "MatWatches.txd" },
+ [19078] = { "TheParrot1.dff", "TheParrot.txd" },
+ [19110] = { "ArmyHelmet10.dff", "ArmyHelmets.txd" },
+ [19142] = { "SWATArmour1.dff", "swat.txd" },
+ [19174] = { "SAMPPicture3.dff", "SAMPPictures.txd" },
+ [19206] = { "MapMarker6.dff", "MapMarkers.txd" },
+ [19238] = { "MapMarker38.dff", "MapMarkers.txd" },
+ [19270] = { "MapMarkerFire1.dff", "MapMarkers.txd" },
+ [19302] = { "pd_jail_door01.dff", "pd_jail_door01.txd" },
+ [19334] = { "Hot_Air_Balloon03.dff", "balloon_texts.txd" },
+ [19366] = { "wall014.dff", "all_walls.txd" },
+ [19398] = { "wall046.dff", "all_walls.txd" },
+ [19430] = { "wall070.dff", "all_walls.txd" },
+ [19462] = { "wall102.dff", "all_walls.txd" },
+ [19494] = { "sfhouse3int.dff", "all_walls.txd" },
+ [19526] = { "ATMFixed.dff", "CJ_CASINO_prop.txd" },
+ [19558] = { "PizzaHat1.dff", "wmypizz.txd" },
+ [19590] = { "WooziesSword1.dff", "ab_wooziec.txd" },
+ [19622] = { "Broom1.dff", "Broom1.txd" },
+ [19654] = { "TubeHalfSpiral2b.dff", "MatTubes.txd" },
+ [19686] = { "TubeHalf15Bend2b.dff", "MatTubes.txd" },
+ [19718] = { "MTubeHalfLight1.dff", "dynsigns.txd" },
+ [19750] = { "STubeHalf180Bend1a.dff", "MatTubes.txd" },
+ [19782] = { "InsigniaSergeant2.dff", "MatPoliceInsignias.txd" },
+ [19814] = { "ElectricalOutlet2.dff", "ElectricalOutlets.txd" },
+ [19846] = { "MetalPanel4.dff", "MetalPanels.txd" },
+ [19878] = { "Skateboard1.dff", "skateboard.txd" },
+ [19910] = { "A51Building3GrgDoor.dff", "a51_ext.txd" },
+ [19942] = { "PoliceRadio1.dff", "PoliceRadio1.txd" },
+ [19974] = { "SAMPRoadSign27.dff", "SAMPRoadSigns.txd" },
+ -- [18631] = { "NoModelFile.dff", "NoModelFile.txd" },
+ [18663] = { "SprayTag5.dff", "SprayTags.txd" },
+ [18695] = { "gunflash.dff", "MatTextures.txd" },
+ [18727] = { "smoke50lit.dff", "MatTextures.txd" },
+ [18759] = { "DMCage1.dff", "DMCages.txd" },
+ [18791] = { "MRoadBend45Deg.dff", "cs_ebridge.txd" },
+ [18823] = { "Tube50m90Bend1.dff", "MatTextures.txd" },
+ [18855] = { "Tube100m180Bend1.dff", "MatTextures.txd" },
+ [18887] = { "ForceField1.dff", "ForceFields.txd" },
+ [18919] = { "Mask9.dff", "MatClothes.txd" },
+ [18951] = { "HatBowler5.dff", "MatClothes.txd" },
+ [18983] = { "Tube100m4.dff", "MatTextures.txd" },
+ [19015] = { "GlassesType10.dff", "MatGlasses.txd" },
+ [19047] = { "WatchType9.dff", "MatWatches.txd" },
+ [19079] = { "TheParrot2.dff", "TheParrot.txd" },
+ [19111] = { "ArmyHelmet11.dff", "ArmyHelmets.txd" },
+ [19143] = { "PinSpotLight1.dff", "PinSpotLights.txd" },
+ [19175] = { "SAMPPicture4.dff", "SAMPPictures.txd" },
+ [19207] = { "MapMarker7.dff", "MapMarkers.txd" },
+ [19239] = { "MapMarker39.dff", "MapMarkers.txd" },
+ [19271] = { "MapMarkerLight1.dff", "MapMarkers.txd" },
+ [19303] = { "pd_jail_door02.dff", "pd_jail_door02.txd" },
+ [19335] = { "Hot_Air_Balloon04.dff", "balloon_texts.txd" },
+ [19367] = { "wall015.dff", "all_walls.txd" },
+ [19399] = { "wall047.dff", "all_walls.txd" },
+ [19431] = { "wall071.dff", "all_walls.txd" },
+ [19463] = { "wall103.dff", "all_walls.txd" },
+ [19495] = { "sfhouse4.dff", "boxhses_sfsx.txd" },
+ [19527] = { "Cauldron1.dff", "Cauldron1.txd" },
+ [19559] = { "HikerBackpack1.dff", "wmybp.txd" },
+ [19591] = { "WooziesHandFan1.dff", "ab_wooziec.txd" },
+ [19623] = { "Camera1.dff", "Camera1.txd" },
+ [19655] = { "TubeHalfSpiral3a.dff", "MatTubes.txd" },
+ [19687] = { "TubeHalf25m1.dff", "MatTubes.txd" },
+ [19719] = { "MTubeHalf5Bend1a.dff", "MatTubes.txd" },
+ [19751] = { "STubeHalf180Bend1b.dff", "MatTubes.txd" },
+ [19783] = { "InsigniaPOfficer2.dff", "MatPoliceInsignias.txd" },
+ [19815] = { "ToolBoard1.dff", "ToolBoard1.txd" },
+ [19847] = { "LegHam1.dff", "LegHam1.txd" },
+ [19879] = { "WellsFargoBuild1.dff", "lanblokc.txd" },
+ [19911] = { "A51HangarDoor1.dff", "a51_ext.txd" },
+ [19943] = { "StonePillar1.dff", "ab_pillartemp.txd" },
+ [19975] = { "SAMPRoadSign28.dff", "SAMPRoadSigns.txd" },
+ [18632] = { "FishingRod.dff", "FishingRod.txd" },
+ [18664] = { "SprayTag6.dff", "SprayTags.txd" },
+ [18696] = { "gunsmoke.dff", "MatTextures.txd" },
+ [18728] = { "smoke_flare.dff", "MatTextures.txd" },
+ [18760] = { "DMCage2.dff", "DMCages.txd" },
+ [18792] = { "MRoadTwist15DegL.dff", "cs_ebridge.txd" },
+ [18824] = { "Tube50mGlass90Bend1.dff", "MatTextures.txd" },
+ [18856] = { "Cage5mx5mx3m.dff", "MatTextures.txd" },
+ [18888] = { "ForceField2.dff", "ForceFields.txd" },
+ [18920] = { "Mask10.dff", "MatClothes.txd" },
+ [18952] = { "BoxingHelmet1.dff", "MatClothes.txd" },
+ [18984] = { "Tube100m5.dff", "MatTextures.txd" },
+ [19016] = { "GlassesType11.dff", "MatGlasses.txd" },
+ [19048] = { "WatchType10.dff", "MatWatches.txd" },
+ [19080] = { "LaserPointer2.dff", "LaserPointer2.txd" },
+ [19112] = { "ArmyHelmet12.dff", "ArmyHelmets.txd" },
+ [19144] = { "PinSpotLight2.dff", "PinSpotLights.txd" },
+ [19176] = { "LSOffice1Door1.dff", "skyscrapelan2.txd" },
+ [19208] = { "MapMarker8.dff", "MapMarkers.txd" },
+ [19240] = { "MapMarker40.dff", "MapMarkers.txd" },
+ [19272] = { "DMCage3.dff", "DMCages.txd" },
+ [19304] = { "pd_jail_door_top01.dff", "pd_jail_door_top01.txd" },
+ [19336] = { "Hot_Air_Balloon05.dff", "balloon_texts.txd" },
+ [19368] = { "wall016.dff", "all_walls.txd" },
+ [19400] = { "wall048.dff", "all_walls.txd" },
+ [19432] = { "wall072.dff", "all_walls.txd" },
+ [19464] = { "wall104.dff", "all_walls.txd" },
+ [19496] = { "sfhouse4int.dff", "all_walls.txd" },
+ [19528] = { "WitchesHat1.dff", "WitchesHat1.txd" },
+ [19560] = { "MeatTray1.dff", "MeatTray1.txd" },
+ [19592] = { "ShopBasket1.dff", "temp_shop.txd" },
+ [19624] = { "Case1.dff", "Case1.txd" },
+ [19656] = { "TubeHalfSpiral3b.dff", "MatTubes.txd" },
+ [19688] = { "TubeHalf45Bend3.dff", "MatTubes.txd" },
+ [19720] = { "MTubeHalf5Bend1b.dff", "MatTubes.txd" },
+ [19752] = { "STubeHalf90Bend1a.dff", "MatTubes.txd" },
+ [19784] = { "InsigniaPOfficer3.dff", "MatPoliceInsignias.txd" },
+ [19816] = { "OxygenCylinder1.dff", "OxygenCylinder1.txd" },
+ [19848] = { "CargoBobPlatform1.dff", "ab_cargo_int.txd" },
+ [19880] = { "WellsFargoGrgDoor1.dff", "lanblokc.txd" },
+ [19912] = { "SAMPMetalGate1.dff", "electricgate.txd" },
+ [19944] = { "BodyBag1.dff", "des_cen.txd" },
+ [19976] = { "SAMPRoadSign29.dff", "SAMPRoadSigns.txd" },
+ [18633] = { "GTASAWrench1.dff", "MatTextures.txd" },
+ [18665] = { "SprayTag7.dff", "SprayTags.txd" },
+ [18697] = { "heli_dust.dff", "MatTextures.txd" },
+ [18729] = { "spraycanp.dff", "MatTextures.txd" },
+ [18761] = { "RaceFinishLine1.dff", "MatRacing.txd" },
+ [18793] = { "MRoadTwist15DegR.dff", "cs_ebridge.txd" },
+ [18825] = { "Tube50m180Bend1.dff", "MatTextures.txd" },
+ [18857] = { "Cage20mx20mx10m.dff", "MatTextures.txd" },
+ [18889] = { "ForceField3.dff", "ForceFields.txd" },
+ [18921] = { "Beret1.dff", "MatClothes.txd" },
+ [18953] = { "CapKnit1.dff", "MatClothes.txd" },
+ [18985] = { "Tube100m6.dff", "MatTextures.txd" },
+ [19017] = { "GlassesType12.dff", "MatGlasses.txd" },
+ [19049] = { "WatchType11.dff", "MatWatches.txd" },
+ [19081] = { "LaserPointer3.dff", "LaserPointer3.txd" },
+ [19113] = { "SillyHelmet1.dff", "SillyHelmets.txd" },
+ [19145] = { "PinSpotLight3.dff", "PinSpotLights.txd" },
+ [19177] = { "MapMarkerNew1.dff", "MapMarkers.txd" },
+ [19209] = { "MapMarker9.dff", "MapMarkers.txd" },
+ [19241] = { "MapMarker41.dff", "MapMarkers.txd" },
+ [19273] = { "KeypadNonDynamic.dff", "keypad.txd" },
+ [19305] = { "sec_keypad2.dff", "keypad.txd" },
+ [19337] = { "Hot_Air_Balloon06.dff", "balloon_texts.txd" },
+ [19369] = { "wall017.dff", "all_walls.txd" },
+ [19401] = { "wall049.dff", "all_walls.txd" },
+ [19433] = { "wall073.dff", "all_walls.txd" },
+ [19465] = { "wall105.dff", "all_walls.txd" },
+ [19497] = { "lvhouse1.dff", "vegashse2.txd" },
+ [19529] = { "Plane125x125Grass1.dff", "beach_sfs.txd" },
+ [19561] = { "CerealBox1.dff", "cj_ss_1.txd" },
+ [19593] = { "ZomboTechBuilding1.dff", "bigwhitesfe.txd" },
+ [19625] = { "Ciggy1.dff", "Ciggy1.txd" },
+ [19657] = { "TubeHalfSpiral4a.dff", "MatTubes.txd" },
+ [19689] = { "TubeHalf45Bend4.dff", "MatTubes.txd" },
+ [19721] = { "MTubeHalf5Bend2a.dff", "MatTubes.txd" },
+ [19753] = { "STubeHalf90Bend1b.dff", "MatTubes.txd" },
+ [19785] = { "InsigniaSeniorLdOff.dff", "MatPoliceInsignias.txd" },
+ [19817] = { "CarFixerRamp1.dff", "CarFixerRamp1.txd" },
+ [19849] = { "MIHouse1Land.dff", "MIHouse1.txd" },
+ [19881] = { "KylieBarnFixed1.dff", "gf3.txd" },
+ [19913] = { "SAMPBigFence1.dff", "electricgate.txd" },
+ [19945] = { "CPSize16Red.dff", "EnExMarkers.txd" },
+ [19977] = { "SAMPRoadSign30.dff", "SAMPRoadSigns.txd" },
+ [18634] = { "GTASACrowbar1.dff", "MatTextures.txd" },
+ [18666] = { "SprayTag8.dff", "SprayTags.txd" },
+ [18698] = { "insects.dff", "MatTextures.txd" },
+ [18730] = { "tank_fire.dff", "MatTextures.txd" },
+ [18762] = { "Concrete1mx1mx5m.dff", "ConcreteBits.txd" },
+ [18794] = { "MRoadBend15Deg1.dff", "cs_ebridge.txd" },
+ [18826] = { "Tube50mGlass180Bend.dff", "MatTextures.txd" },
+ [18858] = { "FoamHoop1.dff", "MatRamps.txd" },
+ [18890] = { "Rake1.dff", "Rake1.txd" },
+ [18922] = { "Beret2.dff", "MatClothes.txd" },
+ [18954] = { "CapKnit2.dff", "MatClothes.txd" },
+ [18986] = { "TubeToPipe1.dff", "MatTextures.txd" },
+ [19018] = { "GlassesType13.dff", "MatGlasses.txd" },
+ [19050] = { "WatchType12.dff", "MatWatches.txd" },
+ [19082] = { "LaserPointer4.dff", "LaserPointer4.txd" },
+ [19114] = { "SillyHelmet2.dff", "SillyHelmets.txd" },
+ [19146] = { "PinSpotLight4.dff", "PinSpotLights.txd" },
+ [19178] = { "MapMarkerNew2.dff", "MapMarkers.txd" },
+ [19210] = { "MapMarker10.dff", "MapMarkers.txd" },
+ [19242] = { "MapMarker42.dff", "MapMarkers.txd" },
+ [19274] = { "Hair5.dff", "wmoice.txd" },
+ [19306] = { "kmb_goflag2.dff", "goflagx2.txd" },
+ [19338] = { "Hot_Air_Balloon07.dff", "balloon_texts.txd" },
+ [19370] = { "wall018.dff", "all_walls.txd" },
+ [19402] = { "wall050.dff", "all_walls.txd" },
+ [19434] = { "wall074.dff", "all_walls.txd" },
+ [19466] = { "window001.dff", "lsmall_shops.txd" },
+ [19498] = { "lvhouse1int.dff", "all_walls.txd" },
+ [19530] = { "Plane125x125Sand1.dff", "beach_sfs.txd" },
+ [19562] = { "CerealBox2.dff", "cj_ss_1.txd" },
+ [19594] = { "ZomboTechLab1.dff", "ZomboTechLab1.txd" },
+ [19626] = { "Spade1.dff", "Spade1.txd" },
+ [19658] = { "TubeHalfSpiral4b.dff", "MatTubes.txd" },
+ [19690] = { "TubeHalfNtoMJoin1a.dff", "MatTubes.txd" },
+ [19722] = { "MTubeHalf5Bend2b.dff", "MatTubes.txd" },
+ [19754] = { "STubeHalf12_5mDip1.dff", "MatTubes.txd" },
+ [19786] = { "LCDTVBig1.dff", "SAMPLCDTVs1.txd" },
+ [19818] = { "WineGlass1.dff", "lee_strip2_1.txd" },
+ [19850] = { "MIHouse1Land2.dff", "MIHouse1.txd" },
+ [19882] = { "MarcosSteak2.dff", "MarcosSteak2.txd" },
+ [19914] = { "CutsceneBat1.dff", "csbat.txd" },
+ [19946] = { "CPSize16Green.dff", "EnExMarkers.txd" },
+ [19978] = { "SAMPRoadSign31.dff", "SAMPRoadSigns.txd" },
+ [18635] = { "GTASAHammer1.dff", "MatTextures.txd" },
+ [18667] = { "SprayTag9.dff", "SprayTags.txd" },
+ [18699] = { "jetpackp.dff", "MatTextures.txd" },
+ [18731] = { "teargas99.dff", "MatTextures.txd" },
+ [18763] = { "Concrete3mx3mx5m.dff", "ConcreteBits.txd" },
+ [18795] = { "MRoadBend15Deg2.dff", "cs_ebridge.txd" },
+ [18827] = { "Tube100m2.dff", "MatTextures.txd" },
+ [18859] = { "QuarterPipe1.dff", "glenpark7_lae.txd" },
+ [18891] = { "Bandana1.dff", "MatClothes.txd" },
+ [18923] = { "Beret3.dff", "MatClothes.txd" },
+ [18955] = { "CapOverEye1.dff", "MatClothes.txd" },
+ [18987] = { "Tube25m1.dff", "MatTextures.txd" },
+ [19019] = { "GlassesType14.dff", "MatGlasses.txd" },
+ [19051] = { "WatchType13.dff", "MatWatches.txd" },
+ [19083] = { "LaserPointer5.dff", "LaserPointer5.txd" },
+ [19115] = { "SillyHelmet3.dff", "SillyHelmets.txd" },
+ [19147] = { "PinSpotLight5.dff", "PinSpotLights.txd" },
+ [19179] = { "MapMarkerNew3.dff", "MapMarkers.txd" },
+ [19211] = { "MapMarker11.dff", "MapMarkers.txd" },
+ [19243] = { "MapMarker43.dff", "MapMarkers.txd" },
+ [19307] = { "kmb_goflag3.dff", "goflagx2.txd" },
+ [19339] = { "coffin01.dff", "coffin01.txd" },
+ [19371] = { "wall019.dff", "all_walls.txd" },
+ [19403] = { "wall051.dff", "all_walls.txd" },
+ [19435] = { "wall075.dff", "all_walls.txd" },
+ [19467] = { "vehicle_barrier01.dff", "speed_bumps.txd" },
+ [19499] = { "lvhouse2.dff", "vegashse3.txd" },
+ [19531] = { "Plane125x125Conc1.dff", "cs_ebridge.txd" },
+ [19563] = { "JuiceBox1.dff", "cj_ss_1.txd" },
+ [19595] = { "LSAppartments1.dff", "LSAppartments1.txd" },
+ [19627] = { "Wrench1.dff", "Wrench1.txd" },
+ [19659] = { "TubeHalf180Bend1a.dff", "MatTubes.txd" },
+ [19691] = { "TubeHalfNtoMJoin1b.dff", "MatTubes.txd" },
+ [19723] = { "MTubeHalf45Bend1a.dff", "MatTubes.txd" },
+ [19755] = { "STubeHalf12_5mBump1.dff", "MatTubes.txd" },
+ [19787] = { "LCDTV1.dff", "SAMPLCDTVs1.txd" },
+ [19819] = { "CocktailGlass1.dff", "lee_strip2_1.txd" },
+ [19851] = { "MIHouse1Land3.dff", "MIHouse1.txd" },
+ [19883] = { "BreadSlice1.dff", "BreadSlice1.txd" },
+ [19915] = { "CutsceneCooker1.dff", "cscooker.txd" },
+ [19947] = { "CPSize16Blue.dff", "EnExMarkers.txd" },
+ [19979] = { "SAMPRoadSign32.dff", "SAMPRoadSigns.txd" },
+ [18636] = { "PoliceCap1.dff", "MatTextures.txd" },
+ [18668] = { "blood_heli.dff", "MatTextures.txd" },
+ [18700] = { "jetthrust.dff", "MatTextures.txd" },
+ [18732] = { "teargasAD.dff", "MatTextures.txd" },
+ [18764] = { "Concrete5mx5mx5m.dff", "ConcreteBits.txd" },
+ [18796] = { "MRoadBend15Deg3.dff", "cs_ebridge.txd" },
+ [18828] = { "SpiralTube1.dff", "MatRamps.txd" },
+ [18892] = { "Bandana2.dff", "MatClothes.txd" },
+ [18924] = { "Beret4.dff", "MatClothes.txd" },
+ [18956] = { "CapOverEye2.dff", "MatClothes.txd" },
+ [18988] = { "Tube25mCutEnd1.dff", "MatTextures.txd" },
+ [19020] = { "GlassesType15.dff", "MatGlasses.txd" },
+ [19052] = { "WatchType14.dff", "MatWatches.txd" },
+ [19084] = { "LaserPointer6.dff", "LaserPointer6.txd" },
+ [19116] = { "PlainHelmet1.dff", "PlainHelmets.txd" },
+ [19148] = { "PinSpotLight6.dff", "PinSpotLights.txd" },
+ [19180] = { "MapMarkerNew4.dff", "MapMarkers.txd" },
+ [19212] = { "MapMarker12.dff", "MapMarkers.txd" },
+ [19244] = { "MapMarker44.dff", "MapMarkers.txd" },
+ [11689] = { "CBoothSeat1.dff", "int_casinoint3.txd" },
+ [19308] = { "taxi01.dff", "taxi01.txd" },
+ [19340] = { "cslab01.dff", "carshow_sfse.txd" },
+ [19372] = { "wall020.dff", "all_walls.txd" },
+ [19404] = { "wall052.dff", "all_walls.txd" },
+ [19436] = { "wall076.dff", "all_walls.txd" },
+ [19468] = { "bucket01.dff", "bucket01.txd" },
+ [19500] = { "lvhouse2int.dff", "all_walls.txd" },
+ [19532] = { "15x125Road1.dff", "cs_ebridge.txd" },
+ [19564] = { "JuiceBox2.dff", "cj_ss_1.txd" },
+ [19628] = { "MRoadBend90Banked1.dff", "cs_ebridge.txd" },
+ [19660] = { "TubeHalf180Bend1b.dff", "MatTubes.txd" },
+ [19692] = { "MTubeSeg5m1.dff", "MatTubes.txd" },
+ [19724] = { "MTubeHalf45Bend1b.dff", "MatTubes.txd" },
+ [19756] = { "STubeHalfBowl1.dff", "MatTubes.txd" },
+ [19788] = { "15x15RoadCorner1.dff", "MatRoadInters1.txd" },
+ [19820] = { "AlcoholBottle1.dff", "lee_strip2_1.txd" },
+ [19852] = { "MIHouse1Land4.dff", "MIHouse1.txd" },
+ [19884] = { "WSBend45Deg2.dff", "WSSections.txd" },
+ [19916] = { "CutsceneFridge1.dff", "csfridge.txd" },
+ [19948] = { "SAMPRoadSign1.dff", "SAMPRoadSigns.txd" },
+ [19980] = { "SAMPRoadSign33.dff", "SAMPRoadSigns.txd" },
+ [18637] = { "PoliceShield1.dff", "MatTextures.txd" },
+ [18669] = { "boat_prop.dff", "MatTextures.txd" },
+ [18701] = { "molotov_flame.dff", "MatTextures.txd" },
+ [18733] = { "tree_hit_fir.dff", "MatTextures.txd" },
+ [18765] = { "Concrete10mx10mx5m.dff", "ConcreteBits.txd" },
+ [18797] = { "MRoadBend15Deg4.dff", "cs_ebridge.txd" },
+ [18829] = { "RTexturetube.dff", "MickyTextures.txd" },
+ [18893] = { "Bandana3.dff", "MatClothes.txd" },
+ [18925] = { "Beret5.dff", "MatClothes.txd" },
+ [18957] = { "CapOverEye3.dff", "MatClothes.txd" },
+ [18989] = { "Tube25m45Bend1.dff", "MatTextures.txd" },
+ [19021] = { "GlassesType16.dff", "MatGlasses.txd" },
+ [19053] = { "WatchType15.dff", "MatWatches.txd" },
+ [19085] = { "EyePatch1.dff", "EyePatch1.txd" },
+ [19117] = { "PlainHelmet2.dff", "PlainHelmets.txd" },
+ [19149] = { "PinSpotLight7.dff", "PinSpotLights.txd" },
+ [19181] = { "MapMarkerNew5.dff", "MapMarkers.txd" },
+ [19213] = { "MapMarker13.dff", "MapMarkers.txd" },
+ [19245] = { "MapMarker45.dff", "MapMarkers.txd" },
+ [19277] = { "LiftType1.dff", "MatLifts.txd" },
+ [19309] = { "taxi02.dff", "taxi02.txd" },
+ [19341] = { "easter_egg01.dff", "egg_texts.txd" },
+ [19373] = { "wall021.dff", "all_walls.txd" },
+ [19405] = { "wall053.dff", "all_walls.txd" },
+ [19437] = { "wall077.dff", "all_walls.txd" },
+ [19469] = { "scarf01.dff", "classy.txd" },
+ [19501] = { "lvhouse3.dff", "vegashse6.txd" },
+ [19533] = { "15x62_5Road1.dff", "cs_ebridge.txd" },
+ [19565] = { "IceCreamBarsBox1.dff", "cj_ss_4.txd" },
+ [19597] = { "LSBeachSideInsides.dff", "LSBeachSide.txd" },
+ [19629] = { "MRoadBend90Banked2.dff", "cs_ebridge.txd" },
+ [19661] = { "TubeHalf90Bend1a.dff", "MatTubes.txd" },
+ [19693] = { "MTubeSeg5m2a.dff", "MatTubes.txd" },
+ [19725] = { "MTubeHalf15Bend1a.dff", "MatTubes.txd" },
+ [19757] = { "STubeSupport1.dff", "dynsigns.txd" },
+ [19789] = { "Cube1mx1m.dff", "cs_ebridge.txd" },
+ [19821] = { "AlcoholBottle2.dff", "lee_strip2_1.txd" },
+ [19853] = { "MIHouse1Land5.dff", "MIHouse1.txd" },
+ [19885] = { "WSStraight2.dff", "WSSections.txd" },
+ [19917] = { "CutsceneEngine1.dff", "csengine.txd" },
+ [19949] = { "SAMPRoadSign2.dff", "SAMPRoadSigns.txd" },
+ [19981] = { "SAMPRoadSign34.dff", "SAMPRoadSigns.txd" },
+ [18638] = { "HardHat1.dff", "wmycon.txd" },
+ [18670] = { "camflash.dff", "MatTextures.txd" },
+ [18702] = { "nitrop.dff", "MatTextures.txd" },
+ [18734] = { "tree_hit_palm.dff", "MatTextures.txd" },
+ [18766] = { "Concrete10mx1mx5m.dff", "ConcreteBits.txd" },
+ [18798] = { "MRoadB45T15DegL.dff", "cs_ebridge.txd" },
+ [18830] = { "RTexturebridge.dff", "MickyTextures.txd" },
+ [18862] = { "GarbagePileRamp1.dff", "MatTextures.txd" },
+ [18894] = { "Bandana4.dff", "MatClothes.txd" },
+ [18926] = { "Hat1.dff", "MatClothes.txd" },
+ [18958] = { "CapOverEye4.dff", "MatClothes.txd" },
+ [18990] = { "Tube25m90Bend1.dff", "MatTextures.txd" },
+ [19022] = { "GlassesType17.dff", "MatGlasses.txd" },
+ [19054] = { "XmasBox1.dff", "XmasBoxes.txd" },
+ [19086] = { "ChainsawDildo1.dff", "sexdetail.txd" },
+ [19118] = { "PlainHelmet3.dff", "PlainHelmets.txd" },
+ [19150] = { "PinSpotLight8.dff", "PinSpotLights.txd" },
+ [19182] = { "MapMarkerNew6.dff", "MapMarkers.txd" },
+ [19214] = { "MapMarker14.dff", "MapMarkers.txd" },
+ [19246] = { "MapMarker46.dff", "MapMarkers.txd" },
+ [19278] = { "LiftPlatform1.dff", "SkyDivePlatforms.txd" },
+ [19310] = { "taxi03.dff", "taxi03.txd" },
+ [19342] = { "easter_egg02.dff", "egg_texts.txd" },
+ [19374] = { "wall022.dff", "all_walls.txd" },
+ [19406] = { "wall054.dff", "all_walls.txd" },
+ [19438] = { "wall078.dff", "all_walls.txd" },
+ [19470] = { "forsale01.dff", "forsale01.txd" },
+ [19502] = { "lvhouse3int.dff", "all_walls.txd" },
+ [19534] = { "15x15RoadInters1.dff", "MatRoadInters1.txd" },
+ [19566] = { "FishFingersBox1.dff", "cj_ss_4.txd" },
+ [19598] = { "SFBuilding1Outside.dff", "SFBuilding1.txd" },
+ [19630] = { "Fish1.dff", "Fish1SAMP.txd" },
+ [19662] = { "TubeHalf90Bend1b.dff", "MatTubes.txd" },
+ [19694] = { "MTubeSeg5m2b.dff", "MatTubes.txd" },
+ [19726] = { "MTubeHalf15Bend1b.dff", "MatTubes.txd" },
+ [19758] = { "STubeSupport2.dff", "dynsigns.txd" },
+ [19790] = { "Cube5mx5m.dff", "cs_ebridge.txd" },
+ [19822] = { "AlcoholBottle3.dff", "lee_strip2_1.txd" },
+ [19854] = { "MIHouse1Outside.dff", "MIHouse1.txd" },
+ [19886] = { "WSStraight3.dff", "WSSections.txd" },
+ [19918] = { "CutsceneBox1.dff", "csheistbox.txd" },
+ [19950] = { "SAMPRoadSign3.dff", "SAMPRoadSigns.txd" },
+ [19982] = { "SAMPRoadSign35.dff", "SAMPRoadSigns.txd" },
+ [18639] = { "BlackHat1.dff", "dwmolc2.txd" },
+ [18671] = { "carwashspray.dff", "MatTextures.txd" },
+ [18703] = { "overheat_car.dff", "MatTextures.txd" },
+ [18735] = { "vent2.dff", "MatTextures.txd" },
+ [18767] = { "ConcreteStair1.dff", "ConcreteBits.txd" },
+ [18799] = { "MRoadB45T15DegR.dff", "cs_ebridge.txd" },
+ [18831] = { "RT25mBend90Tube1.dff", "MickyTextures.txd" },
+ [18863] = { "SnowArc1.dff", "FakeSnow1.txd" },
+ [18895] = { "Bandana5.dff", "MatClothes.txd" },
+ [18927] = { "Hat2.dff", "MatClothes.txd" },
+ [18959] = { "CapOverEye5.dff", "MatClothes.txd" },
+ [18991] = { "Tube25m180Bend1.dff", "MatTextures.txd" },
+ [19023] = { "GlassesType18.dff", "MatGlasses.txd" },
+ [19055] = { "XmasBox2.dff", "XmasBoxes.txd" },
+ [19087] = { "Rope1.dff", "MatRopes.txd" },
+ [19119] = { "PlainHelmet4.dff", "PlainHelmets.txd" },
+ [19151] = { "PinSpotLight9.dff", "PinSpotLights.txd" },
+ [19183] = { "MapMarkerNew7.dff", "MapMarkers.txd" },
+ [19215] = { "MapMarker15.dff", "MapMarkers.txd" },
+ [19247] = { "MapMarker47.dff", "MapMarkers.txd" },
+ [19279] = { "LCSmallLight1.dff", "MatLights.txd" },
+ [19311] = { "taxi04.dff", "taxi04.txd" },
+ [19343] = { "easter_egg03.dff", "egg_texts.txd" },
+ [19375] = { "wall023.dff", "all_walls.txd" },
+ [19407] = { "wall055.dff", "all_walls.txd" },
+ [19439] = { "wall079.dff", "all_walls.txd" },
+ [19471] = { "forsale02.dff", "forsale01.txd" },
+ [19503] = { "lvhouse4.dff", "vegashse4.txd" },
+ [19535] = { "15x15RoadInters2.dff", "MatRoadInters1.txd" },
+ [19567] = { "IcecreamContainer1.dff", "cj_ss_4.txd" },
+ [19599] = { "SFBuilding1Inside.dff", "SFBuilding1.txd" },
+ [19631] = { "SledgeHammer1.dff", "SledgeHammer1.txd" },
+ [19663] = { "TubeHalf50mDip1.dff", "MatTubes.txd" },
+ [19695] = { "MTubeSeg12_5m1.dff", "MatTubes.txd" },
+ [19727] = { "MTubeHalf15Bend2a.dff", "MatTubes.txd" },
+ [19759] = { "STubeHalfLight1.dff", "dynsigns.txd" },
+ [19791] = { "Cube10mx10m.dff", "cs_ebridge.txd" },
+ [19823] = { "AlcoholBottle4.dff", "lee_strip2_1.txd" },
+ [19855] = { "MIHouse1Inside.dff", "MIHouse1.txd" },
+ [19887] = { "WSStart1.dff", "WSSections.txd" },
+ [19919] = { "CutscenePerch1.dff", "csparrotperch.txd" },
+ [19951] = { "SAMPRoadSign4.dff", "SAMPRoadSigns.txd" },
+ [19983] = { "SAMPRoadSign36.dff", "SAMPRoadSigns.txd" },
+ [11753] = { "AreaBoundary1m.dff", "EnExMarkers.txd" },
+ [11752] = { "AreaBoundary10m.dff", "EnExMarkers.txd" },
+ [11751] = { "AreaBoundary50m.dff", "EnExMarkers.txd" },
+ [11750] = { "CSHandcuffs2.dff", "CSHandcuffs1.txd" },
+ [11749] = { "CSHandcuffs1.dff", "CSHandcuffs1.txd" },
+ [11748] = { "BandagePack1.dff", "Bandages.txd" },
+ [11747] = { "Bandage1.dff", "Bandages.txd" },
+ [11746] = { "DoorKey1.dff", "DoorKey1.txd" },
+ [11745] = { "HoldAllEdited1.dff", "kmb_chute.txd" },
+ [11744] = { "MPlate1.dff", "MarcosStuff1.txd" },
+ [11743] = { "MCoffeeMachine1.dff", "MarcosStuff1.txd" },
+ [11742] = { "MCakeSlice1.dff", "MarcosStuff1.txd" },
+ [11741] = { "MCake3.dff", "MarcosStuff1.txd" },
+ [11740] = { "MCake2.dff", "MarcosStuff1.txd" },
+ [11739] = { "MCake1.dff", "MarcosStuff1.txd" },
+ [11738] = { "MedicCase1.dff", "MedicCase1.txd" },
+ [11737] = { "RockstarMat1.dff", "carter_block_2.txd" },
+ [18640] = { "Hair1.dff", "smyst.txd" },
+ [18672] = { "cementp.dff", "MatTextures.txd" },
+ [18704] = { "overheat_car_elec.dff", "MatTextures.txd" },
+ [18736] = { "vent.dff", "MatTextures.txd" },
+ [18768] = { "SkyDivePlatform1.dff", "SkyDivePlatforms.txd" },
+ [18800] = { "MRoadHelix1.dff", "MRoadHelix1.txd" },
+ [18832] = { "RT25mBend180Tube1.dff", "MickyTextures.txd" },
+ [18864] = { "FakeSnow1.dff", "FakeSnow1.txd" },
+ [18896] = { "Bandana6.dff", "MatClothes.txd" },
+ [18928] = { "Hat3.dff", "MatClothes.txd" },
+ [18960] = { "CapRimUp1.dff", "MatClothes.txd" },
+ [18992] = { "Tube10m45Bend1.dff", "MatTextures.txd" },
+ [19024] = { "GlassesType19.dff", "MatGlasses.txd" },
+ [19056] = { "XmasBox3.dff", "XmasBoxes.txd" },
+ [19088] = { "Rope2.dff", "MatRopes.txd" },
+ [19120] = { "PlainHelmet5.dff", "PlainHelmets.txd" },
+ [19152] = { "PinSpotLight10.dff", "PinSpotLights.txd" },
+ [19184] = { "MapMarkerNew8.dff", "MapMarkers.txd" },
+ [19216] = { "MapMarker16.dff", "MapMarkers.txd" },
+ [19248] = { "MapMarker48.dff", "MapMarkers.txd" },
+ [19280] = { "CarRoofLight1.dff", "MatLights.txd" },
+ [19312] = { "a51fencing.dff", "a51fencing.txd" },
+ [19344] = { "easter_egg04.dff", "egg_texts.txd" },
+ [19376] = { "wall024.dff", "all_walls.txd" },
+ [19408] = { "wall056.dff", "all_walls.txd" },
+ [19440] = { "wall080.dff", "all_walls.txd" },
+ [19472] = { "gasmask01.dff", "gasmask01.txd" },
+ [19504] = { "lvhouse4int.dff", "all_walls.txd" },
+ [19536] = { "Plane62_5x125Grass1.dff", "beach_sfs.txd" },
+ [19568] = { "IcecreamContainer2.dff", "cj_ss_4.txd" },
+ [19600] = { "SFBuilding1Land.dff", "SFBuilding1.txd" },
+ [19632] = { "FireWood1.dff", "FireWood1.txd" },
+ [19664] = { "TubeHalf50mBump1.dff", "MatTubes.txd" },
+ [19696] = { "MTubeHalf10m1.dff", "MatTubes.txd" },
+ [19728] = { "MTubeHalf15Bend2b.dff", "MatTubes.txd" },
+ [19760] = { "STubeHalf5Bend1a.dff", "MatTubes.txd" },
+ [19792] = { "SAMPKeycard1.dff", "SAMPKeycard1.txd" },
+ [19824] = { "AlcoholBottle5.dff", "lee_strip2_1.txd" },
+ [19856] = { "MIHouse1IntWalls1.dff", "MIHouse1.txd" },
+ [19888] = { "WSBend45Deg3.dff", "WSSections.txd" },
+ [19920] = { "CutsceneRemote1.dff", "csremote.txd" },
+ [19952] = { "SAMPRoadSign5.dff", "SAMPRoadSigns.txd" },
+ [19984] = { "SAMPRoadSign37.dff", "SAMPRoadSigns.txd" },
+ [11736] = { "MedicalSatchel1.dff", "paperchase_bits2.txd" },
+ [11735] = { "WBoot1.dff", "whore_rms.txd" },
+ [11734] = { "WRockingChair1.dff", "whore_rms.txd" },
+ [11733] = { "WRockingHorse1.dff", "whore_rms.txd" },
+ [11732] = { "WHeartBath1.dff", "whore_rms.txd" },
+ [11731] = { "WHeartBed1.dff", "whore_rms.txd" },
+ [11730] = { "GymLockerOpen1.dff", "intring_gymint3.txd" },
+ [11729] = { "GymLockerClosed1.dff", "intring_gymint3.txd" },
+ [11728] = { "PaperChasePhone1.dff", "papaerchaseoffice.txd" },
+ [11727] = { "PaperChaseLight1.dff", "papaerchaseoffice.txd" },
+ [11726] = { "HangingLight1.dff", "sfhosemed2.txd" },
+ [11725] = { "Fireplace1.dff", "svcunthoose.txd" },
+ [11724] = { "FireplaceSurround1.dff", "svcunthoose.txd" },
+ [11723] = { "SauceBottle2.dff", "ab_trukstpb.txd" },
+ [11722] = { "SauceBottle1.dff", "ab_trukstpb.txd" },
+ [11721] = { "Radiator1.dff", "lahss2_2int2.txd" },
+ [11720] = { "SweetsBed1.dff", "SweetsStuff1.txd" },
+ [11719] = { "SweetsSaucepan2.dff", "SweetsStuff1.txd" },
+ [11718] = { "SweetsSaucepan1.dff", "SweetsStuff1.txd" },
+ [11717] = { "WooziesCouch1.dff", "ab_wooziec.txd" },
+ [11716] = { "MetalKnife1.dff", "gb_dirtycrock01.txd" },
+ [18641] = { "Flashlight1.dff", "Flashlight1.txd" },
+ [18673] = { "cigarette_smoke.dff", "MatTextures.txd" },
+ [18705] = { "petrolcan.dff", "MatTextures.txd" },
+ [18737] = { "wallbust.dff", "MatTextures.txd" },
+ [18769] = { "SkyDivePlatform1a.dff", "SkyDivePlatforms.txd" },
+ [18801] = { "MRoadLoop1.dff", "cs_ebridge.txd" },
+ [18833] = { "RT50mBend45Tube1.dff", "MickyTextures.txd" },
+ [18865] = { "MobilePhone1.dff", "MobilePhone1.txd" },
+ [18897] = { "Bandana7.dff", "MatClothes.txd" },
+ [18929] = { "Hat4.dff", "MatClothes.txd" },
+ [18961] = { "CapTrucker1.dff", "MatClothes.txd" },
+ [18993] = { "Tube10m90Bend1.dff", "MatTextures.txd" },
+ [19025] = { "GlassesType20.dff", "MatGlasses.txd" },
+ [19057] = { "XmasBox4.dff", "XmasBoxes.txd" },
+ [19089] = { "Rope3.dff", "MatRopes.txd" },
+ [19121] = { "BollardLight1.dff", "metal.txd" },
+ [19153] = { "PinSpotLight11.dff", "PinSpotLights.txd" },
+ [19185] = { "MapMarkerNew9.dff", "MapMarkers.txd" },
+ [19217] = { "MapMarker17.dff", "MapMarkers.txd" },
+ [19249] = { "MapMarker49.dff", "MapMarkers.txd" },
+ [19281] = { "PointLight1.dff", "MatLights.txd" },
+ [19313] = { "a51fensin.dff", "a51fencing.txd" },
+ [19345] = { "easter_egg05.dff", "egg_texts.txd" },
+ [19377] = { "wall025.dff", "all_walls.txd" },
+ [19409] = { "wall057.dff", "all_walls.txd" },
+ [19441] = { "wall081.dff", "all_walls.txd" },
+ [19473] = { "grassplant01.dff", "grasshouse.txd" },
+ [19505] = { "lshouse1.dff", "ganghouse1_lax.txd" },
+ [19537] = { "Plane62_5x125Sand1.dff", "beach_sfs.txd" },
+ [19569] = { "MilkCarton1.dff", "cj_ss_2.txd" },
+ [19601] = { "SnowPlow1.dff", "SnowPlow1.txd" },
+ [19633] = { "Ramp360Degree1.dff", "landjump.txd" },
+ [19665] = { "TubeHalfLoop1a.dff", "MatTubes.txd" },
+ [19697] = { "MTubeHalf5mJoin1a.dff", "MatTubes.txd" },
+ [19729] = { "MTubeHalf45Bend3.dff", "MatTubes.txd" },
+ [19761] = { "STubeHalf5Bend1b.dff", "MatTubes.txd" },
+ [19793] = { "FireWoodLog1.dff", "FireWood1.txd" },
+ [19825] = { "SprunkClock1.dff", "SprunkClock1.txd" },
+ [19857] = { "MIHouse1Door1.dff", "MIHouse1.txd" },
+ [19889] = { "WSBend45Deg4.dff", "WSSections.txd" },
+ [19921] = { "CutsceneToolBox1.dff", "cstoolkit.txd" },
+ [19953] = { "SAMPRoadSign6.dff", "SAMPRoadSigns.txd" },
+ [19985] = { "SAMPRoadSign38.dff", "SAMPRoadSigns.txd" },
+ [11715] = { "MetalFork1.dff", "gb_dirtycrock01.txd" },
+ [11714] = { "MaintenanceDoors1.dff", "maint1.txd" },
+ [11713] = { "FireExtPanel1.dff", "maint1.txd" },
+ [11712] = { "Cross1.dff", "Cross1.txd" },
+ [11711] = { "ExitSign1.dff", "eastbeach8_lae2.txd" },
+ [11710] = { "FireExitSign1.dff", "eastbeach8_lae2.txd" },
+ [11709] = { "AbattoirSink1.dff", "ab_abbatoir01.txd" },
+ [11708] = { "BrickSingle1.dff", "sw_well1.txd" },
+ [11707] = { "TowelRack1.dff", "cuntcuts.txd" },
+ [11706] = { "SmallWasteBin1.dff", "cuntcuts.txd" },
+ [11705] = { "BlackTelephone1.dff", "cuntcuts.txd" },
+ [11704] = { "BDupsMask1.dff", "bdupsnew.txd" },
+ [11703] = { "MagnoCrane_03_2.dff", "cranes_dyn2_cj.txd" },
+ [11702] = { "AmbulanceLights2.dff", "AmbulanceLights1.txd" },
+ [11701] = { "AmbulanceLights1.dff", "AmbulanceLights1.txd" },
+ [11700] = { "SAMPRoadSign47.dff", "SAMPRoadSigns.txd" },
+ [11699] = { "SAMPRoadSign46.dff", "SAMPRoadSigns.txd" },
+ [11698] = { "RopeBridgePart2.dff", "RopeBridge.txd" },
+ [11697] = { "RopeBridgePart1.dff", "RopeBridge.txd" },
+ [11696] = { "Hill250x250Rocky3.dff", "Hill250x250Rocky3.txd" },
+ [11695] = { "Hill250x250Rocky2.dff", "des_sw.txd" },
+ [18642] = { "Taser1.dff", "Taser1.txd" },
+ [18674] = { "cloudfast.dff", "MatTextures.txd" },
+ [18706] = { "prt_blood.dff", "MatTextures.txd" },
+ [18738] = { "water_fnt_tme.dff", "MatTextures.txd" },
+ [18770] = { "SkyDivePlatform1b.dff", "SkyDivePlatforms.txd" },
+ [18802] = { "MBridgeRamp1.dff", "cs_ebridge.txd" },
+ [18834] = { "RT50mBend180Tube1.dff", "MickyTextures.txd" },
+ [18866] = { "MobilePhone2.dff", "MobilePhone2.txd" },
+ [18898] = { "Bandana8.dff", "MatClothes.txd" },
+ [18930] = { "Hat5.dff", "MatClothes.txd" },
+ [18962] = { "CowboyHat2.dff", "CowboyHats.txd" },
+ [18994] = { "Tube10m180Bend1.dff", "MatTextures.txd" },
+ [19026] = { "GlassesType21.dff", "MatGlasses.txd" },
+ [19058] = { "XmasBox5.dff", "XmasBoxes.txd" },
+ [19090] = { "PomPomBlue.dff", "PomPoms.txd" },
+ [19122] = { "BollardLight2.dff", "metal.txd" },
+ [19154] = { "PinSpotLight12.dff", "PinSpotLights.txd" },
+ [19186] = { "MapMarkerNew10.dff", "MapMarkers.txd" },
+ [19218] = { "MapMarker18.dff", "MapMarkers.txd" },
+ [19250] = { "MapMarker50.dff", "MapMarkers.txd" },
+ [19282] = { "PointLight2.dff", "MatLights.txd" },
+ [19314] = { "bullhorns01.dff", "bullhorns01.txd" },
+ [19346] = { "hotdog01.dff", "hotdog01.txd" },
+ [19378] = { "wall026.dff", "all_walls.txd" },
+ [19410] = { "wall058.dff", "all_walls.txd" },
+ [19442] = { "wall082.dff", "all_walls.txd" },
+ [19474] = { "pokertable01.dff", "kbroul1.txd" },
+ [19506] = { "lshouse1int.dff", "all_walls.txd" },
+ [19538] = { "Plane62_5x125Conc1.dff", "cs_ebridge.txd" },
+ [19570] = { "MilkBottle1.dff", "cj_ss_2.txd" },
+ [19602] = { "Landmine1.dff", "mine.txd" },
+ [19634] = { "Ramp360Degree2.dff", "landjump.txd" },
+ [19666] = { "TubeHalfLoop1b.dff", "MatTubes.txd" },
+ [19698] = { "MTubeHalf5mJoin1b.dff", "MatTubes.txd" },
+ [19730] = { "MTubeHalf45Bend4.dff", "MatTubes.txd" },
+ [19762] = { "STubeHalf5Bend2a.dff", "MatTubes.txd" },
+ [19794] = { "LSPrisonWalls1.dff", "civic04_lan.txd" },
+ [19826] = { "LightSwitch1.dff", "LightSwitches.txd" },
+ [19858] = { "MIHouse1Door2.dff", "vegashse2.txd" },
+ [19890] = { "WSStraight4.dff", "WSSections.txd" },
+ [19922] = { "MKTable1.dff", "cswzkitch.txd" },
+ [19954] = { "SAMPRoadSign7.dff", "SAMPRoadSigns.txd" },
+ [19986] = { "SAMPRoadSign39.dff", "SAMPRoadSigns.txd" },
+ [11694] = { "Hill250x250Rocky1.dff", "des_sw.txd" },
+ [11693] = { "Hills250x250Grass1.dff", "beach_sfs.txd" },
+ [11692] = { "A51LandBit1.dff", "a51_ext.txd" },
+ [11691] = { "CTable2.dff", "int_casinoint3.txd" },
+ [11690] = { "CTable1.dff", "int_casinoint3.txd" },
+ [11688] = { "CWorkTop1.dff", "int_casinoint3.txd" },
+ [11687] = { "CBarStool1.dff", "int_casinoint3.txd" },
+ [11686] = { "CBarSection1.dff", "int_casinoint3.txd" },
+ [11685] = { "CutsceneCouch4.dff", "csmafcouch.txd" },
+ [11684] = { "CutsceneCouch3.dff", "csmafcouch.txd" },
+ [11683] = { "CutsceneCouch2.dff", "csmafcouch.txd" },
+ [11682] = { "CutsceneCouch1.dff", "csmafcouch.txd" },
+}
+
+addEventHandler("onResourceStart", resourceRoot, function()
+ local MODELS_FOLDER = "models/"
+ local folderPath = ":" .. getResourceName(resource) .. "/" .. MODELS_FOLDER
+ local listToAdd = {}
+
+ for id, modelInfo in pairs(SAMP_FILES) do
+ listToAdd[#listToAdd + 1] = {
+ type = "object",
+ base_id = 1337,
+ id = id,
+ name = modelInfo[1]:gsub(".dff", ""),
+ path = {
+ dff = folderPath .. id .. ".dff",
+ txd = folderPath .. id .. ".txd",
+ col = folderPath .. id .. ".col",
+ },
+ metaDownloadFalse = true,
+ }
+ end
+
+ -- Async loading
+ exports["newmodels_red"]:addExternalModels(listToAdd, true)
+end, false)
diff --git a/[examples]/test_vehicles/c_test_gui.lua b/[examples]/test_vehicles/c_test_gui.lua
index e6cfe36..e82028d 100644
--- a/[examples]/test_vehicles/c_test_gui.lua
+++ b/[examples]/test_vehicles/c_test_gui.lua
@@ -20,11 +20,12 @@ local function spawnVehicleByID(vehicleID)
end
local x, y, z = getElementPosition(localPlayer)
- local _, _ rot = getElementRotation(localPlayer)
+ local _, _
+ rot = getElementRotation(localPlayer)
local offsetDistance = 5
local spawnX = x + offsetDistance * math.sin(math.rad(-rot))
local spawnY = y + offsetDistance * math.cos(math.rad(-rot))
- triggerServerEvent("newmodels-test_vehicles:requestVehicleSpawn", resourceRoot, localPlayer, vehicleID, spawnX, spawnY, z, rot)
+ triggerServerEvent("newmodels-test_vehicles:requestVehicleSpawn", resourceRoot, vehicleID, spawnX, spawnY, z, rot)
end
local function requestVehicleSpawn()
diff --git a/[examples]/test_vehicles/meta.xml b/[examples]/test_vehicles/meta.xml
index 8bbf630..aa19967 100644
--- a/[examples]/test_vehicles/meta.xml
+++ b/[examples]/test_vehicles/meta.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/[examples]/test_vehicles/s_test_cmd.lua b/[examples]/test_vehicles/s_test_cmd.lua
index d17c625..c4af263 100644
--- a/[examples]/test_vehicles/s_test_cmd.lua
+++ b/[examples]/test_vehicles/s_test_cmd.lua
@@ -1,5 +1,5 @@
-- Outputs, for example:
--- This vehicle has the custom model ID -1, which is based on the default model ID 490 (FBI Rancher)
+-- This vehicle has the custom model ID 80001, which is based on the default model ID 507 (Elegant)
addCommandHandler("myvehicle", function(player)
local vehicle = getPedOccupiedVehicle(player)
if not vehicle then
@@ -7,34 +7,45 @@ addCommandHandler("myvehicle", function(player)
return
end
local serversideModel = getElementModel(vehicle)
- local customModel = exports["newmodels_azul"]:getElementCustomModel(vehicle)
+ local customModel = exports["newmodels_red"]:getElementCustomModel(vehicle)
if not customModel then
- outputChatBox("This vehicle has the default model ID " .. serversideModel .. " ("..(tostring(getVehicleNameFromModel(serversideModel)) or "")..")", player, 0, 255, 0)
+ outputChatBox(
+ "This vehicle has the default model ID " ..
+ serversideModel .. " (" .. (tostring(getVehicleNameFromModel(serversideModel)) or "") .. ")", player, 0, 255,
+ 0)
else
- local baseModel = exports["newmodels_azul"]:getElementBaseModel(vehicle)
+ local baseModel = exports["newmodels_red"]:getElementBaseModel(vehicle)
if not baseModel then
- outputChatBox("This vehicle has the custom model ID " .. customModel .. ", but the base model ID could not be determined", player, 255, 0, 0)
+ outputChatBox(
+ "This vehicle has the custom model ID " ..
+ customModel .. ", but the base model ID could not be determined",
+ player, 255, 0, 0)
return
end
- outputChatBox("This vehicle has the custom model ID " .. customModel .. ", which is based on the default model ID " .. baseModel .. " ("..(tostring(getVehicleNameFromModel(baseModel)) or "")..")", player, 0, 255, 0)
+ outputChatBox(
+ "This vehicle has the custom model ID " ..
+ customModel ..
+ ", which is based on the default model ID " ..
+ baseModel .. " (" .. (tostring(getVehicleNameFromModel(baseModel)) or "") .. ")", player, 0, 255, 0)
end
end, false, false)
addEvent("newmodels-test_vehicles:requestVehicleSpawn", true)
-addEventHandler("newmodels-test_vehicles:requestVehicleSpawn", resourceRoot, function(player, vehicleID, x, y, z, rot)
- local customModels = exports['newmodels_azul']:getCustomModels()
+addEventHandler("newmodels-test_vehicles:requestVehicleSpawn", resourceRoot, function(vehicleID, x, y, z, rot)
+ if not client then return end
+ local customModels = exports['newmodels_red']:getCustomModels()
local isValidCustomModel = customModels[vehicleID] and true or false
- local isValidDefaultID = exports['newmodels_azul']:isDefaultID("vehicle", vehicleID)
+ local isValidDefaultID = exports['newmodels_red']:isDefaultID("vehicle", vehicleID)
if not isValidCustomModel and not isValidDefaultID then
- triggerClientEvent(player, "newmodels-test_vehicles:vehicleSpawnResponse", player, false, "Invalid ID")
+ outputChatBox("Invalid ID", client, 255, 0, 0)
return
end
- local vehicle = exports['newmodels_azul']:createVehicle(vehicleID, x, y, z, 0, 0, rot)
+ local vehicle = exports['newmodels_red']:createVehicle(vehicleID, x, y, z, 0, 0, rot)
if isElement(vehicle) then
- triggerClientEvent(player, "newmodels-test_vehicles:vehicleSpawnResponse", player, true, "Vehicle spawned")
+ outputChatBox("Vehicle created with ID " .. vehicleID, client, 0, 255, 0)
else
- triggerClientEvent(player, "newmodels-test_vehicles:vehicleSpawnResponse", player, false, "Failed")
+ outputChatBox("Failed to create vehicle", client, 255, 0, 0)
end
-end)
+end, false)
diff --git a/[examples]/test_vehicles/s_vehicles.lua b/[examples]/test_vehicles/s_vehicles.lua
index a104336..5465a74 100644
--- a/[examples]/test_vehicles/s_vehicles.lua
+++ b/[examples]/test_vehicles/s_vehicles.lua
@@ -1,22 +1,23 @@
-- Method with exports
--- These vehicles will be destroyed if newmodels_azul stops
+-- These vehicles will be destroyed if newmodels_red stops
-- because they are children of that resource.
-- Vehicle model, x,y,z, rx,ry,rz, interior,dimension
local VEHICLE_SPAWNS = {
- {490, -941.95, 1043.03, 24.25, 355.90, 356.51, 199.00, 0, 0},
- {-1, -947.94, 1060.05, 25.96, 356.28, 356.34, 204.01, 0, 0},
- {-420, -926, 1010.67, 22, 356.28, 356.34, 204.01, 0, 0},
+ { 490, -941.95, 1043.03, 24.25, 355.90, 356.51, 199.00, 0, 0 },
+ { 80003, -947.94, 1060.05, 25.96, 356.28, 356.34, 204.01, 0, 0 },
+ { 80006, -926, 1010.67, 22, 356.28, 356.34, 204.01, 0, 0 },
}
local function createVehicles()
- for i, data in ipairs(VEHICLE_SPAWNS) do
+ for _, data in ipairs(VEHICLE_SPAWNS) do
local model, x, y, z, rx, ry, rz, interior, dimension = unpack(data)
- local vehicle = exports["newmodels_azul"]:createVehicle(model, x, y, z, rx, ry, rz)
+ local vehicle = exports["newmodels_red"]:createVehicle(model, x, y, z, rx, ry, rz)
if vehicle then
setElementInterior(vehicle, interior)
setElementDimension(vehicle, dimension)
- print("test_vehicles #" .. i .. " - Created vehicle with ID " .. model .. " at " .. x .. ", " .. y .. ", " .. z)
+ -- print("test_vehicles #" ..
+ -- i .. " - Created vehicle with ID " .. model .. " at " .. x .. ", " .. y .. ", " .. z)
end
end
end
diff --git a/[examples]/test_vehicles/s_vehicles_alt.lua b/[examples]/test_vehicles/s_vehicles_alt.lua
index aae6d00..530782f 100644
--- a/[examples]/test_vehicles/s_vehicles_alt.lua
+++ b/[examples]/test_vehicles/s_vehicles_alt.lua
@@ -1,25 +1,26 @@
-- Alternative method with loadstring
--- These vehicles will not be destroyed if newmodels_azul stops
+-- These vehicles will not be destroyed if newmodels_red stops
-- because the elements are children of this resource on creation.
-- Loads newmodels functions, which allow usage of custom model IDs "as if they were normal IDs"
-loadstring(exports.newmodels_azul:import())()
+loadstring(exports.newmodels_red:import())()
-- Vehicle model, x,y,z, rx,ry,rz, interior,dimension
local VEHICLE_SPAWNS = {
- {525, -938.74, 1034.21, 23.59, 3.42, 2.85, 20.27, 0, 0},
- {-5, -944.88, 1051.90, 24.84, 355.97, 356.23, 198.86, 0, 0},
- {-69, -924.62, 1015.67, 22, 355.97, 0, 0, 0, 0},
+ { 525, -938.74, 1034.21, 23.59, 3.42, 2.85, 20.27, 0, 0 },
+ { 80001, -944.88, 1051.90, 24.84, 355.97, 356.23, 198.86, 0, 0 },
+ { 80002, -924.62, 1015.67, 22, 355.97, 0, 0, 0, 0 },
}
local function createVehicles()
- for i, data in ipairs(VEHICLE_SPAWNS) do
+ for _, data in ipairs(VEHICLE_SPAWNS) do
local model, x, y, z, rx, ry, rz, interior, dimension = unpack(data)
local vehicle = createVehicle(model, x, y, z, rx, ry, rz)
if vehicle then
setElementInterior(vehicle, interior)
setElementDimension(vehicle, dimension)
- print("test_vehicles [alt] #" .. i .. " - Created vehicle with ID " .. model .. " at " .. x .. ", " .. y .. ", " .. z)
+ -- print("test_vehicles [alt] #" ..
+ -- i .. " - Created vehicle with ID " .. model .. " at " .. x .. ", " .. y .. ", " .. z)
end
end
end
diff --git a/newmodels_azul/models/README.md b/newmodels_azul/models/README.md
deleted file mode 100644
index ee94c74..0000000
--- a/newmodels_azul/models/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# `models` folder explained
-
-Model files (ending in `.dff`/`.col`/`.txd`) must be placed in this `models` folder **following specific rules**.
-
-See the [example files](/newmodels_azul/models/) to visualize the structure of this system.
-
-- **Required:** Vehicles must be placed in the `vehicle` folder, Skins in `ped`, and Objects in `object`
-
-- **Required:** Inside the type folder, you must create a folder with the base model ID of the new model (e.g. `490`, which is FBI Rancher)
-
-- *Optional:* Inside the base model folder, you can create a folder with the name of the new model (e.g. `2005 Schafter`)
-
-- **Required:** Then, you must place the model files named as the New ID of the model (e.g. `80001.dff` & `80001.txd`)
-
-
-*Optionally*, models can be customized with a `New ID.txt` file with the following settings (any line that doesn't contain these words is ignored):
-
- - `disableAutoFree`
- - `disableTXDTextureFiltering`
- - `enableDFFAlphaTransparency`
- - `txd=PATH_TO_TXD_FILE_INSIDE_models_FOLDER` (used for shared textures)
- - `dff=PATH_TO_DFF_FILE_INSIDE_models_FOLDER` (used for shared models)
- - `col=PATH_TO_COL_FILE_INSIDE_models_FOLDER` (used for shared collisions)
- - `lodDistance=NUMBER` (used for setting https://wiki.multitheftauto.com/wiki/EngineSetModelLODDistance)
- - `physicalPropsGroup=GROUP_ID` (used for setting https://wiki.multitheftauto.com/wiki/EngineSetModelPhysicalPropertiesGroup)
- - `settings=PATH_TO_ANOTHER_SETTINGS_FILE_INSIDE_models_FOLDER` (in case you want to share the same settings between multiple models)
diff --git a/newmodels_azul/models/object/1337/SAMP/README.md b/newmodels_azul/models/object/1337/SAMP/README.md
deleted file mode 100644
index 62f3d6b..0000000
--- a/newmodels_azul/models/object/1337/SAMP/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# `newmodels_azul`: SA-MP Object Models
-
-With [`newmodels_azul`](https://github.com/Fernando-A-Rocha/mta-add-models) you can add all of [SA-MP's object models](https://dev.prineside.com/en/gtasa_samp_model_id/tag/2-sa-mp/) to your server, so you can use them to create custom maps, or spawn objects using Lua scripts.
-
-## Attention ⚠️
-
-There are over 1,400 SA-MP object models! It is not recommended to add all of them to your server unless you will really need them, as it will increase the download size for players.
-
-Instead, you can add only the models you need.
-
-## How to install
-
-1. [Download](https://www.mediafire.com/file/mgqrk0rq7jrgsuc/models.zip/file) `models.zip` containing all dff/txd/col files required (total of 4,297 files; 404 MB when extracted)
-2. Extract the contents of the zip to [newmodels_azul/models/object/1337/SAMP](/newmodels_azul/models/object/1337/SAMP/).
-
-You're done! Newmodels will load the new models automatically when you start the resource. Their IDs are exactly the same as the IDs used in SA-MP.
-
-You may test spawning a SA-MP object using the test command `/testobj ` e.g. `/testobj 11686` ([this is a bar counter](https://dev.prineside.com/en/gtasa_samp_model_id/model/11686-CBarSection1/)).
-
-
diff --git a/newmodels_azul/models/object/1337/SAMP/sampobj.png b/newmodels_azul/models/object/1337/SAMP/sampobj.png
deleted file mode 100644
index 9fd9943..0000000
Binary files a/newmodels_azul/models/object/1337/SAMP/sampobj.png and /dev/null differ
diff --git a/newmodels_azul/models/object/1337/big_box/40001.txt b/newmodels_azul/models/object/1337/big_box/40001.txt
deleted file mode 100644
index a554dc4..0000000
--- a/newmodels_azul/models/object/1337/big_box/40001.txt
+++ /dev/null
@@ -1 +0,0 @@
-settings=object/1337/boxes.txt
\ No newline at end of file
diff --git a/newmodels_azul/models/object/1337/boxes.txt b/newmodels_azul/models/object/1337/boxes.txt
deleted file mode 100644
index 956ba78..0000000
--- a/newmodels_azul/models/object/1337/boxes.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-txd=object/1337/boxes.txd
-lodDistance=300
\ No newline at end of file
diff --git a/newmodels_azul/models/object/1337/small_box/40002.txt b/newmodels_azul/models/object/1337/small_box/40002.txt
deleted file mode 100644
index a554dc4..0000000
--- a/newmodels_azul/models/object/1337/small_box/40002.txt
+++ /dev/null
@@ -1 +0,0 @@
-settings=object/1337/boxes.txt
\ No newline at end of file
diff --git a/newmodels_azul/models/vehicle/507/Schafter/-5.dff.nandocrypt b/newmodels_azul/models/vehicle/507/Schafter/-5.dff.nandocrypt
deleted file mode 100644
index 7a679a9..0000000
Binary files a/newmodels_azul/models/vehicle/507/Schafter/-5.dff.nandocrypt and /dev/null differ
diff --git a/newmodels_azul/models/vehicle/507/Schafter/-5.txd.nandocrypt b/newmodels_azul/models/vehicle/507/Schafter/-5.txd.nandocrypt
deleted file mode 100644
index 69c18f0..0000000
Binary files a/newmodels_azul/models/vehicle/507/Schafter/-5.txd.nandocrypt and /dev/null differ
diff --git a/newmodels_azul/scripts/core/server_logic.lua b/newmodels_azul/scripts/core/server_logic.lua
deleted file mode 100644
index 0497181..0000000
--- a/newmodels_azul/scripts/core/server_logic.lua
+++ /dev/null
@@ -1,226 +0,0 @@
--- Loading of custom models from the "models" directory.
-
-local VALID_MODEL_TYPES = { "vehicle", "object", "ped" }
-
--- Model .txt settings:
-local CUSTOM_MODEL_BOOL_SETTINGS = {
- ["disableAutoFree"] = true,
- ["disableTXDTextureFiltering"] = true,
- ["enableDFFAlphaTransparency"] = true,
-}
--- - txd=path
--- - dff=path
--- - col=path
--- - lodDistance=number
--- - settings=path
-
-local function stringStartswith(str, start)
- return str:sub(1, #start) == start
-end
-
-local function parseModelSettings(customModel, customModelInfo, thisFullPath, isFromSettingsOption)
- local customModelSettings = {}
- local file = fileOpen(thisFullPath, true)
- if not file then
- return false, "failed to open file: " .. thisFullPath
- end
- local info = fileGetContents(file, false)
- fileClose(file)
- if not info then
- return false, "failed to read file: " .. thisFullPath
- end
- local lines = split(info, "\n")
- for _, settingStr in pairs(lines) do
- settingStr = settingStr:gsub("\r", "")
- if CUSTOM_MODEL_BOOL_SETTINGS[settingStr] then
- customModelSettings[settingStr] = true
- elseif stringStartswith(settingStr, "lodDistance=") then
- local lodDistance = tonumber(settingStr:sub(13))
- if not lodDistance then
- return false, "invalid lodDistance value: " .. settingStr
- end
- customModelSettings.lodDistance = lodDistance
- elseif stringStartswith(settingStr, "physicalPropsGroup=") then
- local physicalPropsGroup = tonumber(settingStr:sub(20))
- if not physicalPropsGroup then
- return false, "invalid physicalPropsGroup value: " .. settingStr
- end
- customModelSettings.physicalPropsGroup = physicalPropsGroup
- elseif stringStartswith(settingStr, "settings=") then
- if isFromSettingsOption then -- prevent inception and recursion
- return false, "settings option cannot point to a settings file that contains another settings option @ " .. thisFullPath
- end
- local settingsPath = settingStr:sub(10)
- local settingsFullPath = "models/" .. settingsPath
- if not fileExists(settingsFullPath) then
- return false, "settings file not found: " .. settingsPath
- end
- local settingsInfo = parseModelSettings(customModel, customModelInfo, settingsFullPath, true)
- if not settingsInfo then
- return false, "failed to parse settings file: " .. settingsPath
- end
- return settingsInfo
- else
- for _, settingModelType in pairs({"txd", "dff", "col"}) do
- if stringStartswith(settingStr, settingModelType.."=") then
- local settingModelPath = settingStr:sub(#settingModelType + 2)
- local settingModelFullPath = "models/" .. settingModelPath
- if not fileExists(settingModelFullPath) then
- return false, "setting " .. settingModelType .. " file not found: " .. settingModelPath
- end
- if customModelInfo[customModel][settingModelType] then
- return false, "duplicate " .. settingModelType .. " file for custom model: " .. customModel
- end
- customModelInfo[customModel][settingModelType] = settingModelFullPath
- end
- end
- end
- end
- return customModelSettings
-end
-
-local function parseOneFile(customModelInfo, thisFileName, thisFullPath, name)
- local isNandoCrypted, fileExt, customModel = isNandoCryptFileName(thisFileName)
- if not isNandoCrypted then
- fileExt = string.sub(thisFileName, -3)
- customModel = tonumber(string.sub(thisFileName, 1, -5))
- end
- if (fileExt == "dff" or fileExt == "txd" or fileExt == "col" or fileExt == "txt") and customModel then
- if not customModelInfo[customModel] then
- if isDefaultID(false, customModel) then
- return false, "custom model is a default ID: " .. customModel
- end
- if customModels[customModel] then
- return false, "duplicate custom model: " .. customModel
- end
- customModelInfo[customModel] = {}
- end
- if fileExt == "txt" then
- local customModelSettings, failReason = parseModelSettings(customModel, customModelInfo, thisFullPath)
- if not customModelSettings then
- return false, failReason
- end
- customModelInfo[customModel].settings = customModelSettings
- else
- if customModelInfo[customModel][fileExt] then
- return false, "duplicate " .. fileExt .. " file for custom model: " .. customModel
- end
- customModelInfo[customModel][fileExt] = thisFullPath
- if name then
- customModelInfo[customModel].name = name
- end
- end
- end
- return true
-end
-
-local function loadModels()
- if not pathIsDirectory("models") then
- return false, "models directory not found"
- end
- local filesAndFolders = pathListDir("models")
- if not filesAndFolders then
- return false, "failed to list models directory"
- end
- local baseModelCounts = {}
- for _, modelType in pairs(VALID_MODEL_TYPES) do
- local modelTypePath = "models/" .. modelType
- if pathIsDirectory(modelTypePath) then
- local filesAndFoldersHere = pathListDir(modelTypePath)
- if not filesAndFoldersHere then
- return false, "failed to list " .. modelTypePath .. " directory"
- end
- for _, fileOrFolder in pairs(filesAndFoldersHere) do
- local fullPath = modelTypePath .. "/" .. fileOrFolder
- if pathIsDirectory(fullPath) then
- local baseModel = tonumber(fileOrFolder)
- if baseModel then
- if not isDefaultID(false, baseModel) then
- return false, "invalid " .. modelType .. " base model: " .. baseModel
- end
- local filesAndFoldersInside = pathListDir(fullPath)
- if not filesAndFoldersInside then
- return false, "failed to list " .. fullPath .. " directory"
- end
- local customModelInfo = {}
- for _, fileOrFolderInside in pairs(filesAndFoldersInside) do
- local fullPathInside = fullPath .. "/" .. fileOrFolderInside
- if pathIsDirectory(fullPathInside) then
- local filesAndFoldersInsideThis = pathListDir(fullPathInside)
- if not filesAndFoldersInsideThis then
- return false, "failed to list " .. fullPathInside .. " directory"
- end
- for _, fileOrFolderInsideThis in pairs(filesAndFoldersInsideThis) do
- local fullPathInsideThis = fullPathInside .. "/" .. fileOrFolderInsideThis
- local parsed, failReason = parseOneFile(customModelInfo, fileOrFolderInsideThis, fullPathInsideThis, fileOrFolderInside)
- if not parsed then
- return false, failReason
- end
- end
- elseif pathIsFile(fullPathInside) then
- local parsed, failReason = parseOneFile(customModelInfo, fileOrFolderInside, fullPathInside)
- if not parsed then
- return false, failReason
- end
- end
- end
- for customModel, info in pairs(customModelInfo) do
- if not info.name then
- baseModelCounts[baseModel] = (baseModelCounts[baseModel] or 0) + 1
- end
- customModels[customModel] = {
- type = modelType,
- baseModel = baseModel,
- dff = info.dff,
- txd = info.txd,
- col = info.col,
- name = info.name or ("%d#%d"):format(baseModel, baseModelCounts[baseModel]),
- settings = info.settings or {},
- }
- end
- end
- end
- end
- end
- end
- return true
-end
-
-local result, failReason = loadModels()
-if not result then
- outputServerLog("[loadModels] " .. failReason)
- outputDebugString("Failed to load models. See server log for details.", 1)
- return
-end
-
--- Save elementModels in root element data to restore on next startup
-addEventHandler("onResourceStop", resourceRoot, function()
- if next(elementModels) then
- setElementData(root, "newmodels_azul:elementModels_backup", elementModels, false)
- end
-end, false)
-
--- Restore elementModels from root element data on startup if any
-local elementModelsBackup = getElementData(root, "newmodels_azul:elementModels_backup")
-if type(elementModelsBackup) == "table" then
- for element, id in pairs(elementModelsBackup) do
- if isElement(element) then
- elementModels[element] = id
- end
- end
-end
-
-
-addEventHandler("onPlayerResourceStart", root, function(res)
- if res == resource then
- triggerClientEvent(source, "newmodels_azul:receiveCustomModels", resourceRoot, customModels, elementModels)
- end
-end)
-
--- Handle element destroy (clear any custom model ID from the table)
--- Syncing with clients is not necessary as they already handle onClientElementDestroy
-addEventHandler("onElementDestroy", root, function()
- if elementModels[source] then
- elementModels[source] = nil
- end
-end)
diff --git a/newmodels_azul/scripts/optional/nando_crypt/nando_decrypter b/newmodels_azul/scripts/optional/nando_crypt/nando_decrypter
deleted file mode 100644
index 0c55c2d..0000000
Binary files a/newmodels_azul/scripts/optional/nando_crypt/nando_decrypter and /dev/null differ
diff --git a/.github/doc/DOCUMENTATION.md b/newmodels_red/DOCUMENTATION.md
similarity index 89%
rename from .github/doc/DOCUMENTATION.md
rename to newmodels_red/DOCUMENTATION.md
index ce50ffc..ee826f1 100644
--- a/.github/doc/DOCUMENTATION.md
+++ b/newmodels_red/DOCUMENTATION.md
@@ -1,4 +1,4 @@
-# `Newmodels v5 Azul 💙` Documentation
+# `Newmodels v6 Red 🍒` Documentation
## Requirements
@@ -19,18 +19,18 @@
|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **What do I need to know to use this system?** | Basic knowledge of MTA scripting (Lua) and how MTA servers and game clients work. |
| **Who is this system intended for?** | Server owners and developers who want to add new models to their MTA server for custom skins, vehicles, objects, etc. |
-| **Will players download the files (DFF/TXD/COL) of the added models automatically?** | Yes, the [`meta.xml`](/newmodels_azul/meta.xml) configuration includes all of these files in the [models folder](/newmodels_azul/models/) so they are automatically downloaded by players when they join the server. |
+| **Will players download the files (DFF/TXD/COL) of the added models automatically?** | Yes, the [`meta.xml`](/newmodels_red/meta.xml) configuration includes all of these files in the [models folder](/newmodels_red/models/) so they are automatically downloaded by players when they join the server. |
| **Why not use the `download="false"` attribute in the `meta.xml` so they are not downloaded by players, then download them on demand?** | This feature is currently not implemented due to common complaints about `downloadFile` sometimes being unreliable and causing server lag. It is better to serve all files to the player at once. |
| **Can I encrypt my model files and hide the decryption key so players cannot steal them?** | Yes, you can use the **NandoCrypt** which is natively supported by this system. |
| **How are added models identified?** | New models are identified by numerical IDs that you define. These IDs can be any number (positive or negative) as long as they do not conflict with existing or reserved game IDs. They are purely arbitrary and do not have to be sequential. |
| **Why new model IDs and not strings/names for identification?** | MTA uses numerical IDs to identify models, so this system follows the same convention. It is more efficient and easier to work with numbers than strings. |
-| **How do I add models using this system?** | Essentially, you add new models by placing the DFF/TXD/COL files in the [models folder](/newmodels_azul/models/) with specific file and folder names. |
+| **How do I add models using this system?** | Essentially, you add new models by placing the DFF/TXD/COL files in the [models folder](/newmodels_red/models/) with specific file and folder names. |
| **How do I use the new models in my scripts?** | You can use the exported functions provided by this system in your own scripts. |
| **Can I use the new models in my existing scripts without modifying them?** | In theory, yes. You can use the `loadstring` method to import the functions at the beginning of your script. This will modify the MTA functions to work with the new IDs. However, always be careful and verify if you do not break any feature in your scripts. |
| **Can I use the new models in my existing scripts by calling the exported functions directly?** | Yes, you can call the exported functions directly in your scripts, without using `loadstring`. |
| **Can I use the new models in both server-side and client-side scripts?** | Yes, the exported functions are shared, meaning you can use them in both client and server side scripts. However, their behaviors are different. |
| **Why is are new models added client-side in MTA and this logic doesn't exist server-side?** | MTA model allocation happens client-side, so the server has no concept of any new IDs. Server-side model allocation is still not implemented on MTA (but may be in the future 😉), so this system provides a way to work around this limitation. |
-| **Are there any premade modpacks for new models that I can drag & drop to my server?** | Sure, the community has created many new object, vehicle and skin mods over the years. You can search for these models online. [You can find more information here about adding all of SA-MP's objects.](/newmodels_azul/models/object/1337/SAMP/README.md) |
+| **Are there any premade modpacks for new models that I can drag & drop to my server?** | Sure, the community has created many new object, vehicle and skin mods over the years. You can search for these models online. You can find more information about adding all of SA-MP's objects [on this document](/[examples]/sampobj_red/README.md) |
## Basics
@@ -51,7 +51,7 @@ A game model can be added with up to **3 different files** for a certain entity
- `vehicle`: DFF and TXD files
- `object`: DFF, COL and TXD files
-The files must be placed in the [models](/newmodels_azul/models/) folder. The system will automatically load them.
+The files must be placed in the [models](/newmodels_red/models/) folder. The system will automatically load them.
#### File & Folder Structure
@@ -67,13 +67,13 @@ e.g. `models/ped/7/Mafioso 1/-2.dff`
#### NandoCrypt Support (Optional)
-You may use [NandoCrypt](https://github.com/Fernando-A-Rocha/mta-nandocrypt) to encrypt your mod files. Place them with file extension `.nandocrypt` so they are automatically recognised. You may customize the file extension in [`shared_local.lua`](/newmodels_azul/scripts/core/shared_local.lua).
+You may use [NandoCrypt](https://github.com/Fernando-A-Rocha/mta-nandocrypt) to encrypt your mod files. Place them with file extension `.nandocrypt` so they are automatically recognised. You may customize the file extension in [`shared_config.lua`](/newmodels_red/scripts/core/shared_config.lua).
A test `nando_decrypter` script is included with the resource, as well as a mod consisting of 2 encrypted files (`-5.dff.nandocrypt` and `-5.txd.nandocrypt`). To use your own mods, you will have to replace `nando_decrypter` with your own decrypter script generated by the NandoCrypt tool.
#### Additional customization
-Models can be customized with `.txt` files. Check [this README](/newmodels_azul/models/README.md) for more information.
+Models can be customized with `.txt` files. Check [this README](/newmodels_red/models/README.md) for more information.
### 2. Using the new models
@@ -96,12 +96,12 @@ In contrast, **client-side** functions do not perform any synchronization of dat
The easiest way is to use the following method in the beginning of your script to load the necessary functions.
```lua
-loadstring( exports['newmodels_azul']:import() )()
+loadstring( exports['newmodels_red']:import() )()
```
This modifies MTA functions such as `createVehicle` or `setElementModel` to work with the new IDs, and adds new functions such as `getElementBaseModel` and `getElementCustomModel` that you can use.
-Check the [`shared_exported.lua`](/newmodels_azul/core/shared_exported.lua) script to know what gets imported.
+Check the [`shared_exported.lua`](/newmodels_red/core/shared_exported.lua) script to know what gets imported.
Example usage:
@@ -114,14 +114,14 @@ local vehicle = createVehicle(id, x, y, z, rx, ry, rz, numberplate)
If you do not wish to use `loadstring` to import the functions, you can call them directly.
-Check the [`meta.xml`](/newmodels_azul/meta.xml) file to see the exported functions.
+Check the [`meta.xml`](/newmodels_red/meta.xml) file to see the exported functions.
Example usage:
```lua
-- The normal createVehicle would not work with invented IDs such as -2,
-- so we call the available exported function
-local vehicle = exports['newmodels_azul']:createVehicle(id, x, y, z, rx, ry, rz, numberplate)
+local vehicle = exports['newmodels_red']:createVehicle(id, x, y, z, rx, ry, rz, numberplate)
```
### Important Tips ⚠️
diff --git a/newmodels_red/MIGRATION.md b/newmodels_red/MIGRATION.md
new file mode 100644
index 0000000..619a2b4
--- /dev/null
+++ b/newmodels_red/MIGRATION.md
@@ -0,0 +1,31 @@
+# Migrating from previous `Newmodels` versions
+
+This article explains how to migrate from previous versions of newmodels to the newest `Newmodels v6 Red 🍒` version.
+
+## Migrating from v4 and v5
+
+It is easy to migrate to v6 from **newmodels v4 and v5**. The **models folder structure remains the same**, but the scripts have changed in the way models are applied to elements.
+
+You just need to migrate your model files (dff, txd, col, etc.) to the new `newmodels_red` resource! They will load and work as before.
+
+## Migrating from v3
+
+Migrating from newmodels v3 is now possible, as the system has now evolved with backwards compatibility in mind. However, some breaking changes have been introduced in v6 that you should be aware of.
+
+### Important changes
+
+This resource no longer uses and relies on the **MTA Element Data system** (`setElementData`) to sync the models to all clients! Instead, newmodels makes use of Lua tables and MTA events. This major change was made to **improve performance** and control the sync of models more efficiently.
+
+### Guide
+
+The following is a guide (best effort basis) on how to update your existing scripts made for newmodels v3 to work with newmodels v6.
+
+- The `newmodels_engine` resource no longer exists, all exports are in `newmodels_red`
+- Remove all `setElementData` and `getElementData` calls related to newmodels
+- Functions `getDataNameFromType` and `getBaseModelDataName` no longer exist since we don't use element data anymore
+- Functions like `isDefaultID` still exist (exported)
+- Other useful functions were renamed
+- Replace `sampobj_reloaded` with `sampobj_red`
+- Use the new exported functions or use the loadstring method
+- Exported functions to add/remove new models have been rewritten, see the documentation for more details
+- See the `test_vehicles` resource for example usage
diff --git a/newmodels_azul/meta.xml b/newmodels_red/meta.xml
similarity index 62%
rename from newmodels_azul/meta.xml
rename to newmodels_red/meta.xml
index bd7c248..07ccbb3 100644
--- a/newmodels_azul/meta.xml
+++ b/newmodels_red/meta.xml
@@ -1,18 +1,59 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
@@ -34,30 +75,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/newmodels_red/models/README.md b/newmodels_red/models/README.md
new file mode 100644
index 0000000..6c1bb76
--- /dev/null
+++ b/newmodels_red/models/README.md
@@ -0,0 +1,29 @@
+# `models` folder explained
+
+Model files (ending in `.dff`/`.col`/`.txd`) must be placed in this `models` folder **following specific rules**.
+
+See the [example files](/newmodels_red/models/) to visualize the structure of this system.
+
+- **Required:** Vehicles must be placed in the `vehicle` folder, Skins in `ped`, and Objects in `object`
+
+- **Required:** Inside the type folder, you must create a folder with the base model ID of the new model (e.g. `490`, which is FBI Rancher)
+
+- *Optional:* Inside the base model folder, you can create a folder with the name of the new model (e.g. `2005 Schafter`)
+
+- **Required:** Then, you must place the model files named as the New ID of the model (e.g. `80001.dff` & `80001.txd`)
+
+---
+
+*Optionally*, models can be customized with `New ID.txt` files placed alongside the model files.
+
+The available options are (any line that doesn't contain a valid setting string is ignored):
+
+- `disableAutoFree`
+- `disableTXDTextureFiltering`
+- `enableDFFAlphaTransparency`
+- `txd=PATH_TO_TXD_FILE_INSIDE_models_FOLDER` (used for shared textures)
+- `dff=PATH_TO_DFF_FILE_INSIDE_models_FOLDER` (used for shared models)
+- `col=PATH_TO_COL_FILE_INSIDE_models_FOLDER` (used for shared collisions)
+- `lodDistance=NUMBER` (used for setting https://wiki.multitheftauto.com/wiki/EngineSetModelLODDistance)
+- `physicalPropsGroup=GROUP_ID` (used for setting https://wiki.multitheftauto.com/wiki/EngineSetModelPhysicalPropertiesGroup)
+- `settings=PATH_TO_ANOTHER_SETTINGS_FILE_INSIDE_models_FOLDER` (in case you want to share the same settings between multiple models)
diff --git a/newmodels_azul/models/object/1337/big_box/40001.col b/newmodels_red/models/object/1337/big_box/50010.col
similarity index 100%
rename from newmodels_azul/models/object/1337/big_box/40001.col
rename to newmodels_red/models/object/1337/big_box/50010.col
diff --git a/newmodels_azul/models/object/1337/big_box/40001.dff b/newmodels_red/models/object/1337/big_box/50010.dff
similarity index 100%
rename from newmodels_azul/models/object/1337/big_box/40001.dff
rename to newmodels_red/models/object/1337/big_box/50010.dff
diff --git a/newmodels_red/models/object/1337/big_box/50010.txt b/newmodels_red/models/object/1337/big_box/50010.txt
new file mode 100644
index 0000000..e74f0a9
--- /dev/null
+++ b/newmodels_red/models/object/1337/big_box/50010.txt
@@ -0,0 +1,2 @@
+#### Read the documentation to understand these settings ####
+settings=object/1337/boxes.txt
\ No newline at end of file
diff --git a/newmodels_azul/models/object/1337/boxes.txd b/newmodels_red/models/object/1337/boxes.txd
similarity index 100%
rename from newmodels_azul/models/object/1337/boxes.txd
rename to newmodels_red/models/object/1337/boxes.txd
diff --git a/newmodels_red/models/object/1337/boxes.txt b/newmodels_red/models/object/1337/boxes.txt
new file mode 100644
index 0000000..e074c85
--- /dev/null
+++ b/newmodels_red/models/object/1337/boxes.txt
@@ -0,0 +1,3 @@
+#### Read the documentation to understand these settings ####
+txd=object/1337/boxes.txd
+lodDistance=300
\ No newline at end of file
diff --git a/newmodels_azul/models/object/1337/small_box/40002.col b/newmodels_red/models/object/1337/small_box/50011.col
similarity index 100%
rename from newmodels_azul/models/object/1337/small_box/40002.col
rename to newmodels_red/models/object/1337/small_box/50011.col
diff --git a/newmodels_azul/models/object/1337/small_box/40002.dff b/newmodels_red/models/object/1337/small_box/50011.dff
similarity index 100%
rename from newmodels_azul/models/object/1337/small_box/40002.dff
rename to newmodels_red/models/object/1337/small_box/50011.dff
diff --git a/newmodels_red/models/object/1337/small_box/50011.txt b/newmodels_red/models/object/1337/small_box/50011.txt
new file mode 100644
index 0000000..e74f0a9
--- /dev/null
+++ b/newmodels_red/models/object/1337/small_box/50011.txt
@@ -0,0 +1,2 @@
+#### Read the documentation to understand these settings ####
+settings=object/1337/boxes.txt
\ No newline at end of file
diff --git a/newmodels_azul/models/ped/7/-3.dff b/newmodels_red/models/ped/7/20011.dff
similarity index 100%
rename from newmodels_azul/models/ped/7/-3.dff
rename to newmodels_red/models/ped/7/20011.dff
diff --git a/newmodels_azul/models/ped/7/-3.txd b/newmodels_red/models/ped/7/20011.txd
similarity index 100%
rename from newmodels_azul/models/ped/7/-3.txd
rename to newmodels_red/models/ped/7/20011.txd
diff --git a/newmodels_azul/models/ped/7/-4.dff b/newmodels_red/models/ped/7/20012.dff
similarity index 100%
rename from newmodels_azul/models/ped/7/-4.dff
rename to newmodels_red/models/ped/7/20012.dff
diff --git a/newmodels_azul/models/ped/7/-4.txd b/newmodels_red/models/ped/7/20012.txd
similarity index 100%
rename from newmodels_azul/models/ped/7/-4.txd
rename to newmodels_red/models/ped/7/20012.txd
diff --git a/newmodels_azul/models/ped/7/Mafioso 1/-2.dff b/newmodels_red/models/ped/7/Mafioso 1/20010.dff
similarity index 100%
rename from newmodels_azul/models/ped/7/Mafioso 1/-2.dff
rename to newmodels_red/models/ped/7/Mafioso 1/20010.dff
diff --git a/newmodels_azul/models/ped/7/Mafioso 1/-2.txd b/newmodels_red/models/ped/7/Mafioso 1/20010.txd
similarity index 100%
rename from newmodels_azul/models/ped/7/Mafioso 1/-2.txd
rename to newmodels_red/models/ped/7/Mafioso 1/20010.txd
diff --git a/newmodels_azul/models/vehicle/462/-420.dff b/newmodels_red/models/vehicle/462/80070.dff
similarity index 100%
rename from newmodels_azul/models/vehicle/462/-420.dff
rename to newmodels_red/models/vehicle/462/80070.dff
diff --git a/newmodels_azul/models/vehicle/462/-420.txd b/newmodels_red/models/vehicle/462/80070.txd
similarity index 100%
rename from newmodels_azul/models/vehicle/462/-420.txd
rename to newmodels_red/models/vehicle/462/80070.txd
diff --git a/newmodels_azul/models/vehicle/520/-69.dff b/newmodels_red/models/vehicle/520/80069.dff
similarity index 100%
rename from newmodels_azul/models/vehicle/520/-69.dff
rename to newmodels_red/models/vehicle/520/80069.dff
diff --git a/newmodels_azul/models/vehicle/520/-69.txd b/newmodels_red/models/vehicle/520/80069.txd
similarity index 100%
rename from newmodels_azul/models/vehicle/520/-69.txd
rename to newmodels_red/models/vehicle/520/80069.txd
diff --git a/newmodels_red/models_alt/objects/50001.col b/newmodels_red/models_alt/objects/50001.col
new file mode 100644
index 0000000..7d94967
Binary files /dev/null and b/newmodels_red/models_alt/objects/50001.col differ
diff --git a/newmodels_red/models_alt/objects/50001.dff b/newmodels_red/models_alt/objects/50001.dff
new file mode 100644
index 0000000..d8ea7c3
Binary files /dev/null and b/newmodels_red/models_alt/objects/50001.dff differ
diff --git a/newmodels_red/models_alt/objects/50001.txd b/newmodels_red/models_alt/objects/50001.txd
new file mode 100644
index 0000000..5978956
Binary files /dev/null and b/newmodels_red/models_alt/objects/50001.txd differ
diff --git a/newmodels_red/models_alt/objects/wrecked_car.txd b/newmodels_red/models_alt/objects/wrecked_car.txd
new file mode 100644
index 0000000..f879bf0
Binary files /dev/null and b/newmodels_red/models_alt/objects/wrecked_car.txd differ
diff --git a/newmodels_red/models_alt/objects/wrecked_car1.col b/newmodels_red/models_alt/objects/wrecked_car1.col
new file mode 100644
index 0000000..21767a1
Binary files /dev/null and b/newmodels_red/models_alt/objects/wrecked_car1.col differ
diff --git a/newmodels_red/models_alt/objects/wrecked_car1.dff b/newmodels_red/models_alt/objects/wrecked_car1.dff
new file mode 100644
index 0000000..9c0950f
Binary files /dev/null and b/newmodels_red/models_alt/objects/wrecked_car1.dff differ
diff --git a/newmodels_red/models_alt/objects/wrecked_car2.col b/newmodels_red/models_alt/objects/wrecked_car2.col
new file mode 100644
index 0000000..6ad363b
Binary files /dev/null and b/newmodels_red/models_alt/objects/wrecked_car2.col differ
diff --git a/newmodels_red/models_alt/objects/wrecked_car2.dff b/newmodels_red/models_alt/objects/wrecked_car2.dff
new file mode 100644
index 0000000..8f6681e
Binary files /dev/null and b/newmodels_red/models_alt/objects/wrecked_car2.dff differ
diff --git a/newmodels_red/models_alt/peds/20001.dff b/newmodels_red/models_alt/peds/20001.dff
new file mode 100644
index 0000000..e5d3872
Binary files /dev/null and b/newmodels_red/models_alt/peds/20001.dff differ
diff --git a/newmodels_red/models_alt/peds/20001.txd b/newmodels_red/models_alt/peds/20001.txd
new file mode 100644
index 0000000..375b017
Binary files /dev/null and b/newmodels_red/models_alt/peds/20001.txd differ
diff --git a/newmodels_red/models_alt/peds/20002.dff b/newmodels_red/models_alt/peds/20002.dff
new file mode 100644
index 0000000..9b1613a
Binary files /dev/null and b/newmodels_red/models_alt/peds/20002.dff differ
diff --git a/newmodels_red/models_alt/peds/20002.txd b/newmodels_red/models_alt/peds/20002.txd
new file mode 100644
index 0000000..93efb6f
Binary files /dev/null and b/newmodels_red/models_alt/peds/20002.txd differ
diff --git a/newmodels_red/models_alt/peds/20003.dff b/newmodels_red/models_alt/peds/20003.dff
new file mode 100644
index 0000000..8b5a625
Binary files /dev/null and b/newmodels_red/models_alt/peds/20003.dff differ
diff --git a/newmodels_red/models_alt/peds/20003.txd b/newmodels_red/models_alt/peds/20003.txd
new file mode 100644
index 0000000..389f2d0
Binary files /dev/null and b/newmodels_red/models_alt/peds/20003.txd differ
diff --git a/newmodels_red/models_alt/s_mod_list.lua b/newmodels_red/models_alt/s_mod_list.lua
new file mode 100644
index 0000000..14c6b5a
--- /dev/null
+++ b/newmodels_red/models_alt/s_mod_list.lua
@@ -0,0 +1,71 @@
+-- mod_list.lua
+-- This system was used in newmodels v3, and can now function in newmodels v6 to add new models.
+--
+-- ..........................
+-- AVAILABLE PARAMETERS .....
+-- ..........................
+--
+-- > Old parameters from newmodels v3:
+--
+-- 'id' must be unique and out of the default GTA (& preferrably SA-MP too) ID ranges
+--
+-- 'base_id' is the model the mod will inherit some properties from
+-- Doesn't make much difference on peds(skins), but it does on vehicles & objects
+--
+-- 'path' can be:
+-- » a string, in which case it expects files to be named ID.dff or ID.txd in that folder
+-- » an array(table), in which case it expects an array of file names like
+-- {dff="filepath.dff", txd="filepath.txd", col="filepath.col"}.
+-- For files encrypted using NandoCrypt, don't add the .nandocrypt extension, it is
+-- defined by the 'NANDOCRYPT_EXT' setting.
+-- All paths defined manually in this file need to be local (this resource)
+-- » To add a mod from another resource see the examples provided in the documentation.
+--
+-- 'name' can be whatever you want (string)
+--
+-- +++ Optional parameters +++
+--
+-- » 'lodDistance' : custom LOD distance in GTA units (number), see possible values https://wiki.multitheftauto.com/wiki/EngineSetModelLODDistance
+-- » 'ignoreTXD', 'ignoreDFF', 'ignoreCOL' : if true, the script won't try to load TXD/DFF/COL for the mod
+-- » 'metaDownloadFalse' : if true, the mod will be only be downloaded when needed (when trying to set model); files must contain download="false" in meta.xml
+-- » 'disableAutoFree' : if true, the allocated mod ID will not be freed when no element streamed in is no longer using the mod ID
+-- This causes the mod to stay in memory, be careful when enabling for big mods
+-- » 'filteringEnabled' (engineLoadTXD)
+-- » 'alphaTransparency' (engineReplaceModel)
+--
+-- > New parameters added in newmodels v6:
+--
+-- None.
+--
+-- ..........................
+-- AVAILABLE TYPES .....
+-- ..........................
+--
+-- New models need to be grouped by type in the 'modList' table below.
+--
+-- - 'ped' (skins for players and peds)
+-- - 'object' (for objects, buildings and pickups)
+-- - 'vehicle'
+--
+
+modList = {
+ ped = {
+
+ { id = 20001, base_id = 1, path = "models_alt/peds/", name = "Mafioso 1", metaDownloadFalse = true },
+ { id = 20003, base_id = 1, path = "models_alt/peds/", name = "Mafioso 2", metaDownloadFalse = true },
+ { id = 20002, base_id = 1, path = "models_alt/peds/", name = "Mafioso 3", metaDownloadFalse = true },
+ },
+ vehicle = {
+ { id = 80001, base_id = 507, path = "models_alt/vehicles/", name = "Schafter", disableAutoFree = true, metaDownloadFalse = true },
+ { id = 80002, base_id = 489, path = "models_alt/vehicles/", name = "02 Landstalker", metaDownloadFalse = true },
+ { id = 80003, base_id = 400, path = "models_alt/vehicles/", name = "86 Landstalker 1", metaDownloadFalse = true },
+ { id = 80004, base_id = 400, path = "models_alt/vehicles/", name = "98 Landstalker 1", metaDownloadFalse = true },
+ { id = 80005, base_id = 468, path = "models_alt/vehicles/", name = "Sanchez Test", ignoreTXD = true, metaDownloadFalse = true },
+ { id = 80006, base_id = 507, path = { dff = "models_alt/vehicles/elegant.dff", txd = "models_alt/vehicles/elegant.txd" }, name = "Elegant Test", metaDownloadFalse = true },
+ },
+ object = {
+ { id = 50001, base_id = 1337, path = "models_alt/objects/", name = "Engine Hoist", metaDownloadFalse = true },
+ { id = 50002, base_id = 3594, lodDistance = 300, path = { txd = "models_alt/objects/wrecked_car.txd", dff = "models_alt/objects/wrecked_car1.dff", col = "models_alt/objects/wrecked_car1.col" }, name = "Wrecked Car 1", metaDownloadFalse = true },
+ { id = 50003, base_id = 3593, lodDistance = 300, path = { txd = "models_alt/objects/wrecked_car.txd", dff = "models_alt/objects/wrecked_car2.dff", col = "models_alt/objects/wrecked_car2.col" }, name = "Wrecked Car 2", metaDownloadFalse = true },
+ },
+}
diff --git a/newmodels_red/models_alt/vehicles/80001.dff b/newmodels_red/models_alt/vehicles/80001.dff
new file mode 100644
index 0000000..c81f5ce
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80001.dff differ
diff --git a/newmodels_red/models_alt/vehicles/80001.txd b/newmodels_red/models_alt/vehicles/80001.txd
new file mode 100644
index 0000000..a607f42
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80001.txd differ
diff --git a/newmodels_azul/models/vehicle/490/-1.dff b/newmodels_red/models_alt/vehicles/80002.dff
similarity index 100%
rename from newmodels_azul/models/vehicle/490/-1.dff
rename to newmodels_red/models_alt/vehicles/80002.dff
diff --git a/newmodels_azul/models/vehicle/490/-1.txd b/newmodels_red/models_alt/vehicles/80002.txd
similarity index 100%
rename from newmodels_azul/models/vehicle/490/-1.txd
rename to newmodels_red/models_alt/vehicles/80002.txd
diff --git a/newmodels_red/models_alt/vehicles/80003.dff b/newmodels_red/models_alt/vehicles/80003.dff
new file mode 100644
index 0000000..e7cfe39
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80003.dff differ
diff --git a/newmodels_red/models_alt/vehicles/80003.txd b/newmodels_red/models_alt/vehicles/80003.txd
new file mode 100644
index 0000000..0fca96b
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80003.txd differ
diff --git a/newmodels_red/models_alt/vehicles/80004.dff b/newmodels_red/models_alt/vehicles/80004.dff
new file mode 100644
index 0000000..dc141ef
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80004.dff differ
diff --git a/newmodels_red/models_alt/vehicles/80004.txd b/newmodels_red/models_alt/vehicles/80004.txd
new file mode 100644
index 0000000..75d3494
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80004.txd differ
diff --git a/newmodels_red/models_alt/vehicles/80005.dff b/newmodels_red/models_alt/vehicles/80005.dff
new file mode 100644
index 0000000..3404111
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/80005.dff differ
diff --git a/newmodels_red/models_alt/vehicles/elegant.dff.nandocrypt b/newmodels_red/models_alt/vehicles/elegant.dff.nandocrypt
new file mode 100644
index 0000000..e815c97
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/elegant.dff.nandocrypt differ
diff --git a/newmodels_red/models_alt/vehicles/elegant.txd.nandocrypt b/newmodels_red/models_alt/vehicles/elegant.txd.nandocrypt
new file mode 100644
index 0000000..9c55a66
Binary files /dev/null and b/newmodels_red/models_alt/vehicles/elegant.txd.nandocrypt differ
diff --git a/newmodels_azul/scripts/core/client_logic.lua b/newmodels_red/scripts/core/client_logic.lua
similarity index 54%
rename from newmodels_azul/scripts/core/client_logic.lua
rename to newmodels_red/scripts/core/client_logic.lua
index 20cf55b..33361cd 100644
--- a/newmodels_azul/scripts/core/client_logic.lua
+++ b/newmodels_red/scripts/core/client_logic.lua
@@ -1,12 +1,23 @@
-addEvent("newmodels_azul:receiveCustomModels", true)
-addEvent("newmodels_azul:setElementCustomModel", true)
+addEvent("newmodels_red:receiveCustomModels", true)
+addEvent("newmodels_red:setElementCustomModel", true)
+
+-- For developers:
+addEvent("newmodels_red:preDownloadNewModels", true)
+
+addEvent("newmodels_red:internal:onModelFilesReady", false)
loadedModels = {}
-local loadingQueue = {}
+local filesBeingPreDownloaded = {}
local reusableModelElements = {}
+local loadingQueue = {}
+local LOADING_QUEUE_PHASES = {
+ DOWNLOAD_FILES = 1,
+ LOAD_MODEL_ELEMENTS = 2,
+ APPLY_NEW_MODEL = 3,
+}
local currFreeIdDelay = 9500 -- ms
local FREE_ID_DELAY_STEP = 500 -- ms
@@ -46,20 +57,17 @@ local function finishLoadCustomModel(customModel)
local queuedInfo = loadingQueue[customModel]
if not queuedInfo then return end
- local customInfo = customModels[customModel]
- if not customInfo then return end
+ if queuedInfo.phase ~= LOADING_QUEUE_PHASES.LOAD_MODEL_ELEMENTS then return end
+ -- Move to phase 3
+ loadingQueue[customModel].phase = LOADING_QUEUE_PHASES.APPLY_NEW_MODEL
local allocatedModel = queuedInfo.allocatedModel
local col, txd, dff = queuedInfo.col, queuedInfo.txd, queuedInfo.dff
local elementToApply = queuedInfo.elementToApply
- local enableDFFAlphaTransparency = customInfo.settings.enableDFFAlphaTransparency
-
- if (col and not engineReplaceCOL(col.element, allocatedModel))
- or (txd and not engineImportTXD(txd.element, allocatedModel))
- or (dff and not engineReplaceModel(dff.element, allocatedModel, enableDFFAlphaTransparency or nil)) then
+ local function cancelLoading()
-- Destroy all non-reused col/txd/dff elements
- for _, modType in pairs({"col", "txd", "dff"}) do
+ for _, modType in pairs({ "col", "txd", "dff" }) do
local mod = queuedInfo[modType]
if mod and (not mod.isReused) and isElement(mod.element) then
destroyElement(mod.element)
@@ -71,6 +79,20 @@ local function finishLoadCustomModel(customModel)
engineFreeModel(allocatedModel)
loadingQueue[customModel] = nil
+ end
+
+ local customInfo = customModels[customModel]
+ if not customInfo then
+ cancelLoading()
+ return
+ end
+
+ local enableDFFAlphaTransparency = customInfo.settings.enableDFFAlphaTransparency
+
+ if (col and not engineReplaceCOL(col.element, allocatedModel))
+ or (txd and not engineImportTXD(txd.element, allocatedModel))
+ or (dff and not engineReplaceModel(dff.element, allocatedModel, enableDFFAlphaTransparency or nil)) then
+ cancelLoading()
outputDebugString("Failed to load custom model " .. customModel .. " due to col/txd/dff replacing failure", 1)
return
end
@@ -129,7 +151,8 @@ local function onFailedToLoadModFile(customModel, filePath, fileType)
local queuedInfo = loadingQueue[customModel]
if queuedInfo then
engineFreeModel(queuedInfo.allocatedModel)
- outputDebugString("Failed to load " .. fileType .. " file for custom model " .. customModel..": "..filePath, 1)
+ outputDebugString("Failed to load " .. fileType .. " file for custom model " .. customModel .. ": " .. filePath,
+ 1)
loadingQueue[customModel] = nil
end
@@ -152,27 +175,29 @@ local function onLoadedModFile(customModel, fileType, filePath, modElement, isRe
isReused = isReused,
}
- local expectingAmount = queuedInfo.expectingAmount - 1
- if expectingAmount == 0 then
+ loadingQueue[customModel].countModFilesLoaded = loadingQueue[customModel].countModFilesLoaded + 1
+
+ if loadingQueue[customModel].countModFilesLoaded == #queuedInfo.filesList then
+ loadingQueue[customModel].countModFilesLoaded = nil
finishLoadCustomModel(customModel)
- else
- loadingQueue[customModel].expectingAmount = expectingAmount
end
end
-local function beginLoadCustomModel(customModel, elementToApply)
+local function beginLoadCustomModelElements(customModel)
+ local queuedInfo = loadingQueue[customModel]
+ if not queuedInfo then return end
+ if queuedInfo.phase ~= LOADING_QUEUE_PHASES.DOWNLOAD_FILES then return end
+ -- Move to phase 2
+ loadingQueue[customModel].phase = LOADING_QUEUE_PHASES.LOAD_MODEL_ELEMENTS
+
local customInfo = customModels[customModel]
if not customInfo then
- outputDebugString("Trying to load custom model " .. customModel .. " that does not exist", 2)
- return
- end
-
- if loadedModels[customModel] then
- outputDebugString("Trying to load custom model " .. customModel .. " that is already loaded", 1)
+ loadingQueue[customModel] = nil
return
end
local colPath, txdPath, dffPath = customInfo.col, customInfo.txd, customInfo.dff
+ local disableTXDTextureFiltering = customInfo.settings.disableTXDTextureFiltering
local decryptFunc = getNandoDecrypterFunction()
@@ -184,31 +209,20 @@ local function beginLoadCustomModel(customModel, elementToApply)
if (encryptedFiles.col or encryptedFiles.txd or encryptedFiles.dff) and not decryptFunc then
-- Cancel as we cannot decrypt the files
- outputDebugString("Failed to load custom model " .. customModel .. " due to missing NandoCrypt decrypter function", 1)
+ outputDebugString(
+ "Failed to load custom model " .. customModel .. " due to missing NandoCrypt decrypter function", 1)
+ loadingQueue[customModel] = nil
return
end
- local expectingAmount = 0
- if colPath then expectingAmount = expectingAmount + 1 end
- if txdPath then expectingAmount = expectingAmount + 1 end
- if dffPath then expectingAmount = expectingAmount + 1 end
-
local allocatedModel = engineRequestModel(customInfo.type, customInfo.baseModel)
if not allocatedModel then
outputDebugString("Failed to load custom model " .. customModel .. " due to model allocation failure", 1)
+ loadingQueue[customModel] = nil
return
end
- loadingQueue[customModel] = {
- allocatedModel = allocatedModel,
- elementToApply = elementToApply,
- expectingAmount = expectingAmount,
- -- These will be set to { path=string, element=col/txd/dff, isReused=true/false } when loaded
- col = nil,
- txd = nil,
- dff = nil,
- }
- local disableTXDTextureFiltering = customInfo.settings.disableTXDTextureFiltering
+ loadingQueue[customModel].allocatedModel = allocatedModel
local function loadModElement(modType, modPath, modData)
local modElement
@@ -233,8 +247,8 @@ local function beginLoadCustomModel(customModel, elementToApply)
onLoadedModFile(customModel, modType, modPath, reusedElement, true)
elseif encryptedFiles[modType] then
if not decryptFunc(modPath, function(data)
- loadModElement(modType, modPath, data)
- end) then
+ loadModElement(modType, modPath, data)
+ end) then
onFailedToLoadModFile(customModel, modPath, modType)
return
end
@@ -245,15 +259,144 @@ local function beginLoadCustomModel(customModel, elementToApply)
end
end
- if colPath then
- loadOneMod("col", colPath)
+ loadingQueue[customModel].countModFilesLoaded = 0
+
+ for _, fileInfo in pairs(queuedInfo.filesList) do
+ loadOneMod(fileInfo.type, fileInfo.path)
+ end
+end
+
+local function onFailedToDownloadModFile(customModel, filePath)
+ local queuedInfo = loadingQueue[customModel]
+ if queuedInfo then
+ outputDebugString(
+ "downloadFile failed for '" .. filePath .. "' for custom model " .. customModel .. ", aborting load process.",
+ 1)
+ -- Inform the client (console, useful log)
+ outputConsole(
+ ("[newmodels_red] Too many failed download attempts for '%s' for custom model %s, aborting load process.")
+ :format(tostring(filePath), tostring(customModel))
+ )
+ loadingQueue[customModel] = nil
+ end
+end
+
+-- Handle file downloads requested by this resource
+addEventHandler("onClientFileDownloadComplete", resourceRoot, function(filePath, success)
+ if filesBeingPreDownloaded[filePath] then
+ if not success then
+ outputDebugString(
+ "Pre-download of file '" .. filePath .. "' failed.", 1)
+ else
+ print("Pre-download of file '" .. filePath .. "' succeeded.")
+ end
+ filesBeingPreDownloaded[filePath] = nil
+ return
+ end
+ for customModel, queuedInfo in pairs(loadingQueue) do
+ local countFilesDownloaded = queuedInfo.countFilesDownloaded
+ if queuedInfo.phase == LOADING_QUEUE_PHASES.DOWNLOAD_FILES then
+ for _, fileInfo in pairs(queuedInfo.filesList) do
+ if fileInfo.path == filePath then
+ if (not success) then
+ if (fileInfo.downloadRetries or 0) < DOWNLOAD_FILE_MAX_RETRIES then
+ fileInfo.downloadRetries = (fileInfo.downloadRetries or 0) + 1
+ print(
+ "/!\\ Retrying download (" ..
+ (fileInfo.downloadRetries) .. "/" .. DOWNLOAD_FILE_MAX_RETRIES .. ") in " ..
+ (math.ceil(DOWNLOAD_RETRY_WAIT_DELAY_MS / 1000)) .. " second(s) for custom model",
+ customModel,
+ "file:", filePath)
+ -- Inform the client (console, useful log)
+ outputConsole(
+ ("[newmodels_red] Retrying download (%d/%d) in %d second(s) for custom model %s file: %s")
+ :
+ format(
+ fileInfo.downloadRetries,
+ DOWNLOAD_FILE_MAX_RETRIES,
+ math.ceil(DOWNLOAD_RETRY_WAIT_DELAY_MS / 1000),
+ tostring(customModel),
+ tostring(filePath)
+ )
+ )
+ setTimer(function()
+ downloadFile(filePath)
+ end, DOWNLOAD_RETRY_WAIT_DELAY_MS, 1)
+ return
+ end
+ onFailedToDownloadModFile(customModel, filePath)
+ return
+ end
+ -- print("Download success for custom model", customModel, "file:", filePath)
+
+ countFilesDownloaded = countFilesDownloaded + 1
+ loadingQueue[customModel].countFilesDownloaded = countFilesDownloaded
+
+ if countFilesDownloaded == #queuedInfo.filesList then
+ -- print(" All files downloaded for custom model", customModel)
+ beginLoadCustomModelElements(customModel)
+ end
+ return
+ end
+ end
+ end
+ end
+end, false)
+
+local function beginDownloadModelFiles(customModel)
+ local queuedInfo = loadingQueue[customModel]
+ if not queuedInfo then return end
+ if queuedInfo.phase ~= LOADING_QUEUE_PHASES.DOWNLOAD_FILES then return end
+
+ local customInfo = customModels[customModel]
+ if not customInfo then
+ loadingQueue[customModel] = nil
+ return
+ end
+
+ if (not customInfo.settings["downloadFilesOnDemand"]) then
+ -- No downloading needed, proceed to load model elements
+ beginLoadCustomModelElements(customModel)
+ return
+ end
+
+ loadingQueue[customModel].countFilesDownloaded = 0
+
+ -- local totalFilesCount = #queuedInfo.filesList
+ -- print(customModel, "downloading files...", totalFilesCount)
+
+ for _, fileInfo in pairs(queuedInfo.filesList) do
+ downloadFile(fileInfo.path)
end
- if txdPath then
- loadOneMod("txd", txdPath)
+end
+
+local function beginLoadCustomModel(customModel, elementToApply)
+ local customInfo = customModels[customModel]
+ if not customInfo then
+ outputDebugString("Trying to load custom model " .. customModel .. " that does not exist", 2)
+ return
end
- if dffPath then
- loadOneMod("dff", dffPath)
+
+ if loadedModels[customModel] then
+ outputDebugString("Trying to load custom model " .. customModel .. " that is already loaded", 1)
+ return
end
+
+ local colPath, txdPath, dffPath = customInfo.col, customInfo.txd, customInfo.dff
+
+ local filesList = {}
+ if colPath then filesList[#filesList + 1] = { type = "col", path = colPath } end
+ if txdPath then filesList[#filesList + 1] = { type = "txd", path = txdPath } end
+ if dffPath then filesList[#filesList + 1] = { type = "dff", path = dffPath } end
+
+ loadingQueue[customModel] = {
+ elementToApply = elementToApply,
+ filesList = filesList,
+ -- Start in Phase 1
+ phase = LOADING_QUEUE_PHASES.DOWNLOAD_FILES,
+ }
+
+ beginDownloadModelFiles(customModel)
end
local function isCustomModelInUse(customModel)
@@ -304,6 +447,7 @@ local function freeAllocatedModelNow(customModel)
-- Unset loadedModel info
loadedModels[customModel] = nil
+ return true
end
local function freeAllocatedModel(customModel)
@@ -337,7 +481,7 @@ local function attemptApplyElementCustomModel(element)
end
end
-addEventHandler("newmodels_azul:setElementCustomModel", root, function(id)
+addEventHandler("newmodels_red:setElementCustomModel", root, function(id)
if not isValidElement(source) then return end
id = tonumber(id) or nil
local oldCustomModel = elementModels[source]
@@ -385,24 +529,25 @@ local function restoreElementBaseModels()
end
end
-addEventHandler("newmodels_azul:receiveCustomModels", resourceRoot, function(customModelsFromServer, elementModelsFromServer)
- restoreElementBaseModels()
+addEventHandler("newmodels_red:receiveCustomModels", resourceRoot,
+ function(customModelsFromServer, elementModelsFromServer)
+ restoreElementBaseModels()
- -- Unload all loaded models
- for customModel, _ in pairs(loadedModels) do
- freeAllocatedModelNow(customModel)
- end
+ -- Unload all loaded models
+ for customModel, _ in pairs(loadedModels) do
+ freeAllocatedModelNow(customModel)
+ end
- customModels = customModelsFromServer
+ customModels = customModelsFromServer
- elementModels = elementModelsFromServer
+ elementModels = elementModelsFromServer
- for _, elementType in pairs(getValidElementTypes()) do
- for _, element in pairs(getElementsByType(elementType, root, true)) do
- attemptApplyElementCustomModel(element)
+ for _, elementType in pairs(getValidElementTypes()) do
+ for _, element in pairs(getElementsByType(elementType, root, true)) do
+ attemptApplyElementCustomModel(element)
+ end
end
- end
-end, false)
+ end, false)
addEventHandler("onClientResourceStop", resourceRoot, function()
-- Free all allocated models instantly
@@ -410,3 +555,39 @@ addEventHandler("onClientResourceStop", resourceRoot, function()
freeAllocatedModelNow(customModel)
end
end, false)
+
+-- Custom event to allow developer to force download some files at a certain moment
+-- Useful for pre-downloading files before they are needed
+-- Usage: triggerEvent("newmodels_red:preDownloadNewModels", localPlayer, { , , ... })
+local function requestPreDownloadNewModels(customModelList)
+ if type(customModelList) ~= "table" then
+ outputDebugString("requestPreDownloadNewModels: Invalid arg(1), table expected", 2)
+ return
+ end
+ for _, customModel in pairs(customModelList) do
+ if type(customModel) ~= "number" then
+ outputDebugString("requestPreDownloadNewModels: Invalid custom model ID: " .. tostring(customModel), 2)
+ elseif not customModels[customModel] then
+ outputDebugString(
+ "requestPreDownloadNewModels: Custom model ID " .. tostring(customModel) .. " does not exist.", 2)
+ elseif loadingQueue[customModel] then
+ outputDebugString(
+ "requestPreDownloadNewModels: Custom model ID " .. tostring(customModel) .. " is already loading...", 2)
+ else
+ local customInfo = customModels[customModel]
+ local filesList = {}
+ if customInfo.col then filesList[#filesList + 1] = { type = "col", path = customInfo.col } end
+ if customInfo.txd then filesList[#filesList + 1] = { type = "txd", path = customInfo.txd } end
+ if customInfo.dff then filesList[#filesList + 1] = { type = "dff", path = customInfo.dff } end
+
+ for _, fileInfo in pairs(filesList) do
+ filesBeingPreDownloaded[fileInfo.path] = {
+ customModel = customModel,
+ }
+ downloadFile(fileInfo.path)
+ end
+ end
+ end
+end
+
+addEventHandler("newmodels_red:preDownloadNewModels", localPlayer, requestPreDownloadNewModels, false)
diff --git a/newmodels_red/scripts/core/lib/server_async.lua b/newmodels_red/scripts/core/lib/server_async.lua
new file mode 100644
index 0000000..db2c5d8
--- /dev/null
+++ b/newmodels_red/scripts/core/lib/server_async.lua
@@ -0,0 +1,457 @@
+local function loadClass()
+ ---------
+ -- Start of slither.lua dependency
+ ---------
+
+ local _LICENSE = -- zlib / libpng
+ [[
+ Copyright (c) 2011-2014 Bart van Strien
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+ ]]
+
+ local class =
+ {
+ _VERSION = "Slither 20140904",
+ -- I have no better versioning scheme, deal with it
+ _DESCRIPTION = "Slither is a pythonic class library for lua",
+ _URL = "http://bitbucket.org/bartbes/slither",
+ _LICENSE = _LICENSE,
+ }
+
+ local function stringtotable(path)
+ local t = _G
+ local name
+
+ for part in path:gmatch("[^%.]+") do
+ t = name and t[name] or t
+ name = part
+ end
+
+ return t, name
+ end
+
+ local function class_generator(name, b, t)
+ local parents = {}
+ for _, v in ipairs(b) do
+ parents[v] = true
+ for _, v in ipairs(v.__parents__) do
+ parents[v] = true
+ end
+ end
+
+ local temp = { __parents__ = {} }
+ for i, v in pairs(parents) do
+ table.insert(temp.__parents__, i)
+ end
+
+ local class = setmetatable(temp, {
+ __index = function(self, key)
+ if key == "__class__" then return temp end
+ if key == "__name__" then return name end
+ if t[key] ~= nil then return t[key] end
+ for i, v in ipairs(b) do
+ if v[key] ~= nil then return v[key] end
+ end
+ if tostring(key):match("^__.+__$") then return end
+ if self.__getattr__ then
+ return self:__getattr__(key)
+ end
+ end,
+
+ __newindex = function(self, key, value)
+ t[key] = value
+ end,
+
+ allocate = function(instance)
+ local smt = getmetatable(temp)
+ local mt = { __index = smt.__index }
+
+ function mt:__newindex(key, value)
+ if self.__setattr__ then
+ return self:__setattr__(key, value)
+ else
+ return rawset(self, key, value)
+ end
+ end
+
+ if temp.__cmp__ then
+ if not smt.eq or not smt.lt then
+ function smt.eq(a, b)
+ return a.__cmp__(a, b) == 0
+ end
+
+ function smt.lt(a, b)
+ return a.__cmp__(a, b) < 0
+ end
+ end
+ mt.__eq = smt.eq
+ mt.__lt = smt.lt
+ end
+
+ for i, v in pairs {
+ __call__ = "__call", __len__ = "__len",
+ __add__ = "__add", __sub__ = "__sub",
+ __mul__ = "__mul", __div__ = "__div",
+ __mod__ = "__mod", __pow__ = "__pow",
+ __neg__ = "__unm", __concat__ = "__concat",
+ __str__ = "__tostring",
+ } do
+ if temp[i] then mt[v] = temp[i] end
+ end
+
+ return setmetatable(instance or {}, mt)
+ end,
+
+ __call = function(self, ...)
+ local instance = getmetatable(self).allocate()
+ if instance.__init__ then instance:__init__(...) end
+ return instance
+ end
+ })
+
+ for i, v in ipairs(t.__attributes__ or {}) do
+ class = v(class) or class
+ end
+
+ return class
+ end
+
+ local function inheritance_handler(set, name, ...)
+ local args = { ... }
+
+ for i = 1, select("#", ...) do
+ if args[i] == nil then
+ error("nil passed to class, check the parents")
+ end
+ end
+
+ local t = nil
+ if #args == 1 and type(args[1]) == "table" and not args[1].__class__ then
+ t = args[1]
+ args = {}
+ end
+
+ for i, v in ipairs(args) do
+ if type(v) == "string" then
+ local t, name = stringtotable(v)
+ args[i] = t[name]
+ end
+ end
+
+ local func = function(t)
+ local class = class_generator(name, args, t)
+ if set then
+ local root_table, name = stringtotable(name)
+ root_table[name] = class
+ end
+ return class
+ end
+
+ if t then
+ return func(t)
+ else
+ return func
+ end
+ end
+
+ function class.private(name)
+ return function(...)
+ return inheritance_handler(false, name, ...)
+ end
+ end
+
+ class = setmetatable(class, {
+ __call = function(self, name)
+ return function(...)
+ return inheritance_handler(true, name, ...)
+ end
+ end,
+ })
+
+
+ function class.issubclass(class, parents)
+ if parents.__class__ then parents = { parents } end
+ for i, v in ipairs(parents) do
+ local found = true
+ if v ~= class then
+ found = false
+ for _, p in ipairs(class.__parents__) do
+ if v == p then
+ found = true
+ break
+ end
+ end
+ end
+ if not found then return false end
+ end
+ return true
+ end
+
+ function class.isinstance(obj, parents)
+ return type(obj) == "table" and obj.__class__ and class.issubclass(obj.__class__, parents)
+ end
+
+ -- Export a Class Commons interface
+ -- to allow interoperability between
+ -- class libraries.
+ -- See https://github.com/bartbes/Class-Commons
+ --
+ -- NOTE: Implicitly global, as per specification, unfortunately there's no nice
+ -- way to both provide this extra interface, and use locals.
+ if common_class ~= false then
+ common = {}
+ function common.class(name, prototype, superclass)
+ prototype.__init__ = prototype.init
+ return class_generator(name, { superclass }, prototype)
+ end
+
+ function common.instance(class, ...)
+ return class(...)
+ end
+ end
+
+ ---------
+ -- End of slither.lua dependency
+ ---------
+
+ return class;
+end
+
+local class = loadClass();
+
+--- GTA:MTA Lua async thread scheduler.
+-- @author Inlife
+-- @license MIT
+-- @url https://github.com/Inlife/mta-lua-async
+-- @dependency slither.lua https://bitbucket.org/bartbes/slither
+
+class "_Async" {
+
+ -- Constructor mehtod
+ -- Starts timer to manage scheduler
+ -- @access public
+ -- @usage local asyncmanager = async();
+ __init__ = function(self)
+ self.threads = {};
+ self.resting = 50; -- in ms (resting time)
+ self.maxtime = 200; -- in ms (max thread iteration time)
+ self.current = 0; -- starting frame (resting)
+ self.state = "suspended"; -- current scheduler executor state
+ self.debug = false;
+ self.priority = {
+ low = { 500, 50 }, -- better fps
+ normal = { 200, 200 }, -- medium
+ high = { 50, 500 } -- better perfomance
+ };
+
+ self:setPriority("normal");
+ end,
+
+
+ -- Switch scheduler state
+ -- @access private
+ -- @param boolean [istimer] Identifies whether or not
+ -- switcher was called from main loop
+ switch = function(self, istimer)
+ self.state = "running";
+
+ if (self.current + 1 <= #self.threads) then
+ self.current = self.current + 1;
+ self:execute(self.current);
+ else
+ self.current = 0;
+
+ if (#self.threads <= 0) then
+ self.state = "suspended";
+ return;
+ end
+
+ -- setTimer(function theFunction, int timeInterval, int timesToExecute)
+ -- (GTA:MTA server scripting function)
+ -- For other environments use alternatives.
+ setTimer(function()
+ self:switch();
+ end, self.resting, 1);
+ end
+ end,
+
+
+ -- Managing thread (resuming, removing)
+ -- In case of "dead" thread, removing, and skipping to the next (recursive)
+ -- @access private
+ -- @param int id Thread id (in table async.threads)
+ execute = function(self, id)
+ local thread = self.threads[id];
+
+ if (thread == nil or coroutine.status(thread) == "dead") then
+ table.remove(self.threads, id);
+ self:switch();
+ else
+ coroutine.resume(thread);
+ self:switch();
+ end
+ end,
+
+
+ -- Adding thread
+ -- @access private
+ -- @param function func Function to operate with
+ add = function(self, func)
+ local thread = coroutine.create(func);
+ table.insert(self.threads, thread);
+ end,
+
+
+ -- Set priority for executor
+ -- Use before you call 'iterate' or 'foreach'
+ -- @access public
+ -- @param string|int param1 "low"|"normal"|"high" or number to set 'resting' time
+ -- @param int|void param2 number to set 'maxtime' of thread
+ -- @usage async:setPriority("normal");
+ -- @usage async:setPriority(50, 200);
+ setPriority = function(self, param1, param2)
+ if (type(param1) == "string") then
+ if (self.priority[param1] ~= nil) then
+ self.resting = self.priority[param1][1];
+ self.maxtime = self.priority[param1][2];
+ end
+ else
+ self.resting = param1;
+ self.maxtime = param2;
+ end
+ end,
+
+ -- Set debug mode enabled/disabled
+ -- @access public
+ -- @param boolean value true - enabled, false - disabled
+ -- @usage async:setDebug(true);
+ setDebug = function(self, value)
+ self.debug = value;
+ end,
+
+
+ -- Iterate on interval (for cycle)
+ -- @access public
+ -- @param int from Iterate from
+ -- @param int to Iterate to
+ -- @param function func Iterate using func
+ -- Function func params:
+ -- @param int [i] Iteration index
+ -- @param function [callback] Callback function, called when execution finished
+ -- Usage:
+ -- @usage async:iterate(1, 10000, function(i)
+ -- print(i);
+ -- end);
+ iterate = function(self, from, to, func, callback)
+ self:add(function()
+ local a = getTickCount();
+ local lastresume = getTickCount();
+ for i = from, to do
+ func(i);
+
+ -- int getTickCount()
+ -- (GTA:MTA server scripting function)
+ -- For other environments use alternatives.
+ if getTickCount() > lastresume + self.maxtime then
+ coroutine.yield()
+ lastresume = getTickCount()
+ end
+ end
+ if (self.debug) then
+ print("[DEBUG]Async iterate: " .. (getTickCount() - a) .. "ms");
+ end
+ if (callback) then
+ callback();
+ end
+ end);
+
+ self:switch();
+ end,
+
+ -- Iterate over array (foreach cycle)
+ -- @access public
+ -- @param table array Input array
+ -- @param function func Iterate using func
+ -- Function func params:
+ -- @param int [v] Iteration value
+ -- @param int [k] Iteration key
+ -- @param function [callback] Callback function, called when execution finished
+ -- Usage:
+ -- @usage async:foreach(vehicles, function(vehicle, id)
+ -- print(vehicle.title);
+ -- end);
+ foreach = function(self, array, func, callback)
+ self:add(function()
+ local a = getTickCount();
+ local lastresume = getTickCount();
+ for k, v in ipairs(array) do
+ func(v, k);
+
+ -- int getTickCount()
+ -- (GTA:MTA server scripting function)
+ -- For other environments use alternatives.
+ if getTickCount() > lastresume + self.maxtime then
+ coroutine.yield()
+ lastresume = getTickCount()
+ end
+ end
+ if (self.debug) then
+ print("[DEBUG]Async foreach: " .. (getTickCount() - a) .. "ms");
+ end
+ if (callback) then
+ callback();
+ end
+ end);
+
+ self:switch();
+ end,
+}
+
+-- Async Singleton wrapper
+Async = {
+ instance = nil,
+};
+
+-- After first call, creates an instance and stores it
+local function getInstance()
+ if Async.instance == nil then
+ Async.instance = _Async();
+ end
+
+ return Async.instance;
+end
+
+-- proxy methods for public members
+function Async:setDebug(...)
+ getInstance():setDebug(...);
+end
+
+function Async:setPriority(...)
+ getInstance():setPriority(...);
+end
+
+function Async:iterate(...)
+ getInstance():iterate(...);
+end
+
+function Async:foreach(...)
+ getInstance():foreach(...);
+end
diff --git a/newmodels_red/scripts/core/server_dev.lua b/newmodels_red/scripts/core/server_dev.lua
new file mode 100644
index 0000000..12d69e6
--- /dev/null
+++ b/newmodels_red/scripts/core/server_dev.lua
@@ -0,0 +1,43 @@
+-- Useful dev commands
+-- Only usable from server console
+
+local function msg(str)
+ outputServerLog(("[%s] %s"):format(getResourceName(resource), str))
+end
+
+local function cmdCheckModelId(executor, cmd, modelId)
+ if getElementType(executor) ~= "console" then return end
+
+ modelId = tonumber(modelId)
+ if not modelId then
+ msg(("Usage: %s "):format(cmd))
+ return
+ end
+
+ local isDef, elementType = isDefaultID(false, modelId)
+ if isDef then
+ msg(("Model ID %d is a default '%s' GTA model."):format(modelId, elementType))
+ return
+ end
+
+ local customInfo = customModels[modelId]
+ if not customInfo then
+ msg(("Model ID %d is NOT loaded as a custom model."):format(modelId))
+ return
+ end
+
+ local customModelName = customInfo.name or ""
+ local baseModel = customInfo.baseModel
+ local baseModelName = ""
+ if customInfo.type == "vehicle" then
+ local vehName = getVehicleNameFromModel(baseModel)
+ if vehName then
+ baseModelName = (" ('%s')"):format(vehName)
+ end
+ end
+
+ msg(("Model ID %d is loaded as custom model '%s' (%s), based on model ID %d%s."):format(
+ modelId, customModelName, customInfo.type, baseModel, baseModelName
+ ))
+end
+addCommandHandler("checkmodelid", cmdCheckModelId)
diff --git a/newmodels_red/scripts/core/server_logic.lua b/newmodels_red/scripts/core/server_logic.lua
new file mode 100644
index 0000000..8f95605
--- /dev/null
+++ b/newmodels_red/scripts/core/server_logic.lua
@@ -0,0 +1,660 @@
+-- Server-side logic for adding new models
+local RESOURCE_NAME = getResourceName(resource)
+local AUTO_MODELS_FOLDER = "models"
+local VALID_MODEL_TYPES = { "vehicle", "object", "ped" }
+
+local baseModelCounts = {}
+
+-- Async:setDebug(true);
+Async:setPriority("low");
+
+local function srvLog(str)
+ outputServerLog("[" .. RESOURCE_NAME .. "] " .. str)
+end
+
+local function stringStartswith(str, start)
+ return str:sub(1, #start) == start
+end
+
+-- .................................................................
+-- ...... Load new models automatically from folder structure ......
+-- .................................................................
+
+-- Model settings (from .txt files overriding defaults):
+local DECLARATIVE_SETTINGS = { "disableAutoFree", "disableTXDTextureFiltering", "enableDFFAlphaTransparency" }
+-- - txd=path
+-- - dff=path
+-- - col=path
+-- - lodDistance=number
+-- - settings=path
+
+local function parseFilesFromMeta()
+ local mxmlFile = xmlLoadFile("meta.xml", true)
+ if not mxmlFile then
+ return false, "failed to load meta.xml"
+ end
+ local nodes = xmlNodeGetChildren(mxmlFile)
+ if not nodes then
+ xmlUnloadFile(mxmlFile)
+ return false, "failed to get meta.xml children nodes"
+ end
+ local filePaths = {}
+ for _, node in pairs(nodes) do
+ if xmlNodeGetName(node) == "file" then
+ local srcAttr = xmlNodeGetAttribute(node, "src")
+ if srcAttr and type(srcAttr) == "string" then
+ local downloadAttr = xmlNodeGetAttribute(node, "download")
+ filePaths[srcAttr] = { downloadOnDemand = (downloadAttr == "false") }
+ end
+ end
+ end
+ xmlUnloadFile(mxmlFile)
+ return filePaths
+end
+
+local metaFilePaths, metaFailReason = parseFilesFromMeta()
+if not metaFilePaths then
+ srvLog("[parseFilesFromMeta] " .. metaFailReason)
+ outputDebugString("Failed to load new models. See server log for details.", 1)
+ return
+end
+
+-- Basic glob to Lua pattern conversion
+function globToPattern(glob, sep)
+ sep = sep or "/"
+
+ local function escape_lua_pattern(s)
+ return s:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
+ end
+
+ local pat = ""
+ local i = 1
+ local len = #glob
+ while i <= len do
+ local c = glob:sub(i, i)
+ if c == "*" then
+ if i + 1 <= len and glob:sub(i + 1, i + 1) == "*" then
+ pat = pat .. ".*"
+ i = i + 2
+ else
+ pat = pat .. "[^" .. escape_lua_pattern(sep) .. "]*"
+ i = i + 1
+ end
+ elseif c == "?" then
+ pat = pat .. "[^" .. escape_lua_pattern(sep) .. "]"
+ i = i + 1
+ else
+ pat = pat .. escape_lua_pattern(c)
+ i = i + 1
+ end
+ end
+
+ pat = "^" .. pat .. "$"
+ return pat
+end
+
+--- Checks if a given file path matches any glob pattern defined in filePaths
+local function matchesMetaFile(filePath, filePaths)
+ for src, data in pairs(filePaths) do
+ local luaPattern = globToPattern(src)
+ -- print("Glob:", src)
+ -- print("Pattern:", luaPattern)
+
+ -- The Lua pattern matching function
+ if filePath:match(luaPattern) then
+ -- print(filePath, "Matched with pattern:", luaPattern)
+ return true, data
+ end
+ end
+ -- print(filePath, "No match found")
+ return false, nil
+end
+
+local function isAnyFileDownloadOnDemand(filePaths)
+ for _, filePath in pairs(filePaths) do
+ if type(filePath) == "string" then
+ local matches, data = matchesMetaFile(filePath, metaFilePaths)
+ if matches and data and data.downloadOnDemand then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+local function parseModelSettings(customModel, customModelInfo, thisFullPath, isFromSettingsOption)
+ local customModelSettings = {}
+ for key, value in pairs(DEFAULT_AUTO_MODEL_SETTINGS) do
+ customModelSettings[key] = value
+ end
+ local file = fileOpen(thisFullPath, true)
+ if not file then
+ return false, "failed to open file: " .. thisFullPath
+ end
+ local info = fileGetContents(file, false)
+ fileClose(file)
+ if not info then
+ return false, "failed to read file: " .. thisFullPath
+ end
+ local lines = split(info, "\n")
+ for _, settingStr in pairs(lines) do
+ settingStr = settingStr:gsub("\r", "")
+ for _, declarativeSetting in pairs(DECLARATIVE_SETTINGS) do
+ if settingStr == declarativeSetting then
+ customModelSettings[declarativeSetting] = true
+ end
+ end
+ if stringStartswith(settingStr, "lodDistance=") then
+ local lodDistance = tonumber(settingStr:sub(13))
+ if not lodDistance then
+ return false, "invalid lodDistance value: " .. settingStr
+ end
+ customModelSettings.lodDistance = lodDistance
+ elseif stringStartswith(settingStr, "physicalPropsGroup=") then
+ local physicalPropsGroup = tonumber(settingStr:sub(20))
+ if not physicalPropsGroup then
+ return false, "invalid physicalPropsGroup value: " .. settingStr
+ end
+ customModelSettings.physicalPropsGroup = physicalPropsGroup
+ elseif stringStartswith(settingStr, "settings=") then
+ if isFromSettingsOption then -- prevent inception and recursion
+ return false,
+ "settings option cannot point to a settings file that contains another settings option @ " ..
+ thisFullPath
+ end
+ local settingsPath = settingStr:sub(10)
+ local settingsFullPath = AUTO_MODELS_FOLDER .. "/" .. settingsPath
+ if not fileExists(settingsFullPath) then
+ return false, "settings file not found: " .. settingsPath
+ end
+ local settingsInfo = parseModelSettings(customModel, customModelInfo, settingsFullPath, true)
+ if not settingsInfo then
+ return false, "failed to parse settings file: " .. settingsPath
+ end
+ return settingsInfo
+ else
+ for _, settingModelType in pairs({ "txd", "dff", "col" }) do
+ if stringStartswith(settingStr, settingModelType .. "=") then
+ local settingModelPath = settingStr:sub(#settingModelType + 2)
+ local settingModelFullPath = AUTO_MODELS_FOLDER .. "/" .. settingModelPath
+ if not fileExists(settingModelFullPath) then
+ return false, "setting " .. settingModelType .. " file not found: " .. settingModelPath
+ end
+ if customModelInfo[customModel][settingModelType] then
+ return false, "duplicate " .. settingModelType .. " file for custom model: " .. customModel
+ end
+ customModelInfo[customModel][settingModelType] = settingModelFullPath
+ end
+ end
+ end
+ end
+ return customModelSettings
+end
+
+local function parseOneFile(customModelInfo, thisFileName, thisFullPath, name)
+ local isNandoCrypted, fileExt, customModel = isNandoCryptFileName(thisFileName)
+ if not isNandoCrypted then
+ fileExt = string.sub(thisFileName, -3)
+ customModel = tonumber(string.sub(thisFileName, 1, -5))
+ end
+ if (fileExt == "dff" or fileExt == "txd" or fileExt == "col" or fileExt == "txt") and customModel then
+ if not customModelInfo[customModel] then
+ if isDefaultID(false, customModel) then
+ return false, "custom model is a default ID: " .. customModel
+ end
+ if customModels[customModel] then
+ return false, "duplicate custom model: " .. customModel
+ end
+ customModelInfo[customModel] = {}
+ end
+ if fileExt == "txt" then
+ local customModelSettings, failReason = parseModelSettings(customModel, customModelInfo, thisFullPath)
+ if not customModelSettings then
+ return false, failReason
+ end
+ customModelInfo[customModel].settings = customModelSettings
+ else
+ if customModelInfo[customModel][fileExt] then
+ return false, "duplicate " .. fileExt .. " file for custom model: " .. customModel
+ end
+ customModelInfo[customModel][fileExt] = thisFullPath
+ if name then
+ customModelInfo[customModel].name = name
+ end
+ end
+ end
+ return true
+end
+
+local function loadModels()
+ if not pathIsDirectory(AUTO_MODELS_FOLDER) then
+ return false, "directory not found: " .. AUTO_MODELS_FOLDER
+ end
+ local filesAndFolders = pathListDir(AUTO_MODELS_FOLDER)
+ if not filesAndFolders then
+ return false, "failed to list directory: " .. AUTO_MODELS_FOLDER
+ end
+ local countLoaded = 0
+ for _, modelType in pairs(VALID_MODEL_TYPES) do
+ local modelTypePath = AUTO_MODELS_FOLDER .. "/" .. modelType
+ if pathIsDirectory(modelTypePath) then
+ local filesAndFoldersHere = pathListDir(modelTypePath)
+ if not filesAndFoldersHere then
+ return false, "failed to list " .. modelTypePath .. " directory"
+ end
+ for _, fileOrFolder in pairs(filesAndFoldersHere) do
+ local fullPath = modelTypePath .. "/" .. fileOrFolder
+ if pathIsDirectory(fullPath) then
+ local baseModel = tonumber(fileOrFolder)
+ if baseModel then
+ if not isDefaultID(false, baseModel) then
+ return false, "invalid " .. modelType .. " base model: " .. baseModel
+ end
+ local filesAndFoldersInside = pathListDir(fullPath)
+ if not filesAndFoldersInside then
+ return false, "failed to list " .. fullPath .. " directory"
+ end
+ local customModelInfo = {}
+ for _, fileOrFolderInside in pairs(filesAndFoldersInside) do
+ local fullPathInside = fullPath .. "/" .. fileOrFolderInside
+ if pathIsDirectory(fullPathInside) then
+ local filesAndFoldersInsideThis = pathListDir(fullPathInside)
+ if not filesAndFoldersInsideThis then
+ return false, "failed to list " .. fullPathInside .. " directory"
+ end
+ for _, fileOrFolderInsideThis in pairs(filesAndFoldersInsideThis) do
+ local fullPathInsideThis = fullPathInside .. "/" .. fileOrFolderInsideThis
+ local parsed, failReason = parseOneFile(customModelInfo, fileOrFolderInsideThis,
+ fullPathInsideThis, fileOrFolderInside)
+ if not parsed then
+ return false, failReason
+ end
+ end
+ elseif pathIsFile(fullPathInside) then
+ local parsed, failReason = parseOneFile(customModelInfo, fileOrFolderInside,
+ fullPathInside)
+ if not parsed then
+ return false, failReason
+ end
+ end
+ end
+ for customModel, info in pairs(customModelInfo) do
+ if not info.name then
+ baseModelCounts[baseModel] = (baseModelCounts[baseModel] or 0) + 1
+ end
+ local settings = info.settings or {}
+ if isAnyFileDownloadOnDemand({ info.dff, info.txd, info.col }) then
+ settings["downloadFilesOnDemand"] = true
+ end
+ customModels[customModel] = {
+ type = modelType,
+ baseModel = baseModel,
+ dff = info.dff,
+ txd = info.txd,
+ col = info.col,
+ name = info.name or ("%d#%d"):format(baseModel, baseModelCounts[baseModel]),
+ settings = info.settings or {},
+ }
+ countLoaded = countLoaded + 1
+ end
+ end
+ end
+ end
+ end
+ end
+ srvLog("Loaded " .. countLoaded .. " models from auto models folder.")
+ return true
+end
+
+local result, failReason = loadModels()
+if not result then
+ srvLog("[loadModels] " .. failReason)
+ outputDebugString("Failed to load new models. See server log for details.", 1)
+ return
+end
+
+-- .................................................................
+-- ...... Load new models from modList table .......................
+-- .................................................................
+
+-- This function is also used by external function to add models
+local function parseModListEntry(modelType, modInfo)
+ if (type(modInfo.id) ~= "number") or (type(modInfo.base_id) ~= "number") or (not modInfo.path) then
+ return false,
+ "Invalid modInfo entry (missing/wrong type for id, base_id, or path) in modList for model type: " ..
+ modelType
+ end
+
+ local customModel = modInfo.id
+ if isDefaultID(false, customModel) then
+ return false, "custom model is a default ID: " .. customModel
+ end
+ if customModels[customModel] then
+ return false, "duplicate custom model: " .. customModel
+ end
+
+ local baseModel = modInfo.base_id
+ if not isDefaultID(false, baseModel) then
+ return false, "invalid " .. modelType .. " base model: " .. baseModel
+ end
+
+ local dff_path, txd_path, col_path
+ local settings = {}
+ local ignoreDFF, ignoreTXD, ignoreCOL = modInfo.ignoreDFF, modInfo.ignoreTXD, modInfo.ignoreCOL
+
+ if modelType ~= "object" then
+ ignoreCOL = true -- COL not used for peds & vehicles
+ end
+
+ if type(modInfo.path) == "string" then
+ local folder = modInfo.path
+
+ if not ignoreDFF then
+ dff_path = folder .. customModel .. ".dff"
+ end
+ if not ignoreTXD then
+ txd_path = folder .. customModel .. ".txd"
+ end
+ if not ignoreCOL then
+ col_path = folder .. customModel .. ".col"
+ end
+ elseif type(modInfo.path) == "table" then
+ if not ignoreDFF and modInfo.path["dff"] then
+ dff_path = modInfo.path["dff"]
+ end
+ if not ignoreTXD and modInfo.path["txd"] then
+ txd_path = modInfo.path["txd"]
+ end
+ if not ignoreCOL and modInfo.path["col"] then
+ col_path = modInfo.path["col"]
+ end
+ else
+ -- Invalid path type
+ return false, "Invalid path type for custom model ID: " .. customModel
+ end
+
+ -- Optional: Update baseModelCounts if no name is provided
+ local modName = modInfo.name
+ if not modName then
+ baseModelCounts[baseModel] = (baseModelCounts[baseModel] or 0) + 1
+ modName = string.format("%d#%d", baseModel, baseModelCounts[baseModel])
+ end
+
+ -- Apply optional settings
+ if type(modInfo.lodDistance) == "number" then
+ settings["lodDistance"] = modInfo.lodDistance
+ end
+ if modInfo.disableAutoFree then
+ settings["disableAutoFree"] = true
+ end
+ if not modInfo.filteringEnabled then
+ settings["disableTXDTextureFiltering"] = true
+ end
+ if modInfo.alphaTransparency then
+ settings["enableDFFAlphaTransparency"] = true
+ end
+ if modInfo.metaDownloadFalse then
+ settings["downloadFilesOnDemand"] = true
+ end
+
+ local ncExt = NANDOCRYPT_EXT
+
+ -- DFF check
+ if dff_path then
+ if not fileExists(dff_path) then
+ if fileExists(dff_path .. ncExt) then
+ dff_path = dff_path .. ncExt
+ else
+ return false, "DFF file not found for custom model ID " .. customModel .. ": " .. dff_path
+ end
+ elseif stringStartswith(dff_path, AUTO_MODELS_FOLDER .. "/") then
+ -- Disallow using auto models folder for modList entries
+ return false,
+ "DFF file for custom model ID " .. customModel .. " cannot be in the auto models folder: " .. dff_path
+ end
+ end
+
+ -- TXD check
+ if txd_path then
+ if not fileExists(txd_path) then
+ if fileExists(txd_path .. ncExt) then
+ txd_path = txd_path .. ncExt
+ else
+ return false, "TXD file not found for custom model ID " .. customModel .. ": " .. txd_path
+ end
+ elseif stringStartswith(txd_path, AUTO_MODELS_FOLDER .. "/") then
+ -- Disallow using auto models folder for modList entries
+ return false,
+ "TXD file for custom model ID " .. customModel .. " cannot be in the auto models folder: " .. txd_path
+ end
+ end
+
+ -- COL check
+ if col_path then
+ if not fileExists(col_path) then
+ if fileExists(col_path .. ncExt) then
+ col_path = col_path .. ncExt
+ else
+ return false, "COL file not found for custom model ID " .. customModel .. ": " .. col_path
+ end
+ elseif stringStartswith(col_path, AUTO_MODELS_FOLDER .. "/") then
+ -- Disallow using auto models folder for modList entries
+ return false,
+ "COL file for custom model ID " .. customModel .. " cannot be in the auto models folder: " .. col_path
+ end
+ end
+
+ return {
+ customModel = customModel,
+ modelType = modelType,
+ baseModel = baseModel,
+ dff_path = dff_path,
+ txd_path = txd_path,
+ col_path = col_path,
+ modName = modName,
+ settings = settings,
+ }
+end
+local function loadModelsViaModList()
+ -- Loading modList is optional, so we return nil to ignore if not found
+ if type(modList) ~= "table" then
+ return nil
+ end
+
+ local countLoaded = 0
+
+ -- Iterate over each model type in modList
+ for modelType, modelList in pairs(modList) do
+ local validType = false
+ for _, vType in pairs(VALID_MODEL_TYPES) do
+ if modelType == vType then
+ validType = true
+ break
+ end
+ end
+ if not validType then
+ return false, "Invalid model type in modList: " .. tostring(modelType)
+ end
+ if type(modelList) ~= "table" then
+ return false, "Invalid Model list in '" .. modelType .. "' modList"
+ end
+
+ -- Iterate over each individual model entry in the list
+ for _, modInfo in ipairs(modelList) do
+ if type(modInfo) ~= "table" then
+ return false, "Found a modInfo entry that is not a table in modList for model type: " .. modelType
+ end
+ local parsedInfo, parsingFailReason = parseModListEntry(modelType, modInfo)
+ if not parsedInfo then
+ return false, parsingFailReason
+ end
+ customModels[parsedInfo.customModel] = {
+ type = modelType,
+ baseModel = parsedInfo.baseModel,
+ dff = parsedInfo.dff_path or nil,
+ txd = parsedInfo.txd_path or nil,
+ col = parsedInfo.col_path or nil,
+ name = parsedInfo.modName,
+ settings = parsedInfo.settings,
+ }
+ countLoaded = countLoaded + 1
+ end
+ end
+
+ srvLog("Loaded " .. countLoaded .. " models via modList Lua.")
+ return true
+end
+local result2, failReason2 = loadModelsViaModList()
+if result2 == false then
+ srvLog("[loadModelsViaModList] " .. failReason2)
+ outputDebugString("Failed to load models via modList. See server log for details.", 1)
+ return
+end
+
+baseModelCounts = {}
+
+-- .................................................................
+-- ...... Logic for syncing custom models with clients .............
+-- .................................................................
+
+-- Save elementModels in root element data to restore on next startup
+addEventHandler("onResourceStop", resourceRoot, function()
+ if next(elementModels) then
+ setElementData(root, "newmodels_red:elementModels_backup", elementModels, false)
+ end
+end, false)
+
+-- Restore elementModels from root element data on startup if any
+local elementModelsBackup = getElementData(root, "newmodels_red:elementModels_backup")
+if type(elementModelsBackup) == "table" then
+ for element, id in pairs(elementModelsBackup) do
+ if isElement(element) then
+ elementModels[element] = id
+ end
+ end
+end
+
+local function sendCustomModelsToPlayer(player)
+ triggerClientEvent(player, "newmodels_red:receiveCustomModels", resourceRoot, customModels, elementModels)
+end
+
+addEventHandler("onPlayerResourceStart", root, function(res)
+ if res == resource then
+ sendCustomModelsToPlayer(source)
+ end
+end)
+
+-- Handle element destroy (clear any custom model ID from the table)
+-- Syncing with clients is not necessary as they already handle onClientElementDestroy
+addEventHandler("onElementDestroy", root, function()
+ if elementModels[source] then
+ elementModels[source] = nil
+ end
+end)
+
+
+
+-- .......................................................................
+-- ...... Exported functions for adding/removing models dynamically ......
+-- .......................................................................
+
+local function sendListToAllPlayers()
+ for _, player in pairs(getElementsByType("player")) do
+ sendCustomModelsToPlayer(player)
+ end
+ srvLog("Sent updated customModels table to online players.")
+end
+
+-- The following 2 add & remove functions were inspired by their newmodels v3 versions.
+
+function addExternalModels(listToAdd, asyncLoad)
+ if type(listToAdd) ~= "table" then
+ return false, "invalid arg 1: not a table"
+ end
+ if #listToAdd == 0 then
+ return false, "invalid arg 1: empty table"
+ end
+ if type(listToAdd[1]) ~= "table" then
+ return false, "invalid arg 1: first entry is not a table"
+ end
+ local srcResName = sourceResource and getResourceName(sourceResource) or "unknown"
+ local function parseOneListEntry(modInfo)
+ if type(modInfo) ~= "table" then
+ return false, "Found a modInfo entry that is not a table in listToAdd"
+ end
+ local modelType = modInfo.type
+ local validType = false
+ for _, vType in pairs(VALID_MODEL_TYPES) do
+ if modelType == vType then
+ validType = true
+ break
+ end
+ end
+ if not validType then
+ return false, "Invalid model type in modInfo entry: " .. tostring(modelType)
+ end
+ local parsedInfo, parsingFailReason = parseModListEntry(modelType, modInfo)
+ if not parsedInfo then
+ return false, parsingFailReason
+ end
+ customModels[parsedInfo.customModel] = {
+ type = parsedInfo.modelType,
+ baseModel = parsedInfo.baseModel,
+ dff = parsedInfo.dff_path or nil,
+ txd = parsedInfo.txd_path or nil,
+ col = parsedInfo.col_path or nil,
+ name = parsedInfo.modName,
+ settings = parsedInfo.settings,
+ }
+ return true
+ end
+ if (asyncLoad == true) then
+ srvLog("Beginning async addition of new models from '" .. srcResName .. "' via addExternalModels...")
+ Async:foreach(listToAdd, function(modInfo)
+ local parsed, parsingFailReason = parseOneListEntry(modInfo)
+ if not parsed then
+ srvLog("Failed to add one external model via addExternalModels (async): " .. parsingFailReason)
+ end
+ end,
+ function()
+ srvLog("Finished adding new models from '" .. srcResName .. "' via addExternalModels (async).")
+ sendListToAllPlayers()
+ end)
+ return true
+ else
+ local countLoaded = 0
+ for _, modInfo in pairs(listToAdd) do
+ local parsed, parsingFailReason = parseOneListEntry(modInfo)
+ if not parsed then
+ return false, parsingFailReason
+ end
+ countLoaded = countLoaded + 1
+ end
+ srvLog("Added " .. countLoaded .. " new models from '" .. srcResName .. "' via addExternalModels.")
+ sendListToAllPlayers()
+ return true
+ end
+end
+
+function removeExternalModels(listToRemove)
+ if type(listToRemove) ~= "table" then
+ return false, "invalid arg 1: not a table"
+ end
+ local countRemoved = 0
+ for _, customModel in pairs(listToRemove) do
+ if type(customModel) ~= "number" then
+ return false, "Found a custom model ID that is not a number in listToRemove"
+ end
+ if customModels[customModel] then
+ customModels[customModel] = nil
+ countRemoved = countRemoved + 1
+ end
+ end
+
+ srvLog("Removed " .. countRemoved .. " external models via removeExternalModels.")
+ srvLog("Sending new customModels to online players...")
+ for _, player in pairs(getElementsByType("player")) do
+ sendCustomModelsToPlayer(player)
+ end
+ return true
+end
diff --git a/newmodels_red/scripts/core/shared_config.lua b/newmodels_red/scripts/core/shared_config.lua
new file mode 100644
index 0000000..bcca463
--- /dev/null
+++ b/newmodels_red/scripts/core/shared_config.lua
@@ -0,0 +1,15 @@
+-- ....... CONFIGURATION ............................................
+
+-- These default values can be overridden per model.
+DEFAULT_AUTO_MODEL_SETTINGS = {
+ ["disableAutoFree"] = false,
+ ["disableTXDTextureFiltering"] = false,
+ ["enableDFFAlphaTransparency"] = false,
+}
+
+-- For downloadFile behavior
+DOWNLOAD_FILE_MAX_RETRIES = 3
+DOWNLOAD_RETRY_WAIT_DELAY_MS = 1000
+
+-- NandoCrypt file extension
+NANDOCRYPT_EXT = ".nandocrypt"
diff --git a/newmodels_azul/scripts/core/shared_exported.lua b/newmodels_red/scripts/core/shared_exported.lua
similarity index 82%
rename from newmodels_azul/scripts/core/shared_exported.lua
rename to newmodels_red/scripts/core/shared_exported.lua
index d8957eb..814ac57 100644
--- a/newmodels_azul/scripts/core/shared_exported.lua
+++ b/newmodels_red/scripts/core/shared_exported.lua
@@ -25,11 +25,9 @@ setElementModelMTA = setElementModel
spawnPlayerMTA = spawnPlayer
-newmodelsUtils.resources = {}
-
newmodelsUtils.getSharedCustomModelsTbl = function()
if IS_IMPORTED then
- return exports["newmodels_azul"]:getCustomModels()
+ return exports["newmodels_red"]:getCustomModels()
end
-- Script is running within this resource, so we can access the table directly
return customModels
@@ -37,7 +35,7 @@ end
newmodelsUtils.getSharedElementModelsTbl = function()
if IS_IMPORTED then
- return exports["newmodels_azul"]:getElementModels()
+ return exports["newmodels_red"]:getElementModels()
end
-- Script is running within this resource, so we can access the table directly
return elementModels
@@ -45,19 +43,23 @@ end
newmodelsUtils.setElementCustomModel = function(...)
if IS_IMPORTED then
- return exports["newmodels_azul"]:setElementCustomModel(...)
+ return exports["newmodels_red"]:setElementCustomModel(...)
end
return setElementCustomModel(...)
end
function isCustomModelCompatible(id, elementOrElementType)
- assert(type(id) == "number", "Bad argument @ isCustomModelCompatible [expected number at argument 1, got " .. type(id) .. "]")
- assert(type(elementOrElementType) == "string" or isElement(elementOrElementType), "Bad argument @ isCustomModelCompatible [expected string/element at argument 2, got " .. type(elementOrElementType) .. "]")
+ assert(type(id) == "number",
+ "Bad argument @ isCustomModelCompatible [expected number at argument 1, got " .. type(id) .. "]")
+ assert(type(elementOrElementType) == "string" or isElement(elementOrElementType),
+ "Bad argument @ isCustomModelCompatible [expected string/element at argument 2, got " ..
+ type(elementOrElementType) .. "]")
local customInfo = newmodelsUtils.getSharedCustomModelsTbl()[id]
if not customInfo then return false end
- local elementType = type(elementOrElementType) == "string" and elementOrElementType or getElementType(elementOrElementType)
+ local elementType = type(elementOrElementType) == "string" and elementOrElementType or
+ getElementType(elementOrElementType)
if elementType == "object" or elementType == "pickup" then
return customInfo.type == "object"
elseif elementType == "ped" or elementType == "player" then
@@ -108,16 +110,16 @@ function isDefaultID(elementType, id)
if not elementType then
-- Check all IDs
if newmodelsUtils.isDefaultObjectID(id) then
- return true
+ return true, "object"
end
for _, id2 in pairs(IDS_PEDS) do
if id2 == id then
- return true
+ return true, "ped"
end
end
for _, id2 in pairs(IDS_VEHICLES) do
if id2 == id then
- return true
+ return true, "vehicle"
end
end
elseif elementType == "ped" or elementType == "player" then
@@ -150,23 +152,11 @@ function isValidElement(element)
end
return false
end
+
function getValidElementTypes()
return VALID_ELEMENT_TYPES
end
--- In MTA Elements are always destroyed when the resource that created them is stopped: this cannot be changed.
--- So we use an internal table to keep track of elements created by resources.
-newmodelsUtils.setElementResource = function(element, theResource)
- if isElement(element) then
- -- if not isElement(theResource) then theResource = resource end
- if (not theResource) or (not isElement(getResourceRootElement(theResource))) then theResource = resource end
- if type(newmodelsUtils.resources[theResource]) ~= "table" then
- newmodelsUtils.resources[theResource] = {}
- end
- table.insert(newmodelsUtils.resources[theResource], element)
- end
-end
-
function getBaseModelIdFromCustomModelId(id)
local customInfo = newmodelsUtils.getSharedCustomModelsTbl()[id]
if customInfo then
@@ -190,6 +180,23 @@ newmodelsUtils.createElementWithModel = function(elementType, modelid, ...)
return false
end
+if not IS_IMPORTED then
+ newmodelsUtils.resourceElements = {}
+ -- In MTA Elements are always destroyed when the resource that created them is stopped: this cannot be changed.
+ -- So we use an internal table to keep track of elements created by resources.
+ newmodelsUtils.assignElementToResource = function(theElement, theResource)
+ if not theResource then return end
+ local resRoot = getResourceRootElement(theResource)
+ if not resRoot then return end
+ if not isElement(theElement) then return end
+ local theResName = getResourceName(theResource)
+ if not newmodelsUtils.resourceElements[theResName] then
+ newmodelsUtils.resourceElements[theResName] = {}
+ end
+ newmodelsUtils.resourceElements[theResName][#newmodelsUtils.resourceElements[theResName] + 1] = theElement
+ end
+end
+
newmodelsUtils.createElementSafe = function(elementType, id, ...)
local baseModel = getBaseModelIdFromCustomModelId(id)
local element = newmodelsUtils.createElementWithModel(elementType, baseModel, ...)
@@ -200,20 +207,19 @@ newmodelsUtils.createElementSafe = function(elementType, id, ...)
-- Custom model
newmodelsUtils.setElementCustomModel(element, id)
end
+ if not IS_IMPORTED then newmodelsUtils.assignElementToResource(element, sourceResource) end
return element
end
function createObject(id, ...)
assert(type(id) == "number", "Invalid model ID passed: " .. tostring(id))
local object = newmodelsUtils.createElementSafe("object", id, ...)
- newmodelsUtils.setElementResource(object, sourceResource)
return object
end
function createVehicle(id, ...)
assert(type(id) == "number", "Invalid model ID passed: " .. tostring(id))
local vehicle = newmodelsUtils.createElementSafe("vehicle", id, ...)
- newmodelsUtils.setElementResource(vehicle, sourceResource)
return vehicle
end
@@ -228,7 +234,6 @@ end
function createPed(id, ...)
assert(type(id) == "number", "Invalid model ID passed: " .. tostring(id))
local ped = newmodelsUtils.createElementSafe("ped", id, ...)
- newmodelsUtils.setElementResource(ped, sourceResource)
return ped
end
@@ -242,7 +247,6 @@ function createPickup(x, y, z, theType, id, respawnTime, ammo)
else
pickup = createPickupMTA(x, y, z, theType, id, respawnTime, ammo)
end
- newmodelsUtils.setElementResource(pickup, sourceResource)
return pickup
end
@@ -287,7 +291,8 @@ function getElementBaseModel(element)
end
function getCustomModelName(id)
- assert(type(id) == "number", "Bad argument @ getCustomModelName [expected number at argument 1, got " .. type(id) .. "]")
+ assert(type(id) == "number",
+ "Bad argument @ getCustomModelName [expected number at argument 1, got " .. type(id) .. "]")
local customInfo = newmodelsUtils.getSharedCustomModelsTbl()[id]
return customInfo and customInfo.name or nil
end
@@ -323,18 +328,22 @@ if not isClientsideScript then
end
end
-newmodelsUtils.handleResourceStop = function(stoppedRes)
- if newmodelsUtils.resources[stoppedRes] then
- for i = 1, #newmodelsUtils.resources[stoppedRes] do
- local element = newmodelsUtils.resources[stoppedRes][i]
- if isElement(element) then
- destroyElement(element)
+if not IS_IMPORTED then
+ newmodelsUtils.handleResourceStop = function(stoppedRes)
+ local theResName = getResourceName(stoppedRes)
+ if type(newmodelsUtils.resourceElements) == "table" and type(newmodelsUtils.resourceElements[theResName]) == "table" then
+ for i = 1, #newmodelsUtils.resourceElements[theResName] do
+ local element = newmodelsUtils.resourceElements[theResName][i]
+ if isElement(element) then
+ destroyElement(element)
+ end
end
+ newmodelsUtils.resourceElements[theResName] = nil
end
end
-end
-if isClientsideScript then
- addEventHandler("onClientResourceStop", root, newmodelsUtils.handleResourceStop)
-else
- addEventHandler("onResourceStop", root, newmodelsUtils.handleResourceStop)
+ if isClientsideScript then
+ addEventHandler("onClientResourceStop", root, newmodelsUtils.handleResourceStop)
+ else
+ addEventHandler("onResourceStop", root, newmodelsUtils.handleResourceStop)
+ end
end
diff --git a/newmodels_azul/scripts/core/shared_importfunc.lua b/newmodels_red/scripts/core/shared_importfunc.lua
similarity index 100%
rename from newmodels_azul/scripts/core/shared_importfunc.lua
rename to newmodels_red/scripts/core/shared_importfunc.lua
diff --git a/newmodels_azul/scripts/core/shared_local.lua b/newmodels_red/scripts/core/shared_local.lua
similarity index 71%
rename from newmodels_azul/scripts/core/shared_local.lua
rename to newmodels_red/scripts/core/shared_local.lua
index 18d0eff..664fa8c 100644
--- a/newmodels_azul/scripts/core/shared_local.lua
+++ b/newmodels_red/scripts/core/shared_local.lua
@@ -1,13 +1,10 @@
local isClientsideScript = localPlayer ~= nil
--- NandoCrypt file extension
-local NANDOCRYPT_EXT = ".nandocrypt"
-
function isNandoCryptFileName(fn)
if type(fn) == "string" then
- if fn:sub(-#NANDOCRYPT_EXT) == NANDOCRYPT_EXT then
- local precedingFileExt = fn:sub(-#NANDOCRYPT_EXT - 3, -#NANDOCRYPT_EXT - 1)
- local precedingNumber = tonumber(fn:sub(1, -#NANDOCRYPT_EXT - 5))
+ if fn:sub(- #NANDOCRYPT_EXT) == NANDOCRYPT_EXT then
+ local precedingFileExt = fn:sub(- #NANDOCRYPT_EXT - 3, - #NANDOCRYPT_EXT - 1)
+ local precedingNumber = tonumber(fn:sub(1, - #NANDOCRYPT_EXT - 5))
return true, precedingFileExt, precedingNumber
end
end
@@ -49,17 +46,21 @@ function setElementCustomModel(element, id)
return false
end
if not isCustomModelCompatible(id, element) then
- outputDebugString("Custom model ID " .. id .. " is not compatible with element type " .. getElementType(element), 1)
+ iprint(customModelInfo)
+ outputDebugString(
+ "Custom model ID " .. id .. " is not compatible with element type " .. getElementType(element), 1)
return false
end
end
if not isClientsideScript then
elementModels[element] = id -- Set serverside
setTimer(function()
- triggerClientEvent(getElementsByType("player"), "newmodels_azul:setElementCustomModel", element, id)
+ triggerClientEvent(getElementsByType("player"), "newmodels_red:setElementCustomModel", element, id)
end, 50, 1)
else
- triggerEvent("newmodels_azul:setElementCustomModel", element, id)
+ triggerEvent("newmodels_red:setElementCustomModel", element, id)
end
return true
end
+
+-- ..................................................................
diff --git a/newmodels_azul/scripts/optional/README.md b/newmodels_red/scripts/optional/README.md
similarity index 100%
rename from newmodels_azul/scripts/optional/README.md
rename to newmodels_red/scripts/optional/README.md
diff --git a/newmodels_azul/scripts/optional/debug/c_debug.lua b/newmodels_red/scripts/optional/debug/c_debug.lua
similarity index 96%
rename from newmodels_azul/scripts/optional/debug/c_debug.lua
rename to newmodels_red/scripts/optional/debug/c_debug.lua
index 4261600..029337b 100644
--- a/newmodels_azul/scripts/optional/debug/c_debug.lua
+++ b/newmodels_red/scripts/optional/debug/c_debug.lua
@@ -65,7 +65,7 @@ local function updateDebugViewInfo()
end
local function drawDebug()
- dxDrawText("Newmodels v5 Azul", SW/2, 15, SW/2, 15, 0xff70e2ff, 1.5, "default-bold", "center", "center")
+ dxDrawText("Newmodels v6 Red", SW/2, 15, SW/2, 15, 0xffff7070, 1.5, "default-bold", "center", "center")
dxDrawText(loadedModelsStr, SW/2, 32, SW/2, 32, 0xFFFFFFFF, 1, "default-bold", "center", "top")
for element, customModelStr in pairs(streamedElements) do
diff --git a/newmodels_azul/scripts/optional/debug/g_debug.lua b/newmodels_red/scripts/optional/debug/g_debug.lua
similarity index 100%
rename from newmodels_azul/scripts/optional/debug/g_debug.lua
rename to newmodels_red/scripts/optional/debug/g_debug.lua
diff --git a/newmodels_azul/scripts/optional/debug/s_debug.lua b/newmodels_red/scripts/optional/debug/s_debug.lua
similarity index 100%
rename from newmodels_azul/scripts/optional/debug/s_debug.lua
rename to newmodels_red/scripts/optional/debug/s_debug.lua
diff --git a/newmodels_red/scripts/optional/nando_crypt/nando_decrypter b/newmodels_red/scripts/optional/nando_crypt/nando_decrypter
new file mode 100644
index 0000000..fafa372
Binary files /dev/null and b/newmodels_red/scripts/optional/nando_crypt/nando_decrypter differ
diff --git a/newmodels_azul/scripts/optional/update_checker/s_update_checker.lua b/newmodels_red/scripts/optional/update_checker/s_update_checker.lua
similarity index 100%
rename from newmodels_azul/scripts/optional/update_checker/s_update_checker.lua
rename to newmodels_red/scripts/optional/update_checker/s_update_checker.lua