rollout CameraDataParser "Manhunt TVP Editor 1.9 PROTOTYPE" ( label lblInfo "Enter Vecpair strings:" align:#center label spacer1 "" height:0.5 editText txtInput "" width:380 height:90 align:#center tooltip:"copy/paste the vecpair lines here or use the file import" label spacer2 "" height:5 checkbox chkDeleteOnReset "Delete cameras on reset" checked:true align:#center button btnreset "Reset" align:#center width:50 height:30 tooltip:"Clears vecpair field text." label spacer4 "" height:10 group "Controls" ( button btnParse "Import Cameras" align:#center width:150 height:20 button btnSelectAll "Select all Cameras" align:#center width:150 height:20 button btnExport "Export Selected to Clipboard" align:#center width:150 height:20 button btnCreateHelper "Add Helper" width:100 height:20 tooltip:"Adds a helper object for easy navigation in viewport" ) group "File Import" ( button btnImportFile "Load GLG File..." width:150 height:20 align:#center tooltip:"Import a Resource13.glg file directly" dropdownlist ddlRecords "Records" width:150 height:10 align:#center label lblRecSearch "Search in Records:" align:#center editText txtRecordSearch "" width:150 height:18 align:#center tooltip:"Search records" dropdownlist ddlAngleSets "Angle Sets" width:150 height:10 align:#center button btnImportAngleSet "Import Angle Set" width:150 height:20 align:#center button btnReplaceAngleSet "Replace Angle Set in File" align:#center width:150 height:20 tooltip:"Replace selected angle set in the original file" checkbox chkBackup "Create Backup (.bak)" checked:true align:#center tooltip:"Toggle backup creation when replacing angle sets" label lblwarn1 "Export feature is broken! WIP" align:#center ) label lblCredit "Coded by Hellwire/WhoIsPrice" align:#center global createdCameras = #() global cameraMetaData = #() global parsedRecords = #() global currentAngleSets = #() global cameraHelperObj = undefined global allRecordNames = #() global filteredRecordIndices = #() global currentGLGFilePath = undefined fn joinString arr delimiter = ( local str = "" for i = 1 to arr.count do ( str += arr[i] if i < arr.count do str += delimiter ) return str ) fn deleteCreatedCameras = ( for cam in createdCameras where isValidNode cam do delete cam createdCameras = #() cameraMetaData = #() ) fn parseVecpairLine line = ( local hasAtSymbol = matchPattern line pattern:"*@*" local tokens = filterString (trimLeft line) " \t" if tokens.count >= 10 then ( local vecpairScale = tokens[2] as float local posX = tokens[3] as float local posY = tokens[4] as float local posZ = tokens[5] as float local lookAtX = tokens[6] as float local lookAtY = tokens[7] as float local lookAtZ = tokens[8] as float local unknownVal = tokens[9] as float local rollVal = tokens[10] as float local cam = freecamera() cam.name = uniquename "ImportedCamera" cam.position = [posX, posY, posZ] cam.wirecolor = random (color 50 50 50) (color 255 255 255) local targetPos = [lookAtX, lookAtY, lookAtZ] local lookDist = length (targetPos - cam.position) local forwardVec = normalize (cam.position - targetPos) local upVec = [0,0,1] local rightVec = normalize (cross upVec forwardVec) upVec = cross forwardVec rightVec cam.transform = matrix3 rightVec upVec forwardVec cam.position append createdCameras cam local handle = getHandleByAnim cam cameraMetaData[handle] = #(vecpairScale, lookDist, unknownVal, rollVal, hasAtSymbol) ) ) fn getExportStringsFromCameras cameras = ( local exportStrings = #() for cam in cameras do ( local handle = getHandleByAnim cam if cameraMetaData[handle] != undefined then ( local meta = cameraMetaData[handle] local pos = cam.position local forwardVec = -normalize (cam.transform.row3) local lookAt = pos + (forwardVec * meta[2]) local prefix = if meta[5] then "VECPAIR@" else "VECPAIR" local exportStr = "\t" + prefix + " " + (meta[1] as string) + " " + (pos.x as string) + " " + (pos.y as string) + " " + (pos.z as string) + " " + (lookAt.x as string) + " " + (lookAt.y as string) + " " + (lookAt.z as string) + " " + (meta[3] as string) + " " + (meta[4] as string) append exportStrings exportStr ) ) return exportStrings ) fn replaceAngleSetInFile filePath recordIndex angleSetIndex newAngleSet = ( try ( -- Read the entire file local file = openFile filePath mode:"r" if file == undefined then return false local fileLines = #() while not eof file do ( append fileLines (readLine file) ) close file -- Find the target record local currentRecordIndex = 0 local recordStartLine = 0 local recordEndLine = 0 local recordIndent = "" for i = 1 to fileLines.count do ( local line = fileLines[i] local trimmed = trimLeft line if matchPattern trimmed pattern:"RECORD*" then ( currentRecordIndex += 1 if currentRecordIndex == recordIndex then ( recordStartLine = i recordIndent = substring line 1 (line.count - trimmed.count) ) else if currentRecordIndex > recordIndex and recordEndLine == 0 then ( recordEndLine = i - 1 exit ) ) ) -- If we didn't find the end by another RECORD, look for END if recordEndLine == 0 then ( for i = recordStartLine to fileLines.count do ( if matchPattern (trimLeft fileLines[i]) pattern:"END" then ( recordEndLine = i exit ) ) ) if recordStartLine == 0 or recordEndLine == 0 then ( messageBox "Could not locate the record!" title:"Error" return false ) -- Parse angle sets by grouping between VECPAIR@ markers local angleSets = #() local currentSet = #() local inAngleSet = false for i = recordStartLine + 1 to recordEndLine - 1 do ( local line = fileLines[i] local trimmed = trimLeft line if matchPattern trimmed pattern:"VECPAIR@*" then ( if inAngleSet then ( append angleSets currentSet currentSet = #() ) inAngleSet = true ) if inAngleSet then ( append currentSet line ) ) -- Add final set if exists if currentSet.count > 0 then ( append angleSets currentSet ) -- Verify angle set index if angleSets.count == 0 then ( messageBox "No angle sets found in this record!" title:"Error" return false ) if angleSetIndex < 1 or angleSetIndex > angleSets.count then ( messageBox ("Invalid angle set index (" + angleSetIndex as string + ")\n" + \ "Record contains " + angleSets.count as string + " angle sets") \ title:"Error" return false ) -- Create backup if chkBackup.checked do ( local backupPath = filePath + ".bak" if not (copyFile filePath backupPath) then ( messageBox "Failed to create backup file!" title:"Error" return false ) ) -- Write modified file local outFile = createFile filePath if outFile == undefined then ( messageBox "Failed to create output file!" title:"Error" return false ) -- 1. Write everything before our record for i = 1 to recordStartLine do ( format "%\n" fileLines[i] to:outFile ) -- 2. Write content before first angle set local firstAngleSetLine = undefined for i = recordStartLine + 1 to recordEndLine - 1 do ( if matchPattern (trimLeft fileLines[i]) pattern:"VECPAIR@*" then ( firstAngleSetLine = i exit ) format "%\n" fileLines[i] to:outFile ) -- 3. Write all angle sets with replacement for i = 1 to angleSets.count do ( if i == angleSetIndex then ( -- Write modified angle set for line in newAngleSet do ( format "%%\n" recordIndent line to:outFile ) ) else ( -- Write original angle set for line in angleSets[i] do ( format "%\n" line to:outFile ) ) ) -- 4. Write content after last angle set if angleSets.count > 0 then ( local lastAngleSetLine = undefined for i = recordEndLine - 1 to recordStartLine + 1 by -1 do ( if matchPattern (trimLeft fileLines[i]) pattern:"VECPAIR@*" then ( lastAngleSetLine = i + angleSets[angleSets.count].count - 1 exit ) ) if lastAngleSetLine != undefined and lastAngleSetLine + 1 < recordEndLine then ( for i = lastAngleSetLine + 1 to recordEndLine - 1 do ( format "%\n" fileLines[i] to:outFile ) ) ) -- 5. Write END marker format "END\n" to:outFile -- 6. Write everything after our record for i = recordEndLine + 1 to fileLines.count do ( format "%\n" fileLines[i] to:outFile ) close outFile return true ) catch ( messageBox ("Error replacing angle set: " + getCurrentException()) title:"Error" return false ) ) on btnParse pressed do ( if txtInput.text == "" then ( messageBox "Please enter some Vecpair lines to import." title:"Input Error" ) else ( local lines = filterString txtInput.text "\n" for line in lines where matchPattern (trimLeft line) pattern:"VECPAIR*" do ( parseVecpairLine line ) if createdCameras.count > 0 then ( select createdCameras ) ) ) on btnExport pressed do ( local selectedCameras = for obj in selection where isKindOf obj camera collect obj if selectedCameras.count > 0 then ( local exportStrings = getExportStringsFromCameras selectedCameras if exportStrings.count > 0 then ( setClipboardText (joinString exportStrings "\n") messageBox ((exportStrings.count as string) + " camera(s) exported to clipboard!") title:"Success" ) else ( messageBox "No metadata found for selected cameras." title:"Error" ) ) else ( messageBox "Please select one or more cameras to export." title:"Error" ) ) on btnReplaceAngleSet pressed do ( if currentGLGFilePath == undefined then ( messageBox "No GLG file loaded!" title:"Error" return undefined ) if ddlRecords.selection == 0 then ( messageBox "Please select a record first!" title:"Error" return undefined ) if ddlAngleSets.selection == 0 then ( messageBox "Please select an angle set first!" title:"Error" return undefined ) local selectedCameras = for obj in selection where isKindOf obj camera collect obj if selectedCameras.count == 0 then ( messageBox "Please select cameras to export!" title:"Error" return undefined ) local actualRecordIndex = if filteredRecordIndices.count > 0 then filteredRecordIndices[ddlRecords.selection] else ddlRecords.selection local angleSetIndex = ddlAngleSets.selection local exportStrings = getExportStringsFromCameras selectedCameras if exportStrings.count > 0 then ( local result = replaceAngleSetInFile currentGLGFilePath actualRecordIndex angleSetIndex exportStrings if result then ( messageBox ("Successfully replaced angle set " + angleSetIndex as string + "!") title:"Success" -- Reload the file to update our UI btnImportFile.pressed() ) ) else ( messageBox "No valid camera data to export!" title:"Error" ) ) on btnCreateHelper pressed do ( if isValidNode cameraHelperObj then ( rollout errorRolloutHelper "Helper Already Exists" ( label lblMsg "A helper is already present." align:#center button btnDeleteHelper "Delete Helper" width:120 align:#center button btnOkayHelper "Okay" width:120 align:#center on btnOkayHelper pressed do ( DestroyDialog errorRolloutHelper ) on btnDeleteHelper pressed do ( if isValidNode cameraHelperObj do ( delete cameraHelperObj cameraHelperObj = undefined destroyDialog errorRolloutHelper ) ) ) createDialog errorRolloutHelper width:200 height:100 ) else ( cameraHelperObj = cylinder() cameraHelperObj.name = "Viewport Helper Object" cameraHelperObj.radius = 0.02 cameraHelperObj.height = 1.652 cameraHelperObj.pos = [0,0,0] cameraHelperObj.wirecolor = color 255 0 0 select cameraHelperObj ) ) on btnreset pressed do ( txtInput.text = "" if chkDeleteOnReset.checked do deleteCreatedCameras() ) on btnImportFile pressed do ( local filePath = getOpenFileName caption:"Select Camera Data File" types:"GLG File (*.glg)|*.glg|All Files (*.*)|*.*|" if filePath != undefined do ( currentGLGFilePath = filePath local fileContents = "" local file = undefined try ( file = openFile filePath mode:"r" if file != undefined do ( while not eof file do ( fileContents += readLine file + "\n" ) close file file = undefined ) parsedRecords = #() local currentRecord = #() local inRecord = false local lines = filterString fileContents "\n\r" for line in lines do ( line = trimLeft line case of ( (matchPattern line pattern:"RECORD*"): ( inRecord = true currentRecord = #() local tokens = filterString line " \t" currentRecord[1] = if tokens.count >= 2 then tokens[2] else "Unnamed Record" currentRecord[2] = #() currentRecord[3] = #() ) (matchPattern line pattern:"END"): ( if inRecord do ( if currentRecord[3].count > 0 do ( append currentRecord[2] currentRecord[3] ) append parsedRecords currentRecord inRecord = false ) ) (inRecord and matchPattern line pattern:"VECPAIR*"): ( if matchPattern line pattern:"*@*" then ( append currentRecord[3] line if currentRecord[3].count > 0 do ( append currentRecord[2] currentRecord[3] ) currentRecord[3] = #() ) else ( append currentRecord[3] line ) ) ) ) allRecordNames = for record in parsedRecords collect record[1] ddlRecords.items = allRecordNames ddlAngleSets.items = #() filteredRecordIndices = #() ) catch ( if file != undefined do (try(close file)catch()) messageBox "Error reading file. It may be locked by another program." title:"Error" ) ) ) on ddlRecords selected sel do ( local actualIndex = if filteredRecordIndices.count > 0 then filteredRecordIndices[sel] else sel if actualIndex > 0 and actualIndex <= parsedRecords.count do ( local record = parsedRecords[actualIndex] currentAngleSets = #() for i = 1 to record[2].count do ( append currentAngleSets ("Angle Set " + i as string) ) ddlAngleSets.items = currentAngleSets ) ) on btnImportAngleSet pressed do ( if parsedRecords.count > 0 and ddlRecords.selection > 0 and ddlAngleSets.selection > 0 do ( local actualIndex = if filteredRecordIndices.count > 0 then filteredRecordIndices[ddlRecords.selection] else ddlRecords.selection local selectedRecord = parsedRecords[actualIndex] local angleSetIndex = ddlAngleSets.selection if angleSetIndex <= selectedRecord[2].count do ( deleteCreatedCameras() for line in selectedRecord[2][angleSetIndex] do ( parseVecpairLine line ) if createdCameras.count > 0 then ( select createdCameras txtInput.text = joinString selectedRecord[2][angleSetIndex] "\n" ) ) ) ) on btnSelectAll pressed do ( if createdCameras.count > 0 then ( select (for cam in createdCameras where isValidNode cam collect cam) ) else ( messageBox "No cameras have been imported yet." title:"Error" ) ) on txtRecordSearch changed searchText do ( if allRecordNames.count > 0 do ( local filteredNames = #() filteredRecordIndices = #() for i = 1 to allRecordNames.count do ( if matchPattern (toLower allRecordNames[i]) pattern:("*" + toLower searchText + "*") then ( append filteredNames allRecordNames[i] append filteredRecordIndices i ) ) ddlRecords.items = filteredNames ) ) ) createDialog CameraDataParser width:420 height:620