Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[ Central Construction Control Computer
- TO DO:
- -- change cID to distinguish between ID and channel
- -- move "action" menu selections out of setMenuList
- - add 'resend last' routines for repeating
- a coordinate block that was assigned but
- not completed
- - move project management routines to
- central computer (off turtle)
- blockRegistry contains information on all
- computers and turtles communicating with
- the central PC. Codes for first value in
- block registry as follows:
- 1 - central PC (this PC)
- 2 - satellite control PC
- 3 - gps relay PC
- 4 - unassigned
- 5 - building turtle
- 6 - supply turtle
- 7 - survey turtle
- 8+ - undefined
- Communication packets use format:
- msgType, senderBlockType, senderLabel,
- senderID, senderChannel, msgID
- (table indexes 7+ are for message specific information)
- msgTypes as follows:
- 0 - satellite registration request
- 1 - request device status
- 2 - relocate turtle
- 3 - assign build task to turtle
- 4 - assign resupply task to turtle
- 5 - assign survey task to turtle
- 6+ - undefined
- --]]
- local maxMenuItems = 3
- local modemChan = 1
- local masterCoordTable = {}
- local blockIndex = {}
- local blockRegistry = {}
- local monitorOutput, termOutput, menuList = {}, {}, {}
- local gctFileList = {}
- local rawEventTab = {}
- local m, v, d = nil, nil, nil
- -- handles for modem, monitor, and disk drive
- local mPointer, mPtIndex, mPointerStat, listOffset = 1, 1, 1, 0
- -- variable for list location of menu pointer,
- -- index of current menu, and status of pointer
- local cID, cLabel = os.getComputerID (), os.getComputerLabel ()
- function outMon (monMsg)
- local vX, vY = 0, 0
- if not (monMsg == nil) then
- if not (v == nil) then
- vX, vY = v.getSize ()
- vY = vY - 16
- if not (type (monMsg) == string) then
- monMsg = tostring (monMsg)
- end
- local newStr = monMsg..(string.rep (" ", ((vX - 33) - #monMsg)))
- table.insert (monitorOutput, newStr)
- if #monitorOutput > vY then
- table.remove (monitorOutput, 1)
- end
- end
- vX, vY = term.getSize ()
- local newStr = monMsg..(string.rep((" "), (vX - #monMsg)))
- table.insert (termOutput, newStr)
- if #termOutput > vY then
- table.remove(termOutput, 1)
- end
- end
- if not (monitorOutput == {}) then
- for i = 1, #monitorOutput do
- v.setCursorPos (32, 15 + i)
- v.write (monitorOutput[i])
- end
- end
- local mOptions = #menuList.options
- term.clear()
- if not (menuList == {}) then
- term.setCursorPos (1, 1)
- write (menuList.header)
- term.setCursorPos (1, 2)
- term.clearLine ()
- if mOptions > maxMenuItems then
- mOptions = maxMenuItems
- end
- for i = 1, mOptions do
- term.setCursorPos (1, i + 2)
- write (' '..menuList.options[i + listOffset][1])
- end
- term.setCursorPos (1, mOptions + 3)
- term.clearLine ()
- term.setCursorPos (1, mOptions + 4)
- term.write (menuList.footer..tostring(mOptions))
- end
- vX, vY = term.getSize ()
- local ttY = mOptions + 5
- if not (termOutput == {}) then
- for i = ttY, vY, 1 do
- term.setCursorPos (1, i + 1)
- if not (termOutput[i + 1] == nil) then
- write (termOutput[i])
- else
- term.clearLine ()
- end
- end
- end
- end
- function getGCTFileList ()
- gctFileList = {}
- local tFileTab = fs.list("")
- for i = 1, #tFileTab do
- if not (string.find(tFileTab[i], ".gct") == nil) then
- table.insert(gctFileList, { tFileTab[i], 0 } )
- end
- end
- end
- function initCentralPC ()
- local cStr, nStr = "", ""
- nStr = 'Central_PC_'..tostring(cID)
- if (cLabel == nil) or (cLabel ~= nStr) then
- os.setComputerLabel (nStr)
- cLabel = nStr
- end
- local periLoc = {"top", "bottom", "left",
- "right", "front", "back"}
- local periStr = ""
- print ('Device ID: '..tostring(cID))
- print ('Adjacent peripherals:')
- for i = 1, 6, 1 do
- periStr = peripheral.getType(periLoc[i])
- if periStr == "modem" then
- m = peripheral.wrap(periLoc[i])
- m.open (modemChan)
- elseif periStr == "monitor" then
- v = peripheral.wrap(periLoc[i])
- elseif periStr == "drive" then
- d = peripheral.wrap(periLoc[i])
- end
- if periStr == nil then
- print (periLoc[i]..': none')
- else print (periLoc[i]..': '..periStr)
- end
- end
- if m == nil then
- print ('WARNING! No modem detected!')
- print ('Please install a Wireless Modem')
- print ('on the computer and run this')
- print ('startup script again.')
- return false
- else
- for i = 1, 7, 1 do
- blockRegistry[i] = {}
- end
- -- first block registered is always central PC
- -- registry stores by [block type][index][label, ID,
- -- modem channel, comm log table]
- blockRegistry[1][1] = {cLabel, cID, 0, {}}
- return true
- end
- end
- function getCoordTable (fileName)
- local coordFile = fs.open (fileName, "r")
- local coordStr = ""
- local nextLine = coordFile.readLine()
- while not (nextLine == nil) do
- coordStr = coordStr..nextLine
- nextLine = coordFile.readLine ()
- end
- coordFile.close ()
- local cTable = textutils.unserialize (coordStr)
- local ctI, ctG, ctC = 0, 0, 0
- local tx, ty, tz = 0, 0, 0
- for ctI = 1, #cTable, 1 do
- ty = ty + cTable[ctI][1]
- for ctG = 1, #cTable[ctI][2], 1 do
- if cTable[ctI][2][ctG][1] > tx
- then tx = cTable[ctI][2][ctG][1]
- end
- if cTable[ctI][2][ctG][2] > tz
- then tz = cTable[ctI][2][ctG][2]
- end
- end
- end
- outMon ('Coordinate table parsed.')
- outMon ('Structure dimensions: y='..tostring(ty)..' x='..tostring(tx)..' z='..tostring(tz))
- local bX = math.ceil ((tx / 16))
- local bZ = math.ceil ((tz / 16))
- local bY = math.ceil ((ty / 16))
- outMon ('Coordinate block dimensions: y='..tostring(bY)..' x='..tostring(bX)..' z='..tostring(bZ))
- for i = 1, (bY * 16), 1 do
- masterCoordTable[i] = {}
- for ii = 1, (bX * 16), 1 do
- masterCoordTable[i][ii] = {}
- for iii = 1, (bZ * 16), 1 do
- masterCoordTable[i][ii][iii] = {0, 0}
- end
- end
- end
- for i = 1, bY, 1 do
- blockIndex[i] = {}
- for ii = 1, bX, 1 do
- blockIndex[i][ii] = {}
- for iii = 1, bZ, 1 do
- blockIndex[i][ii][iii] = {0, 0, 0}
- end
- end
- end
- local aY, aX, aZ, aM, aD = 0, 0, 0, 0, 0
- for ctI = 1, #cTable, 1 do
- for i = 1, cTable[ctI][1], 1 do
- aY = aY + 1
- for ctG = 1, #cTable[ctI][2], 1 do
- aX = cTable[ctI][2][ctG][1]
- aZ = cTable[ctI][2][ctG][2]
- aM = cTable[ctI][2][ctG][3]
- aD = cTable[ctI][2][ctG][4]
- masterCoordTable[aY][aX][aZ] = {aM, aD}
- end
- end
- end
- end
- function prepStrBlock (bY, bX, bZ)
- local aY, aX, aZ = ((bY - 1) * 16), ((bX - 1) * 16),
- ((bZ - 1) * 16)
- print ('prepStrBlock absolute: ', aY, '; ', aX, '; ', aZ)
- local blockCoordTable = {}
- for i = 1, 16, 1 do
- blockCoordTable[i] = {}
- for ii = 1, 16, 1 do
- blockCoordTable[i][ii] = {}
- for iii = 1, 16, 1 do
- blockCoordTable[i][ii][iii] =
- (masterCoordTable[aY + i][aX + ii][aZ + iii])
- end
- end
- end
- return (blockCoordTable)
- end
- function assignBlock (turtleID)
- if not (blockIndex[1] == nil) then
- for i = 1, #blockIndex[1][1], 1 do
- for ii = 1, #blockIndex[1], 1 do
- for iii = 1, #blockIndex, 1 do
- if (blockIndex[iii][i][ii][1] == 0) and
- (not (blockIndex[iii][i][ii][2] == 1)) then
- blockIndex[iii][i][ii] = {0, 1, turtleID}
- print ('assignBlock returning: ', iii, '; ', i, '; ', ii)
- return iii, i, ii
- end
- end
- end
- end
- end
- return 0, 0, 0
- end
- function reqMessageID ()
- local IDStr = tostring( os.day() )..'-'..tostring( os.time () )
- return IDStr
- end
- function logComm (tPacket)
- local isRegd, isDupe = false, false
- local sType = tPacket[2]
- local numBlocks = #blockRegistry[sType]
- for i = 1, numBlocks, 1 do
- if blockRegistry[sType][i][1] ==
- tPacket[3] then
- isRegd = true
- local numEntries = #blockRegistry[sType][i][4]
- for ii = 1, numEntries, 1 do
- if tPacket[6] == blockRegistry[sType][i][4][ii] then
- isDupe = true
- end
- end
- if not (isDupe) then
- if numEntries > 4 then
- table.remove (blockRegistry[sType][i][4], 1)
- end
- table.insert (blockRegistry[sType][i][4], tPacket[6])
- end
- end
- end
- if not (isRegd) then
- return 1
- elseif isDupe then
- return 2
- else return 0
- end
- end
- function listen (inMsgTab)
- local wy, wx, wz = 0, 0, 0
- local outMsgTab = {}
- local retNum = logComm (inMsgTab)
- local msgCode = inMsgTab[1]
- if (retNum == 2) then
- outMon ('Rejected message: duplicate ID.')
- elseif (msgCode == 0) and (retNum ~= 1) then
- outMon ('Registration request rejected:')
- outMon ('Duplicate label specified.')
- outMon (inMsgTab[3])
- elseif (msgCode == 0) and (retNum == 1) then
- outMon ('Registration requested:')
- outMon (inMsgTab[3]..' ('..tostring(inMsgTab[4])..') on channel '..tostring(inMsgTab[5])..' | '..tostring(inMsgTab[6]))
- local blockType = inMsgTab[2]
- local tRgDat = {inMsgTab[3], inMsgTab[4], inMsgTab[5], { inMsgTab[6] } }
- table.insert (blockRegistry[blockType], tRgDat)
- outMon ('Successfully registered.')
- -- TO DO
- -- add acknowledged response
- return 0, 0
- elseif msgCode == 1 then
- outMon ('Builder: new block request.')
- wy, wx, wz = assignBlock (inMsgTab[4])
- outMon ('Assigning block at: '..wy..'; '..wx..'; '..wz)
- if not (wy == 0) and not (wx == 0) and not (wz == 0) then
- outMsgTab = {3, 1, cLabel, cID, cID, reqMessageID (),
- prepStrBlock (wy, wx, wz)}
- outMon (outMsgTab[6])
- outMon (inMsgTab[5])
- m.transmit (inMsgTab[5], cID, textutils.serialize(outMsgTab))
- else
- outMon ('No unassigned blocks available.')
- outMsgTab = {3, 1, cLabel, cID, cID, reqMessageID (), nil}
- m.transmit (inMsgTab[5], cID, textutils.serialize (outMsgTab))
- end
- return 1, inMsgTab[4]
- elseif msgCode == 2 then
- outMon ('Builder: block complete.')
- for i = 1, #blockIndex, 1 do
- for ii = 1, #blockIndex[i], 1 do
- for iii = 1, #blockIndex[ii], 1 do
- if (blockIndex[i][ii][iii][1] == 0)
- and (blockIndex[i][ii][iii][2] == 1)
- and (blockIndex[i][ii][iii][3] == inMsgTab[4]) then
- blockIndex[i][ii][iii][1] = 1
- end
- end
- end
- end
- return 2, inMsgTab[4]
- end
- end
- function sendReloc (sCh, sMsg)
- m.transmit (sCh, modemChan, sMsg)
- end
- function setMenuList (mIndex)
- -- Primary menus are indexed by integer values < 100
- -- Secondary menus, and especially menus containing dynamic lists,
- -- are indexed by integer values > 100, where the truncated number
- -- derived from dividing the index number by 100 provides the
- -- 'context', and the remainder integer value provides the list index.
- -- ie An index value of 503 would be referencing a builder turtle (menu
- -- index 5) third in the list.
- -- Index values over 1000 are the last tier, indicating a
- -- specific task for a specific device of a specific type.
- local tBlockDat = {}
- local oMsg = {}
- if (mIndex == nil) then
- mIndex = 1
- end
- if mIndex <= 99 then
- if mIndex == 1 then
- menuList = {header = "Main Menu:",
- options = { {"Load .gct file", 2}, {"Turtle menu", 3}, {'Help Index', 9} },
- previous = 0,
- footer = "Up/Down or W/S to Select, Enter/D to Confirm"}
- elseif mIndex == 2 then
- getGCTFileList ()
- local tGCTList = {}
- if gctFileList == {} then
- tGCTList = {'no .GCT files found', 2}
- else
- for i = 1, #gctFileList do
- table.insert (tGCTList, { gctFileList[i][1], ((mIndex * 100) + i) } )
- end
- end
- menuList = {header = "Available .GCT Files:",
- options = tGCTList,
- previous = 1,
- footer = "Left/A for previous menu"}
- elseif mIndex == 3 then
- menuList = {header = "Turtle Menu",
- options = { {"Select turtle", 4}, {"Emergency halt", 301} },
- previous = 1,
- footer = "Left/A for previous menu"}
- elseif mIndex == 4 then
- menuList = {header = "Turtle Selection Menu",
- options = { {"Builders", 5}, {"Suppliers", 6}, {"Surveyors", 7} },
- previous = 3,
- footer = "Left/A for previous menu"}
- elseif mIndex == 5 then
- tBlockDat = {}
- if (blockRegistry[5][1] == nil) or (blockRegistry[5][1] == {}) then
- tBlockDat = { {"none registered", 5} }
- else
- for i = 1, #blockRegistry[5] do
- table.insert (tBlockDat, {blockRegistry[5][i][1], ((mIndex * 100) + i)})
- end
- end
- menuList = {header = "Registered Builder Turtles:",
- options = tBlockDat,
- previous = 4,
- footer = "Left/A for previous menu"}
- elseif mIndex == 6 then
- tBlockDat = {}
- if (blockRegistry[6][1] == nil) or (blockRegistry[6][1] == {}) then
- tBlockDat = { {"none registered", 6} }
- else
- for i = 1, #blockRegistry[6] do
- table.insert (tBlockDat, {blockRegistry[6][i][1], ((mIndex * 100) + i)})
- end
- end
- menuList = {header = "Registered Supply Turtles:",
- options = tBlockDat,
- previous = 4,
- footer = "Left/A for previous menu"}
- elseif mIndex == 7 then
- tBlockDat = {}
- if (blockRegistry[7][1] == nil) or (blockRegistry[7][1] == {}) then
- tBlockDat = { {"none registered", 7} }
- else
- for i = 1, #blockRegistry[7] do
- table.insert (tBlockDat, {blockRegistry[7][i][1], ((mIndex * 100) + i)})
- end
- end
- menuList = {header = "Registered Survey Turtles:",
- options = tBlockDat,
- previous = 4,
- footer = "Left/A for previous menu"}
- elseif mIndex == 9 then
- menuList = {header = "Help Categories:",
- options = { {"Central PC", 1}, {"Turtles", 2}, {"GPS Relay PCs", 3}, {".GCT Files", 4} },
- previous = 1,
- footer = "Left/A for previous menu"}
- end
- local mX, mY = term.getSize ()
- menuList.header = menuList.header..string.rep (" ", mX - #menuList.header)
- for i = 1, #menuList.options do
- menuList.options[i][1] = menuList.options[i][1]..string.rep (" ", mX - #menuList.options[1])
- end
- menuList.footer = menuList.footer..string.rep (" ", mX - #menuList.footer)
- elseif (mIndex >= 100) and (mIndex < 1000) then
- local mContext = math.floor((mIndex / 100))
- local dIndex = math.fmod (mIndex, 100)
- local aIndex = mIndex * 10
- outMon ('mIndex = '..tostring(mIndex)..' mContext = '..tostring(mContext)..' dIndex = '..tostring(dIndex))
- if mContext == 5 then
- menuList = {header = "Builder Turtle: "..tostring(blockRegistry[5][dIndex][1]),
- options = { {"View status", (aIndex + 1)}, {"Relocate", (aIndex + 2)}, {"Assign to Build", (aIndex + 3)},
- {"Force Resupply", (aIndex + 4)} },
- footer = "Left/A for previous menu"}
- elseif mContext == 6 then
- menuList = {header = "Supply Turtle: "..tostring(blockRegistry[6][dIndex][1]),
- options = { {"View status", (aIndex + 1)}, {"Relocate", (aIndex + 2)}, {"Assign to Supply Post", (aIndex + 3)},
- {"Force Resupply", (aIndex + 4)} },
- footer = "Left/A for previous menu"}
- elseif mContext == 7 then
- menuList = {header = "Survey Turtle: "..tostring(blockRegistry[7][dIndex][1]),
- options = { {"View status", (aIndex + 1)}, {"Relocate", (aIndex + 2)}, {"Assign to Survey Job", (aIndex + 3)},
- {"Force Resupply", (aIndex + 4)} },
- footer = "Left/A for previous menu"}
- end
- elseif (mIndex >= 1000) then
- outMon ('Reached individual system command peak.')
- local mContext = math.floor (mIndex / 1000)
- local dIndex = math.fmod (mIndex, 1000)
- local tIndex = math.fmod(dIndex, 10)
- local dIndex = math.floor(dIndex / 10)
- outMon ('Device category: '..tostring(mContext)..' | Device Index: '..tostring(dIndex))
- outMon ('Task Index: '..tostring(tIndex))
- if mContext == 5 then
- if tIndex == 1 then
- outMon ('Request and display status from turtle')
- elseif tIndex == 2 then
- local oCh = blockRegistry[5][dIndex][3]
- outMon ('Relocate builder turtle '..tostring(oCh))
- oMsg = {2, 1, cLabel, cID, modemChan, reqMessageID(), {100, 71, -205, 113}}
- local oStr = textutils.serialize (oMsg)
- sendReloc (oCh, oStr)
- outMon ('Should have sent...')
- --above transmit is for test purposes only
- elseif tIndex == 3 then
- outMon ('Assign build job to turtle')
- elseif tIndex == 4 then
- outMon ('Force resupply on builder')
- end
- elseif mContext == 6 then
- if tIndex == 1 then
- outMon ('Request and display status from turtle')
- elseif tIndex == 2 then
- outMon ('Relocate builder turtle')
- elseif tIndex == 3 then
- outMon ('Assign build job to turtle')
- elseif tIndex == 4 then
- outMon ('Force resupply on builder')
- end
- elseif mContext == 7 then
- if tIndex == 1 then
- outMon ('Request and display status from turtle')
- elseif tIndex == 2 then
- outMon ('Relocate builder turtle')
- elseif tIndex == 3 then
- outMon ('Assign build job to turtle')
- elseif tIndex == 4 then
- outMon ('Force resupply on builder')
- end
- end
- end
- end
- function updateMonitor ()
- local vX, vY = v.getSize ()
- v.clear ()
- v.setCursorPos (1, 1)
- v.write (' Registered Satellites')
- v.setCursorPos (1, 3)
- v.write (' GPS Relays: | Builders: | Surveyors: | Suppliers: | Sub Control:')
- v.setCursorPos (4, 18)
- v.write ('GCT Files:')
- for i = 4, 14 do
- v.setCursorPos (16, i)
- v.write ('|')
- v.setCursorPos (32, i)
- v.write ('|')
- v.setCursorPos (48, i)
- v.write ('|')
- v.setCursorPos (64, i)
- v.write ('|')
- end
- for i = 1, vX do
- v.setCursorPos (i, 15)
- v.write ('-')
- end
- for i = 16, vY do
- v.setCursorPos (30, i)
- v.write ('|')
- end
- for i = 1, #blockRegistry[3] do
- v.setCursorPos (2, 4 + i)
- v.write (blockRegistry[3][i][1])
- end
- for i = 1, #blockRegistry[5] do
- v.setCursorPos (18, 4 + i)
- v.write (blockRegistry[5][i][1])
- end
- for i = 1, #blockRegistry[7] do
- v.setCursorPos (34, 4 + i)
- v.write (blockRegistry[7][i][1])
- end
- for i = 1, #blockRegistry[6] do
- v.setCursorPos (50, 4 + i)
- v.write (blockRegistry[6][i][1])
- end
- for i = 1, #blockRegistry[2] do
- v.setCursorPos (66, 4 + i)
- v.write (blockRegistry[2][i][1])
- end
- for i = 1, #gctFileList do
- v.setCursorPos (2, 19 + i)
- v.write (gctFileList[i][1])
- end
- outMon()
- end
- function pointerSpin ()
- term.setCursorPos (1, 2 + mPointer)
- if mPointerStat == 1 then
- write ('--')
- mPointerStat = 2
- elseif mPointerStat == 2 then
- write ('->')
- mPointerStat = 1
- end
- os.startTimer(0.5)
- end
- function eventHandler ()
- local haltNow = false
- local globalTable = {}
- while not haltNow do
- pointerSpin ()
- rawEventTab = {}
- rawEventTab = { os.pullEvent () }
- --rawEventTab[1], rawEventTab[2], rawEventTab[3], rawEventTab[4],
- -- rawEventTab[5], rawEventTab[6], rawEventTab[7], rawEventTab[8] = os.pullEvent ()
- if rawEventTab[1] == "key" then
- outMon ('Key event...')
- if (rawEventTab[2] == 17) or (rawEventTab[2] == 200) then
- local oldPtr = mPointer
- mPointer = mPointer - 1
- if mPointer < 1 then
- mPointer = 1
- if listOffset > 0 then
- listOffset = listOffset - 1
- end
- if mPtIndex > 1 then
- mPtIndex = mPtIndex - 1
- end
- else
- mPtIndex = mPtIndex - 1
- end
- if mPtIndex < 1 then
- mPtIndex = 1
- end
- term.setCursorPos (1, 2 + oldPtr)
- term.write (' ')
- outMon ('mPointer '..tostring(mPointer)..' mPtIndex '..tostring(mPtIndex))
- elseif (rawEventTab[2] == 31) or (rawEventTab[2] == 208) then
- local oldPtr = mPointer
- mPointer = mPointer + 1
- mPtIndex = mPtIndex + 1
- if mPtIndex > #menuList.options then
- mPtIndex = #menuList.options
- mPointer = mPointer - 1
- end
- if mPointer > maxMenuItems then
- mPointer = mPointer - 1
- if not (listOffset == #menuList.options) then
- listOffset = listOffset + 1
- end
- end
- term.setCursorPos (1, 2 + oldPtr)
- write (' ')
- outMon ()
- elseif (rawEventTab[2] == 28) or (rawEventTab[2] == 32)
- or (rawEventTab[2] == 205) then
- local oldIndex = mPtIndex
- mPointer = 1
- mPtIndex = 1
- listOffset = 0
- if menuList.options[oldIndex][2] == 0 then
- outMon ('0 index option selected')
- else
- setMenuList (menuList.options[oldIndex][2])
- end
- outMon ()
- elseif (rawEventTab[2] == 14) or (rawEventTab[2] == 30)
- or (rawEventTab[2] == 203)then
- mPointer = 1
- mPtIndex = 1
- listOffset = 0
- setMenuList (menuList.previous)
- outMon ()
- else
- outMon ('Unassigned key pressed. '..tostring(rawEventTab[2]))
- end
- elseif rawEventTab[1] == "modem_message" then
- outMon ("Modem event..."..tostring(#rawEventTab))
- listen (textutils.unserialize (rawEventTab[5]))
- end
- updateMonitor ()
- end
- end
- term.clear ()
- setMenuList ()
- if not (initCentralPC ()) then
- print ('Script halted.')
- else
- getGCTFileList ()
- if gctFileList == {} then
- outMon ('No .GCT files found.')
- end
- updateMonitor ()
- eventHandler ()
- end
- --[[
- getCoordTable ("gpstower.gct")
- while true do
- local tCode, tID = listen()
- updateMonitor ()
- if tCode == 0 then
- print ('Processed registration request.')
- elseif tCode == 1 then
- outMon ('Processed coordinate block request.')
- elseif tCode == 2 then
- outMon ('Received block complete notice.')
- end
- end--]]
Advertisement
Add Comment
Please, Sign In to add comment