-- Custom Vinewood Text - MTA:SA Resource -- Original CLEO mod by Junior_Djjr (MixMods.com.br) -- MTA port by Dryxio local config = { text = "METROPOLES", position = { x = 1413.0, y = -804.0, z = 100.0 }, spacing = 1.2, zOffset = 0.0, drawDistance = 500 } local originalSignModels = { 13722, 13831, 13759 } local charToFiles = {} for i = 0, 9 do local char = tostring(i) charToFiles[char] = { dff = "assets/dff/cust_vin_text_" .. i .. ".dff", col = "assets/col/cust_vin_text_" .. i .. ".col" } end for i = 0, 25 do local char = string.char(65 + i) charToFiles[char] = { dff = "assets/dff/cust_vin_text_" .. char .. ".dff", col = "assets/col/cust_vin_text_" .. char .. ".col" } end local allocatedModels = {} local charModelCache = {} local charWidthCache = {} local createdObjects = {} local loadedDFFs = {} local loadedCOLs = {} local loadedTXD = nil local isInitialized = false local SPACE_WIDTH = 2.0 local function loadConfig() local configFile = xmlLoadFile("config.xml") if configFile then local textNode = xmlFindChild(configFile, "text", 0) if textNode then config.text = xmlNodeGetValue(textNode) or config.text end local posNode = xmlFindChild(configFile, "position", 0) if posNode then config.position.x = tonumber(xmlNodeGetAttribute(posNode, "x")) or config.position.x config.position.y = tonumber(xmlNodeGetAttribute(posNode, "y")) or config.position.y config.position.z = tonumber(xmlNodeGetAttribute(posNode, "z")) or config.position.z end local drawDistNode = xmlFindChild(configFile, "drawDistance", 0) if drawDistNode then config.drawDistance = tonumber(xmlNodeGetValue(drawDistNode)) or config.drawDistance end local spacingNode = xmlFindChild(configFile, "spacing", 0) if spacingNode then config.spacing = tonumber(xmlNodeGetValue(spacingNode)) or config.spacing end local zOffsetNode = xmlFindChild(configFile, "zOffset", 0) if zOffsetNode then config.zOffset = tonumber(xmlNodeGetValue(zOffsetNode)) or config.zOffset end xmlUnloadFile(configFile) end config.text = string.upper(config.text) end local function removeOriginalSigns() for _, modelID in ipairs(originalSignModels) do removeWorldModel(modelID, 10000, 0, 0, 0) end end local function loadCharacterModel(char) if charModelCache[char] then return charModelCache[char] end if char == " " then return nil end local files = charToFiles[char] if not files then return nil end local modelID = engineRequestModel("object") if not modelID then return nil end allocatedModels[modelID] = true local dff = engineLoadDFF(files.dff) if not dff then engineFreeModel(modelID) allocatedModels[modelID] = nil return nil end loadedDFFs[char] = dff if not engineReplaceModel(dff, modelID) then engineFreeModel(modelID) allocatedModels[modelID] = nil return nil end if not loadedTXD then loadedTXD = engineLoadTXD("assets/mulhouslahills.txd") end if loadedTXD then engineImportTXD(loadedTXD, modelID) end local col = engineLoadCOL(files.col) if col then loadedCOLs[char] = col engineReplaceCOL(col, modelID) end engineSetModelLODDistance(modelID, config.drawDistance) local tempObj = createObject(modelID, 0, 0, -1000) if tempObj then local minX, minY, minZ, maxX, maxY, maxZ = getElementBoundingBox(tempObj) destroyElement(tempObj) if minX and maxX then charWidthCache[char] = math.abs(maxX - minX) * config.spacing else charWidthCache[char] = SPACE_WIDTH end else charWidthCache[char] = SPACE_WIDTH end charModelCache[char] = modelID return modelID end local function loadAllCharacters() local uniqueChars = {} for i = 1, #config.text do local char = string.sub(config.text, i, i) if char ~= " " and not uniqueChars[char] then uniqueChars[char] = true end end for char in pairs(uniqueChars) do loadCharacterModel(char) end end local function getCharWidth(char) if char == " " then return SPACE_WIDTH end return charWidthCache[char] or SPACE_WIDTH end local function createLetterAt(char, x, y, z) local modelID = charModelCache[char] if not modelID then return nil end local groundZ = getGroundPosition(x, y, z + 50) if groundZ and groundZ > 10.0 then z = groundZ end local finalY = y + 2.0 local finalZ = z - 1.0 + config.zOffset local obj = createObject(modelID, x, finalY, finalZ, 0, 0, 0) if obj then setElementDoubleSided(obj, true) setElementCollisionsEnabled(obj, false) local lodObj = createObject(modelID, x, finalY, finalZ, 0, 0, 0, true) if lodObj then setLowLODElement(obj, lodObj) setElementDoubleSided(lodObj, true) setElementCollisionsEnabled(lodObj, false) table.insert(createdObjects, lodObj) end table.insert(createdObjects, obj) return obj end return nil end local function enableLetterCollisions() for _, obj in ipairs(createdObjects) do if isElement(obj) then setElementCollisionsEnabled(obj, true) end end end local function createLetterObjects() local text = config.text local textLen = #text if textLen == 0 then return end local baseX = config.position.x local baseY = config.position.y local baseZ = config.position.z local centerIndex = math.floor(textLen / 2) local luaCenterIndex = centerIndex + 1 local offset = 0 local firstWidth = 0 -- Place center character local centerChar = string.sub(text, luaCenterIndex, luaCenterIndex) local centerWidth = getCharWidth(centerChar) local posX = baseX + offset if centerChar ~= " " then createLetterAt(centerChar, posX, baseY, baseZ) end firstWidth = centerWidth -- Place left side offset = -firstWidth local leftIndex = centerIndex while leftIndex > 0 do leftIndex = leftIndex - 1 local luaIndex = leftIndex + 1 local char = string.sub(text, luaIndex, luaIndex) local charWidth = getCharWidth(char) offset = offset - charWidth posX = baseX + offset if char ~= " " then createLetterAt(char, posX, baseY, baseZ) end offset = offset - charWidth end -- Place right side offset = firstWidth local rightIndex = centerIndex while textLen > rightIndex + 1 do rightIndex = rightIndex + 1 local luaIndex = rightIndex + 1 local char = string.sub(text, luaIndex, luaIndex) local charWidth = getCharWidth(char) offset = offset + charWidth posX = baseX + offset if char ~= " " then createLetterAt(char, posX, baseY, baseZ) end offset = offset + charWidth end enableLetterCollisions() end local function cleanup() for _, obj in ipairs(createdObjects) do if isElement(obj) then destroyElement(obj) end end createdObjects = {} for _, dff in pairs(loadedDFFs) do if isElement(dff) then destroyElement(dff) end end loadedDFFs = {} for _, col in pairs(loadedCOLs) do if isElement(col) then destroyElement(col) end end loadedCOLs = {} if loadedTXD and isElement(loadedTXD) then destroyElement(loadedTXD) loadedTXD = nil end for modelID in pairs(allocatedModels) do engineFreeModel(modelID) end allocatedModels = {} charModelCache = {} charWidthCache = {} for _, modelID in ipairs(originalSignModels) do restoreWorldModel(modelID, 10000, 0, 0, 0) end isInitialized = false end local function initialize() if isInitialized then return end loadConfig() removeOriginalSigns() loadAllCharacters() createLetterObjects() isInitialized = true end addEventHandler("onClientResourceStart", resourceRoot, initialize) addEventHandler("onClientResourceStop", resourceRoot, cleanup) function setVinewoodText(newText) if type(newText) ~= "string" or #newText == 0 then return false end for _, obj in ipairs(createdObjects) do if isElement(obj) then setElementCollisionsEnabled(obj, false) end end for _, obj in ipairs(createdObjects) do if isElement(obj) then destroyElement(obj) end end createdObjects = {} config.text = string.upper(newText) loadAllCharacters() createLetterObjects() return true end addCommandHandler("vinewood", function(cmd, ...) local newText = table.concat({...}, " ") if #newText > 0 then if setVinewoodText(newText) then outputChatBox("[CustomVinewood] Text changed to: " .. newText) else outputChatBox("[CustomVinewood] Failed to change text", 255, 0, 0) end else outputChatBox("[CustomVinewood] Usage: /vinewood ") end end)