commit 0bb2a6be238587e1292bb1e3d429e4471b38c017 Author: empathicqubit Date: Fri May 28 16:30:20 2021 -0400 I wish my cat wouldn't lick so loudly diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd0d916 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.zip +/plugin/Data/Scripts/ +/plugin/Data/Meshes/ +/build/ +node_modules/ +*.blend[0-9]* \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1897174 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "papyrus", + "name": "Skyrim", + "request": "attach", + "game": "skyrimSpecialEdition" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..76ae18b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "group": { + "isDefault": true, + "kind": "build" + }, + "command": "${workspaceFolder}/build.ps1", + "args": ["-Reload"], + "problemMatcher": [ + "$PapyrusCompiler" + ] + } + ] +} \ No newline at end of file diff --git a/Gameloop.Vdf.dll b/Gameloop.Vdf.dll new file mode 100644 index 0000000..95cfd59 Binary files /dev/null and b/Gameloop.Vdf.dll differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9432c3d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# skyrim-item-roulette + +Roulettes items \ No newline at end of file diff --git a/Source/Meshes/_EQ_ItemRoulette/roulette_container.blend b/Source/Meshes/_EQ_ItemRoulette/roulette_container.blend new file mode 100644 index 0000000..38bca27 Binary files /dev/null and b/Source/Meshes/_EQ_ItemRoulette/roulette_container.blend differ diff --git a/Source/Scripts/_EQ_ItemRoulette_Main.psc b/Source/Scripts/_EQ_ItemRoulette_Main.psc new file mode 100644 index 0000000..c4ae190 --- /dev/null +++ b/Source/Scripts/_EQ_ItemRoulette_Main.psc @@ -0,0 +1,5 @@ +Scriptname _EQ_ItemRoulette_Main extends ReferenceAlias + +Event OnPlayerLoadGame() + (GetOwningQuest() as _EQ_ItemRoulette_Quest).Main() +EndEvent diff --git a/Source/Scripts/_EQ_ItemRoulette_Quest.psc b/Source/Scripts/_EQ_ItemRoulette_Quest.psc new file mode 100644 index 0000000..51b3428 --- /dev/null +++ b/Source/Scripts/_EQ_ItemRoulette_Quest.psc @@ -0,0 +1,54 @@ +Scriptname _EQ_ItemRoulette_Quest extends Quest + +Actor Property PlayerRef Auto +Static Property _EQ_ItemRoulette_Roulette Auto + +ObjectReference[] DisplayItems + +Int MAX_ITEMS = 6 +Int CIRCLE_RADIUS = 16 + +Event OnInit() + DisplayItems = New ObjectReference[127] + int index = 0 + While index < MAX_ITEMS + DisplayItems[index] = None + index += 1 + EndWhile + + Main() +EndEvent + +Function Main() + Debug.Trace("Item Roulette loaded") + RegisterForModEvent("_EQ_ItemRoulette_Activate", "OnMyAction") + VRIK.VrikAddGestureAction("_EQ_ItemRoulette_Activate", "Activate Item Roulette") + RegisterForSingleUpdate(0.01) +EndFunction + +Event OnUpdate() + Int index = 0 + While index < MAX_ITEMS && DisplayItems[index] != None + DisplayItems[index].SetPosition(VRIK.VrikGetHandX(true) + CIRCLE_RADIUS * Math.sin(60 * index), VRIK.VrikGetHandY(true), VRIK.VrikGetHandZ(true) + CIRCLE_RADIUS * Math.cos(60 * index)) + index += 1 + EndWhile + + RegisterForSingleUpdate(0.01) +EndEvent + +Event OnMyAction(string eventName, string strArg, float numArg, Form sender) + Debug.Trace("VRIK activated me!") + PlayerRef.PlaceAtMe(_EQ_ItemRoulette_Roulette) + Int numItems = PlayerRef.getNumItems() + Int formIndex = numItems + Int count = 0 + While formIndex > 0 && formIndex > numItems - MAX_ITEMS + formIndex -= 1 + count = numItems - formIndex + Form invItem = PlayerRef.GetNthForm(formIndex) + ObjectReference invItemInst = PlayerRef.DropObject(invItem) + invItemInst.SetScale(0.1) + invItemInst.SetMotionType(invItemInst.Motion_Keyframed) + DisplayItems[count - 1] = invItemInst + EndWhile +EndEvent \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..a1e0951 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,137 @@ +<# + .SYNOPSIS + Builds the plugin and optionally reloads it in Vortex. + + .PARAMETER Reload + Reloads Skyrim after building. Reloading assumes you have symlinked the mod + into Vortex's staging area using the method in setup-dev.ps1. It completely + kills and restarts the game as I had trouble getting hlp and reloadscript + commands to work. +#> +param ( + [Parameter(Mandatory = $False)] + [Switch] + $Reload +) + +New-Item -ItemType Directory "$PSScriptRoot/build" -ErrorAction SilentlyContinue + +Add-Type -Path "$PSScriptRoot/Gameloop.Vdf.dll" -ErrorAction SilentlyContinue + +$Proc = Get-Process Skyrim* + +# Use the running instance +if($Proc) { + $Wmi = Get-WmiObject -Class win32_process -filter "ProcessId=$($Proc.Id)" + $SkyrimBase = Split-Path $Wmi.ExecutablePath +} + +# Check for Steam version +if(-not $SkyrimBase) { + if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { + $steamInstallPath = "${env:ProgramFiles(x86)}/Steam" + } + else { + $steamInstallPath = "$env:ProgramFiles/Steam" + } + + $libraryFolders = [Gameloop.Vdf.VdfConvert]::Deserialize((Get-Content "$steamInstallPath/steamapps/libraryfolders.vdf") -join "`n") + + $steamLibraries = @() + + $steamLibraries += @($steamInstallPath) + $steamLibraries += Get-Member -InputObject $libraryFolders.Value -MemberType Dynamic | where-object -Property Name -Match "[0-9]+" | foreach { $libraryFolders.Value[$_.Name].ToString() } + + $manifestPath = $None + foreach($steamLibrary in $steamLibraries) { + $manifestPath = Get-Item -Path @( + "$steamLibrary/steamapps/appmanifest_611670.acf", + "$steamLibrary/steamapps/appmanifest_489930.acf", + "$steamLibrary/steamapps/appmanifest_72850.acf" + ) -ErrorAction SilentlyContinue | Select-Object -First 1 + + if($manifestPath) { + Write-Host "Manifest found at $($manifestPath.FullName)" + break + } + } + + if($manifestPath) { + $appManifest = [Gameloop.Vdf.VdfConvert]::Deserialize((Get-Content $manifestPath) -join "`n") + $installDir = $appManifest.Value["installdir"].ToString() + $SkyrimBase = "$(Split-Path $manifestPath)/common/$installDir" + } +} + +# Default to non-Steam version +if(-not $SkyrimBase) { + $SkyrimBase = (Get-Item -Path @( + "$env:ProgramFiles/Bethesda*/*Skyrim*" + "${env:ProgramFiles(x86)}/Bethesda*/*Skyrim*" + )).FullName +} + +# Compile the scripts +& "$SkyrimBase/Papyrus Compiler/PapyrusCompiler.exe" ` + "$PsScriptRoot/Source/Scripts" ` + "-f=$SkyrimBase/Data/Source/Scripts/TESV_Papyrus_Flags.flg" ` + "-i=$SkyrimBase/Data/Source/Scripts;$PsScriptRoot/Source/Scripts" ` + "-o=$PsScriptRoot/plugin/Data/Scripts" ` + "-all" + +if($LastExitCode -ne 0) { + return $LastExitCode +} + +# Build the models +blender.exe --background --python "$PSScriptRoot/export-blender-models.py" + +if($LastExitCode -ne 0) { + return $LastExitCode +} + +# ZIP up the deployment package +$ZipPath = "$PSScriptRoot/build/Item Roulette for VRIK.zip" + +Remove-Item $ZipPath -ErrorAction SilentlyContinue +Compress-Archive -Path $PSScriptRoot/plugin/* -DestinationPath $ZipPath + +if($Reload) { + if($Proc) { + Stop-Process $Proc + } + + # Restart Vortex and kick off deploy-mods event via Chrome Debug Protocol. + Stop-Process -Name Vortex + $env:KICK_PORT=6969 + $VortexPath = (Get-ItemProperty HKLM:\SOFTWARE\57979c68-f490-55b8-8fed-8b017a5af2fe).InstallLocation + $GameId = (Get-Item "$env:APPDATA/Vortex/skyrim*").BaseName + & "$VortexPath/Vortex.exe" --remote-debugging-port=$env:KICK_PORT --game=$GameId + + pnpm install -C "$PSScriptRoot" + + node.exe "$PSScriptRoot/kick-vortex.js" + Stop-Process -Name Vortex -ErrorAction SilentlyContinue + + # Prefer SKSE loader if we have it installed + $SkyrimExe = Get-Item -Path @( + "$SkyrimBase/skse*_loader.exe", + "$SkyrimBase/Skyrim*.exe" + ) | Select-Object -First 1 + + Start-Process -WorkingDirectory $SkyrimExe.DirectoryName -FilePath $SkyrimExe + + # Send JSON command to load first autosave. Doesn't currently work. + do { + $wrFail = $None + Start-Sleep -Seconds 1 + Invoke-WebRequest -Uri "http://localhost:8558/api/command" ` + -Method Post ` + -ErrorVariable $wrFail ` + -ContentType 'application/json' ` + -Headers @{ Accept = 'application/json' } ` + -Body @" +{ "command": "load \"autosave1\" " } +"@ + } while ($wrFail) +} \ No newline at end of file diff --git a/export-blender-models.py b/export-blender-models.py new file mode 100644 index 0000000..a0b9d7b --- /dev/null +++ b/export-blender-models.py @@ -0,0 +1,24 @@ +import bpy +import os + +from pathlib import Path +curdir = Path(__file__).parent +print("Current directory: " + str(curdir)) +plugin_data_dir = curdir.joinpath("plugin/Data") +print("Plugin directory: " + str(plugin_data_dir)) +mesh_src_dir = curdir.joinpath("Source/Meshes") +print("Mesh Source directory: " + str(mesh_src_dir)) +mesh_dest_dir = plugin_data_dir.joinpath("Meshes") +print("Mesh Dest directory: " + str(mesh_dest_dir)) +blend_paths = Path(mesh_src_dir).rglob("*.blend") +for blend_path in blend_paths: + dest_blend_path = mesh_dest_dir.joinpath(blend_path.relative_to(mesh_src_dir)) + nif_parent = dest_blend_path.parent + nif_path = nif_parent.joinpath(dest_blend_path.stem + '.nif') + print(str(blend_path) + " -> " + str(nif_path)) + bpy.ops.wm.open_mainfile(filepath=str(blend_path)) + try: + nif_parent.mkdir(parents=True) + except FileExistsError: + pass + bpy.ops.export_scene.nif(filepath=str(nif_path)) \ No newline at end of file diff --git a/kick-vortex.js b/kick-vortex.js new file mode 100644 index 0000000..2b5595a --- /dev/null +++ b/kick-vortex.js @@ -0,0 +1,58 @@ +const CDP = require('chrome-remote-interface'); +const waitPort = require('wait-port') +const util = require('util') + +const main = async() => { + const port = parseInt(process.env.KICK_PORT); + await waitPort({ + host: '127.0.0.1', + port: port, + }) + let client; + while(!client) { + try { + client = await CDP({ + host: '127.0.0.1', + port: port, + }); + } + catch(e) { + console.error(e); + } + } + console.log('Connected to CDP') + let target; + while(!target || splash) { + await new Promise(res => setTimeout(res, 100)) + const targets = await client.Target.getTargets(); + splash = targets.targetInfos.find(y => y.url.endsWith('splash.html')) + target = targets.targetInfos.find(y => y.url.endsWith('index.html')) + } + console.log('Found target!') + await new Promise(res => client.close(res)); + client = await CDP({ + host: '127.0.0.1', + port: port, + }); + await client.Runtime.evaluate({ + awaitPromise: true, + expression: '(' + (async function() { + await new Promise(function(res, rej) { + async function handler() { + if(_API && _API.events && _API.events.emit) { + await _API.awaitUI(); + _API.events.emit("deploy-mods", res); + } + else { + setTimeout(handler, 100); + } + } + handler(); + }); + }).toString() + ')()' + }) + client.close(); + console.log('Finished everything!') +}; + +main().catch(console.error) \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..781678f --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "skyrim-item-roulette", + "version": "1.0.0", + "description": "Roulettes items", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "chrome-remote-interface": "^0.30.0", + "wait-port": "^0.2.9" + } +} diff --git a/plugin/Data/_EQ_ItemRoulette.esp b/plugin/Data/_EQ_ItemRoulette.esp new file mode 100644 index 0000000..04f10b0 Binary files /dev/null and b/plugin/Data/_EQ_ItemRoulette.esp differ diff --git a/plugin/Data/_EQ_ItemRoulette_Placeholder.md b/plugin/Data/_EQ_ItemRoulette_Placeholder.md new file mode 100644 index 0000000..cb6e5ce --- /dev/null +++ b/plugin/Data/_EQ_ItemRoulette_Placeholder.md @@ -0,0 +1 @@ +This is a placeholder file for development purposes. \ No newline at end of file diff --git a/plugin/fomod/ModuleConfig.xml b/plugin/fomod/ModuleConfig.xml new file mode 100644 index 0000000..c6ad056 --- /dev/null +++ b/plugin/fomod/ModuleConfig.xml @@ -0,0 +1,14 @@ + + + + + Item Roulette Selector for VRIK + + + + + + + + + diff --git a/plugin/fomod/info.xml b/plugin/fomod/info.xml new file mode 100644 index 0000000..9e97bfd --- /dev/null +++ b/plugin/fomod/info.xml @@ -0,0 +1,6 @@ + + + + Item Roulette Selector for VRIK + https://www.nexusmods.com/skyrimspecialedition/mods/FIXME + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..8fc6e23 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,114 @@ +lockfileVersion: 5.3 + +specifiers: + chrome-remote-interface: ^0.30.0 + wait-port: ^0.2.9 + +devDependencies: + chrome-remote-interface: 0.30.0 + wait-port: 0.2.9 + +packages: + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chrome-remote-interface/0.30.0: + resolution: {integrity: sha512-krIHiWQCRvlLhHmjWuGgHJfqhj2Jcj3mtVebq3c/Pwv4RF7P0A2eSy6YRqrBPwcziYJrtsPGR0rm7uCe4987rQ==} + hasBin: true + dependencies: + commander: 2.11.0 + ws: 7.4.6 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-name/1.1.3: + resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} + dev: true + + /commander/2.11.0: + resolution: {integrity: sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==} + dev: true + + /commander/3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} + dev: true + + /debug/4.3.1: + resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} + engines: {node: '>=0.8.0'} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} + engines: {node: '>=4'} + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /wait-port/0.2.9: + resolution: {integrity: sha512-hQ/cVKsNqGZ/UbZB/oakOGFqic00YAMM5/PEj3Bt4vKarv2jWIWzDbqlwT94qMs/exAQAsvMOq99sZblV92zxQ==} + engines: {node: '>=8'} + hasBin: true + dependencies: + chalk: 2.4.2 + commander: 3.0.2 + debug: 4.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /ws/7.4.6: + resolution: {integrity: sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true diff --git a/setup-dev.ps1 b/setup-dev.ps1 new file mode 100644 index 0000000..1bae1ee --- /dev/null +++ b/setup-dev.ps1 @@ -0,0 +1,39 @@ +mkdir "$PSScriptRoot/build" -ErrorAction SilentlyContinue + +pnpm install -C $PSScriptRoot + +$ZipName = "EmpathicQubit-ItemRoulette-Dev.zip" +$ModPath = (Get-Item "$env:APPDATA/Vortex/skyrim*/mods").FullName+"/"+[IO.Path]::GetFileNameWithoutExtension($ZipName) + +if((Get-Item $ModPath).Attributes.HasFlag([IO.FileAttributes]::ReparsePoint)) { + Write-Host "Already setup." + return +} + +$ZipPath = "$PSScriptRoot/build/$ZipName" +Compress-Archive -Update -Path "$PSScriptRoot/plugin/Data/_EQ_ItemRoulette_Placeholder.md" -DestinationPath $ZipPath + +$VortexPath = (Get-ItemProperty HKLM:\SOFTWARE\57979c68-f490-55b8-8fed-8b017a5af2fe).InstallLocation + +Invoke-WebRequest -Uri http://nginx.org/download/nginx-1.21.0.zip -OutFile "$PSScriptRoot/build/nginx.zip" +Expand-Archive -Path "$PSScriptRoot/build/nginx.zip" -DestinationPath "$PSScriptRoot/build" +$NginxPath = "$PSScriptRoot/build/nginx-1.21.0" + +Move-Item "$NginxPath/conf/nginx.conf" "$NginxPath/conf/nginx.conf.old" +(Get-Content "$NginxPath/conf/nginx.conf.old") ` + -replace '^([^#]*root).*$', "`$1 $PSScriptRoot/build;" ` + -replace '^([^#]*listen).*$', '$1 8998;' ` + | Out-File -Encoding ascii "$NginxPath/conf/nginx.conf" + +$job = Start-Job { & "$NginxPath/nginx.exe" -g 'daemon off;' } + +Start-Sleep -Seconds 1 + +& "$VortexPath/Vortex.exe" -i "http://localhost:8998/$ZipName" + +Start-Sleep -Seconds 10 + +Stop-Job $job + +Remove-Item -Recurse $ModPath +New-Item -Path $ModPath -ItemType SymbolicLink -Value "$PSScriptRoot/plugin/Data" \ No newline at end of file