Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- MineColonies Builder Request Fulfillment Bot
- Version 3.2
- Changelog v3.2:
- - Random Work Order Selection: If multiple suitable work orders are found,
- one is chosen at random to prevent one problematic order from blocking others.
- - Reinforces delivering partial shipments (already mostly in place by design).
- - Previous fixes from v3.1 remain.
- Setup: (Same as v2.0)
- ]]
- -- Configuration (same as v3.1)
- local ME_BRIDGE_NAME = "meBridge"
- local END_AUTOMATA_NAME = "endAutomata"
- local COLONY_INTEGRATOR_NAME = "colonyIntegrator"
- local HOME_WARP_POINT = "home"
- local COLONY_WARP_POINT = "colony"
- local NAMED_BUILDING_STORAGE_SUFFIX = "_storage"
- local COAL_SLOT = 16
- local COAL_RESERVE_AMOUNT = 64
- local LOW_FUEL_THRESHOLD = 200
- local DETAILED_RESOURCE_LOG_FILE = "builder_bot_resource_details.log"
- local MAIN_LOG_FILE = "builder_bot_main.log"
- local automata = nil
- -- [[ Logging Function (same as v3.0) ]]
- local function log(message)
- local timestamp = textutils.formatTime(os.time(), false)
- local logMessage = "[" .. timestamp .. "] " .. tostring(message)
- print(logMessage)
- local file = fs.open(MAIN_LOG_FILE, "a")
- if file then file.writeLine(logMessage) file.close()
- else print("ERROR: Could not write to main log file: " .. MAIN_LOG_FILE) end
- end
- -- [[ HELPER FUNCTIONS (initialPeripheralCheck, ensureFuel, warpTo, findEmptySlot, consolidateInventory, returnUnusedItemsToME, getItemsFromME, deliverItems, prepareAtHomeBase - same as v3.1) ]]
- -- These functions are assumed to be identical to v3.1 and are omitted for brevity.
- local function initialPeripheralCheck()
- automata = peripheral.find(END_AUTOMATA_NAME)
- if not automata then log("CRITICAL ERROR: End Automata ('" .. END_AUTOMATA_NAME .. "') not found!") return false end
- log("End Automata found.")
- return true
- end
- local function ensureFuel(atHome)
- log("Ensuring fuel. Turtle is " .. (atHome and "at home." or "not at home."))
- if turtle.getFuelLevel() < LOW_FUEL_THRESHOLD then
- turtle.select(COAL_SLOT)
- if turtle.getItemCount(COAL_SLOT) > 0 then log("Fuel is low (" .. turtle.getFuelLevel() .. "). Refueling from coal slot...") turtle.refuel() log("Fuel level after attempting refuel: " .. turtle.getFuelLevel())
- else log("Fuel is low, but no coal in slot " .. COAL_SLOT .. " to use.") end
- end
- if atHome then
- log("At home: Attempting to stock coal and maximize fuel.")
- turtle.select(COAL_SLOT)
- local currentCoal = turtle.getItemCount(COAL_SLOT)
- if currentCoal < COAL_RESERVE_AMOUNT then
- local amountToSuck = COAL_RESERVE_AMOUNT - currentCoal
- log("Coal slot " .. COAL_SLOT .. " has " .. currentCoal .. "/" .. COAL_RESERVE_AMOUNT .. ". Sucking up to " .. amountToSuck .. " from chest below...")
- local suckResult = turtle.suckDown(amountToSuck)
- if type(suckResult) == "number" then
- if suckResult > 0 then log("Sucked " .. suckResult .. " additional coal (numeric return).")
- else log("Sucked 0 items (numeric return; chest empty or other issue).") end
- elseif suckResult == true then log("suckDown returned true. Assuming " .. amountToSuck .. " (or available) coal sucked.")
- elseif suckResult == false then log("Failed to suck coal (suckDown returned false).")
- else log("WARN: turtle.suckDown returned unexpected type: " .. type(suckResult) .. ", value: " .. tostring(suckResult)) end
- else log("Coal slot " .. COAL_SLOT .. " is already full or has sufficient reserve.") end
- turtle.select(COAL_SLOT)
- if turtle.getItemCount(COAL_SLOT) > 0 then log("Refueling turtle at home base from slot " .. COAL_SLOT .. "...") turtle.refuel() log("Fuel level after home refueling: " .. turtle.getFuelLevel())
- else log("No coal in slot " .. COAL_SLOT .. " to perform home refueling.") end
- end
- if turtle.getFuelLevel() == 0 then log("CRITICAL: Turtle has zero fuel!") return false end
- log("Fuel check complete. Current level: " .. turtle.getFuelLevel())
- return true
- end
- local function warpTo(pointName)
- log("Attempting to warp to '" .. pointName .. "'...")
- if turtle.getFuelLevel() == 0 then log(" ERROR: Zero fuel. Cannot warp.") return false end
- local success, reason = automata.warpToPoint(pointName)
- if success then log("Warp successful to " .. pointName) os.sleep(2) return true
- else log("ERROR: Failed to warp to " .. pointName .. ": " .. (reason or "unknown error")) return false end
- end
- local function findEmptySlot() for i = 1, 15 do if turtle.getItemCount(i) == 0 then return i end end return nil end
- local function consolidateInventory() log("Consolidating inventory...") for i = 1, 15 do turtle.select(i) if turtle.getItemCount(i) > 0 then for j = i + 1, 15 do if turtle.compareTo(j) then local space = turtle.getItemSpace(i) if space > 0 then turtle.select(j) turtle.transferTo(i, math.min(space, turtle.getItemCount(j))) end end end end end turtle.select(1) end
- local function returnUnusedItemsToME(bridge)
- if not bridge then log("ERROR (returnUnused): ME Bridge not provided!") return end
- log("Returning unused items to ME system (at home)...")
- turtle.up()
- local itemsDroppedDetails = {}
- for i = 1, 15 do
- turtle.select(i)
- local itemDetail = turtle.getItemDetail(i)
- if itemDetail and itemDetail.count > 0 then
- local displayNameToPrint = itemDetail.displayName or itemDetail.name or "UnknownItemInTurtle"
- log(" Attempting to drop " .. itemDetail.count .. " of " .. displayNameToPrint .. " (Registry: " .. itemDetail.name .. ", Slot " .. i .. ") into IO chest.")
- if turtle.drop() then
- log(" Dropped successfully.")
- table.insert(itemsDroppedDetails, itemDetail)
- else
- log(" WARN: Failed to drop " .. displayNameToPrint .. " into IO chest from slot " .. i)
- end
- end
- end
- turtle.down()
- if #itemsDroppedDetails == 0 then log(" No items were in turtle (slots 1-15) to return, or all drops failed.") return end
- log(" Importing items from IO chest into ME system:")
- for _, itemToImport in ipairs(itemsDroppedDetails) do
- local displayNameToImport = itemToImport.displayName or itemToImport.name or "UnknownItemToImport"
- local importFilter = {name=itemToImport.name, count=itemToImport.count}
- if itemToImport.nbt then importFilter.nbt = itemToImport.nbt end
- log(" Attempting to import " .. itemToImport.count .. " of " .. displayNameToImport)
- local importedAmount, importError = bridge.importItem(importFilter, "up")
- if importError then log(" ERROR importing " .. displayNameToImport .. ": " .. importError)
- elseif importedAmount > 0 then log(" Successfully imported " .. importedAmount .. " of " .. displayNameToImport)
- if importedAmount < itemToImport.count then log(" WARN: Imported " .. importedAmount .. "/" .. itemToImport.count) end
- else log(" Imported 0 of " .. displayNameToImport .. ". (No error, 0 moved)") end
- os.sleep(0.2)
- end
- log("Finished returning items.")
- end
- local function getItemsFromME(bridge, itemsToGet)
- if not bridge then log("ERROR (getItems): ME Bridge not provided!") return {} end
- log("Requesting items from ME system (at home):") local successfullyFetched = {}
- for _, itemReq in ipairs(itemsToGet) do
- local itemName = "" ; local itemDisplayName = ""
- if type(itemReq.item) == "table" then itemName = itemReq.item.name or "UnknownItemRegistry"
- elseif type(itemReq.item) == "string" then itemName = itemReq.item else itemName = "InvalidItemRegistry" end
- if type(itemReq.displayName) == "table" then itemDisplayName = itemReq.displayName.name or itemReq.displayName.displayName or itemName
- elseif type(itemReq.displayName) == "string" then itemDisplayName = itemReq.displayName else itemDisplayName = itemName end
- local neededCount = itemReq.needs
- if neededCount == nil or type(neededCount) ~= "number" or neededCount <= 0 then
- log(" Skipping item " .. itemDisplayName .. " for ME fetch (invalid neededCount: " .. tostring(neededCount) .. ")")
- goto continue_me_req_v32
- end
- local emptySlot = findEmptySlot() if not emptySlot then log(" No empty slot. Skipping ME requests.") break end
- turtle.select(emptySlot)
- local exportFilter = {name=itemName, count=neededCount}
- if itemReq.nbt then exportFilter.nbt = itemReq.nbt end
- local exportedAmount, exportReason = bridge.exportItem(exportFilter, "up")
- if not exportedAmount or exportedAmount == 0 then log(" Export failed for " .. itemDisplayName .. ": " .. (exportReason or "0 items")) goto continue_me_req_v32 end
- turtle.up() local itemActuallySucked = 0 turtle.select(emptySlot)
- for _ = 1, exportedAmount do if turtle.suck(1) then itemActuallySucked = itemActuallySucked + 1 else break end end
- turtle.down()
- if itemActuallySucked > 0 then
- local fetchedDetail = turtle.getItemDetail(emptySlot)
- table.insert(successfullyFetched, { slot = emptySlot, name = fetchedDetail.name, displayName = fetchedDetail.displayName, count = itemActuallySucked, nbt = fetchedDetail.nbt })
- local fetchedDisplayName = fetchedDetail.displayName or fetchedDetail.name or "UnknownFetchedItem"
- if itemActuallySucked < neededCount then log(" WARN: Sucked " ..itemActuallySucked.."/"..neededCount .. " of " .. fetchedDisplayName)
- else log(" Sucked " ..itemActuallySucked .. " of " .. fetchedDisplayName) end
- elseif exportedAmount > 0 then log(" Exported " .. exportedAmount .. " but failed to suck " .. itemDisplayName) end
- ::continue_me_req_v32::
- end
- consolidateInventory() return successfullyFetched
- end
- local function deliverItems(deliveryWarpPointName, itemsExpectedToDeliver)
- log("Attempting delivery to " .. deliveryWarpPointName)
- log("Delivering items by dropping down:")
- local allItemsFullyDelivered = true
- local remainingToDeliver = {}
- for _, itemDetail in ipairs(itemsExpectedToDeliver) do
- table.insert(remainingToDeliver, { name = itemDetail.name, displayName = itemDetail.displayName or itemDetail.name or "UnknownItem", nbt = itemDetail.nbt, count = itemDetail.count })
- end
- local deliveryAttempted = false
- for deliveryPass = 1, 2 do
- if deliveryAttempted and deliveryPass == 2 then
- local anyLeft = false; for _, item in ipairs(remainingToDeliver) do if item.count > 0 then anyLeft = true break end end
- if not anyLeft then break end -- All delivered, no need for 2nd pass
- log(" Some items remain. Trying alternative drop spot...")
- turtle.turnLeft() turtle.forward()
- elseif deliveryPass == 2 then break end
- deliveryAttempted = true
- for i = 1, #remainingToDeliver do
- local wantedItem = remainingToDeliver[i]
- if wantedItem.count > 0 then
- local wantedItemDisplayName = wantedItem.displayName
- log(" Seeking to deliver " .. wantedItem.count .. " of " .. wantedItemDisplayName)
- for slot = 1, 15 do
- turtle.select(slot)
- local itemInSlot = turtle.getItemDetail(slot)
- if itemInSlot and itemInSlot.name == wantedItem.name and (itemInSlot.nbt == wantedItem.nbt) then
- local amountToDrop = math.min(itemInSlot.count, wantedItem.count)
- if amountToDrop > 0 then
- log(" Found " .. itemInSlot.count .. " of " .. wantedItemDisplayName .. " in slot " .. slot .. ". Attempting to drop " .. amountToDrop)
- if turtle.dropDown(amountToDrop) then
- log(" Dropped " .. amountToDrop .. " successfully.")
- wantedItem.count = wantedItem.count - amountToDrop
- if wantedItem.count == 0 then log(" Requirement for " .. wantedItemDisplayName .. " met.") break end
- else
- log(" DropDown failed for " .. amountToDrop .. " of " .. wantedItemDisplayName .. " from slot " .. slot .. ".")
- allItemsFullyDelivered = false
- end
- end
- end
- end
- end
- end
- local allMetThisPass = true
- for _, item in ipairs(remainingToDeliver) do if item.count > 0 then allMetThisPass = false break end end
- if allMetThisPass then log(" All item requirements met in this delivery pass.") break end
- if deliveryPass == 2 then turtle.back() turtle.turnRight() end
- end
- for _, item in ipairs(remainingToDeliver) do
- if item.count > 0 then log(" WARN: Could not deliver " .. item.count .. " of " .. item.displayName .. ".") allItemsFullyDelivered = false end
- end
- if allItemsFullyDelivered then log("All requested items appear fully delivered to " .. deliveryWarpPointName)
- else log("Some items were not fully delivered to " .. deliveryWarpPointName) end
- log("Waiting 10 seconds at builder's hut: " .. deliveryWarpPointName)
- os.sleep(10)
- return allItemsFullyDelivered
- end
- function prepareAtHomeBase()
- log("Ensuring turtle is prepared at home base...")
- if not warpTo(HOME_WARP_POINT) then log("CRITICAL: Cannot reach " .. HOME_WARP_POINT) return false, nil end
- local bridge = peripheral.find(ME_BRIDGE_NAME)
- if not bridge then log("ERROR: ME Bridge ('"..ME_BRIDGE_NAME.."') not found at " .. HOME_WARP_POINT .. "!") return false, nil end
- log("ME Bridge found at home base.")
- if not ensureFuel(true) then log("CRITICAL: Failed to ensure fuel at " .. HOME_WARP_POINT .. ".") return false, bridge end
- returnUnusedItemsToME(bridge)
- log("Turtle is at home and prepared.")
- return true, bridge
- end
- -- MAIN SCRIPT EXECUTION
- log("Builder Bot v3.2 Initializing...")
- if not initialPeripheralCheck() then log("Essential End Automata not found. Exiting.") return end
- local resLogClearFile = fs.open(DETAILED_RESOURCE_LOG_FILE, "w")
- if resLogClearFile then resLogClearFile.close() log("Cleared " .. DETAILED_RESOURCE_LOG_FILE) end
- local homePrepared, currentBridge = prepareAtHomeBase()
- if not homePrepared then
- log("Initial home preparation failed. Waiting 5 minutes...") os.sleep(300)
- homePrepared, currentBridge = prepareAtHomeBase()
- if not homePrepared then log("Second home prep failed. Exiting.") return end
- end
- log("Successfully initialized. Starting main loop.")
- while true do
- log("\n--- Starting New Cycle ---")
- local workOrderFoundAndTargeted = false ; local actualDeliveryWarpPoint = nil
- local originalTargetBuildingName = nil ; local currentWorkOrderItems = nil
- local detailedResFile = nil
- log("Going to colony for work orders...")
- if not ensureFuel(false) then log("Warn: Low fuel before leaving home.") end
- if not warpTo(COLONY_WARP_POINT) then
- log("Failed to reach colony.") ; homePrepared, currentBridge = prepareAtHomeBase()
- if not homePrepared then log("CRIT: No recovery. Wait 5m...") os.sleep(300) end
- goto continue_main_loop_v32
- end
- local colony = peripheral.find(COLONY_INTEGRATOR_NAME)
- if not colony then
- log("ERROR: CI not found at "..COLONY_WARP_POINT) ; homePrepared, currentBridge = prepareAtHomeBase()
- if not homePrepared then os.sleep(300) end
- goto continue_main_loop_v32
- end
- log("CI found. Checking work orders...")
- local workOrders = colony.getWorkOrders()
- -- ***** V3.2 MODIFICATION: Collect suitable WOs and pick one randomly *****
- local suitableWorkOrders = {}
- if not workOrders or #workOrders == 0 then log("No active work orders.")
- else
- log("Found " .. #workOrders .. " WOs. Evaluating...")
- for _, wo in ipairs(workOrders) do
- if not wo or wo.id == nil then log(" WARN: Invalid WO entry.") goto continue_eval_wo_loop_v32 end
- log("\n Evaluating WO ID: " .. wo.id .. " for: " .. (wo.buildingName or "N/A"))
- detailedResFile = fs.open(DETAILED_RESOURCE_LOG_FILE, "a")
- if detailedResFile then detailedResFile.write("\n--- Eval WO ID: " .. wo.id .. " for: " .. (wo.buildingName or "N/A") .. ", Type: " .. (wo.workOrderType or "N/A") .. " ---\n") end
- local itemsNeededForThisOrder = {} ; local needsSomething = false
- local resources = colony.getWorkOrderResources(wo.id)
- if resources then
- if #resources > 0 then
- local headerMsg = " Found " .. #resources .. " resource entries:"
- if detailedResFile then detailedResFile.write(headerMsg .. "\n") else log(headerMsg) end
- for resIdx, res in ipairs(resources) do
- local sResItem = "UnknownItemRegistry"
- if type(res.item) == "table" then sResItem = res.item.name or (res.item.displayName or "UnknownInTable") elseif type(res.item) == "string" then sResItem = res.item end
- local sResDisplayName = sResItem
- if type(res.displayName) == "table" then sResDisplayName = res.displayName.name or (res.displayName.displayName or sResItem) elseif type(res.displayName) == "string" then sResDisplayName = res.displayName end
- local res_needs_val = res.needs ; local resAvailable = res.available
- local resStatus = res.status ; local resDelivering = res.delivering
- local logEntry = (" Res #" .. resIdx .. ": '" .. sResDisplayName .. "' (Reg: " .. sResItem .. ")\n") ..
- (" Raw needs: " .. tostring(res_needs_val) .. " (Type: " .. type(res_needs_val) .. ")\n") ..
- (" Raw Avail: " .. tostring(resAvailable) .. " (Type: " .. type(resAvailable) .. ")\n") ..
- (" Raw Status: " .. tostring(resStatus) .. " (Type: " .. type(resStatus) .. ")\n") ..
- (" Raw Deliver: " .. tostring(resDelivering) .. " (Type: " .. type(resDelivering) .. ")\n")
- if detailedResFile then detailedResFile.write(logEntry) else log(logEntry) end
- if res_needs_val == nil then
- local skipMsg = " SKIP: 'needs' is nil."
- if detailedResFile then detailedResFile.write(skipMsg .. "\n") else log(skipMsg) end
- goto continue_res_loop_v32
- end
- local isStillNeeded = false ; local evalMsg = ""
- if type(res_needs_val) == "number" and res_needs_val > 0 then
- if resAvailable == false then isStillNeeded = true evalMsg = " EVAL: Need (avail false).\n"
- elseif resStatus and string.lower(resStatus) ~= "met" and string.lower(resStatus) ~= "fulfilled" and string.lower(resStatus) ~= "not_needed" then
- isStillNeeded = true evalMsg = " EVAL: Need (status '"..tostring(resStatus).."').\n"
- else evalMsg = " EVAL: Not Need (avail true or status met/fulfilled/not_needed). Needs: "..tostring(res_needs_val)..", Avail: "..tostring(resAvailable)..", Status: "..tostring(resStatus).."\n" end
- else evalMsg = " EVAL: Not Need (needs not >0 or not num). Needs: "..tostring(res_needs_val).."\n" end
- if detailedResFile then detailedResFile.write(evalMsg) else log(evalMsg) end
- if isStillNeeded then
- local actionMsg = " ACTION: Add " .. res_needs_val .. " of " .. sResDisplayName .. " to list.\n"
- if detailedResFile then detailedResFile.write(actionMsg) else log(actionMsg) end
- table.insert(itemsNeededForThisOrder, {item = sResItem, displayName = sResDisplayName, needs = res_needs_val, nbt = res.nbt})
- needsSomething = true
- end
- ::continue_res_loop_v32::
- end
- else
- local noResMsg = " WO " .. wo.id .. " no resource entries."
- if detailedResFile then detailedResFile.write(noResMsg .. "\n") else log(noResMsg) end
- end
- else
- local failGetResMsg = " WARN: Failed to get resources for WO " .. wo.id
- if detailedResFile then detailedResFile.write(failGetResMsg .. "\n") else log(failGetResMsg) end
- end
- if detailedResFile then detailedResFile.close() detailedResFile = nil end
- if needsSomething then
- -- Determine warp point for this suitable WO
- local tempDeliveryWarpPoint = nil
- if wo.buildingName and not string.match(string.lower(wo.buildingName), "com.minecolonies.building.") and not string.match(string.lower(wo.buildingName), "minecolonies:") then
- tempDeliveryWarpPoint = wo.buildingName .. NAMED_BUILDING_STORAGE_SUFFIX
- elseif wo.builder and wo.builder.x ~= nil then tempDeliveryWarpPoint = wo.builder.x .. "," .. wo.builder.y .. "," .. wo.builder.z
- end
- if tempDeliveryWarpPoint then
- log(" WO ID " .. wo.id .. " for " .. (wo.buildingName or "N/A") .. " is suitable and has a valid warp point.")
- table.insert(suitableWorkOrders, {
- woData = wo,
- items = itemsNeededForThisOrder,
- warpPoint = tempDeliveryWarpPoint
- })
- else
- log(" WO ID " .. wo.id .. " for " .. (wo.buildingName or "N/A") .. " needs items, but no valid warp point determined. Skipping as suitable.")
- end
- else log(" WO " .. wo.id .. " no items needed based on eval.") end
- ::continue_eval_wo_loop_v32::
- end
- end
- colony = nil
- if #suitableWorkOrders > 0 then
- local randomIndex = math.random(1, #suitableWorkOrders)
- local chosenTask = suitableWorkOrders[randomIndex]
- log("Found " .. #suitableWorkOrders .. " suitable work order(s). Randomly selected task for WO ID: " .. chosenTask.woData.id)
- originalTargetBuildingName = chosenTask.woData.buildingName or "Unknown Target"
- currentWorkOrderItems = chosenTask.items
- actualDeliveryWarpPoint = chosenTask.warpPoint
- workOrderFoundAndTargeted = true
- else
- log("No suitable work orders found this cycle that require items and have valid warp points.")
- end
- if not workOrderFoundAndTargeted then log("No suitable WOs at colony.") log("Wait 10s at colony...") os.sleep(10) end
- -- ***** END V3.2 MODIFICATION *****
- if workOrderFoundAndTargeted and actualDeliveryWarpPoint and currentWorkOrderItems then
- log("WO targeted. Home for items: " .. originalTargetBuildingName)
- if not warpTo(HOME_WARP_POINT) then log("Failed warp home.") goto continue_main_loop_v32 end
- currentBridge = peripheral.find(ME_BRIDGE_NAME)
- if not currentBridge then log("ERROR: ME Bridge missing at home!") goto continue_main_loop_v32 end
- if not ensureFuel(true) then log("Fuel fail home.") goto continue_main_loop_v32 end
- local fetchedItems = getItemsFromME(currentBridge, currentWorkOrderItems)
- -- Even if fetchedItems is empty or partial, we proceed to deliver what we got (if anything)
- if not fetchedItems then fetchedItems = {} end -- Ensure it's a table for deliverItems
- log("ME fetch attempt complete. Fetched " .. #fetchedItems .. " types of items. Delivering to " .. originalTargetBuildingName .. " via " .. actualDeliveryWarpPoint)
- if not ensureFuel(false) then log("Warn: Low fuel pre-delivery.") end
- if warpTo(actualDeliveryWarpPoint) then
- if #fetchedItems > 0 then -- Only deliver if we actually got something
- deliverItems(actualDeliveryWarpPoint, fetchedItems)
- else
- log("No items were successfully fetched from ME. Nothing to deliver for this WO.")
- log("Waiting 10 seconds at builder's hut (even with no items, to prevent immediate re-check of same empty WO): " .. actualDeliveryWarpPoint)
- os.sleep(10)
- end
- else log("Failed warp to " .. actualDeliveryWarpPoint .. ". Items (if any) remain.") end
- log("Delivery attempt for " .. originalTargetBuildingName .. " done.")
- else log("No WO targeted this cycle. Home if not there.") end
- log("Cycle end. Prep for next.")
- homePrepared, currentBridge = prepareAtHomeBase()
- if not homePrepared then log("Home prep fail. Wait 5m...") os.sleep(300) homePrepared, currentBridge = prepareAtHomeBase()
- if not homePrepared then log("2nd home prep fail. Exit.") return end
- end
- log("Wait 10s before next cycle...") os.sleep(10)
- ::continue_main_loop_v32::
- end
Add Comment
Please, Sign In to add comment