-- Rockstar mloPortal Object -- Rockstar North -- 14/10/2005 -- by Greg Smith -- -- -- 12/03/2007 -- DHM -- -- Add multiple portal objects parameter block and associated UI -- Add version update event handler to cope with this change -- Remove "Door/Window Object" rollout -- -- 04/03/2010 -- Luke Openshaw -- Check for rollout initialisation before accessing pick button rollout controls -- or Max 2010 will crash like the piece of shit that it is -- -- 28/02/2012 -- Matt Harrad -- Added set size from scale function and button. -- -- helper object for milo portal setup -- global RsAudioOcclusionFile = RsMakeSafeSlashes (RsProjectGetAudioDir() + "/assets/Objects/Core/Audio/GAMEOBJECTS/ENVIRONMENT/PORTAL_SETTINGS.XML") global gRsExtraAudioOccluderFile = RsMakeSafeSlashes (RsConfigGetExtraAudioOcclusionDir()) global RsSyncAudioOcclusion = true plugin helper GtaMloPortal name:"Gta MloPortal" classID:#(0xa697b45, 0x197567c7) category:"Gta" extends:RSGrid version:2 ( parameters extraParams rollout:extraRollout ( AudioOcclusionHash type:#string ) rollout extraRollout "Audio Occlusion" ( hyperlink hlp "Help" color:blue address:"https://devstar.rockstargames.com/wiki/index.php/Milo_and_Portals#Audio_Occluders" align:#right dotnetControl edtAudioOccl "Combobox" height:25 width:154 offset:[-8,0] button btnUpdateAudioFile "Update Occluder List" group "Add Occluder Info"( edittext txtFolder "Folder" spinner txtValue "Occ Value" range:[0,1,0] scale:0.01 indeterminate:true button btnAddAudOccl "Add new Occluder to XML" ) fn UpdateAudioFile = ( -- Update global path to extraOccluder xml gRsExtraAudioOccluderFile = RsMakeSafeSlashes (RsConfigGetExtraAudioOcclusionDir()) local occlFiles = #(RsAudioOcclusionFile) edtAudioOccl.Items.Clear() if RsIsDLCProj() and (RsFileExists gRsExtraAudioOccluderFile) then ( append occlFiles gRsExtraAudioOccluderFile ) local occluderItems = #("") for i = 1 to occlFiles.count do( if RsSyncAudioOcclusion and i == 1 do ( gRsPerforce.sync occlFiles silent:true RsSyncAudioOcclusion = false ) if RsFileExists occlFiles[i] then ( local docNav = dotnetobject "System.Xml.XPath.XPathDocument" occlFiles[i] local nav = docNav.CreateNavigator() local strExpression = dotnetobject "System.String" "//PortalSettings/@name" local NodeIter = nav.Select strExpression while (NodeIter.MoveNext()) do ( append occluderItems NodeIter.Current.Value ) ) else ( gRsUlog.LogWarning ("No audio occlusion file found at \""+occlFiles[i]+"\"") quiet:false if undefined!=AudioOcclusionHash and ""!=AudioOcclusionHash then ( local occluderItems = #("", AudioOcclusionHash) edtAudioOccl.items.addrange occluderItems edtAudioOccl.SelectedIndex = 1 ) ) ) edtAudioOccl.items.addrange occluderItems local itemIndex = findItem occluderItems AudioOcclusionHash if 0!=itemIndex then( edtAudioOccl.SelectedIndex = (itemIndex-1) ) AudioOcclusionHash = edtAudioOccl.Text txtFolder.text = "" txtValue.value = 0 ) on btnAddAudOccl pressed do ( local newOcclName = edtAudioOccl.Text format "edtAudioOccl.Text: %\n" (edtAudioOccl.Text as string) for i = 0 to edtAudioOccl.Items.Count - 1 do ( if (edtAudioOccl.Items.Item i) == newOcclName then return false ) local occlFile = RsAudioOcclusionFile format "occlFile: %\n" (occlFile as string) -- Create a new file if dlc specific one doesn't exist. if(RsIsDLCProj()) then ( occlFile = gRsExtraAudioOccluderFile if not (RsFileExists gRsExtraAudioOccluderFile) then ( local xdoc = XmlDocument() xdoc.Init() elem = xdoc.createelement "Objects" appendTo:xdoc.document xdoc.save occlFile xdoc.Release() ) ) if((newOcclName != "") and (RsFileExists occlFile)) then( if not (gRsPerforce.readOnlyP4Check #(occlFile)) then return false local clnum = gRsPerforce.createChangelist ("Adding '" + newOcclName + "' to audio occluders") gRsPerforce.add_or_edit #(occlFile) queueAdd:true gRsPerforce.addToChangelist clnum #(occlFile) try( local doc = XmlDocument() doc.load occlFile obj = RsGetXMLElement doc.document "Objects" local occls = #() for i = 0 to ((obj.childnodes.count) - 1) do( append occls (((obj.childnodes.Item i).Attributes.item 0).Value) ) local found = false for o in occls while not found do( found = (o == newOcclName) ) if(found) then ( return false )else( folderName = txtFolder.text maxOcclValue = txtValue.value local att = #(#("name",newOcclName)) if(folderName != "" and folderName != " ") then (append att #("folder",folderName)) ps = doc.createelement "PortalSettings" attrs:att appendTo:obj maxOccl = doc.createelement "MaxOcclusion" value:maxOcclValue appendTo:ps doc.save occlFile doc.Release() AudioOcclusionHash = newOcclName gRsPerforce.postExportAdd changelistNum:clnum UpdateAudioFile() ) )catch( format "Exception: %\n" (getCurrentException()) stack() ) ) ) on btnUpdateAudioFile pressed do ( UpdateAudioFile() ) on extraRollout open do ( UpdateAudioFile() edtAudioOccl.DropDownWidth = 300 ) on edtAudioOccl SelectedIndexChanged e do ( AudioOcclusionHash = edtAudioOccl.SelectedItem ) ) parameters objParams rollout:objRollout ( LinkFirst type:#node ui:btnFirst LinkSecond type:#node ui:btnSecond ) rollout objRollout "Rooms" ( fn isRoom obj = ( isKindOf obj GtaMloRoom ) label lblFirst "1st:" width:18 offset:[-6,0] across:2 align:#left pickbutton btnFirst "none" width:124 offset:[4,-3] filter:isRoom autoDisplay:true align:#right label lblSecond "2nd:" width:18 offset:[-6,0] across:2 align:#left pickbutton btnSecond "none" width:124 offset:[4,-3] filter:isRoom autoDisplay:true align:#right label lblRgtClick "(rightclick buttons to clear)" offset:[0,-2] label lblAudioOccl "Audio Occlusion:" align:#left button btnFlip "Flip Portal" width:120 offset:[0,8] tooltip:"Flips this portal" button btnRetard "Am I Retarded?" width:120 offset:[0,-4] tooltip:"Does this portal probably need to be flipped?" button btnSizeFromScale "Set Size From Scale" width:120 tooltip:"Set the size of the portal from its scale." fn CalcPortalVec portalObj = ( portalPt = [ 0.0, 0.0, 5.0 ] portalPt = portalPt * portalObj.transform portalVec = portalPt - portalObj.position portalVec = normalize portalVec return portalVec ) fn RetardationCheck = ( portalObj = selection[1] room1 = portalObj.LinkFirst room2 = portalObj.LinkSecond bbMin = [10000, 10000, 10000] bbMax = [-10000, -10000, -10000] if room1 != undefined do ( for roomObj in room1.children do ( if roomObj.position.x < bbMin.x then bbMin.x = roomObj.position.x if roomObj.position.y < bbMin.y then bbMin.y = roomObj.position.y if roomObj.position.z < bbMin.z then bbMin.z = roomObj.position.z if roomObj.position.x > bbMax.x then bbMax.x = roomObj.position.x if roomObj.position.y > bbMax.y then bbMax.y = roomObj.position.y if roomObj.position.z > bbMax.z then bbMax.z = roomObj.position.z ) centroid = (bbMin + bbMax) / 2 roomVec = portalObj.position - centroid roomVec = normalize roomVec portalVec = CalcPortalVec portalObj if (dot portalVec roomVec) < 0 then return true return false ) if room2 != undefined do ( for roomObj in room2.children do ( if roomObj.position.x < bbMin.x then bbMin.x = roomObj.position.x if roomObj.position.y < bbMin.y then bbMin.y = roomObj.position.y if roomObj.position.z < bbMin.z then bbMin.z = roomObj.position.z if roomObj.position.x > bbMax.x then bbMax.x = roomObj.position.x if roomObj.position.y > bbMax.y then bbMax.y = roomObj.position.y if roomObj.position.z > bbMax.z then bbMax.z = roomObj.position.z ) centroid = (bbMin + bbMax) / 2 roomVec = portalObj.position - centroid roomVec = normalize roomVec portalPt = [ 0.0, 0.0, 5.0 ] portalPt = portalPt * portalObj.transform portalVec = portalPt - portalObj.position portalVec = normalize portalVec if (dot portalVec roomVec) > 0 then return true return false ) --else ( -- messagebox "Portal has no rooms." -- ) return true ) fn sizeFromScale = ( portal = selection[1] portal.grid.length = portal.grid.length*portal.scale.y portal.grid.width = portal.grid.width*portal.scale.x portal.scale = [1,1,1] ) on btnFirst rightClick do ( this.linkFirst = undefined ) on btnSecond rightClick do ( this.linkSecond = undefined ) on btnFlip pressed do ( portalVec = CalcPortalVec selection[1] if (dot portalVec [0.0,0.0,1.0]) > 0.95 or (dot portalVec [0.0,0.0,1.0]) < -0.95 then ( rotatePortal = eulerangles 180 0 0 rotate selection[1] rotatePortal ) else ( rotatePortal = eulerangles 0 0 180 rotate selection[1] rotatePortal ) ) on btnRetard pressed do ( if RetardationCheck() == true then messagebox "This portal is probably reversed. If it does not behave correctly in game, flip it and re-export." else messagebox "Portal seems OK." ) on btnSizeFromScale pressed do sizeFromScale() ) -- Unused but maintained for backwards compatibility parameters objParams2 --DHM -- rollout:objRollout2 ( PortalObject type:#node -- DHM -- ui:btnPortalObject on PortalObject set val do ( if objRollout2 == undefined then return false if val != undefined then ( objRollout2.btnPortalObject.caption = val.name ) else( objRollout2.btnPortalObject.caption = "none" ) ) ) parameters objParams3 rollout:objRollout3 ( PortalObjects type:#nodeTab tabSizeVariable:true ) -- Interface rollout to allow multiple objects to be attached to Mlo Portal objects rollout objRollout3 "Door/Window Objects" ( listbox lbObjects "Objects:" height:4 align:#left toolTip:"List of objects attached to the Portal" selection:0 button btnAdd "Add (by Pick)" width:100 toolTip:"Add a mesh/poly/ref object by clicking here and then picking the object to add from a viewport" button btnRemove "Delete" width:100 toolTip:"Select an object in the list and click here to remove the object from the Portal" fn updateListBox = ( -- Populate our listbox if (portalObjects != undefined) do ( lbObjects.items = for obj in portalObjects where (obj != undefined) collect obj.name ) ) on lbObjects doubleClicked objNum do ( if (objNum != 0) do ( undo "select door/window object" on ( select portalObjects[objNum] ) ) ) -- Rollout open event handler on objRollout3 open do ( -- Take this opportunity to get rid of invalid list-items: local newArray = for obj in portalObjects where isValidNode obj collect obj portalObjects = newArray updateListBox() ) fn objFilterFn obj = ( (isKindOf obj Editable_Poly) or (isKindOf obj Editable_mesh) or (isRefObj obj) ) -- Add button event handler on btnAdd pressed do ( -- This check is done at run-time (and a variable array is used) so that we -- can support more attached objects in the future (if required) if (portalObjects.count == 4) do ( MessageBox( "Portals can only have upto 4 attached objects. No more objects may be attached to this Portal." ) return 0 ) obj = pickObject "Select object to attach to Portal" filter:objFilterFn -- Append to our PortalObjects parameter array only if the object -- isn't already attached if (obj != undefined) and ((findItem portalObjects obj) == 0) do ( append portalObjects obj ) -- Update the rollout list: updateListBox() ) -- Remove button event handler on btnRemove pressed do ( if (lbObjects.selection == 0) then ( MessageBox( "You must select an object to remove from the Portal first." ) ) else ( -- Remove from our PortalObjects parameter array deleteItem PortalObjects lbObjects.selection updateListBox() ) ) ) -- Our update event handler -- Here we maintain backwards compatibility by moving the single reference in -- objParams2 to the objParams3 array. on update do ( -- Update version 1 instances to version 2 -- by moving the reference in objParams2 to objParams3 and then setting the -- objParams2.PortalObject to undefined if ( version == 1 ) then ( if ( this.PortalObject != undefined ) then ( this.PortalObjects = append this.PortalObjects this.PortalObject this.PortalObject = undefined ) ) ) ) MiloPortalColourChangeCallback = " for obj in selection where (getAttrClass obj == \"Gta MloPortal\") do ( if ((getAttr obj (getAttrIndex \"Gta MloPortal\" \"Mirror Type\")) == 0) then ( obj.Grid.colour = ([0, 255, 255] as color) ) else ( obj.grid.colour = orange ) ) " gRsExtraAttrCommands.setCmd #updateMLOPortals class:"Gta MloPortal" attrs:#() cmd:MiloPortalColourChangeCallback priority:5