diff --git a/newmodels/_config.lua b/newmodels/_config.lua
index c607b5e..ef7139f 100644
--- a/newmodels/_config.lua
+++ b/newmodels/_config.lua
@@ -19,6 +19,18 @@ dataNames = {
-- useful for getting a vehicle's base model to fetch its original handling, etc
baseDataName = "baseID"
+-- If you are 100% sure that the mods in mod_list.lua are valid, you can disable initial checks
+-- for faster startup time
+STARTUP_VERIFICATIONS = true
+
+-- Newmodels will try to start these resources (identified by their names) after newmodels has started,
+-- as well as stop them when newmodels is stopped
+-- Usually these are resources that use newmodels and you want them to start only after newmodels,
+-- and they can be stopped when newmodels stops
+OTHER_RESOURCES = {
+ -- { name = "sampobj_reloaded", start = true, stop = true}
+}
+
-- Mod file download feature
SHOW_DOWNLOADING = true -- display the downloading progress dxDraw
KICK_ON_DOWNLOAD_FAILS = true -- kick player if failed to download a file more than X times
diff --git a/newmodels/client.lua b/newmodels/client.lua
index abf717f..d0cce11 100644
--- a/newmodels/client.lua
+++ b/newmodels/client.lua
@@ -631,7 +631,6 @@ function updateStreamedOutElement(source)
end
end
addEventHandler( "onClientElementStreamOut", root, function () updateStreamedOutElement(source) end)
-addEventHandler( "onClientElementDestroy", root, function () updateStreamedOutElement(source) end) -- same behavior for stream out
-- (4) updateModelChangedElement
function updateModelChangedElement(source, oldModel, newModel)
@@ -686,6 +685,30 @@ function updateModelChangedElement(source, oldModel, newModel)
end
addEventHandler( "onClientElementModelChange", root, function (oldModel, newModel) updateModelChangedElement(source, oldModel, newModel) end)
+function handleDestroyedElement()
+ if not received_modlist then return end
+ local et = getElementType(source)
+ if not isElementTypeSupported(et) then
+ return
+ end
+
+ local id = tonumber(getElementData(source, dataNames[et]))
+ if not (id) then return end -- doesn't have a custom model
+
+ if isCustomModID(id) then
+
+ local allocated_id = allocated_ids[id]
+ if not allocated_id then return end -- was not allocated
+
+ if not hasOtherElementsWithModel(source, id) then
+ freeElementCustomMod(id)
+ return
+ end
+ end
+end
+addEventHandler( "onClientElementDestroy", root, handleDestroyedElement)
+
+
-- Free waiting_queue memory when player leaves
addEventHandler( "onClientPlayerQuit", root,
function (reason)
diff --git a/newmodels/meta.xml b/newmodels/meta.xml
index aecfe3a..b5fb1e5 100644
--- a/newmodels/meta.xml
+++ b/newmodels/meta.xml
@@ -1,10 +1,6 @@
-
+ description="minimalistic library for adding new models to your server" version="2.0.1" type="script"/>
+
+
+
+
diff --git a/newmodels/server.lua b/newmodels/server.lua
index 5a4098d..63731d0 100644
--- a/newmodels/server.lua
+++ b/newmodels/server.lua
@@ -214,6 +214,82 @@ function modCheckError(text)
return false
end
+function verifyOneMod(mod, elementType, used_ids)
+ coroutine.yield()
+
+ -- 1. verify IDs
+ if not tonumber(mod.id) then
+ return modCheckError("Invalid mod ID '"..tostring(mod.id).."'")
+ else
+ if mod.id == 0 then
+ return modCheckError("Invalid mod ID '"..tostring(mod.id).."', must be >0")
+ end
+
+ if isDefaultID(false, mod.id) then
+ return modCheckError("Invalid mod ID '"..tostring(mod.id).."', must be out of the default GTA:SA and SAMP ID Range, see shared.lua isDefaultID")
+ end
+
+ for _,id in pairs(used_ids) do
+ if id == mod.id then
+ return modCheckError("Duplicated mod ID '"..id.."'")
+ end
+ end
+
+ table.insert(used_ids, mod.id)
+ end
+ if not tonumber(mod.base_id) then
+ return modCheckError("Invalid mod base ID '"..tostring(mod.base_id).."'")
+ else
+ if not isDefaultID(false, mod.base_id) then
+ return modCheckError("Invalid mod base ID '"..tostring(mod.base_id).."', must be a default GTA:SA ID")
+ end
+ end
+
+ -- 2. verify name
+ if not mod.name or type(mod.name)~="string" then
+
+ return modCheckError("Missing/Invalid mod name '"..tostring(mod.name).."' for mod ID "..mod.id)
+ end
+
+ -- 3. verify path
+ if (not mod.path) then
+
+ return modCheckError("Missing mod path '"..tostring(mod.path).."' for mod ID "..mod.id)
+ end
+ if not (type(mod.path)=="string" or type(mod.path)=="table") then
+
+ return modCheckError("Invalid mod path '"..tostring(mod.path).."' for mod ID "..mod.id)
+ end
+
+ -- 4. verify files exist
+ local ignoreTXD, ignoreDFF, ignoreCOL = mod.ignoreTXD, mod.ignoreDFF, mod.ignoreCOL
+ local paths
+ local path = mod.path
+ if type(path)=="table" then
+ paths = path
+ else
+ paths = getActualModPaths(path, mod.id)
+ end
+ for pathType, path2 in pairs(paths) do
+ if type(pathType) ~= "string" then
+ return modCheckError("Invalid path type '"..tostring(pathType).."' for mod ID "..mod.id)
+ end
+ if type(path2) ~= "string" then
+ return modCheckError("Invalid file path '"..tostring(pathType).."' for mod ID "..mod.id)
+ end
+ if (not fileExists(path2)) and ((ENABLE_NANDOCRYPT) and not fileExists(path2..NANDOCRYPT_EXT)) then
+ if (not ignoreTXD and pathType == "txd")
+ or (not ignoreDFF and pathType == "dff")
+ or ((not ignoreCOL) and elementType == "object" and pathType == "col") then
+
+ return modCheckError("File doesn't exist: '"..tostring(path2).."' for mod ID "..mod.id)
+ end
+ end
+ end
+
+ return true
+end
+
-- verifies mapList, because people can fuck up sometimes :)
function doModListChecks()
@@ -237,74 +313,12 @@ function doModListChecks()
for k,mod in pairs(mods) do
- -- 1. verify IDs
- if not tonumber(mod.id) then
- return modCheckError("Invalid mod ID '"..tostring(mod.id).."'")
- else
- if mod.id == 0 then
- return modCheckError("Invalid mod ID '"..tostring(mod.id).."', must be >0")
- end
-
- if isDefaultID(false, mod.id) then
- return modCheckError("Invalid mod ID '"..tostring(mod.id).."', must be out of the default GTA:SA and SAMP ID Range, see shared.lua isDefaultID")
- end
-
- for _,id in pairs(used_ids) do
- if id == mod.id then
- return modCheckError("Duplicated mod ID '"..id.."'")
- end
- end
-
- table.insert(used_ids, mod.id)
- end
- if not tonumber(mod.base_id) then
- return modCheckError("Invalid mod base ID '"..tostring(mod.base_id).."'")
- else
- if not isDefaultID(false, mod.base_id) then
- return modCheckError("Invalid mod base ID '"..tostring(mod.base_id).."', must be a default GTA:SA ID")
- end
- end
-
- -- 2. verify name
- if not mod.name or type(mod.name)~="string" then
-
- return modCheckError("Missing/Invalid mod name '"..tostring(mod.name).."' for mod ID "..mod.id)
- end
-
- -- 3. verify path
- if (not mod.path) then
-
- return modCheckError("Missing mod path '"..tostring(mod.path).."' for mod ID "..mod.id)
- end
- if not (type(mod.path)=="string" or type(mod.path)=="table") then
-
- return modCheckError("Invalid mod path '"..tostring(mod.path).."' for mod ID "..mod.id)
- end
+ local co = coroutine.create(verifyOneMod)
+ coroutine.resume(co, mod, elementType, used_ids)
- -- 4. verify files exist
- local ignoreTXD, ignoreDFF, ignoreCOL = mod.ignoreTXD, mod.ignoreDFF, mod.ignoreCOL
- local paths
- local path = mod.path
- if type(path)=="table" then
- paths = path
- else
- paths = getActualModPaths(path, mod.id)
- end
- for pathType, path2 in pairs(paths) do
- if type(pathType) ~= "string" then
- return modCheckError("Invalid path type '"..tostring(pathType).."' for mod ID "..mod.id)
- end
- if type(path2) ~= "string" then
- return modCheckError("Invalid file path '"..tostring(pathType).."' for mod ID "..mod.id)
- end
- if (not fileExists(path2)) and ((ENABLE_NANDOCRYPT) and not fileExists(path2..NANDOCRYPT_EXT)) then
- if (not ignoreTXD and pathType == "txd")
- or (not ignoreDFF and pathType == "dff")
- or ((not ignoreCOL) and elementType == "object" and pathType == "col") then
-
- return modCheckError("File doesn't exist: '"..tostring(path2).."' for mod ID "..mod.id)
- end
- end
+ local _, result = coroutine.resume(co)
+ if not result then
+ return result
end
end
end
@@ -318,9 +332,29 @@ function (startedResource)
startTickCount = getTickCount()
- -- STARTUP CHECKS
- if doModListChecks() then
+ if (STARTUP_VERIFICATIONS) then
+
+ -- STARTUP CHECKS
+ if not doModListChecks() then return end
+
SERVER_READY = true -- all checks passed
+
+ else
+ SERVER_READY = true -- checks ignored
+ end
+
+ for k, v in ipairs(OTHER_RESOURCES) do
+ local name, start, stop = v.name, v.start, v.stop
+ if type(name)=="string" and start == true then
+ local res = getResourceFromName(name)
+ if res and getResourceState(res) == "loaded" then
+ if not startResource(res) then
+ outputDebugString("Failed to start resource '"..name.."' on "..resName.." res-start")
+ else
+ outputDebugString("Started resource '"..name.."' on "..resName.." res-start")
+ end
+ end
+ end
end
end)
@@ -336,13 +370,33 @@ function (stoppedResource, wasDeleted)
end
end
+ local willStart = {}
+ for k, v in ipairs(OTHER_RESOURCES) do
+ local name, start, stop = v.name, v.start, v.stop
+ if type(name)=="string" then
+ if start == true then
+ willStart[name] = true
+ end
+ if stop == true then
+ local res = getResourceFromName(name)
+ if res and getResourceState(res) == "running" then
+ if not stopResource(res) then
+ outputDebugString("Failed to stop resource '"..name.."' on "..resName.." res-stop")
+ else
+ outputDebugString("Stopped resource '"..name.."' on "..resName.." res-stop")
+ end
+ end
+ end
+ end
+ end
+
local notified = {}
for elementType,mods in pairs(modList) do
for k,mod in pairs(mods) do
local srcRes = mod.srcRes
if srcRes then
local res = getResourceFromName(srcRes)
- if res and not notified[srcRes] then
+ if res and not notified[srcRes] and not willStart[srcRes] then
outputDebugString("Resource '"..srcRes.."' needs to be restarted because '"..resName.."' stopped", 0, 211, 255, 0)
notified[srcRes] = true
@@ -457,11 +511,42 @@ end
+--[[
+ This function exists to avoid too many exports calls of the function below from
+ external resources to add mods from those
+ With this one you can just pass a table of mods and it calls that function for you
+]]
+function addExternalMods_IDFilenames(list) -- [Exported]
+ if type(list) ~= "table" then
+ return false, "Missing/Invalid 'list' table passed: "..tostring(list)
+ end
+ local countWorked = 0
+ for _, modInfo in ipairs(list) do
+ if type(modInfo) ~= "table" then
+ return false, "Missing/Invalid 'modInfo' table passed: "..tostring(modInfo)
+ end
+ local elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse = unpack(modInfo)
+
+ local co = coroutine.create(addExternalMod_IDFilenames)
+ coroutine.resume(co, elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, true)
+ local _, worked, reason = coroutine.resume(co)
+ if worked then
+ countWorked = countWorked + 1
+ else
+ return false, "Aborting, one failed, reason: "..tostring(reason)
+ end
+ end
+ return countWorked
+end
+
--[[
The difference between this function and addExternalMod_CustomFilenames is that
you pass a folder path in 'path' and it will search for ID.dff ID.txd etc
]]
-function addExternalMod_IDFilenames(elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse) -- [Exported]
+function addExternalMod_IDFilenames(elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, usingCoroutine) -- [Exported]
+ if (usingCoroutine == true) then
+ coroutine.yield()
+ end
local sourceResName = getResourceName(sourceResource)
if sourceResName == resName then
@@ -581,7 +666,11 @@ function addExternalMods_CustomFileNames(list) -- [Exported]
if type(modInfo) ~= "table" then
return false, "Missing/Invalid 'modInfo' table passed: "..tostring(modInfo)
end
- local worked, reason = addExternalMod_CustomFilenames(unpack(modInfo))
+ local elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse = unpack(modInfo)
+
+ local co = coroutine.create(addExternalMod_CustomFilenames)
+ coroutine.resume(co, elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, true)
+ local _, worked, reason = coroutine.resume(co)
if worked then
countWorked = countWorked + 1
else
@@ -595,7 +684,10 @@ end
The difference between this function and addExternalMod_IDFilenames is that
you pass directly individual file paths for dff, txd and col files
]]
-function addExternalMod_CustomFilenames(elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse) -- [Exported]
+function addExternalMod_CustomFilenames(elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, usingCoroutine) -- [Exported]
+ if (usingCoroutine == true) then
+ coroutine.yield()
+ end
local sourceResName = getResourceName(sourceResource)
if sourceResName == resName then
diff --git a/newmodels/shared.lua b/newmodels/shared.lua
index 76186b9..d2cbe84 100644
--- a/newmodels/shared.lua
+++ b/newmodels/shared.lua
@@ -75,7 +75,17 @@ function isDefaultID(elementType, id) -- [Exported]
if not id then return end
if not elementType then -- check all IDs
- return isDefaultID("ped", id) or isDefaultID("object", id) or isDefaultID("vehicle", id)
+ for k,id2 in pairs(pedIds) do
+ if id2 == id then
+ return true
+ end
+ end
+ for k,id2 in pairs(vehicleIds) do
+ if id2 == id then
+ return true
+ end
+ end
+ return isDefaultObjectID(id)
else
if elementType == "ped" or elementType == "player" then
for k,id2 in pairs(pedIds) do