r2_core = {} r2_core.UserComponentsPath = "./examples/user_components/" r2_core.UserComponentTable = {} r2_core.UserComponentManager = {} local userComponentManager = r2_core.UserComponentManager userComponentManager.CurrentExportList = {} userComponentManager.InstanceIds = {} userComponentManager.Texts = {} userComponentManager.PlotItems = {} userComponentManager.Positions = {} userComponentManager.CurrentDesc = "" userComponentManager.IsInitialized = false userComponentManager.InitialComponentToLoad = 0 -------------------------------------------------------------------------------------------------- -- Methods called when the holder specific ui buttons are pressed function r2_core:doNothing(x, y, z) end function r2_core:testIsExportable(entityId) local holder = r2:getInstanceFromId(r2_core.CurrentHolderId) assert(holder) local k, v = next(holder.Components, nil) local entity = r2:getInstanceFromId(entityId) while k do if entity.ParentInstance:isKindOf("NpcGrpFeature") and entity.ParentInstance.InstanceId == v.InstanceId then return false end if v.InstanceId == entityId then return false end k, v = next(holder.Components, k) end return true end local function getIndex(id, tbl) local k, v = next(tbl, nil) while k do if v.InstanceId and v.InstanceId == id then return k end k, v = next(tbl, k) end return -1 end function r2_core:addEntityToExporter(instanceId) local entityId = instanceId local tmpInstance = r2:getInstanceFromId(entityId) if tmpInstance:isKindOf("Npc") and tmpInstance.ParentInstance:isKindOf("NpcGrpFeature") then entityId = tmpInstance.ParentInstance.InstanceId end local entity = r2:getInstanceFromId(entityId) assert(entity) local parent = entity.ParentInstance local parentId = parent.InstanceId r2_core.UserComponentManager:replacePosition(entityId) if parent:isKindOf("DefaultFeature") then local index = getIndex(entityId, parent.Components) r2.requestMoveNode(parentId, "Components", index, r2_core.CurrentHolderId, "Components", -1) elseif parent:isKindOf("Act") then local index = getIndex(entityId, parent.Features) r2.requestMoveNode(r2:getCurrentAct().InstanceId, "Features", index, r2_core.CurrentHolderId, "Components", -1) end local container = getUI("ui:interface:r2ed_scenario") local tree = container:find("content_tree_list") tree:forceRebuild() end function r2_core:testCanPickUserComponentElement(instanceId) local instance = r2:getInstanceFromId(instanceId) assert(instance) local parent = instance.ParentInstance local holder = r2:getInstanceFromId(r2_core.CurrentHolderId) assert(holder) if parent:isKindOf("NpcGrpFeature") and holder.InstanceId == parent.ParentInstance.InstanceId then return true end if holder.InstanceId == parent.InstanceId then return true end return false end function r2_core:removeUserComponentElement(instanceId) local holder = r2:getInstanceFromId(r2_core.CurrentHolderId) assert(holder) local index = getIndex(instanceId, holder.Components) local entity = r2:getInstanceFromId(instanceId) assert(entity) local currentAct = r2:getCurrentAct() if entity:isKindOf("Region") or entity:isKindOf("Road") or entity:isBotObject() then local refPosition = r2.Scenario.Acts[0].Position r2_core.UserComponentManager:restorePosition(instanceId, refPosition) r2.requestMoveNode(r2_core.CurrentHolderId, "Components", index, r2.Scenario.Acts[0].Features[0].InstanceId, "Components", -1) elseif entity:isKindOf("Npc") then local refPosition = currentAct.Position if entity.ParentInstance:isKindOf("NpcGrpFeature") then local grpId = entity.ParentInstance.InstanceId local grpIndex = getIndex(grpId, holder.Components) r2_core.UserComponentManager:restorePosition(grpId, refPosition) r2.requestMoveNode(r2_core.CurrentHolderId, "Components", grpIndex, currentAct.InstanceId, "Features", -1) else r2_core.UserComponentManager:restorePosition(instanceId, refPosition) r2.requestMoveNode(r2_core.CurrentHolderId, "Components", index, currentAct.Features[0].InstanceId, "Components", -1) --getCurrentAct().Features[0].Components end else local refPosition = currentAct.Position r2_core.UserComponentManager:restorePosition(instanceId, refPosition) r2.requestMoveNode(r2_core.CurrentHolderId, "Components", index, currentAct.InstanceId, "Features", -1) --getCurrentAct().Features end local container = getUI("ui:interface:r2ed_scenario") local tree = container:find("content_tree_list") tree:forceRebuild() end -------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------- -- Debug local printDebug printDebug = function(tbl) local k,v = next(tbl, nil) while k do if type(v) == "table" then debugInfo(k .. " : TABLE") printDebug(v) else debugInfo(k .. " = " ..tostring(v)) end k,v = next(tbl, k) end end local foundInTable = function(tbl, key) local k, v = next(tbl, nil) while k do if (k == key) then return true end k,v = next(tbl, k) end return false end local insertExistingId = function(tblSearch, tblInsert, clientid) local k, v = next(tblSearch, nil) while k do if (k == clientid) then tblInsert[clientid] = v end k, v = next(tblSearch, k) end end local checkLinkedId = function(instanceIdTbl, refIdTbl) local k, v = next(refIdTbl, nil) while k do local key, value = next(instanceIdTbl, nil) while key do if key == k and value ~= v then refIdTbl[k] = value end key, value = next(instanceIdTbl, key) end k, v = next(refIdTbl, k) end end local countIds = function(tbl) local count = 0 local k, v = next(tbl, nil) while k do count = count + 1 k, v = next(tbl, k) end return count end local countUCIds countUCIds = function(tbl) local count = 0 local k, v = next(tbl, nil) while k do if type(v) ~= "table" and type(v) ~= "userdata" then if k == "InstanceId" or string.find(tostring(v), "UserComponent_") ~= nil or string.find(tostring(v), "UserText_") ~= nil or string.find(tostring(v), "UserItem_") ~= nil then count = count + 1 end elseif type(v) == "table" then count = count + countUCIds(v) end k, v = next(tbl, k) end return count end local findUserSlotIn = function(str) local currentSlot = r2.getUserSlot() .."_" if string.find(str, currentSlot) ~= nil then return true end return false end -------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------- function userComponentManager:init() --init usercomponent table & retrieves entries from file if not empty r2_core.UserComponentTable = {} local userComponentTableOk = loadfile("save/r2_core_user_component_table.lua") if userComponentTableOk then userComponentTableOk() end --doFile on previously loaded user component files local ucTable = r2_core.UserComponentTable self.InitialComponentToLoad = table.getn(ucTable) local k, v = next(ucTable, nil) while k do --local fun = loadfile(v[2]) r2.loadUserComponentFile(v[2]) --if not fun then -- debugInfo("Couldn't load file '"..v[2].."' while initializing UserComponentManager.") --else -- local ok, msg = pcall(fun) -- if not ok then -- debugInfo("Error while loading component '"..filename.."' err: "..msg) -- end -- debugInfo("Component '" ..v[2].."' loaded.") --end k, v = next(ucTable, k) end if r2_core.UserComponentManager.IsInitialized == false and r2_core.UserComponentManager.InitialComponentToLoad == 0 then r2_core.UserComponentManager.IsInitialized = true end end function userComponentManager:isInitialized() return self.IsInitialized end -------------------------------------------------------------------------------------------------------------------- -- Gets all the InstanceIds and the references to InstanceIds and store them separately. -- Each refId is unique and is renamed from client1_XX to userComponent_XX. -- Returns a table containing all the instanceIds and refIds. -- function userComponentManager:getRefIdTableFrom(argTbl) local refIdTbl = {} local instanceIdTbl = {} local plotItemTbl = {} local nbInstance = 0 local nbText = 0 local nbItems = 0 local addTextToUCManager = function(instanceId) local key, value = next(r2.Scenario.Texts.Texts, nil) while key do if value.InstanceId == instanceId then userComponentManager.Texts[instanceIdTbl[instanceId]] = value.Text return end key, value = next(r2.Scenario.Texts.Texts, key) end end local addItemToUCManager = function(instanceId) local key, value = next(r2.Scenario.PlotItems, nil) debugInfo("Adding '" ..instanceId.."' to manager item table") while key do if value.InstanceId == instanceId then --copy the full plot item into manager's table userComponentManager.PlotItems[plotItemTbl[instanceId]] = value return end key, value = next(r2.Scenario.PlotItems, key) end end -- Doesn't insert the value if it's already in the table. parseInstanceTable = function(tbl) --parsing string properties local key,val = next(tbl, nil) while key do if (type(val) ~= "userdata") then -- If the property is a real InstanceId if (key == "InstanceId") then -- If the instanceId is not already in the instanceIdTbl then add it as a key, with its usercomponent -- translation as value. if not foundInTable(instanceIdTbl, tostring(val)) then nbInstance = nbInstance + 1 instanceIdTbl[val] = "UserComponent_"..nbInstance end -- else if the instanceid found is a reference to another InstanceId --elseif (string.find(tostring(val), "Client1_") ~= nil) then elseif findUserSlotIn(tostring(val)) then --if exporting a dialog, rename specifically the refId pointing to the said text (stored in r2.Scenario.Texts) if key == "Says" then if not foundInTable(instanceIdTbl, tostring(val)) then nbText = nbText + 1 instanceIdTbl[val] = "UserText_" ..nbText addTextToUCManager(val) end --else if plotItem XXXXXX elseif string.find(key, "Item") ~= nil then if not foundInTable(plotItemTbl, tostring(val)) then nbItems = nbItems + 1 plotItemTbl[val] = "UserItem_" ..nbItems addItemToUCManager(val) end elseif not foundInTable(refIdTbl, tostring(val)) then if foundInTable(instanceIdTbl, tostring(val)) then --a refid pointing to an instanceid present in the component itself (zone...) insertExistingId(instanceIdTbl, refIdTbl, tostring(val)) else refIdTbl[val] = "Not exported" end end end end key,val = next(tbl, key) end --parsing userdatas key, val = next(tbl, nil) while key do if (type(val) == "userdata") then parseInstanceTable(val) end if (type(val) == "table") then --inspect(val) assert(nil) end key, val = next(tbl, key) end end local i = 1 --printDebug(argTbl) local nbExportedInstances = table.getn(argTbl) local k, v = next(argTbl, nil) while k do local tmpInstance = r2:getInstanceFromId(v) if tmpInstance ~= nil then parseInstanceTable(tmpInstance) end k, v = next(argTbl, k) end checkLinkedId(instanceIdTbl, refIdTbl) userComponentManager.InstanceIds = instanceIdTbl local refIdTable = { RefIds = refIdTbl, InstanceIds = instanceIdTbl, PlotItemsId = plotItemTbl } return refIdTable end -------------------------------------------------------------------------------------------------------------------- local generatePreCode = function(fName) local userComponentBody = "" local writePlotItemBlock = function(plotItem, ucId, index) --file:write("\t{\n") local str = "\t{\n" local k, v = next(plotItem, nil) while k do if k == "InstanceId" then --file:write("\t\t[" ..string.format("%q", k).. "]\t=\t" ..string.format("%q", ucId).. ",\n") str = str .."\t\t[" ..string.format("%q", k).. "]\t=\t" ..string.format("%q", ucId).. ",\n" elseif type(v) == "string" then --file:write("\t\t[" ..string.format("%q", k).. "]\t=\t" ..string.format("%q", v).. ",\n") str = str .."\t\t[" ..string.format("%q", k).. "]\t=\t" ..string.format("%q", v).. ",\n" else --file:write("\t\t[" ..string.format("%q", k).. "]\t=\t" ..tostring(v).. ",\n") str = str .."\t\t[" ..string.format("%q", k).. "]\t=\t" ..tostring(v).. ",\n" end k, v = next(plotItem, k) end --file:write("\t},\n") str = str .."\t},\n" return str end local featureName = fName if featureName == nil or featureName == "" then featureName = "UnnamedComponent" end local str = "" -- "-- \n" --.."-- r2_core.CurrentFeatureName='" ..featureName.. "'\n" --.."-- \n\n\n" str = "r2.Features."..featureName.. " = {}\n\n" .."local feature = r2.Features." ..featureName.."\n\n" .."feature.Name=\"".. featureName.."\"\n\n" .."feature.Description=\"A user exported feature.\"\n" .."feature.Components = {}\n\n" --file:write(str) userComponentBody = userComponentBody .. str do local count = 0 --file:write("feature.PlotItems = \n{\n") userComponentBody = userComponentBody .."feature.PlotItems = \n{\n" local k, v = next(userComponentManager.PlotItems, nil) while k do count = count + 1 userComponentBody = userComponentBody..writePlotItemBlock(v, k, count) k, v = next(userComponentManager.PlotItems, k) end --file:write("}\n\n") userComponentBody = userComponentBody .."}\n\n" end -- component.createComponent str = "feature.createUserComponent = function(x, y)\n\t" .."local comp = r2.newComponent('UserComponentHolder')\n\t" .."assert(comp)\n\n\t" .."comp.Base = \"palette.entities.botobjects.user_event\"\n\t" .."comp.Name = r2:genInstanceName(ucstring('"..featureName.."')):toUtf8()\n\t" .."comp.Position.x = x\n\t" .."comp.Position.y = y\n\t" .."comp.Position.z = r2:snapZToGround(x, y)\n\n\t" .."comp.Description = '" ..userComponentManager.CurrentDesc.. "'\n\n\t" --file:write(str) userComponentBody = userComponentBody .. str do --file:write("comp.Texts = \n\t{\n") userComponentBody = userComponentBody .."comp.Texts = \n\t{\n" local key, value = next(userComponentManager.Texts, nil) while key do --file:write("\t\t[" ..string.format("%q", key).. "]\t=\t[[" ..value.. "]],\n") userComponentBody = userComponentBody .. "\t\t[" ..string.format("%q", key).. "]\t=\t[[" ..value.. "]],\n" key, value = next(userComponentManager.Texts, key) end --file:write("\t}\n\n\t") userComponentBody = userComponentBody .. "\t}\n\n\t" end str = "comp.Components = {}\n\t" .."local tmpComponents = {}\n\t" userComponentBody = userComponentBody..str --file:write(str) return userComponentBody end -------------------------------------------------------------------------------------------------------------------- local generatePostCode = function(fName) local featureName = fName if featureName == nil or featureName == "" then featureName = "UserFeature" end local str = "" str = str .."r2_core.UserComponentManager:insertAll(feature, comp, tmpComponents)\n\n\t" .."comp._Seed = os.time()\n\n\t" .."return comp\n" .."end\n\n\n" -- ! component.CreateComponent .."\n\nr2.Features[\""..featureName.."\"] = feature" .."\n -- !"..featureName.." user exported\\o/ \n\n\n" return str --file:write(str) end ------------------------------------------------------------------------------------------------------------------- -- Generates the LUA code corresponding to the selected instances. -- filename: file in which the code will be written. -- argTbl: contains all the instanceIds selected for export. -- instanceIdTbl: hashtable containing all clientIds (key) and their matching user component id (value). This table -- is returned by the getRefIdTableFrom method. -- refPosition: reference coordinates chosen by the user on export. function userComponentManager:componentToFile(filename, featureName, argTbl, refIdTbl, refPosition) local instanceIdTbl = refIdTbl.InstanceIds local plotItemsTbl = refIdTbl.PlotItemsId local body = "" local function writeTab(file, nb) local i = nb while i > 0 do file:write("\t") i = i - 1 end end local function writeTabInString(str, nb) local tmp = str local i = nb while i > 0 do tmp = tmp..("\t") i = i - 1 end return tmp end --local function writeDataToFile(file, tbl, nbTab, isTopLevel) local function writeDataToString(str, tbl, nbTab, isTopLevel) local userComponentBody = str local key,val = next(tbl, nil) while key do --writing all properties except userdatas if type(val) ~= "userdata" then --writeTab(file, nbTab) userComponentBody = writeTabInString(userComponentBody, nbTab) if isTopLevel == true and key == "InheritPos" then --file:write("InheritPos = 1,\n") userComponentBody = userComponentBody .. "InheritPos = 1,\n" else if type(key) == "number" then --file:write("[" ..tostring(key + 1).. "] = ") userComponentBody = userComponentBody .. "[" ..tostring(key + 1).. "] = " else --file:write(tostring(key) .. " = ") end userComponentBody = userComponentBody .. tostring(key).. " = " end -- String/number value if type(val) == "number" then --file:write(val..",\n") userComponentBody = userComponentBody..tostring(val)..", \n" else local str = val if key == "InstanceId" or findUserSlotIn(val) then str = instanceIdTbl[val] end if str == nil then str = plotItemsTbl[val] end if str == nil then str = "" end --when finding a instanceId which is referring to a non exported entity --file:write(string.format("%q", str) ..",\n") userComponentBody = userComponentBody .. string.format("%q", str) ..",\n" end end end key,val = next(tbl, key) end --writing userdatas key, val = next(tbl, key) while key do if type(val) == "userdata" then --writeTab(file, nbTab) userComponentBody = writeTabInString(userComponentBody, nbTab) if type(key) ~= "number" then --file:write(tostring(key) .. " = ") userComponentBody = userComponentBody ..tostring(key).. " = " end --file:write("\n") userComponentBody = userComponentBody .."\n" --writeTab(file, nbTab) userComponentBody = writeTabInString(userComponentBody, nbTab) --file:write("{\n") userComponentBody = userComponentBody .."{\n" userComponentBody = writeDataToString(userComponentBody, val, nbTab + 1, false) --writeTab(file, nbTab) userComponentBody = writeTabInString(userComponentBody, nbTab) --file:write("},\n") userComponentBody = userComponentBody .."}, \n" end key,val = next(tbl, key) end return userComponentBody end -- -- Write a code block for each component of the user feature. local function writeComponentBlocks(str, tbl) local userComponentBody = str local i = 1 local nbInstanceIds = table.getn(tbl) while i <= nbInstanceIds do local tmpInstance = r2:getInstanceFromId(tbl[i]) if (tmpInstance == nil) then debugInfo("Cannot export entity with intanceId= " ..tostring(tbl[i])) assert(tmpInstance) else local compName = "uComp" ..i --+ 1 --file:write("do\n\t\t") userComponentBody = userComponentBody .. "do\n\t\t" --file:write("local " ..compName.. " = \n\t\t{\n") userComponentBody = userComponentBody .. "local " ..compName.. " = \n\t\t{\n" --writeDataToFile(file, tmpInstance, 3, true) userComponentBody = writeDataToString(userComponentBody, tmpInstance, 3, true) --file:write("\t\t} --!" ..compName.. " \n\n\t\t") userComponentBody = userComponentBody .."\t\t} --!" ..compName.. " \n\n\t\t" --file:write("table.insert(tmpComponents, "..compName.. ")\n\t") userComponentBody = userComponentBody .."table.insert(tmpComponents, "..compName.. ")\n\t" --file:write("end\n\n\t") userComponentBody = userComponentBody .."end\n\n\t" end i = i + 1 end return userComponentBody end --f = io.open(filename, "w") --assert(f) body = body..generatePreCode(featureName) body = writeComponentBlocks(body, argTbl) body = body .. generatePostCode(featureName) --f:close() --return res local headerInfo = {} table.insert(headerInfo, {CurrentFeatureName = featureName}) r2.saveUserComponent(filename, headerInfo, body) end ------------------------------------------------------------------------------------------------------------------- -- Builds a table containing all user component ids (key) with their new instance id (ClientId_n) on import. -- tmpComponents: contains all the user feature components, in which all instance ids are user component ids. -- currentMaxId: max entity id in the current act (on user feature import). local function generateReverseIdTable(tmpComponents, texts, currentMaxId) function findpattern(text, pattern, start) return string.sub(text, string.find(text, pattern, start)) end local function findMaxId(component, maxId, maxPlotItemId) local maxIdLocal = maxId local maxItemId = maxPlotItemId local key, value = next(component, nil) while key do if type(value) ~= "table" then if key == "InstanceId" or string.find(tostring(value), "UserComponent_") ~= nil then local tmpId = tonumber(findpattern(tostring(value), "%d+")) if tmpId and tmpId > maxIdLocal then maxIdLocal = tmpId end end if string.find(tostring(value), "UserItem_") ~= nil then local tmpItemId = tonumber(findpattern(tostring(value), "%d+")) if tmpItemId and tmpItemId > maxItemId then maxItemId = tmpItemId end end end key, value = next(component, key) end --maxIdLocal = maxIdLocal + maxItemId key, value = next(component, nil) while key do if type(value) == "table" then local tmpId, tmpItemId = findMaxId(value, maxIdLocal, maxItemId) if tmpId and tmpId > maxIdLocal then maxIdLocal = tmpId end if tmpItemId and tmpItemId > maxItemId then maxItemId = tmpItemId end end key, value = next(component, key) end return maxIdLocal, maxItemId end local reverseIdTable = {} local maxId, maxPlotItemId = findMaxId(tmpComponents, 0, 0) local i = 1 local id = 0 while i <= maxId do id = i + currentMaxId local ucName = "UserComponent_" ..i --reverseIdTable[ucName] = "Client1_" ..id reverseIdTable[ucName] = tostring(r2.getUserSlot()).."_"..id i = i + 1 end local j = 1 while j <= maxPlotItemId do id = i + currentMaxId local ucName = "UserItem_" ..j --reverseIdTable[ucName] = "Client1_" ..id reverseIdTable[ucName] = tostring(r2.getUserSlot()).."_"..id j = j + 1 i = i + 1 end --register component texts when a dialog has been exported if texts ~= nil then key, value = next(texts, nil) while key do local id = r2.registerText(tostring(value)) reverseIdTable[key] = id.InstanceId key, value = next(texts, key) end end return reverseIdTable end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:reattributeClientId(tbl, reverseIdTable) local key, value = next(tbl, nil) while key do if (type(value) ~= "userdata") then if key == "InstanceId" or string.find(tostring(value), "UserComponent_") ~= nil or string.find(tostring(value), "UserText_") ~= nil or string.find(tostring(value), "UserItem_") ~= nil then local id = reverseIdTable[value] assert(id) tbl[key] = id end end key,value = next(tbl, key) end key, value = next(tbl, nil) while key do if type(value) ~= "string" and type(value) ~= "number" then userComponentManager:reattributeClientId(value, reverseIdTable) end key, value = next(tbl, key) end return tbl end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:insertPlotItem(plotItem, reverseIdTable) local function isAlreadyUsed(plotItem) local k, v = next(r2.Scenario.PlotItems, nil) while k do if v.SheetId and v.SheetId == plotItem.SheetId then reverseIdTable[plotItem.InstanceId] = v.InstanceId return true end k, v = next(r2.Scenario.PlotItems, k) end userComponentManager:reattributeClientId(plotItem, reverseIdTable) return false end if isAlreadyUsed(plotItem) == false then --insert plot item in scenario r2.requestInsertNode(r2.Scenario.InstanceId, "PlotItems", -1, "", plotItem) --r2:setCookie(newItem.InstanceId, "SelfCreate", true) --TODO = delete plotItem from pitem table in scenario so that it cannot be used twice return true end return false end -------------------------------------------------------------------------------------------------------------------- -- insertAll is called in the generated userFeature upon instanciation. -- Inserts all userComponents into the userFeature's components table after having renamed all -- userComponentIds into clientIds. -- function userComponentManager:insertAll(feature, comp, tmpComponents) local texts = comp.Texts local items = feature.PlotItems local components = comp.Components local currentMaxId = r2.getMaxId() local reverseIdTable = generateReverseIdTable(tmpComponents, texts, currentMaxId) local range = 0 local nbItems = 0 local k, v = next(items, nil) while k do local inserted = self:insertPlotItem(v, reverseIdTable) if inserted == true then nbItems = nbItems + 1 end k, v = next(items, k) end local key, value = next(tmpComponents, nil) while key do range= range + countUCIds(value) + 3 userComponentManager:reattributeClientId(value, reverseIdTable) table.insert(components , value) key, value = next(tmpComponents, key) end r2.reserveIdRange(range + nbItems) end --------------------------------------------------------------------------------------------------------------------- -- register the forms needed by the manager (save form, missing ids warning form). -- Called along with the other features registerForms method. function userComponentManager.registerForms() local fileListXML = [[ ]] function getComponentNameFromFile(filename) local prefixedFilename = r2_core.UserComponentsPath..filename local f = io.open(prefixedFilename,"r") assert(f) f:read("*line") f:read("*line") f:read("*line") f:read("*line") f:read("*line") local line = f:read("*line") if string.find(line, "CurrentFeatureName") == nil then messageBox("Unable to load a component from this file. Please select another file.") return "No components found" end f:close() local luaString = string.sub(line, 3) loadstring(luaString)() local componentName = CurrentFeatureName CurrentFeatureName = "" return componentName end local function getComponentFiles(searchPath) local files = getPathContent(searchPath) local componentFiles = {} for k, v in pairs(files) do local prefixedFilename = r2_core.UserComponentsPath..nlfile.getFilename(v) local f = io.open(prefixedFilename,"r") assert(f) local header = r2.getFileHeader(prefixedFilename) --TODO : accès fichier md5 --local line = f:read("*line") --if line == "-- " then if header.CurrentFeatureName then table.insert(componentFiles, v) end end return componentFiles end function r2_core.setCurrSelectedFile(filename, formAttr) local formInstance = r2.CurrentForm.Env.FormInstance --inspect(formInstance) if formInstance.LastFileButton then formInstance.LastFileButton.pushed = false end getUICaller().pushed = true formInstance.LastFileButton = getUICaller() r2.CurrentForm.Env.FormInstance[formAttr] = filename if r2.CurrentForm.Env.FormInstance["ComponentName"] then r2.CurrentForm.Env.FormInstance["ComponentName"] = getComponentNameFromFile(filename) end r2.CurrentForm.Env.updateAll() local eb = r2.CurrentForm:find("eb") setCaptureKeyboard(eb) eb:setSelectionAll() end function r2.setCurrSelectedComponent(compName) local formInstance = r2.CurrentForm.Env.FormInstance if formInstance.LastFileButton then formInstance.LastFileButton.pushed = false end getUICaller().pushed = true formInstance.LastFileButton = getUICaller() r2.CurrentForm.Env.FormInstance.ComponentName = compName r2.CurrentForm.Env.updateAll() local eb = r2.CurrentForm:find("eb") setCaptureKeyboard(eb) eb:setSelectionAll() end -- called at init to fill the file list local function showFileList(formInstance) local fileGroupList = r2.CurrentForm:find('file_list') r2.CurrentForm.Env.FormInstance["ComponentFileName"] = "UserComponent1.lua" r2.CurrentForm.Env.FormInstance["Name"] = "UserComponent1" r2.CurrentForm.Env.updateAll() --local searchPath = select(config.R2ScenariiPath, "save") local searchPath = r2_core.UserComponentsPath --local files = getPathContent(searchPath) local files = getComponentFiles(searchPath) table.sort(files) fileGroupList:clear() for k, v in pairs(files) do if string.lower(nlfile.getExtension(v)) == "lua" then local shortFilename = nlfile.getFilename(v) local entry = createGroupInstance("r2ed_filelist_entry", "", { id = tostring(k), text = shortFilename, params_l="r2_core.setCurrSelectedFile('" .. shortFilename .. "', 'ComponentFileName')" }) fileGroupList:addChild(entry) end end setCaptureKeyboard(r2.CurrentForm:find("eb")) end --called at init to fill load form local function showUserComponentFileList(formInstance) local fileGroupList = r2.CurrentForm:find('file_list') --local searchPath = select(config.R2ScenariiPath, "save") local searchPath = r2_core.UserComponentsPath local files = getPathContent(searchPath) table.sort(files) local defaultValue = "" fileGroupList:clear() for k, v in pairs(files) do if string.lower(nlfile.getExtension(v)) == "lua" then local shortFilename = nlfile.getFilename(v) if defaultValue == "" then defaultValue = shortFilename end local entry = createGroupInstance("r2ed_filelist_entry", "", { id = tostring(k), text = shortFilename, params_l="r2_core.setCurrSelectedFile('" .. shortFilename .. "', 'FileName')" }) fileGroupList:addChild(entry) end end setCaptureKeyboard(r2.CurrentForm:find("eb")) end --called at init to fill Unload Form local function showUserComponentList(formInstance) local fileGroupList = r2.CurrentForm:find('file_list') local featureNameTable = r2.FeatureTree.getUserComponentList() for k, v in pairs(featureNameTable) do local entry = createGroupInstance("r2ed_filelist_entry", "", {id = tostring(v), text=tostring(v), params_l="r2.setCurrSelectedComponent('"..tostring(v).."')" }) fileGroupList:addChild(entry) end setCaptureKeyboard(r2.CurrentForm:find("eb")) end local function showMissingComponents() local formUI = r2:getForm("MissingIdsForm") local text = "The following objects are referenced in exported objects but will not be exported:\n" local k, v = next(formUI.Env.FormInstance.Value, nil) while k do text = text .."# " ..v.."\n" k, v = next(formUI.Env.FormInstance.Value, k) end text = text .."Continue anyway?" formUI:find('name_list').hardtext = text formUI.Env:updateAll() formUI.Env.updateSize() formUI:updateCoords() formUI:center() formUI:updateCoords() end r2.Forms.MissingIdsForm = { Caption = "uiR2EdMissingRefsWarning", PropertySheetHeader = [[ ]], Prop = { {Name="Value", Type="Table", Visible=false} }, onShow = showMissingComponents } r2.Forms.SaveUserComponent = { Caption = "uiR2EDExportUserComponent", PropertySheetHeader = fileListXML, Prop = { {Name="Name", Type="String", Category="uiR2EDRollout_Save", ValidateOnEnter = true }, {Name="ComponentFileName", Type="String", Category="uiR2EDRollout_Save"}, {Name="ComponentName", Type="Table", Visible = false} }, onShow = showFileList } r2.Forms.LoadUserComponent = { Caption = "uiR2EDExportLoadUserComponent", PropertySheetHeader = fileListXML, Prop = { {Name="FileName", Type="String", Category="uiR2EDRollout_Load", ValidateOnEnter = true }, {Name="ComponentName", Type="String", Category="uiR2EDRollout_Load", WidgetStyle="StaticText"}, }, onShow = showUserComponentFileList } r2.Forms.UnloadUserComponent = { Caption = "uiR2EDExportUnloadUserComponent", PropertySheetHeader = fileListXML, Prop = { {Name="ComponentName", Type="String", Category="uiR2EDRollout_Unload", ValidateOnEnter = true }, }, onShow = showUserComponentList } end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:buildCurrentExportList(holder) table.clear(self.CurrentExportList) local components = holder.Components local k, v = next(components, nil) while k do if v and v.InstanceId then table.insert(self.CurrentExportList, v.InstanceId) end k, v = next(components, k) end end -------------------------------------------------------------------------------------------------------------------- -- Tbl is a table containing all the instanceIds selected for export. function userComponentManager:export(list, refX, refY, refZ) --local exportList = self.CurrentExportList local exportList = list assert(exportList) --builds a table containing all instanceIds and their corresponding userComponentId local refIdTable = userComponentManager:getRefIdTableFrom(exportList) local missingIds = refIdTable.RefIds local missingIdsCount = 0 --XXXXXX --TODO = reattribute UC ids for plotitems + container -- User component filename confirmation local function onFileOk(form) if (form.ComponentFileName ~= nil and type(form.ComponentFileName) == "string" and form.RefPosition ~= nil) then if form.ComponentFileName == "" or string.len(form.ComponentFileName) < 2 then messageBox(i18n.get("uiR2EDInvalidName")) return end if string.find(form.ComponentFileName, '\.lua', -4) == nil then form.ComponentFileName = form.ComponentFileName .. ".lua" end local refPosition = form.RefPosition local filename = form.ComponentFileName local prefixedFilename = r2_core.UserComponentsPath..filename local featureName = form.Name userComponentManager:computeAllPositions(exportList, refPosition, refIdTable.InstanceIds) userComponentManager:componentToFile(prefixedFilename, featureName, exportList, refIdTable, refPosition) userComponentManager:clear() --the component is automatically loaded on receive save callback --r2.loadUserComponentFile(prefixedFilename) end end local function onFileCancel(form) end -- Position confirmation local function posOk(x, y, z) debugInfo(string.format("Validate export with reference position (%d, %d, %d)", x, y, z)) local refPosition = {} refPosition["x"] = x refPosition["y"] = y refPosition["z"] = z refPosition["InstanceId"] = "" refPosition["Class"] = "Position" r2:doForm("SaveUserComponent", {RefPosition=refPosition}, onFileOk, onFileCancel) end local function posCancel() debugInfo("Export canceled.") end -- Export confirmation when missing some ids local function confirmExport() debugInfo("Export confirmed.") r2:choosePos("", posOk, posCancel, "") end local function cancelExport() debugInfo("Export canceled.") end local missingTbl = {} local key, value = next(missingIds, nil) while key do if value == "Not exported" then local tmpInstance = r2:getInstanceFromId(key) if tmpInstance ~= nil then missingIdsCount = missingIdsCount + 1 table.insert(missingTbl, tmpInstance.Name) debugInfo("Object named '" ..tmpInstance.Name .."' is referenced in an exported object but will not be exported.") end end key, value = next(missingIds, key) end if missingIdsCount ~= 0 then debugInfo(tostring(missingIdsCount) .." object(s) referenced but not exported. Continue anyway?y/n") r2:doForm("MissingIdsForm", {Value=missingTbl}, confirmExport, cancelExport) else --r2:choosePos("", posOk, posCancel, "") posOk(refX, refY, refZ) end end -- !export -------------------------------------------------------------------------------------------------------------------- function userComponentManager:getCurrentExportList() return userComponentManager.CurrentExportList end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:isInExportList(target) local targetId = target.InstanceId local k, v = next(self.CurrentExportList, nil) while k do if v == targetId then return true end -- test if not a son of an already exported element local currParent = target.ParentInstance while currParent ~= nil do if currParent.InstanceId == v then return true end currParent = currParent.ParentInstance end k, v = next(self.CurrentExportList, k) end return false end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:getCurrentExportList() return userComponentManager.CurrentExportList end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:isInExportList(target) local targetId = target.InstanceId local k, v = next(self.CurrentExportList, nil) while k do if v == targetId then return true end -- test if not a son of an already exported element local currParent = target.ParentInstance while currParent ~= nil do if currParent.InstanceId == v then return true end currParent = currParent.ParentInstance end k, v = next(self.CurrentExportList, k) end return false end --------------------------------------------------------------------------------------------------------------------- function userComponentManager:addToExportList(instanceId) local instance = r2:getInstanceFromId(instanceId) if instance == nil then debugInfo("UserComponentManager:AddToExportList : no instance") return false end table.insert(self.CurrentExportList, instanceId) return true end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:clear() table.clear(userComponentManager.InstanceIds) table.clear(userComponentManager.Texts) table.clear(userComponentManager.PlotItems) table.clear(userComponentManager.Positions) table.clear(userComponentManager.CurrentExportList) userComponentManager.CurrentExportList = {} userComponentManager.CurrentDesc = "" debugInfo("UserComponentManager tables cleared.") end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:computeAllPositions(argTbl, refPosition, refIdTable) local function buildPosition(instance, isTopLevel, refPosition) local position = {} if not instance.Position then return nil end if instance ~= nil and (instance.InheritPos == 0 or isTopLevel == true) then position["x"] = r2.getWorldPos(instance).x - refPosition["x"] position["y"] = r2.getWorldPos(instance).y - refPosition["y"] position["z"] = 0 else position["x"] = instance.Position.x position["y"] = instance.Position.y position["z"] = instance.Position.z end position["InstanceId"] = refIdTable[instance.Position.InstanceId] position["Class"] = "Position" return position end local function computePositions(instance, isTopLevel, refPosition) assert(instance) local localRefPos = nil localRefPos = buildPosition(instance, isTopLevel, refPosition) if localRefPos ~= nil and not userComponentManager.Positions[instance.Position.InstanceId] then userComponentManager.Positions[instance.Position.InstanceId] = localRefPos elseif localRefPos == nil then localRefPos = refPosition end local key, value = next(instance, nil) while key do if type(value) == "userdata" then local subInstance = value computePositions(subInstance, false, localRefPos) end key, value = next(instance, key) end end if argTbl == nil then else local i = 1 local nbExportedInstances = table.getn(argTbl) while i <= nbExportedInstances do local tmpInstance = r2:getInstanceFromId(argTbl[i]) if tmpInstance ~= nil then computePositions(tmpInstance, true, refPosition) end i = i + 1 end end end --------------------------------------------------------------------------------------------------------------- function userComponentManager:removeIdFromExportList(id) local exportList = self.CurrentExportList assert(exportList) local k, v = next(exportList, nil) while k do if v == id then break end k, v = next(exportList, k) end if k ~= nil then table.remove(exportList,k) if table.getn(exportList) == 0 then self.CurrentExportList = {} end debugInfo(tostring(v) .." removed from exportList") end end -------------------------------------------------------------------------------------------------------------- function userComponentManager:registerUserComponentData(fileName) local function checkEntry(entry) local ucTable = r2_core.UserComponentTable local k, v = next(ucTable, nil) while k do if v[1] == entry[1] and self:isInitialized() == true then messageBox("A UserComponent called '"..entry[1].."' is already loaded. Unload it before loading another component with the same name.") return false end if v[2] == entry[2] and self:isInitialized() == true then messageBox("This file has already been loaded.") return false end k, v = next(ucTable, k) end return true end local f = io.open(fileName,"r") assert(f) f:read("*line") f:read("*line") f:read("*line") f:read("*line") f:read("*line") local line = f:read("*line") f:close() local luaString = string.sub(line, 3) loadstring(luaString)() local currentFeatureName = tostring(CurrentFeatureName) local entry = { currentFeatureName, fileName } if checkEntry(entry) == false then return end table.insert(r2_core.UserComponentTable, entry) local userComponentTable = r2_core.UserComponentTable local userComponentTableFile = io.open("save/r2_core_user_component_table.lua", "w") userComponentTableFile:write("r2_core.UserComponentTable = \n{\n") local k, v = next(userComponentTable , nil) while k do if v then userComponentTableFile:write("\t{") userComponentTableFile:write(string.format("%q", v[1]) ..", ") userComponentTableFile:write(string.format("%q", v[2]) ..", ") userComponentTableFile:write("},\n") end k, v = next(userComponentTable, k) end userComponentTableFile:write("} --!UserComponentTable") userComponentTableFile:close() end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:unregisterComponent(featureName) local userComponentTable = r2_core.UserComponentTable local k, v = next(userComponentTable, nil) while k do if v[1] == featureName then break end k, v = next(userComponentTable, k) end if k ~= nil then table.remove(userComponentTable,k) if table.getn(userComponentTable) == 0 then r2_core.UserComponentTable = {} end debugInfo(tostring(v[1]) .." removed from loaded usercomponent table.") end local userComponentTableFile = io.open("save/r2_core_user_component_table.lua", "w") userComponentTableFile:write("r2_core.UserComponentTable = \n{\n") local k, v = next(userComponentTable , nil) while k do if v then userComponentTableFile:write("\t{") userComponentTableFile:write(string.format("%q", v[1]) ..", ") userComponentTableFile:write(string.format("%q", v[2]) ..", ") userComponentTableFile:write("},\n") end k, v = next(userComponentTable, k) end userComponentTableFile:write("} --!UserComponentTable") userComponentTableFile:close() end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:unloadUserComponent() local function checkFeatureName(name) local tbl = r2_core.UserComponentTable local k, v = next(tbl, nil) while k do if v[1] == name then return true end k, v = next(tbl, k) end return false end local function onChoiceOk(form) local featureName = form.ComponentName if featureName == "" or checkFeatureName(featureName) == false then messageBox("This User Component is not loaded.") return end userComponentManager:unregisterComponent(featureName) r2.FeatureTree.removeUCFromTree(featureName) local featureGroupList = r2:getForm("UnloadUserComponent"):find('file_list'):clear() --inspect(featureGroupList) --local featureNode = featureGroupList:getRootNode():getNodeFromId(featureName) --featureNode:getFather():deleteChild(featureNode) end local function onChoiceCancel() local featureGroupList = r2:getForm("UnloadUserComponent"):find('file_list'):clear() end r2:doForm("UnloadUserComponent", {}, onChoiceOk, onChoiceCancel) end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:loadUserComponent(fileName, body, header) --TODO : loading md5 --local ok = loadfile(fileName) local ok = loadstring(body) if not ok then messageBox("UserComponentManager: Couldn't load file '" ..fileName.."'.") return false end ok() assert(header["CurrentFeatureName"]) local currentFeatureName = header["CurrentFeatureName"] local userFeature = r2.Features[currentFeatureName] local componentId, component = next(userFeature.Components, nil) while (component ~= nil) do debugInfo("Registering user component " .. component.Name) r2.registerComponent(component) local class = r2.Classes[component.Name] class.NameToProp = {} for k, prop in pairs(class.Prop) do if prop.Name == nil then debugInfo("Found a property in class " .. tostring(class.Name) .. " with no field 'Name'") end class.NameToProp[prop.Name] = prop end assert(class) r2.Subclass(class) r2.registerGenerator(class) componentId, component = next(userFeature.Components, componentId) end r2.FeatureTree.addUserComponentNode(currentFeatureName) if self.IsInitialized == false then self.InitialComponentToLoad = self.InitialComponentToLoad - 1 else userComponentManager:registerUserComponentData(fileName) end end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:loadUserComponentFile() local function onChoiceOk(form) local filename = form.FileName local prefixedFilename = r2_core.UserComponentsPath..filename r2.loadUserComponentFile(prefixedFilename) end local function onChoiceCancel() end r2:doForm("LoadUserComponent", {}, onChoiceOk, onChoiceCancel) end --------------------------------------------------------------------------------------------------------------- function userComponentManager:computeNewPosition(instanceId, refPosition) local function buildPosition(instance, isTopLevel, refPosition) --local position = {} local position = r2.newComponent("Position") if not instance.Position then return nil end if instance ~= nil and (instance.InheritPos == 0 or isTopLevel == true) then position["x"] = r2.getWorldPos(instance).x - refPosition["x"] position["y"] = r2.getWorldPos(instance).y - refPosition["y"] position["z"] = r2:snapZToGround(r2.getWorldPos(instance).x, r2.getWorldPos(instance).y) - refPosition["z"] else position["x"] = instance.Position.x position["y"] = instance.Position.y position["z"] = instance.Position.z end --position["InstanceId"] = instance.Position.InstanceId --position["Class"] = "Position" return position end local function computePositions(instance, isTopLevel, refPosition) assert(instance) local localRefPos = nil localRefPos = buildPosition(instance, isTopLevel, refPosition) if localRefPos ~= nil and not userComponentManager.Positions[instance.Position.InstanceId] then userComponentManager.Positions[instance.Position.InstanceId] = localRefPos elseif localRefPos == nil then localRefPos = refPosition end local key, value = next(instance, nil) while key do if type(value) == "userdata" then local subInstance = value computePositions(subInstance, false, localRefPos) end key, value = next(instance, key) end end local tmpInstance = r2:getInstanceFromId(instanceId) if tmpInstance ~= nil then computePositions(tmpInstance, true, refPosition) end --table.clear(userComponentManager.Positions) end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:insertNewPosition(instanceId) local instance = r2:getInstanceFromId(instanceId) local newPosition = userComponentManager.Positions[instance.Position.InstanceId] if not newPosition then debugInfo("Can't retrieve new position for '"..tostring(instance.Position.InstanceId)) assert(0) end r2.requestSetNode(instanceId, "Position", newPosition) end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:replacePosition(instanceId) local currentHolder = r2:getInstanceFromId(r2_core.CurrentHolderId) assert(currentHolder) local refPosition = currentHolder.Position self:computeNewPosition(instanceId, refPosition) self:insertNewPosition(instanceId) end -------------------------------------------------------------------------------------------------------------------- function userComponentManager:restorePosition(instanceId, refPosition) local instance = r2:getInstanceFromId(instanceId) local newPosition = r2.newComponent("Position") if not instance.Position then return nil end if instance ~= nil then newPosition["x"] = r2.getWorldPos(instance).x - refPosition["x"] newPosition["y"] = r2.getWorldPos(instance).y - refPosition["y"] newPosition["z"] = r2:snapZToGround(r2.getWorldPos(instance).x, r2.getWorldPos(instance).y) - refPosition["z"] end r2.requestSetNode(instanceId, "Position", newPosition) end -------------------------------------------------------------------------------------------------------------------- function r2.loadUserComponentCallback(filename, body, header) r2_core.UserComponentManager:loadUserComponent(filename, body, header) if r2_core.UserComponentManager.IsInitialized == false and r2_core.UserComponentManager.InitialComponentToLoad == 0 then debugInfo("# UserComponentManager init done.") r2_core.UserComponentManager.IsInitialized = true end end -------------------------------------------------------------------------------------------------------------------- function r2.displayModifiedUserComponentFileError() messageBox("You cannot load a UserComponent file which has been externally modified") end