diff --git a/.github/workflows/csprojAdapter.ps1 b/.github/workflows/csprojAdapter.ps1 deleted file mode 100644 index c557e6c..0000000 --- a/.github/workflows/csprojAdapter.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -param ([string]$filePath) -[xml]$xdoc = get-content $filePath - -# Use dll reference instead of project reference -$reference = $xdoc.Project.ItemGroup.Reference[0].Clone() -$reference.SetAttribute("Include", "NotooShabby.RimWorldUtility") -$reference.Private = "False" -$xdoc.Project.ItemGroup.AppendChild($reference) - -# Remove project reference -$nsmgr = New-Object -TypeName System.Xml.XmlNamespaceManager -ArgumentList $xdoc.NameTable -$nsmgr.AddNamespace("ns", $xdoc.DocumentElement.NamespaceURI) -$delete = $xdoc.DocumentElement.SelectSingleNode('/ns:Project/ns:ItemGroup/ns:ProjectReference[ns:Name="RimWorldUtility"]', $nsmgr) -$delete.ParentNode.RemoveChild($delete) - -# Remove generation of documentation file -$delete = $xdoc.DocumentElement.SelectNodes('/ns:Project/ns:PropertyGroup/ns:DocumentationFile', $nsmgr) -Foreach ($doc in $delete) -{ - $doc.ParentNode.RemoveChild($doc) -} - -# Remove PostBuild event used by MSVS -$delete = $xdoc.DocumentElement.SelectSingleNode('/ns:Project/ns:PropertyGroup/ns:PostBuildEvent', $nsmgr) -$delete.ParentNode.RemoveChild($delete) - -$xdoc.Save($filePath) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 5bbb8d2..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Publish latest build on Github -on: - push: - tags: - - v* - -jobs: - build-solution: - name: Build solution - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - - name: Set up donet build - uses: microsoft/setup-msbuild@v1.0.1 - with: - vs-version: '[16.6,]' - - - name: Build assemblies - run: | - powershell .\.github\workflows\csprojAdapter.ps1 -filePath .\MoveBase.csproj - Remove-Item .\Directory.Build.targets - msbuild -p:"ReferencePath=.\references" -p:OutDir=".\HomeMover\v1.1\Assemblies" -p:Configuration=Release -r MoveBase.csproj - - - name: Pack mod - run: | - powershell .\.github\workflows\packMod.ps1 -ModDir .\HomeMover -Src .\src - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: HomeMover - path: .\HomeMover.zip - - publish: - needs: [build-solution] - name: publish build - runs-on: windows-latest - if: contains(github.ref, 'refs/tags') - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Download artifact - uses: actions/download-artifact@v2 - with: - name: HomeMover - path: .\ - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - body: | - Move buildings around - draft: false - prerelease: false - - - name: Print contents - run: Get-ChildItem - - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: .\HomeMover.zip - asset_name: HomeMover.zip - asset_content_type: application/zip diff --git a/.github/workflows/packMod.ps1 b/.github/workflows/packMod.ps1 deleted file mode 100644 index d73fc89..0000000 --- a/.github/workflows/packMod.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -param ([string]$ModDir, [string]$Src) - -$common = "Common" -$about = "About" -$loadFolder = "LoadFolders.xml" -$defs = "Defs" -$languages = "Languages" -$patches = "Patches" -$texture = "Textures" -$assemblies = "Assemblies" -$RwUtil = "NotooShabby.RimWorldUtility.dll" - -# Copy About folder -Copy-Item -Path ([System.IO.Path]::Combine($Src, $about)) -Destination ([System.IO.Path]::Combine($ModDir, $about)) -Recurse -PassThru - -# Copy LoadFolders.xml -Copy-Item ([System.IO.Path]::Combine($Src, $loadFolder)) -Destination $ModDir -PassThru - -# Copy supporting libraries -$destination = ([System.IO.Path]::Combine($ModDir, $common, $assemblies, $RwUtil)) -New-Item -ItemType File -Path $destination -Force -Copy-Item "$Src\..\references\$RwUtil" -Destination $destination -Force -PassThru - -# Copy defs folder -Copy-Item -Path ([System.IO.Path]::Combine($Src, $defs)) -Destination ([System.IO.Path]::Combine($ModDir, $common, $defs)) -Recurse -PassThru - -# Copy Languages folder -Copy-Item -Path ([System.IO.Path]::Combine($Src, $languages)) -Destination ([System.IO.Path]::Combine($ModDir, $common, $languages)) -Recurse -PassThru - -# Copy Patches folder -Copy-Item -Path ([System.IO.Path]::Combine($Src, $patches)) -Destination ([System.IO.Path]::Combine($ModDir, $common, $patches)) -Recurse -PassThru - -# Copy Textures folder -Copy-Item -Path ([System.IO.Path]::Combine($Src, $texture)) -Destination ([System.IO.Path]::Combine($ModDir, $common, $texture)) -Recurse -PassThru - -# Zip the directory -$dir = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $ModDir -Compress-Archive -Path "$ModDir\*" -DestinationPath ".\$($dir.Name).zip" diff --git a/.gitignore b/.gitignore index 6d98a71..18716ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,346 +1,8 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- Backup*.rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -obj/ - -# Vim files -*.orig -MigrationBackup/ +/.vscode/obj +/*.sln + +# /*/Assemblies +# /*/Assemblies/*.pdb +# /.vscode +# /Source +/ModCompat/* \ No newline at end of file diff --git a/.vscode/build b/.vscode/build new file mode 100644 index 0000000..164efa1 --- /dev/null +++ b/.vscode/build @@ -0,0 +1,7 @@ +#!/bin/bash + +# remove unnecessary assemblies +find . -type d -name 'Assemblies' -exec rm -f {}/* \; + +# build dll +dotnet build .vscode diff --git a/.vscode/build.bat b/.vscode/build.bat new file mode 100644 index 0000000..1710249 --- /dev/null +++ b/.vscode/build.bat @@ -0,0 +1,234 @@ +@echo off +setlocal + +REM ================================ +REM Config +REM ================================ +set "CURRENTVER=1.6" +REM Determine build configuration from arg/env/MSBuild (default: Release) +set "_CONFIG_ARG=%~1" +if defined _CONFIG_ARG ( + set "CONFIG=%_CONFIG_ARG%" +) else if defined Configuration ( + REM Provided by MSBuild/VS (e.g., Debug/Release) + set "CONFIG=%Configuration%" +) else if defined ConfigurationName ( + REM Alternate MSBuild var + set "CONFIG=%ConfigurationName%" +) else if not defined CONFIG ( + set "CONFIG=Release" +) +set "_CONFIG_ARG=" + +REM Check for --no-version flag to skip version increment (for local testing) +set "SKIP_VERSION=" +if "%~2"=="--no-version" set "SKIP_VERSION=1" +if "%~2"=="--noversion" set "SKIP_VERSION=1" +if "%~2"=="/noversion" set "SKIP_VERSION=1" + +REM Local mirror output (second output) +set "BASE=.\%CURRENTVER%\Assemblies" + +REM Hardcoded fallback (your known-good location) +set "FALLBACK_MODS=F:\SteamLibrary\steamapps\common\RimWorld\Mods" + +REM ================================ +REM RimWorld Mods path auto-detect (multi-library aware, brace-safe) +REM ================================ +set "MODS_PATH=" +set "TMPPS=%TEMP%\rw_findmods_%RANDOM%.ps1" + +REM Build a small PowerShell script that prints the Mods path and exits +> "%TMPPS%" echo $steam = (Get-ItemProperty -Path HKCU:\Software\Valve\Steam -Name SteamPath -ErrorAction SilentlyContinue).SteamPath +>>"%TMPPS%" echo if ($steam) { +>>"%TMPPS%" echo ^ $paths = @($steam) +>>"%TMPPS%" echo ^ $default = Join-Path $steam 'steamapps\common\RimWorld\Mods' +>>"%TMPPS%" echo ^ if (Test-Path $default) { $default; exit } +>>"%TMPPS%" echo ^ $vdf = Join-Path $steam 'steamapps\libraryfolders.vdf' +>>"%TMPPS%" echo ^ if (Test-Path $vdf) { +>>"%TMPPS%" echo ^ $txt = Get-Content -Raw $vdf +>>"%TMPPS%" echo ^ foreach ($line in $txt -split "`n") { +>>"%TMPPS%" echo ^ if ($line -match '"path"\s+"([^"]+)"') { $paths += $Matches[1] } +>>"%TMPPS%" echo ^ } +>>"%TMPPS%" echo ^ } +>>"%TMPPS%" echo ^ foreach ($p in $paths) { +>>"%TMPPS%" echo ^ $mods = Join-Path $p 'steamapps\common\RimWorld\Mods' +>>"%TMPPS%" echo ^ if (Test-Path $mods) { $mods; exit } +>>"%TMPPS%" echo ^ } +>>"%TMPPS%" echo } + +for /f "usebackq delims=" %%P in (`powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%TMPPS%"`) do ( + set "MODS_PATH=%%P" +) + +del /q "%TMPPS%" >nul 2>&1 + +if not defined MODS_PATH ( + set "MODS_PATH=%FALLBACK_MODS%" +) + +echo RimWorld Mods path is: "%MODS_PATH%" + +REM ================================ +REM Read mod name from About/About.xml +REM ================================ +set "MODNAME=" +for /f "delims=" %%a in ('powershell -NoLogo -NoProfile -Command ^ + "[xml]$about=Get-Content '.\About\About.xml'; $about.ModMetaData.name"') do ( + set "MODNAME=%%a" +) +if not defined MODNAME ( + echo Failed to read mod name from About\About.xml. Aborting. + exit /b 1 +) +echo Mod name is: %MODNAME% + +REM Destination root = Mods\\ +set "MODROOT=%MODS_PATH%\%MODNAME%" +set "MODVER=%MODROOT%\%CURRENTVER%" +set "OUTDIR=%MODVER%\Assemblies" + +REM ================================ +REM Prepare output folders +REM ================================ +if exist "%OUTDIR%" ( + echo Removing existing output directory: "%OUTDIR%" + rmdir /s /q "%OUTDIR%" +) +if not exist "%OUTDIR%" ( + echo Creating output dir: "%OUTDIR%" + mkdir "%OUTDIR%" +) +if not exist "%BASE%" ( + echo Creating local mirror dir: "%BASE%" + mkdir "%BASE%" +) + +REM ================================ +REM Clean old DLLs +REM ================================ +if exist "%OUTDIR%\*.dll" ( + echo Removing existing DLLs in OUTDIR... + del /q "%OUTDIR%\*.dll" +) else ( + echo No existing DLLs in OUTDIR. +) + +if exist "%BASE%\*.dll" ( + echo Removing existing DLLs in BASE mirror... + del /q "%BASE%\*.dll" +) else ( + echo No existing DLLs in BASE mirror. +) + +REM ================================ +REM Version Management +REM ================================ +if defined SKIP_VERSION ( + echo Skipping version increment [--no-version flag set] + if exist "Source\VersionInfo.cs" ( + echo Using existing version info... + ) else ( + echo WARNING: VersionInfo.cs does not exist. Generating without increment... + powershell -NoProfile -ExecutionPolicy Bypass -File ".vscode\update-version.ps1" -Config "%CONFIG%" -SkipIncrement + ) +) else ( + echo Updating version... + powershell -NoProfile -ExecutionPolicy Bypass -File ".vscode\update-version.ps1" -Config "%CONFIG%" + if errorlevel 1 ( + echo Version update failed. Aborting. + exit /b 1 + ) +) + +REM ================================ +REM Build +REM ================================ +echo Building %MODNAME% with configuration: %CONFIG% ... +dotnet build .vscode -c %CONFIG% -o "%OUTDIR%" +if errorlevel 1 ( + echo Build failed. Aborting. + exit /b %errorlevel% +) + +REM Remove bundled Harmony if it appears +if exist "%OUTDIR%\0Harmony.dll" ( + echo Removing bundled Harmony DLL... + del /q "%OUTDIR%\0Harmony.dll" +) + +REM Remove base bundled Harmony if it appears +if exist "%BASE%\0Harmony.dll" ( + echo Removing base bundled Harmony DLL... + del /q "%BASE%\0Harmony.dll" +) + +REM Mirror build to BASE +echo Mirroring build to BASE... +xcopy /Y /E "%OUTDIR%\*" "%BASE%\" >nul + +setlocal EnableExtensions EnableDelayedExpansion + +REM ================================ +REM Copy assets into Mod folder +REM ================================ +for %%V in (1.0 1.1 1.2 1.3 1.4 1.5 1.6) do if exist "%%V" ( + echo Copying version folder %%V... + if exist "!MODROOT!\%%V" ( + echo Cleaning existing version folder %%V in mod root... + rmdir /s /q "!MODROOT!\%%V" + ) + xcopy "%%V" "!MODROOT!\%%V" /E /I /Y >nul +) + +REM Remove any quarantined XML files that should not load (keep .xml.off only) +if exist "!MODROOT!\%CURRENTVER%\Patches\_removed\*.xml" ( + echo Removing quarantined XML files from _removed directory... + del /q "!MODROOT!\%CURRENTVER%\Patches\_removed\*.xml" >nul 2>&1 +) + +if exist "About" ( + echo Copying About... + xcopy "About" "!MODROOT!\About" /E /I /Y >nul +) + +if exist "Assemblies" ( + echo Copying root Assemblies... + xcopy "Assemblies" "!MODROOT!\Assemblies" /E /I /Y >nul +) + +if exist "LoadFolders.xml" ( + echo Copying LoadFolders.xml... + copy /Y "LoadFolders.xml" "!MODROOT!\LoadFolders.xml" >nul +) + +for %%D in ("Languages" "Textures" "Defs" "Patches") do if exist "%%~fD" ( + echo Copying %%~nxD to "!MODROOT!\%%~nxD" ... + xcopy "%%~fD" "!MODROOT!\%%~nxD" /E /I /Y >nul +) + +setlocal EnableExtensions EnableDelayedExpansion + +REM Always include configuration in zip name (e.g., -Release or -Debug) +set "ZIPSUFFIX=-!CONFIG!" +set "ZIPNAME=!MODNAME!_!CURRENTVER!!ZIPSUFFIX!.zip" +set "ZIPSOURCE=!MODROOT!" +set "ZIPDEST=%CD%\!ZIPNAME!" + +if exist "!ZIPDEST!" ( + echo Removing existing ZIP... + del /q "!ZIPDEST!" +) + +echo Creating ZIP archive (tar)... +tar.exe -a -cf "!ZIPDEST!" -C "!ZIPSOURCE!" . + +if exist "!ZIPDEST!" ( + echo ZIP created at: "!ZIPDEST!" +) else ( + echo ZIP creation failed. + exit /b 1 +) + +echo Done. +endlocal diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..f00e1dd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-dotnettools.csharp" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..aa3d2ae --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + // "stopAtEntry": true, + "version": "0.2.0", + "configurations": [ + { + "name": "Rimworld Mod Project (Debug)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "BuildWindowsDLL: Debug", + "buildConfiguration": "Debug", + "args": [], + "program": "F:/SteamLibrary/steamapps/common/RimWorld/RimWorldWin64.exe", + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "internalConsoleOptions":"neverOpen", + "linux": { + "program": "../../RimWorldLinux", + "preLaunchTask": "BuildLinuxDLL", + } + }, + { + "name": "Rimworld Mod Project (Release)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "BuildWindowsDLL: Release", + "buildConfiguration": "Release", + "args": [], + "program": "F:/SteamLibrary/steamapps/common/RimWorld/RimWorldWin64.exe", + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "internalConsoleOptions":"neverOpen", + "linux": { + "program": "../../RimWorldLinux", + "preLaunchTask": "BuildLinuxDLL", + } + } + ] +} diff --git a/.vscode/mod.csproj b/.vscode/mod.csproj new file mode 100644 index 0000000..c39a242 --- /dev/null +++ b/.vscode/mod.csproj @@ -0,0 +1,27 @@ + + + + Library + net480 + x64 + + HomeMover + HomeMover + ../1.6/Assemblies + 0.1.0.0 + + none + false + false + ../../../RimWorldWin64_Data/Managed + ../../../RimWorldLinux_Data/Managed + + + + + + + + all + + diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..2eed148 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,56 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "BuildWindowsDLL: Debug", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + }, + "command": ".vscode/build.bat", + "args": [ + "${input:buildConfig}", + "${input:versionIncrement}" + ] + } + ], + "inputs": [ + { + "id": "buildConfig", + "type": "pickString", + "description": "Select build configuration", + "options": [ + "Debug", + "Release" + ], + "default": "Debug" + }, + { + "id": "versionIncrement", + "type": "pickString", + "description": "Increment version number?", + "options": [ + "", + "--no-version" + ], + "default": "" + } + ] +} +/* { + "version": "2.0.0", + "tasks": [ + { + "label": "BuildWindowsDLL: Release", + "type": "shell", + "command": "${workspaceFolder}/.vscode/build.bat", + "args": ["Release"], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + } + ] +} */ diff --git a/.vscode/update-version.ps1 b/.vscode/update-version.ps1 new file mode 100644 index 0000000..6cd888c --- /dev/null +++ b/.vscode/update-version.ps1 @@ -0,0 +1,69 @@ +# Auto-increment version and generate VersionInfo.cs +param( + [string]$Config = "Release", + [switch]$SkipIncrement +) + +$versionFile = "version.txt" +$versionCs = "Source\VersionInfo.cs" + +function Resolve-BuildNamespace { + $projectDir = ".vscode" + $candidates = Get-ChildItem -Path $projectDir -Filter "*.csproj" -File -ErrorAction SilentlyContinue + if (-not $candidates -or $candidates.Count -eq 0) { + return "Template" + } + + try { + [xml]$proj = Get-Content -Path $candidates[0].FullName -Raw + $rootNsNode = $proj.SelectSingleNode('/Project/PropertyGroup/RootNamespace') + if ($rootNsNode -and -not [string]::IsNullOrWhiteSpace($rootNsNode.InnerText)) { + return $rootNsNode.InnerText.Trim() + } + } + catch { + # Fall through to default namespace if project parsing fails. + } + + return "Template" +} + +# Read current version +$ver = [decimal](Get-Content $versionFile -Raw).Trim() + +# Increment version unless skip flag is set +if (-not $SkipIncrement) { + $ver += 0.001 + $ver = [Math]::Round($ver, 3) + # Write back to file + Set-Content $versionFile $ver.ToString('0.000') +} + +# Determine build type +$buildType = if ($Config -eq 'Debug') {'D'} else {'R'} +$versionString = "$ver($buildType)" +$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' +$buildNamespace = Resolve-BuildNamespace + +# Generate VersionInfo.cs +$cs = @" +// Auto-generated by update-version.ps1 - DO NOT EDIT MANUALLY +// Build timestamp: $timestamp +// Build configuration: $Config + +namespace $buildNamespace +{ + public static class BuildVersion + { + public const string Version = "$versionString"; + public const string VersionNumber = "$ver"; + public const string BuildType = "$buildType"; + public const string BuildConfig = "$Config"; + public const string BuildTimestamp = "$timestamp"; + } +} +"@ + +Set-Content $versionCs $cs + +Write-Host "Version updated to: $versionString" diff --git a/1.1/Assemblies/MoveBase.dll b/1.1/Assemblies/MoveBase.dll new file mode 100644 index 0000000..546eff6 Binary files /dev/null and b/1.1/Assemblies/MoveBase.dll differ diff --git a/src/Defs/Letter.xml b/1.1/Defs/Letter.xml similarity index 100% rename from src/Defs/Letter.xml rename to 1.1/Defs/Letter.xml diff --git a/src/Defs/MoveBase.xml b/1.1/Defs/MoveBase.xml similarity index 100% rename from src/Defs/MoveBase.xml rename to 1.1/Defs/MoveBase.xml diff --git "a/1.1/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" "b/1.1/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" new file mode 100644 index 0000000..6163470 --- /dev/null +++ "b/1.1/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" @@ -0,0 +1,23 @@ + + + + 将建筑物移动到另一个位置 + + 搬家公司 + + 已跳过项目 + + 部分物品无法放置,已被跳过: +{0} + + {0} 无法放置:{1} + + 已在目标位置排队建造 {0} 块地板。 + + 被 {0} 阻挡。请清理障碍物或启用自动处理。 + + {0} 阻挡了目标位置,且无法自动小型化。 + + {0} 因不可小型化而被跳过。 + + 移动已阻止:检测到山体厚顶。启用“允许在厚顶下移动”可继续,并忽略厚顶迁移。 diff --git a/1.1/Languages/English/Keyed/MoveBase.xml b/1.1/Languages/English/Keyed/MoveBase.xml new file mode 100644 index 0000000..eb69bc0 --- /dev/null +++ b/1.1/Languages/English/Keyed/MoveBase.xml @@ -0,0 +1,14 @@ + + + + Move building to another location + Home Mover + Items Skipped + Some items could not be placed and were skipped: +{0} + {0} could not be placed: {1} + Queued construction for {0} floor tile(s) at destination. + Blocked by {0}. Clear the obstruction or enable automatic handling. + {0} blocks the move destination and cannot be minified automatically. + {0} was skipped because it is not minifiable. + Move blocked: overhead mountain roof detected. Enable "Allow moves under thick roof" to continue and ignore thick-roof migration. diff --git a/src/Patches/AddDesignator.xml b/1.1/Patches/AddDesignator.xml similarity index 100% rename from src/Patches/AddDesignator.xml rename to 1.1/Patches/AddDesignator.xml diff --git a/src/Textures/UI/Designations/DesignationMoveBase.png b/1.1/Textures/UI/Designations/DesignationMoveBase.png similarity index 100% rename from src/Textures/UI/Designations/DesignationMoveBase.png rename to 1.1/Textures/UI/Designations/DesignationMoveBase.png diff --git a/src/Textures/UI/Designations/MoveBase.png b/1.1/Textures/UI/Designations/MoveBase.png similarity index 100% rename from src/Textures/UI/Designations/MoveBase.png rename to 1.1/Textures/UI/Designations/MoveBase.png diff --git a/1.3/Assemblies/MoveBase.dll b/1.3/Assemblies/MoveBase.dll new file mode 100644 index 0000000..f5d466e Binary files /dev/null and b/1.3/Assemblies/MoveBase.dll differ diff --git a/references/NotooShabby.RimWorldUtility.dll b/1.3/Assemblies/NotooShabby.RimWorldUtility.dll similarity index 100% rename from references/NotooShabby.RimWorldUtility.dll rename to 1.3/Assemblies/NotooShabby.RimWorldUtility.dll diff --git a/1.3/Defs/Letter.xml b/1.3/Defs/Letter.xml new file mode 100644 index 0000000..6af6ce1 --- /dev/null +++ b/1.3/Defs/Letter.xml @@ -0,0 +1,13 @@ + + + + + NotooShabbyFeatureUpdate + MoveBase.FeatureUpdateLetter + (120, 176, 216) + (106, 179, 231) + 90 + LetterArrive_Good + + + diff --git a/1.3/Defs/MoveBase.xml b/1.3/Defs/MoveBase.xml new file mode 100644 index 0000000..4e820c0 --- /dev/null +++ b/1.3/Defs/MoveBase.xml @@ -0,0 +1,8 @@ + + + + MoveBase + UI/Designations/DesignationMoveBase + Thing + + diff --git "a/1.3/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" "b/1.3/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" new file mode 100644 index 0000000..6163470 --- /dev/null +++ "b/1.3/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" @@ -0,0 +1,23 @@ + + + + 将建筑物移动到另一个位置 + + 搬家公司 + + 已跳过项目 + + 部分物品无法放置,已被跳过: +{0} + + {0} 无法放置:{1} + + 已在目标位置排队建造 {0} 块地板。 + + 被 {0} 阻挡。请清理障碍物或启用自动处理。 + + {0} 阻挡了目标位置,且无法自动小型化。 + + {0} 因不可小型化而被跳过。 + + 移动已阻止:检测到山体厚顶。启用“允许在厚顶下移动”可继续,并忽略厚顶迁移。 diff --git a/1.3/Languages/English/Keyed/MoveBase.xml b/1.3/Languages/English/Keyed/MoveBase.xml new file mode 100644 index 0000000..eb69bc0 --- /dev/null +++ b/1.3/Languages/English/Keyed/MoveBase.xml @@ -0,0 +1,14 @@ + + + + Move building to another location + Home Mover + Items Skipped + Some items could not be placed and were skipped: +{0} + {0} could not be placed: {1} + Queued construction for {0} floor tile(s) at destination. + Blocked by {0}. Clear the obstruction or enable automatic handling. + {0} blocks the move destination and cannot be minified automatically. + {0} was skipped because it is not minifiable. + Move blocked: overhead mountain roof detected. Enable "Allow moves under thick roof" to continue and ignore thick-roof migration. diff --git a/1.3/Patches/AddDesignator.xml b/1.3/Patches/AddDesignator.xml new file mode 100644 index 0000000..bc0ce9b --- /dev/null +++ b/1.3/Patches/AddDesignator.xml @@ -0,0 +1,10 @@ + + + + + /Defs/DesignationCategoryDef[defName="Orders"]/specialDesignatorClasses + +
  • MoveBase.DesignatorMoveBase
  • +
    +
    +
    diff --git a/1.3/Textures/UI/Designations/DesignationMoveBase.png b/1.3/Textures/UI/Designations/DesignationMoveBase.png new file mode 100644 index 0000000..990cd6b Binary files /dev/null and b/1.3/Textures/UI/Designations/DesignationMoveBase.png differ diff --git a/1.3/Textures/UI/Designations/MoveBase.png b/1.3/Textures/UI/Designations/MoveBase.png new file mode 100644 index 0000000..c37581c Binary files /dev/null and b/1.3/Textures/UI/Designations/MoveBase.png differ diff --git a/1.4/Assemblies/MoveBase.dll b/1.4/Assemblies/MoveBase.dll new file mode 100644 index 0000000..cba2e7f Binary files /dev/null and b/1.4/Assemblies/MoveBase.dll differ diff --git a/1.4/Defs/MoveBase.xml b/1.4/Defs/MoveBase.xml new file mode 100644 index 0000000..4e820c0 --- /dev/null +++ b/1.4/Defs/MoveBase.xml @@ -0,0 +1,8 @@ + + + + MoveBase + UI/Designations/DesignationMoveBase + Thing + + diff --git "a/1.4/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" "b/1.4/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" new file mode 100644 index 0000000..6163470 --- /dev/null +++ "b/1.4/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" @@ -0,0 +1,23 @@ + + + + 将建筑物移动到另一个位置 + + 搬家公司 + + 已跳过项目 + + 部分物品无法放置,已被跳过: +{0} + + {0} 无法放置:{1} + + 已在目标位置排队建造 {0} 块地板。 + + 被 {0} 阻挡。请清理障碍物或启用自动处理。 + + {0} 阻挡了目标位置,且无法自动小型化。 + + {0} 因不可小型化而被跳过。 + + 移动已阻止:检测到山体厚顶。启用“允许在厚顶下移动”可继续,并忽略厚顶迁移。 diff --git a/1.4/Languages/English/Keyed/MoveBase.xml b/1.4/Languages/English/Keyed/MoveBase.xml new file mode 100644 index 0000000..eb69bc0 --- /dev/null +++ b/1.4/Languages/English/Keyed/MoveBase.xml @@ -0,0 +1,14 @@ + + + + Move building to another location + Home Mover + Items Skipped + Some items could not be placed and were skipped: +{0} + {0} could not be placed: {1} + Queued construction for {0} floor tile(s) at destination. + Blocked by {0}. Clear the obstruction or enable automatic handling. + {0} blocks the move destination and cannot be minified automatically. + {0} was skipped because it is not minifiable. + Move blocked: overhead mountain roof detected. Enable "Allow moves under thick roof" to continue and ignore thick-roof migration. diff --git a/1.4/Patches/AddDesignator.xml b/1.4/Patches/AddDesignator.xml new file mode 100644 index 0000000..bc0ce9b --- /dev/null +++ b/1.4/Patches/AddDesignator.xml @@ -0,0 +1,10 @@ + + + + + /Defs/DesignationCategoryDef[defName="Orders"]/specialDesignatorClasses + +
  • MoveBase.DesignatorMoveBase
  • +
    +
    +
    diff --git a/1.4/Textures/UI/Designations/DesignationMoveBase.png b/1.4/Textures/UI/Designations/DesignationMoveBase.png new file mode 100644 index 0000000..990cd6b Binary files /dev/null and b/1.4/Textures/UI/Designations/DesignationMoveBase.png differ diff --git a/1.4/Textures/UI/Designations/MoveBase.png b/1.4/Textures/UI/Designations/MoveBase.png new file mode 100644 index 0000000..c37581c Binary files /dev/null and b/1.4/Textures/UI/Designations/MoveBase.png differ diff --git a/1.5/Assemblies/MoveBase.dll b/1.5/Assemblies/MoveBase.dll new file mode 100644 index 0000000..026c8e1 Binary files /dev/null and b/1.5/Assemblies/MoveBase.dll differ diff --git a/1.5/Defs/MoveBase.xml b/1.5/Defs/MoveBase.xml new file mode 100644 index 0000000..4e820c0 --- /dev/null +++ b/1.5/Defs/MoveBase.xml @@ -0,0 +1,8 @@ + + + + MoveBase + UI/Designations/DesignationMoveBase + Thing + + diff --git "a/1.5/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" "b/1.5/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" new file mode 100644 index 0000000..6163470 --- /dev/null +++ "b/1.5/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" @@ -0,0 +1,23 @@ + + + + 将建筑物移动到另一个位置 + + 搬家公司 + + 已跳过项目 + + 部分物品无法放置,已被跳过: +{0} + + {0} 无法放置:{1} + + 已在目标位置排队建造 {0} 块地板。 + + 被 {0} 阻挡。请清理障碍物或启用自动处理。 + + {0} 阻挡了目标位置,且无法自动小型化。 + + {0} 因不可小型化而被跳过。 + + 移动已阻止:检测到山体厚顶。启用“允许在厚顶下移动”可继续,并忽略厚顶迁移。 diff --git a/1.5/Languages/English/Keyed/MoveBase.xml b/1.5/Languages/English/Keyed/MoveBase.xml new file mode 100644 index 0000000..eb69bc0 --- /dev/null +++ b/1.5/Languages/English/Keyed/MoveBase.xml @@ -0,0 +1,14 @@ + + + + Move building to another location + Home Mover + Items Skipped + Some items could not be placed and were skipped: +{0} + {0} could not be placed: {1} + Queued construction for {0} floor tile(s) at destination. + Blocked by {0}. Clear the obstruction or enable automatic handling. + {0} blocks the move destination and cannot be minified automatically. + {0} was skipped because it is not minifiable. + Move blocked: overhead mountain roof detected. Enable "Allow moves under thick roof" to continue and ignore thick-roof migration. diff --git a/1.5/Patches/AddDesignator.xml b/1.5/Patches/AddDesignator.xml new file mode 100644 index 0000000..bc0ce9b --- /dev/null +++ b/1.5/Patches/AddDesignator.xml @@ -0,0 +1,10 @@ + + + + + /Defs/DesignationCategoryDef[defName="Orders"]/specialDesignatorClasses + +
  • MoveBase.DesignatorMoveBase
  • +
    +
    +
    diff --git a/1.5/Textures/UI/Designations/DesignationMoveBase.png b/1.5/Textures/UI/Designations/DesignationMoveBase.png new file mode 100644 index 0000000..990cd6b Binary files /dev/null and b/1.5/Textures/UI/Designations/DesignationMoveBase.png differ diff --git a/1.5/Textures/UI/Designations/MoveBase.png b/1.5/Textures/UI/Designations/MoveBase.png new file mode 100644 index 0000000..c37581c Binary files /dev/null and b/1.5/Textures/UI/Designations/MoveBase.png differ diff --git a/1.6/Assemblies/HomeMover.dll b/1.6/Assemblies/HomeMover.dll new file mode 100644 index 0000000..b15f348 Binary files /dev/null and b/1.6/Assemblies/HomeMover.dll differ diff --git a/1.6/Defs/MoveBase.xml b/1.6/Defs/MoveBase.xml new file mode 100644 index 0000000..fd961b0 --- /dev/null +++ b/1.6/Defs/MoveBase.xml @@ -0,0 +1,8 @@ + + + + HomeMover + UI/Designations/DesignationMoveBase + Thing + + diff --git "a/1.6/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" "b/1.6/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" new file mode 100644 index 0000000..8d57c9a --- /dev/null +++ "b/1.6/Languages/ChineseSimplified (\347\256\200\344\275\223\344\270\255\346\226\207)/Keyed/MoveBase.xml" @@ -0,0 +1,72 @@ + + + + 将建筑物移动到另一个位置 + + 搬家公司 + + 已跳过项目 + + 部分物品无法放置,已被跳过: +{0} + + {0} 无法放置:{1} + + 已在目标位置排队建造 {0} 块地板。 + + 被 {0} 阻挡。请清理障碍物或启用自动处理。 + + {0} 阻挡了目标位置,且无法自动小型化。 + + {0} 因不可小型化而被跳过。 + + 移动已阻止:检测到山体厚顶。启用“允许在厚顶下移动”可继续,并忽略厚顶迁移。 + + 智能依赖放置(电缆/地板优先,遵循依赖关系) + + 启用后,放置将使用确定性的层级顺序:先电缆,再地板,再结构/依赖项。这可避免大多数放置错误。 + + 跳过无法放置的物品(而不是中止整个移动) + + 启用后,存在放置错误的物品将被跳过。禁用后,只要有任意物品无法放置,整个移动将被取消。 + + 显示被跳过物品的消息 + + 显示因错误被跳过的物品列表消息。 + + 将地板类型复制到目标位置 + + 启用后,会在目标位置排队建造与所选区域一致的地板。 + + 自动处理目标位置障碍物 + + 当阻挡移动放置时,将植物指定为砍伐/收获、可采矿物指定为采矿、玩家建筑指定为卸载。 + + 自动卸载玩家阻挡建筑 + + 启用后,可小型化的玩家阻挡建筑将被自动指定为卸载。 + + 对不可小型化阻挡物发出警告 + + 对无法自动移动的阻挡物显示警告消息。 + + 在目标位置排队建造屋顶 + + 启用后,会将源区域屋顶模式镜像为目标位置的“建造屋顶”指定。 + + 允许在厚顶下移动(忽略厚顶迁移) + + 启用后,山体厚顶不再阻止移动操作。将跳过厚顶迁移记录逻辑。 + + 启用调试日志(需要开发者模式) + + 物品:{0} + + 不可小型化:{0} + + 光标处障碍物:{0} + + 厚顶覆盖:开启 + + 厚顶覆盖:关闭 + \ No newline at end of file diff --git a/1.6/Languages/ChineseSimplified/Keyed/MoveBase.xml b/1.6/Languages/ChineseSimplified/Keyed/MoveBase.xml new file mode 100644 index 0000000..8d57c9a --- /dev/null +++ b/1.6/Languages/ChineseSimplified/Keyed/MoveBase.xml @@ -0,0 +1,72 @@ + + + + 将建筑物移动到另一个位置 + + 搬家公司 + + 已跳过项目 + + 部分物品无法放置,已被跳过: +{0} + + {0} 无法放置:{1} + + 已在目标位置排队建造 {0} 块地板。 + + 被 {0} 阻挡。请清理障碍物或启用自动处理。 + + {0} 阻挡了目标位置,且无法自动小型化。 + + {0} 因不可小型化而被跳过。 + + 移动已阻止:检测到山体厚顶。启用“允许在厚顶下移动”可继续,并忽略厚顶迁移。 + + 智能依赖放置(电缆/地板优先,遵循依赖关系) + + 启用后,放置将使用确定性的层级顺序:先电缆,再地板,再结构/依赖项。这可避免大多数放置错误。 + + 跳过无法放置的物品(而不是中止整个移动) + + 启用后,存在放置错误的物品将被跳过。禁用后,只要有任意物品无法放置,整个移动将被取消。 + + 显示被跳过物品的消息 + + 显示因错误被跳过的物品列表消息。 + + 将地板类型复制到目标位置 + + 启用后,会在目标位置排队建造与所选区域一致的地板。 + + 自动处理目标位置障碍物 + + 当阻挡移动放置时,将植物指定为砍伐/收获、可采矿物指定为采矿、玩家建筑指定为卸载。 + + 自动卸载玩家阻挡建筑 + + 启用后,可小型化的玩家阻挡建筑将被自动指定为卸载。 + + 对不可小型化阻挡物发出警告 + + 对无法自动移动的阻挡物显示警告消息。 + + 在目标位置排队建造屋顶 + + 启用后,会将源区域屋顶模式镜像为目标位置的“建造屋顶”指定。 + + 允许在厚顶下移动(忽略厚顶迁移) + + 启用后,山体厚顶不再阻止移动操作。将跳过厚顶迁移记录逻辑。 + + 启用调试日志(需要开发者模式) + + 物品:{0} + + 不可小型化:{0} + + 光标处障碍物:{0} + + 厚顶覆盖:开启 + + 厚顶覆盖:关闭 + \ No newline at end of file diff --git a/1.6/Languages/English/Keyed/MoveBase.xml b/1.6/Languages/English/Keyed/MoveBase.xml new file mode 100644 index 0000000..9316efe --- /dev/null +++ b/1.6/Languages/English/Keyed/MoveBase.xml @@ -0,0 +1,39 @@ + + + + Move building to another location + Home Mover + Items Skipped + Some items could not be placed and were skipped: +{0} + {0} could not be placed: {1} + Queued construction for {0} floor tile(s) at destination. + Blocked by {0}. Clear the obstruction or enable automatic handling. + {0} blocks the move destination and cannot be minified automatically. + {0} was skipped because it is not minifiable. + Move blocked: overhead mountain roof detected. Enable "Allow moves under thick roof" to continue and ignore thick-roof migration. + Smart dependency placement (conduits/floors first, dependencies respected) + When enabled, placement uses deterministic tiers: conduits first, then floors, then structures/dependencies. This prevents most placement errors. + Skip items that can't be placed (instead of aborting entire move) + When enabled, items that have placement errors will be skipped. When disabled, the entire move will be cancelled if any item can't be placed. + Show message for skipped items + Display a message listing items that were skipped due to errors. + Copy floor types to destination + When enabled, queues construction of floors at the destination to match the selected area. + Handle destination obstructions automatically + Designate plants for cutting/harvest, mineables for mining, and player buildings for uninstall when they block move placement. + Auto-uninstall player blockers + When enabled, blocking player structures that can be minified are designated for uninstall automatically. + Warn on non-minifiable blockers + Show warning messages for blockers that cannot be automatically moved. + Queue roof construction at destination + When enabled, source roof pattern is mirrored as Build Roof designations at the destination. + Allow moves under thick roof (ignore thick-roof migration) + When enabled, thick mountain roofs no longer block move operations. Thick-roof migration bookkeeping is skipped. + Enable debug logging (requires dev mode) + Items: {0} + Non-minifiable: {0} + Obstructions at cursor: {0} + Thick roof override: ON + Thick roof override: OFF + \ No newline at end of file diff --git a/1.6/Languages/French/Keyed/MoveBase.xml b/1.6/Languages/French/Keyed/MoveBase.xml new file mode 100644 index 0000000..02a7dc4 --- /dev/null +++ b/1.6/Languages/French/Keyed/MoveBase.xml @@ -0,0 +1,39 @@ + + + + Déplacer un bâtiment vers un autre emplacement + Home Mover + Éléments ignorés + Certains éléments n'ont pas pu être placés et ont été ignorés : +{0} + {0} n'a pas pu être placé : {1} + {0} dalle(s) de sol déplacée(s) vers la destination. + Bloqué par {0}. Dégagez l'obstacle ou activez la gestion automatique. + {0} bloque la destination et ne peut pas être minifié automatiquement. + {0} a été ignoré car il n'est pas minifiable. + Déplacement bloqué : toit de montagne épais détecté au-dessus. Activez « Autoriser les déplacements sous toit épais » pour continuer et ignorer la migration du toit épais. + Placement intelligent des dépendances (conduits/sols d'abord, dépendances respectées) + Activé, le placement utilise des niveaux déterministes : conduits d'abord, puis sols, puis structures/dépendances. Cela évite la plupart des erreurs de placement. + Ignorer les éléments qui ne peuvent pas être placés (au lieu d'annuler tout le déplacement) + Si activé, les éléments avec erreur de placement seront ignorés. Si désactivé, tout le déplacement sera annulé si un élément ne peut pas être placé. + Afficher un message pour les éléments ignorés + Affiche un message listant les éléments ignorés à cause d'erreurs. + Copier les types de sol vers la destination + Si activé, met en file la construction de sols à la destination pour correspondre à la zone sélectionnée. + Gérer automatiquement les obstacles de destination + Désigne les plantes à couper/récolter, les objets minables à miner et les bâtiments du joueur à désinstaller lorsqu'ils bloquent le placement. + Désinstaller automatiquement les bloqueurs du joueur + Si activé, les structures du joueur bloquantes pouvant être minifiées sont automatiquement désignées pour désinstallation. + Avertir pour les bloqueurs non minifiables + Affiche des avertissements pour les bloqueurs qui ne peuvent pas être déplacés automatiquement. + Mettre en file la construction du toit à la destination + Si activé, le motif de toit source est reproduit comme désignations de construction de toit à la destination. + Autoriser les déplacements sous toit épais (ignorer la migration du toit épais) + Si activé, les toits de montagne épais ne bloquent plus les déplacements. La logique de migration du toit épais est ignorée. + Activer les journaux de débogage (mode développeur requis) + Éléments : {0} + Non minifiables : {0} + Obstacles au curseur : {0} + Forçage toit épais : ACTIVÉ + Forçage toit épais : DÉSACTIVÉ + diff --git a/1.6/Languages/German/Keyed/MoveBase.xml b/1.6/Languages/German/Keyed/MoveBase.xml new file mode 100644 index 0000000..7103bfe --- /dev/null +++ b/1.6/Languages/German/Keyed/MoveBase.xml @@ -0,0 +1,39 @@ + + + + Gebäude an einen anderen Ort verschieben + Home Mover + Übersprungene Objekte + Einige Objekte konnten nicht platziert werden und wurden übersprungen: +{0} + {0} konnte nicht platziert werden: {1} + {0} Bodenfeld(er) wurden an das Ziel verschoben. + Blockiert durch {0}. Entferne das Hindernis oder aktiviere die automatische Behandlung. + {0} blockiert das Ziel und kann nicht automatisch minifiziert werden. + {0} wurde übersprungen, weil es nicht minifizierbar ist. + Verschieben blockiert: Überkopf wurde ein dickes Bergdach erkannt. Aktiviere „Verschieben unter dickem Dach erlauben“, um fortzufahren und die Dachmigration zu ignorieren. + Intelligente Abhängigkeitsplatzierung (Leitungen/Böden zuerst, Abhängigkeiten beachtet) + Wenn aktiviert, nutzt die Platzierung feste Stufen: zuerst Leitungen, dann Böden, dann Strukturen/Abhängigkeiten. Das verhindert die meisten Platzierungsfehler. + Objekte überspringen, die nicht platziert werden können (statt den gesamten Vorgang abzubrechen) + Wenn aktiviert, werden Objekte mit Platzierungsfehlern übersprungen. Wenn deaktiviert, wird der gesamte Vorgang abgebrochen, sobald ein Objekt nicht platziert werden kann. + Meldung für übersprungene Objekte anzeigen + Zeigt eine Meldung mit den wegen Fehlern übersprungenen Objekten an. + Bodentypen zum Ziel kopieren + Wenn aktiviert, wird der Bau passender Böden am Ziel entsprechend dem ausgewählten Bereich in die Warteschlange gestellt. + Hindernisse am Ziel automatisch behandeln + Markiert Pflanzen zum Schneiden/Ernten, abbaubare Objekte zum Abbauen und Spielergebäude zum Deinstallieren, wenn sie die Platzierung blockieren. + Blockierende Spielerbauten automatisch deinstallieren + Wenn aktiviert, werden blockierende Spielerstrukturen, die minifizierbar sind, automatisch zur Deinstallation markiert. + Vor nicht minifizierbaren Blockern warnen + Zeigt Warnungen für Blocker an, die nicht automatisch bewegt werden können. + Dachbau am Ziel in die Warteschlange stellen + Wenn aktiviert, wird das Quell-Dachmuster als „Dach bauen“-Markierungen am Ziel gespiegelt. + Verschieben unter dickem Dach erlauben (Dachmigration ignorieren) + Wenn aktiviert, blockieren dicke Bergdächer den Verschiebevorgang nicht mehr. Die Migrationslogik für dicke Dächer wird übersprungen. + Debug-Protokollierung aktivieren (Entwicklermodus erforderlich) + Objekte: {0} + Nicht minifizierbar: {0} + Hindernisse am Cursor: {0} + Dickes-Dach-Override: EIN + Dickes-Dach-Override: AUS + diff --git a/1.6/Languages/Japanese/Keyed/MoveBase.xml b/1.6/Languages/Japanese/Keyed/MoveBase.xml new file mode 100644 index 0000000..25b3839 --- /dev/null +++ b/1.6/Languages/Japanese/Keyed/MoveBase.xml @@ -0,0 +1,39 @@ + + + + 建物を別の場所へ移動する + Home Mover + スキップされた項目 + 配置できないため、次の項目はスキップされました: +{0} + {0} を配置できませんでした: {1} + {0} 枚の床タイルを目的地に移動しました。 + {0} によってブロックされています。障害物を除去するか、自動処理を有効にしてください。 + {0} が目的地を塞いでおり、自動でミニ化できません。 + {0} はミニ化できないためスキップされました。 + 移動がブロックされました: 上部に厚い山岳屋根が検出されました。続行して厚屋根の移行を無視するには「厚い屋根の下での移動を許可」を有効にしてください。 + 依存関係に基づくスマート配置(導管/床を先に、依存関係を尊重) + 有効にすると、配置は決定的な段階順を使用します: 導管→床→構造/依存関係。これにより多くの配置エラーを防げます。 + 配置できない項目をスキップする(移動全体を中止しない) + 有効時、配置エラーのある項目をスキップします。無効時、1つでも配置できない項目があると移動全体を中止します。 + スキップ項目のメッセージを表示 + エラーでスキップされた項目一覧をメッセージ表示します。 + 床タイプを目的地へコピー + 有効にすると、選択範囲に合わせて目的地の床建設をキューに追加します。 + 目的地の障害物を自動処理 + 配置を妨げる場合、植物を伐採/収穫、採掘可能物を採掘、プレイヤー建築をアンインストール指定します。 + プレイヤーの障害建築を自動アンインストール + 有効にすると、ミニ化可能なプレイヤー障害建築を自動でアンインストール指定します。 + ミニ化不可の障害物を警告 + 自動で移動できない障害物に警告メッセージを表示します。 + 目的地の屋根建設をキューに追加 + 有効にすると、元の屋根パターンを目的地の「屋根を作る」指定として反映します。 + 厚い屋根の下での移動を許可(厚屋根移行を無視) + 有効にすると、厚い山岳屋根は移動をブロックしません。厚屋根移行の記録処理はスキップされます。 + デバッグログを有効化(開発者モードが必要) + 項目数: {0} + ミニ化不可: {0} + カーソル位置の障害物: {0} + 厚屋根オーバーライド: ON + 厚屋根オーバーライド: OFF + diff --git a/1.6/Languages/Russian/Keyed/MoveBase.xml b/1.6/Languages/Russian/Keyed/MoveBase.xml new file mode 100644 index 0000000..0f934df --- /dev/null +++ b/1.6/Languages/Russian/Keyed/MoveBase.xml @@ -0,0 +1,39 @@ + + + + Переместить постройку в другое место + Home Mover + Пропущенные объекты + Некоторые объекты не удалось разместить, и они были пропущены: +{0} + Не удалось разместить {0}: {1} + Перемещено {0} плит(ок) пола в точку назначения. + Заблокировано объектом {0}. Уберите препятствие или включите автоматическую обработку. + {0} блокирует точку назначения и не может быть автоматически минифицирован. + {0} пропущен, потому что не минифицируется. + Перемещение заблокировано: обнаружена толстая горная крыша сверху. Включите «Разрешить перемещение под толстой крышей», чтобы продолжить и игнорировать перенос толстой крыши. + Умное размещение зависимостей (сначала кабели/полы, с учетом зависимостей) + При включении используется фиксированный порядок: сначала кабели, затем полы, затем конструкции/зависимости. Это предотвращает большинство ошибок размещения. + Пропускать объекты, которые нельзя разместить (вместо отмены всего переноса) + Если включено, объекты с ошибками размещения будут пропущены. Если выключено, весь перенос будет отменен, если хотя бы один объект нельзя разместить. + Показывать сообщение о пропущенных объектах + Показывает сообщение со списком объектов, пропущенных из-за ошибок. + Копировать типы пола в точку назначения + При включении ставит в очередь строительство полов в точке назначения по образцу выбранной области. + Автоматически обрабатывать препятствия в точке назначения + Назначает растения на срез/сбор, добываемые объекты на добычу, а постройки игрока на деустановку, если они блокируют размещение. + Авто-деустановка препятствующих построек игрока + При включении препятствующие постройки игрока, которые можно минифицировать, автоматически помечаются на деустановку. + Предупреждать о неминифицируемых препятствиях + Показывает предупреждения для препятствий, которые нельзя автоматически переместить. + Ставить строительство крыши в очередь в точке назначения + При включении шаблон крыши источника зеркалируется как назначения «Строить крышу» в точке назначения. + Разрешить перемещения под толстой крышей (игнорировать перенос толстой крыши) + При включении толстые горные крыши больше не блокируют перенос. Логика учета переноса толстой крыши пропускается. + Включить отладочное логирование (требуется режим разработчика) + Объектов: {0} + Неминифицируемых: {0} + Препятствий под курсором: {0} + Переопределение толстой крыши: ВКЛ + Переопределение толстой крыши: ВЫКЛ + diff --git a/1.6/Languages/Spanish/Keyed/MoveBase.xml b/1.6/Languages/Spanish/Keyed/MoveBase.xml new file mode 100644 index 0000000..0d6b12c --- /dev/null +++ b/1.6/Languages/Spanish/Keyed/MoveBase.xml @@ -0,0 +1,39 @@ + + + + Mover edificio a otra ubicación + Home Mover + Elementos omitidos + Algunos elementos no se pudieron colocar y se omitieron: +{0} + No se pudo colocar {0}: {1} + Se han movido {0} baldosa(s) de suelo al destino. + Bloqueado por {0}. Despeja el obstáculo o activa el manejo automático. + {0} bloquea el destino y no se puede minificar automáticamente. + {0} se omitió porque no es minificable. + Movimiento bloqueado: se detectó un techo montañoso grueso arriba. Activa «Permitir mover bajo techo grueso» para continuar e ignorar la migración de techo grueso. + Colocación inteligente por dependencias (conductos/suelos primero, dependencias respetadas) + Al activarlo, la colocación usa niveles deterministas: primero conductos, luego suelos y después estructuras/dependencias. Esto evita la mayoría de errores de colocación. + Omitir elementos que no se puedan colocar (en lugar de cancelar todo el traslado) + Si está activado, se omitirán los elementos con errores de colocación. Si está desactivado, se cancelará todo el traslado si algún elemento no se puede colocar. + Mostrar mensaje de elementos omitidos + Muestra un mensaje con los elementos omitidos por errores. + Copiar tipos de suelo al destino + Si está activado, pone en cola la construcción de suelos en el destino para coincidir con el área seleccionada. + Manejar obstrucciones del destino automáticamente + Designa plantas para cortar/cosechar, minables para minar y edificios del jugador para desinstalar cuando bloquean la colocación. + Desinstalar automáticamente bloqueadores del jugador + Si está activado, las estructuras del jugador que bloquean y se pueden minificar se designan automáticamente para desinstalar. + Advertir sobre bloqueadores no minificables + Muestra advertencias para bloqueadores que no se pueden mover automáticamente. + Poner en cola construcción de techo en el destino + Si está activado, el patrón de techo de origen se refleja como designaciones de construir techo en el destino. + Permitir mover bajo techo grueso (ignorar migración de techo grueso) + Si está activado, los techos montañosos gruesos dejan de bloquear el traslado. Se omite la lógica de migración de techo grueso. + Activar registro de depuración (requiere modo desarrollador) + Elementos: {0} + No minificables: {0} + Obstrucciones en el cursor: {0} + Anulación de techo grueso: ACTIVADA + Anulación de techo grueso: DESACTIVADA + diff --git a/1.6/Patches/AddDesignator.xml b/1.6/Patches/AddDesignator.xml new file mode 100644 index 0000000..735dd85 --- /dev/null +++ b/1.6/Patches/AddDesignator.xml @@ -0,0 +1,10 @@ + + + + + /Defs/DesignationCategoryDef[defName="Orders"]/specialDesignatorClasses + +
  • HomeMover.DesignatorHomeMover
  • +
    +
    +
    diff --git a/1.6/Textures/UI/Designations/DesignationMoveBase.png b/1.6/Textures/UI/Designations/DesignationMoveBase.png new file mode 100644 index 0000000..990cd6b Binary files /dev/null and b/1.6/Textures/UI/Designations/DesignationMoveBase.png differ diff --git a/1.6/Textures/UI/Designations/MoveBase.png b/1.6/Textures/UI/Designations/MoveBase.png new file mode 100644 index 0000000..c37581c Binary files /dev/null and b/1.6/Textures/UI/Designations/MoveBase.png differ diff --git a/About/About.xml b/About/About.xml new file mode 100644 index 0000000..7c8487c --- /dev/null +++ b/About/About.xml @@ -0,0 +1,56 @@ + + + Home Mover + Jellypowered + NotooShabby.HomeMover + [v1.0.0] +A simple mod that moves buildings around. +It works better with "Minify Everything". + + https://github.com/Mhburg/AwsomeInventory + +
  • 1.1
  • +
  • 1.3
  • +
  • 1.4
  • +
  • 1.5
  • +
  • 1.6
  • +
    + +
  • + brrainz.harmony + Harmony + steam://url/CommunityFilePage/2009463077 + https://github.com/pardeike/HarmonyRimWorld/releases/latest +
  • +
    + + +
  • + erdelf.MinifyEverything + Minify Everything + steam://url/CommunityFilePage/872762753 + https://github.com/erdelf/MinifyEverything +
  • +
    + +
  • + erdelf.MinifyEverything + Minify Everything + steam://url/CommunityFilePage/872762753 + https://github.com/erdelf/MinifyEverything +
  • +
    + +
  • + erdelf.MinifyEverything + Minify Everything + steam://url/CommunityFilePage/872762753 + https://github.com/erdelf/MinifyEverything +
  • +
    +
    + +
  • brrainz.harmony
  • +
  • erdelf.MinifyEverything
  • +
    +
    diff --git a/src/About/Home_Mover_Preview.gif b/About/Home_Mover_Preview.gif similarity index 100% rename from src/About/Home_Mover_Preview.gif rename to About/Home_Mover_Preview.gif diff --git a/About/Preview.png b/About/Preview.png new file mode 100644 index 0000000..d7033bd Binary files /dev/null and b/About/Preview.png differ diff --git a/About/PublishedFileId.txt b/About/PublishedFileId.txt new file mode 100644 index 0000000..8ee8892 --- /dev/null +++ b/About/PublishedFileId.txt @@ -0,0 +1 @@ +2569949146 \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets deleted file mode 100644 index 1c96c9d..0000000 --- a/Directory.Build.targets +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Home Mover_1.6-Debug.zip b/Home Mover_1.6-Debug.zip new file mode 100644 index 0000000..a7801b1 Binary files /dev/null and b/Home Mover_1.6-Debug.zip differ diff --git a/Home Mover_1.6-Release.zip b/Home Mover_1.6-Release.zip new file mode 100644 index 0000000..ecb1db4 Binary files /dev/null and b/Home Mover_1.6-Release.zip differ diff --git a/HomeMover.sln b/HomeMover.sln new file mode 100644 index 0000000..279821c --- /dev/null +++ b/HomeMover.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeMover", ".vscode\mod.csproj", "{BF6F91F1-EBA4-5DA6-C59D-92CD2808FD17}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BF6F91F1-EBA4-5DA6-C59D-92CD2808FD17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF6F91F1-EBA4-5DA6-C59D-92CD2808FD17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF6F91F1-EBA4-5DA6-C59D-92CD2808FD17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF6F91F1-EBA4-5DA6-C59D-92CD2808FD17}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE313062-D0CC-4DD2-8C0C-F5BC89DBB1A2} + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0a04128..0000000 --- a/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LoadFolders.xml b/LoadFolders.xml new file mode 100644 index 0000000..830112d --- /dev/null +++ b/LoadFolders.xml @@ -0,0 +1,20 @@ + + + + +
  • 1.1
  • +
    + +
  • 1.3
  • +
    + +
  • 1.4
  • +
    + +
  • 1.5
  • +
    + +
  • 1.6
  • +
    + +
    \ No newline at end of file diff --git a/MoveBase.csproj b/MoveBase.csproj deleted file mode 100644 index bc8a174..0000000 --- a/MoveBase.csproj +++ /dev/null @@ -1,134 +0,0 @@ - - - - - Debug - AnyCPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F} - Library - Properties - MoveBase - MoveBase - v4.7.2 - 512 - true - - - true - portable - false - G:\SteamLibrary\steamapps\common\RimWorld\Mods\MoveBase\v1.1\Assemblies\ - DEBUG;TRACE - prompt - 4 - G:\SteamLibrary\steamapps\common\RimWorld\Mods\MoveBase\v1.1\Assemblies\MoveBase.xml - - - pdbonly - true - G:\SteamLibrary\steamapps\common\RimWorld\Mods\MoveBase\v1.1\Assemblies\ - TRACE - prompt - 4 - G:\SteamLibrary\steamapps\common\RimWorld\Mods\MoveBase\v1.1\Assemblies\MoveBase.xml - - - false - - - - G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\Assembly-CSharp.dll - False - - - - - - - - - - - - G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.dll - False - - - G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll - False - - - G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll - False - - - G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.InputLegacyModule.dll - False - - - G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {5d54e978-4aec-45e5-86ee-4e30216a445e} - RimWorldUtility - False - - - - - - - - runtime - 2.0.1 - - - - - - call "$(SolutionDir)SolutionItems\Deploy.bat" "$(ProjectDir)" "$(OutDir)" $(Configuration) - xcopy /i /e /d /y "$(SolutionDir)src\About" "$(OutDir)..\..\About" - copy /d /y "$(SolutionDir)src\LoadFolders.xml" "$(OutDir)..\..\LoadFolders.xml" - - \ No newline at end of file diff --git a/MoveBase.sln b/MoveBase.sln deleted file mode 100644 index 3d2b1a4..0000000 --- a/MoveBase.sln +++ /dev/null @@ -1,64 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoveBase", "MoveBase.csproj", "{854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{D1B3ACA6-F0CD-4CBE-8D1B-6E1DD6E46E71}" - ProjectSection(SolutionItems) = preProject - SolutionItems\Deploy.bat = SolutionItems\Deploy.bat - Directory.Build.targets = Directory.Build.targets - EndProjectSection -EndProject -Project("{911E67C6-3D85-4FCE-B560-20A9C3E3FF48}") = "RimWorldWin64", "G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64.exe", "{923566DE-2349-46F6-89D3-55199CF49AB0}" - ProjectSection(DebuggerProjectSystem) = preProject - PortSupplier = 00000000-0000-0000-0000-000000000000 - Executable = G:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64.exe - RemoteMachine = DESKTOP-WDHELIX - StartingDirectory = G:\SteamLibrary\steamapps\common\RimWorld - Environment = dnspy_unity_dbg2=--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:56000,suspend=y,no-hide-debugger - LaunchingEngine = 00000000-0000-0000-0000-000000000000 - UseLegacyDebugEngines = No - LaunchSQLEngine = No - AttachLaunchAction = No - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RimWorldUtility", "..\RimWorldUtility\RimWorldUtility.csproj", "{5D54E978-4AEC-45E5-86EE-4E30216A445E}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Debug|x64.ActiveCfg = Debug|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Debug|x64.Build.0 = Debug|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Release|Any CPU.Build.0 = Release|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Release|x64.ActiveCfg = Release|Any CPU - {854D4C2A-CBD0-4FF3-B8E8-0DA655889B8F}.Release|x64.Build.0 = Release|Any CPU - {923566DE-2349-46F6-89D3-55199CF49AB0}.Debug|Any CPU.ActiveCfg = Release|x64 - {923566DE-2349-46F6-89D3-55199CF49AB0}.Debug|x64.ActiveCfg = Release|x64 - {923566DE-2349-46F6-89D3-55199CF49AB0}.Release|Any CPU.ActiveCfg = Release|x64 - {923566DE-2349-46F6-89D3-55199CF49AB0}.Release|x64.ActiveCfg = Release|x64 - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Debug|x64.ActiveCfg = Debug|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Debug|x64.Build.0 = Debug|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Release|Any CPU.Build.0 = Release|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Release|x64.ActiveCfg = Release|Any CPU - {5D54E978-4AEC-45E5-86EE-4E30216A445E}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {4DDA6E0C-53F7-4497-820F-F54CDAC97E77} - EndGlobalSection -EndGlobal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index 90bc8d3..0000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MoveBase")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MoveBase")] -[assembly: AssemblyCopyright("Copyright © 2020 Zizhen Li")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("854d4c2a-cbd0-4ff3-b8e8-0da655889b8f")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md index 13262bf..1a16d8c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# MoveBase -A RimWorld mod that helps move buildings around. That's about it :slightly_smiling_face: +# Home Mover + +A RimWorld mod for moving minifiable buildings to a new location. Handles groups of buildings at once, with rotation support and smart placement ordering. + +## Features + +- Select multiple buildings and move them as a group +- Rotation support during placement +- Handles roof removal for buildings that support roofs +- **Smart dependency placement** — wall attachments (lights, ACs, vents, coolers) and power conduits are placed *after* the structures they attach to, preventing most "must be placed against a wall" errors +- **Skip-on-error mode** — items that can't be placed are skipped rather than aborting the entire move; a message lists what was skipped and why +- **Copy floor types** — queues construction of matching floor tiles at the destination + +## Settings + +All options are available under Mod Settings → Home Mover: + +| Setting | Default | Description | +|---|---|---| +| Smart dependency placement | ON | Places wall attachments and conduits after structures | +| Copy floor types | ON | Queues floor construction at the destination to match the source | +| Skip items with errors | ON | Skips unplaceable items instead of cancelling the whole move | +| Show message for skipped items | ON | Shows a notification listing skipped items and reasons | +| Enable debug logging | OFF | Logs placement details to the dev console (requires dev mode) | + +## Known Limitations + +- **Floors and roofs** are terrain, not buildings — they cannot be selected or moved directly. The "Copy floor types" setting will queue construction of matching floors at the destination. +- **Wall attachments** (lights, ACs, vents) require their wall to exist at the destination. Smart dependency placement handles this automatically when walls are moved in the same group. If moving attachments to an already-built wall elsewhere, it works as normal. +- **Power conduits** — effects may not transfer properly after moving. Verify connections afterwards. +- **Items with complex multi-step dependencies** may need to be moved in stages. + +## Requirements + +- [Minify Everything](https://steamcommunity.com/sharedfiles/filedetails/?id=836912371) (to move non-minifiable buildings) + +## Credits + +- Original mod by NotTooShabbySoftware +- Current maintainer: Jellypowered +- Inspiration from [Multi-Reinstall](https://steamcommunity.com/sharedfiles/filedetails/?id=2048885052) by oelsart +- Chinese translation by 不久 diff --git a/SolutionItems/Deploy.bat b/SolutionItems/Deploy.bat deleted file mode 100644 index f7e10bd..0000000 --- a/SolutionItems/Deploy.bat +++ /dev/null @@ -1,32 +0,0 @@ -REM @echo off -REM %1 should be the $(ProjectDir) macro in msbuild. -REM %2 should be the $(OutDir) macro in msbuild. - -SET _src=src\ -SET _common=Common\ -SET _defs=Defs -IF EXIST "%~dp1%_src%%_defs%" ( - xcopy /i /e /d /y "%~dp1%_src%%_defs%" "%~dp2..\..\%_common%%_defs%" -) - -SET _languages=Languages -IF EXIST "%~dp1%_src%%_languages%" ( - xcopy /i /e /d /y "%~dp1%_src%%_languages%" "%~dp2..\..\%_common%%_languages%" -) - -SET _patches=Patches -IF EXIST "%~dp1%_src%%_patches%" ( - xcopy /i /e /d /y "%~dp1%_src%%_patches%" "%~dp2..\..\%_common%%_patches%" -) - -SET _textures=Textures -IF EXIST "%~dp1%_src%%_textures%" ( - xcopy /i /e /d /y "%~dp1%_src%%_textures%" "%~dp2..\..\%_common%%_textures%" -) - -SET _assemblies=Assemblies -IF NOT "%3"=="Debug" ( - IF EXIST "%~dp2*.pdb" ( - DEL /q "%~dp2*.pdb" - ) -) \ No newline at end of file diff --git a/src/common/GameSaveComponent.cs b/Source/Core/GameSaveComponent.cs similarity index 52% rename from src/common/GameSaveComponent.cs rename to Source/Core/GameSaveComponent.cs index bb9051c..9ce2792 100644 --- a/src/common/GameSaveComponent.cs +++ b/Source/Core/GameSaveComponent.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using RimWorld; -using RimWorldUtility; using Verse; -namespace MoveBase +namespace HomeMover { public class GameSaveComponent : GameComponent { @@ -15,22 +9,32 @@ public class GameSaveComponent : GameComponent private static int _lastUpdateTick = 0; - private Mod _mod; - - public GameSaveComponent(Game game) - { - _mod = LoadedModManager.GetMod(); - } + public GameSaveComponent(Game game) { } public override void ExposeData() { if (Scribe.mode == LoadSaveMode.LoadingVars) { - DesignatorMoveBase.ClearCache(); + DesignatorHomeMover.ClearCache(); + RoofUtility.ClearCache(); _lastUpdateTick = 0; } - DesignatorMoveBase.ExposeData(); + DesignatorHomeMover.ExposeData(); + } + + public override void StartedNewGame() + { + base.StartedNewGame(); + DesignatorHomeMover.ClearCache(); + RoofUtility.ClearCache(); + _lastUpdateTick = 0; + } + + public override void LoadedGame() + { + base.LoadedGame(); + RoofUtility.ClearCache(); } public override void GameComponentTick() @@ -42,24 +46,15 @@ public override void GameComponentTick() } else { - DesignatorMoveBase.PlaceWaitingBuildings(); + DesignatorHomeMover.PlaceWaitingBuildings(); _lastUpdateTick += UpdateInterval; - PerfProfile.OutputLog(); } } public override void FinalizeInit() { base.FinalizeInit(); - - foreach (FeatureNews news in MoveBaseMod.Setting.FeatureNews) - { - if (!news.Received && news.ReleaseDate > MoveBaseMod.CreationTime) - { - Find.LetterStack.ReceiveLetter(new FeatureUpdateLetter(news, _mod)); - } - } } } } diff --git a/Source/Core/HomeMoverDefOf.cs b/Source/Core/HomeMoverDefOf.cs new file mode 100644 index 0000000..355e652 --- /dev/null +++ b/Source/Core/HomeMoverDefOf.cs @@ -0,0 +1,16 @@ +using RimWorld; +using Verse; + +namespace HomeMover +{ + [DefOf] + public static class HomeMoverDefOf + { + public static DesignationDef HomeMover; + + static HomeMoverDefOf() + { + DefOfHelper.EnsureInitializedInCtor(typeof(HomeMoverDefOf)); + } + } +} diff --git a/Source/Core/HomeMoverMod.cs b/Source/Core/HomeMoverMod.cs new file mode 100644 index 0000000..d3e18e0 --- /dev/null +++ b/Source/Core/HomeMoverMod.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using RimWorld; +using Verse; + +namespace HomeMover +{ + public class HomeMoverMod : Mod + { + private static FieldInfo _rootdir = typeof(ModContentPack).GetField( + "rootDirInt", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + public static DateTime CreationTime; + public static HomeMoverSetting Setting { get; private set; } + + private static List queuedLogs = new List(); + private static bool readyToLog = false; + + // Deduplication: suppress identical messages logged within this many ticks. + private const int DebugLogThrottleTicks = 300; // ~5 seconds at 60 tps + private static readonly Dictionary _lastLoggedTick = new Dictionary(); + + public HomeMoverMod(ModContentPack content) + : base(content) + { + Setting = GetSettings(); + CreationTime = (_rootdir.GetValue(this.Content) as DirectoryInfo).CreationTimeUtc; + + readyToLog = true; + FlushQueuedLogs(); + + Log.Message($"[HomeMover] {BuildVersion.Version} Mod initialized."); + } + + public override string SettingsCategory() => UIText.Label.TranslateSimple(); + + public override void DoSettingsWindowContents(UnityEngine.Rect inRect) + { + base.DoSettingsWindowContents(inRect); + Setting.DoSettingsWindowContents(inRect); + } + + /// + /// Safe debug logger: respects dev mode and mod setting. + /// Identical messages are throttled to once per 300 ticks to prevent log spam. + /// + public static void DebugLog(string msg) + { + if (!readyToLog) + { + queuedLogs.Add(msg); + return; + } + + if (!Prefs.DevMode || !(Setting?.enableDebugLogging ?? false)) + return; + + int now = Current.Game?.tickManager?.TicksGame ?? 0; + if (_lastLoggedTick.TryGetValue(msg, out int lastTick) && now - lastTick < DebugLogThrottleTicks) + return; + + _lastLoggedTick[msg] = now; + Log.Message($"[HomeMover] {msg}"); + } + + private static void FlushQueuedLogs() + { + if (Prefs.DevMode && (Setting?.enableDebugLogging ?? false)) + { + foreach (var msg in queuedLogs) + { + Log.Message($"[HomeMover] {msg}"); + } + } + queuedLogs.Clear(); + } + } +} diff --git a/Source/Core/HomeMoverSetting.cs b/Source/Core/HomeMoverSetting.cs new file mode 100644 index 0000000..116f8d4 --- /dev/null +++ b/Source/Core/HomeMoverSetting.cs @@ -0,0 +1,94 @@ +using Verse; + +namespace HomeMover +{ + public class HomeMoverSetting : ModSettings + { + public bool enableDebugLogging = false; + public bool skipItemsWithErrors = true; + public bool showSkippedItemsMessage = true; + public bool smartDependencyPlacement = true; + public bool copyFloorTypes = true; + public bool handleObstructions = true; + public bool autoMinifyBlockers = true; + public bool warnNonMinifiable = true; + public bool queueDestinationRoof = true; + public bool allowThickRoofMoves = false; + + public override void ExposeData() + { + Scribe_Values.Look(ref enableDebugLogging, "enableDebugLogging", false); + Scribe_Values.Look(ref skipItemsWithErrors, "skipItemsWithErrors", true); + Scribe_Values.Look(ref showSkippedItemsMessage, "showSkippedItemsMessage", true); + Scribe_Values.Look(ref smartDependencyPlacement, "smartDependencyPlacement", true); + Scribe_Values.Look(ref copyFloorTypes, "copyFloorTypes", true); + Scribe_Values.Look(ref handleObstructions, "handleObstructions", true); + Scribe_Values.Look(ref autoMinifyBlockers, "autoMinifyBlockers", true); + Scribe_Values.Look(ref warnNonMinifiable, "warnNonMinifiable", true); + Scribe_Values.Look(ref queueDestinationRoof, "queueDestinationRoof", true); + Scribe_Values.Look(ref allowThickRoofMoves, "allowThickRoofMoves", false); + } + + public void DoSettingsWindowContents(UnityEngine.Rect inRect) + { + Listing_Standard listing = new Listing_Standard(); + listing.Begin(inRect); + listing.CheckboxLabeled( + UIText.SettingsSmartDependencyLabel.TranslateSimple(), + ref smartDependencyPlacement, + UIText.SettingsSmartDependencyDesc.TranslateSimple() + ); + listing.CheckboxLabeled( + UIText.SettingsSkipItemsLabel.TranslateSimple(), + ref skipItemsWithErrors, + UIText.SettingsSkipItemsDesc.TranslateSimple() + ); + if (skipItemsWithErrors) + { + listing.CheckboxLabeled( + UIText.SettingsShowSkippedLabel.TranslateSimple(), + ref showSkippedItemsMessage, + UIText.SettingsShowSkippedDesc.TranslateSimple() + ); + } + listing.CheckboxLabeled( + UIText.SettingsCopyFloorsLabel.TranslateSimple(), + ref copyFloorTypes, + UIText.SettingsCopyFloorsDesc.TranslateSimple() + ); + listing.CheckboxLabeled( + UIText.SettingsHandleObstructionsLabel.TranslateSimple(), + ref handleObstructions, + UIText.SettingsHandleObstructionsDesc.TranslateSimple() + ); + if (handleObstructions) + { + listing.CheckboxLabeled( + UIText.SettingsAutoUninstallLabel.TranslateSimple(), + ref autoMinifyBlockers, + UIText.SettingsAutoUninstallDesc.TranslateSimple() + ); + listing.CheckboxLabeled( + UIText.SettingsWarnNonMinifiableLabel.TranslateSimple(), + ref warnNonMinifiable, + UIText.SettingsWarnNonMinifiableDesc.TranslateSimple() + ); + } + listing.CheckboxLabeled( + UIText.SettingsQueueRoofLabel.TranslateSimple(), + ref queueDestinationRoof, + UIText.SettingsQueueRoofDesc.TranslateSimple() + ); + listing.CheckboxLabeled( + UIText.SettingsAllowThickRoofLabel.TranslateSimple(), + ref allowThickRoofMoves, + UIText.SettingsAllowThickRoofDesc.TranslateSimple() + ); + listing.CheckboxLabeled( + UIText.SettingsDebugLoggingLabel.TranslateSimple(), + ref enableDebugLogging + ); + listing.End(); + } + } +} diff --git a/Source/Core/UIText.cs b/Source/Core/UIText.cs new file mode 100644 index 0000000..7d307d6 --- /dev/null +++ b/Source/Core/UIText.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HomeMover +{ + public static class UIText + { + public const string Description = "MoveBase_Description"; + + public const string Label = "MoveBase_Label"; + + public const string ItemsSkipped = "MoveBase_ItemsSkipped"; + + public const string ItemsSkippedTitle = "MoveBase_ItemsSkippedTitle"; + + public const string PlacementError = "MoveBase_PlacementError"; + + public const string FloorsQueued = "MoveBase_FloorsQueued"; + + public const string BlockerCannotBeMoved = "MoveBase_BlockerCannotBeMoved"; + + public const string BlockerNotMinifiable = "MoveBase_BlockerNotMinifiable"; + + public const string ItemNotMinifiable = "MoveBase_ItemNotMinifiable"; + + public const string ThickRoofBlocked = "MoveBase_ThickRoofBlocked"; + + public const string SettingsSmartDependencyLabel = "MoveBase_SettingsSmartDependencyLabel"; + public const string SettingsSmartDependencyDesc = "MoveBase_SettingsSmartDependencyDesc"; + public const string SettingsSkipItemsLabel = "MoveBase_SettingsSkipItemsLabel"; + public const string SettingsSkipItemsDesc = "MoveBase_SettingsSkipItemsDesc"; + public const string SettingsShowSkippedLabel = "MoveBase_SettingsShowSkippedLabel"; + public const string SettingsShowSkippedDesc = "MoveBase_SettingsShowSkippedDesc"; + public const string SettingsCopyFloorsLabel = "MoveBase_SettingsCopyFloorsLabel"; + public const string SettingsCopyFloorsDesc = "MoveBase_SettingsCopyFloorsDesc"; + public const string SettingsHandleObstructionsLabel = "MoveBase_SettingsHandleObstructionsLabel"; + public const string SettingsHandleObstructionsDesc = "MoveBase_SettingsHandleObstructionsDesc"; + public const string SettingsAutoUninstallLabel = "MoveBase_SettingsAutoUninstallLabel"; + public const string SettingsAutoUninstallDesc = "MoveBase_SettingsAutoUninstallDesc"; + public const string SettingsWarnNonMinifiableLabel = "MoveBase_SettingsWarnNonMinifiableLabel"; + public const string SettingsWarnNonMinifiableDesc = "MoveBase_SettingsWarnNonMinifiableDesc"; + public const string SettingsQueueRoofLabel = "MoveBase_SettingsQueueRoofLabel"; + public const string SettingsQueueRoofDesc = "MoveBase_SettingsQueueRoofDesc"; + public const string SettingsAllowThickRoofLabel = "MoveBase_SettingsAllowThickRoofLabel"; + public const string SettingsAllowThickRoofDesc = "MoveBase_SettingsAllowThickRoofDesc"; + public const string SettingsDebugLoggingLabel = "MoveBase_SettingsDebugLoggingLabel"; + + public const string OverlayItems = "MoveBase_OverlayItems"; + public const string OverlayNonMinifiable = "MoveBase_OverlayNonMinifiable"; + public const string OverlayObstructions = "MoveBase_OverlayObstructions"; + public const string OverlayThickRoofOn = "MoveBase_OverlayThickRoofOn"; + public const string OverlayThickRoofOff = "MoveBase_OverlayThickRoofOff"; + } +} diff --git a/Source/Designator/DesignatorHomeMover.cs b/Source/Designator/DesignatorHomeMover.cs new file mode 100644 index 0000000..68134d7 --- /dev/null +++ b/Source/Designator/DesignatorHomeMover.cs @@ -0,0 +1,1635 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace HomeMover +{ + /// + /// Designator for Home Mover. + /// + [StaticConstructorOnStartup] + public class DesignatorHomeMover : Designator + { + private static readonly MethodInfo _setBuildingToReinstall = + typeof(Blueprint_Install).GetMethod( + "SetBuildingToReinstall", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + private static Texture2D _icon; + + private static Rot4 _rotation = Rot4.North; + private static bool _originFound = false; + private static IntVec3 _origin = IntVec3.Zero; + private static HashSet _selectedCells = new HashSet(); + private static Dictionary _ghostPos = new Dictionary(); + private static Dictionary _floorPattern = new Dictionary(); + private static HashSet _nonMinifiableThings = new HashSet(); + private static List _removeRoofModels = new List(); + + private static Mode _mode = Mode.Select; + + private const int RotationControlWindowId = 743256897; + + /// + /// Gets or sets whether designation should be kept when the designator is deselected. + /// + public bool KeepDesignation { get; set; } = false; + + /// + /// Gets or sets a list of designated things. + /// + public List DesignatedThings { get; set; } = new List(); + + /// + /// Dimension used by desigator when drag. + /// + public override DrawStyleCategoryDef DrawStyleCategory => DrawStyleCategoryDefOf.Orders; + + /// + /// Gets the def for this designation. + /// + protected override DesignationDef Designation => HomeMoverDefOf.HomeMover; + + enum Mode + { + Select, + Place, + } + + /// + /// Create instance of DesignatorMovebase. + /// + public DesignatorHomeMover() + { + this.icon = _icon ?? (_icon = ContentFinder.Get("UI/Designations/MoveBase")); + this.useMouseIcon = true; + this.defaultDesc = UIText.Description.TranslateSimple(); + this.defaultLabel = UIText.Label.TranslateSimple(); + } + + public static void PlaceWaitingBuildings() + { + GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Place; + foreach (RemoveRoofModel model in _removeRoofModels) + { + HashSet moveGroup = model.DesignatedThings?.ToHashSet() ?? new HashSet(); + + foreach (Thing thingToProcess in model.WaitingThings.ToList()) + { + if (thingToProcess.DestroyedOrNull() || thingToProcess.MapHeld != model.Map) + { + model.WaitingThings.Remove(thingToProcess); + continue; + } + + // Start with the thing as-is, but we may force-minify it + Thing thingToPlace = thingToProcess; + + IntVec3 deltaCell = GetDeltaCell(thingToPlace, model.MousePos, model.GhostPosition); + + Thing inner = thingToPlace.GetInnerIfMinified(); + if ( + GenConstruct + .CanPlaceBlueprintAt( + inner.def, + deltaCell, + inner.Rotate(model.Rotation), + inner.MapHeld, + thing: inner + ) + .Accepted + ) + { + // For non-minifiable buildings in the selection, force-minify them so they can be moved + if (!(thingToPlace is MinifiedThing) && inner is Building building && !building.def.Minifiable) + { + try + { + MinifiedThing forceMinified = MinifyUtility.MakeMinified(building); + if (forceMinified != null) + { + thingToPlace = forceMinified; + inner = forceMinified.GetInnerIfMinified(); + HomeMoverMod.DebugLog($"Force-minified non-minifiable building: {building.LabelCap}"); + } + else + { + model.WaitingThings.Remove(thingToProcess); + if (HomeMoverMod.Setting.warnNonMinifiable) + { + Messages.Message( + UIText.ItemNotMinifiable.Translate(building.LabelCap), + building, + MessageTypeDefOf.CautionInput + ); + } + continue; + } + } + catch (Exception ex) + { + HomeMoverMod.DebugLog($"Failed to force-minify {building.LabelCap}: {ex.Message}"); + model.WaitingThings.Remove(thingToProcess); + if (HomeMoverMod.Setting.warnNonMinifiable) + { + Messages.Message( + UIText.ItemNotMinifiable.Translate(building.LabelCap), + building, + MessageTypeDefOf.CautionInput + ); + } + continue; + } + } + + // ?? Despawn it first + if (thingToPlace.Spawned) + { + thingToPlace.DeSpawn(DestroyMode.Vanish); + } + if (thingToPlace is MinifiedThing minifiedThing) + GenConstruct.PlaceBlueprintForInstall( + minifiedThing, + deltaCell, + minifiedThing.MapHeld, + inner.Rotate(model.Rotation), + Faction.OfPlayer + ); + else + GenConstruct.PlaceBlueprintForReinstall( + thingToPlace as Building, + deltaCell, + thingToPlace.MapHeld, + thingToPlace.Rotate(model.Rotation), + Faction.OfPlayer + ); + + model.WaitingThings.Remove(thingToProcess); + } + else + { + ObstructionHandler.HandleObstructionsAt( + inner, + deltaCell, + inner.Rotate(model.Rotation), + inner.MapHeld, + moveGroup + ); + } + } + } + } + + /// + /// Clear cache of roof to remove. + /// + public static void ClearCache() + { + _removeRoofModels.Clear(); + } + + /// + /// Save state. + /// + public static void ExposeData() + { + if (Scribe.mode == LoadSaveMode.Saving) + RemoveEmptyCache(); + + Scribe_Collections.Look( + ref _removeRoofModels, + nameof(_removeRoofModels), + LookMode.Deep + ); + _removeRoofModels = _removeRoofModels ?? new List(); + } + + /// + /// It should be invoked when a building is removed from designation. + /// + /// Building is being removed from designation. + public static void Notify_Removing_Callback(Thing thing) + { + if (!(thing is Building building) || !building.def.holdsRoof || !building.Spawned) + return; + + foreach (RemoveRoofModel model in _removeRoofModels) + { + if (model.BuildingsToReinstall.Contains(building)) + { + IntVec3 pos = thing.Position; + List workingSet = new List(model.RoofToRemove); + foreach (IntVec3 roof in workingSet) + { + if (pos.InHorDistOf(roof, RoofCollapseUtility.RoofMaxSupportDistance)) + { + model.RoofToRemove.Remove(roof); + model.Map.areaManager.NoRoof[roof] = false; + } + } + + model.BuildingsToReinstall.Remove(building); + + break; + } + } + } + + /// + /// Add building to cache when a building is removed and being in transport/reinstallation by a pawn. + /// + /// Building under transportation or reinstallation. + public static void AddBeingReinstalledBuilding(Building building) + { + if (building == null) + return; + + foreach (RemoveRoofModel model in _removeRoofModels) + { + if (model.BuildingsToReinstall.Contains(building)) + { + model.BuildingsBeingReinstalled.Add(building); + break; + } + } + } + + /// + /// Get a list of building being reinstalled that are in the same designation group as . + /// + /// Building in question. + /// A list of buildings that are being reinstalled. + public static HashSet GetBuildingsBeingReinstalled(Building building) + { + if (building == null) + return new HashSet(); + + foreach (RemoveRoofModel model in _removeRoofModels) + { + if (model.BuildingsToReinstall.Contains(building)) + { + return model.BuildingsBeingReinstalled; + } + } + + return new HashSet(); + } + + /// + /// Remove building from cache. + /// + /// Building in question. + public static void RemoveBuildingFromCache(Building building) + { + if (building == null) + return; + + foreach (RemoveRoofModel model in _removeRoofModels) + { + if (model.BuildingsToReinstall.Contains(building)) + { + model.BuildingsBeingReinstalled.Remove(building); + model.BuildingsToReinstall.Remove(building); + if (!model.BuildingsToReinstall.Any() && !model.RoofToRemove.Any()) + { + _removeRoofModels.Remove(model); + } + + break; + } + } + } + + /// + /// This method is invoked when at least one thing is selected by this selector. + /// + protected override void FinalizeDesignationSucceeded() + { + IncludeConduitsFromSelectedCells(); + + if (SelectionHasThickRoof() && !HomeMoverMod.Setting.allowThickRoofMoves) + { + Messages.Message(UIText.ThickRoofBlocked.Translate(), MessageTypeDefOf.RejectInput); + this.KeepDesignation = false; + _mode = Mode.Select; + Find.DesignatorManager.Deselect(); + return; + } + + _mode = Mode.Place; + } + + /// + /// This method is invoked if this selector is selected. + /// + public override void Selected() + { + _mode = Mode.Select; + _originFound = false; + _origin = IntVec3.Zero; + _rotation = Rot4.North; + DesignatedThings.Clear(); + _selectedCells = new HashSet(); + _ghostPos = new Dictionary(); + _floorPattern = new Dictionary(); + _nonMinifiableThings = new HashSet(); + this.KeepDesignation = false; + } + + /// + /// Rotation control. Code copied from vanilla. + /// + /// X position on screen. + /// Bottom Y position on screen. + public override void DoExtraGuiControls(float leftX, float bottomY) + { + Rect winRect = new Rect(leftX, bottomY - 90f, 200f, 90f); + Find.WindowStack.ImmediateWindow( + RotationControlWindowId, + winRect, + WindowLayer.GameUI, + delegate + { + RotationDirection rotationDirection = RotationDirection.None; + Text.Anchor = TextAnchor.MiddleCenter; + Text.Font = GameFont.Medium; + Rect rect = new Rect(winRect.width / 2f - 64f - 5f, 15f, 64f, 64f); + if (Widgets.ButtonImage(rect, TexUI.RotLeftTex)) + { + SoundDefOf.DragSlider.PlayOneShotOnCamera(); + rotationDirection = RotationDirection.Counterclockwise; + Event.current.Use(); + } + Widgets.Label(rect, KeyBindingDefOf.Designator_RotateLeft.MainKeyLabel); + Rect rect2 = new Rect(winRect.width / 2f + 5f, 15f, 64f, 64f); + if (Widgets.ButtonImage(rect2, TexUI.RotRightTex)) + { + SoundDefOf.DragSlider.PlayOneShotOnCamera(); + rotationDirection = RotationDirection.Clockwise; + Event.current.Use(); + } + Widgets.Label(rect2, KeyBindingDefOf.Designator_RotateRight.MainKeyLabel); + if (rotationDirection != 0) + { + foreach (Thing thing in DesignatedThings) + { + _ghostPos[thing] = this.VectorRotation( + _ghostPos[thing], + rotationDirection, + thing + ); + } + _rotation.Rotate(rotationDirection); + } + Text.Anchor = TextAnchor.UpperLeft; + Text.Font = GameFont.Small; + } + ); + + if (_mode == Mode.Place && DesignatedThings.Any()) + { + Rect summaryRect = new Rect(leftX, bottomY - 220f, 380f, 120f); + Find.WindowStack.ImmediateWindow( + RotationControlWindowId + 1, + summaryRect, + WindowLayer.GameUI, + delegate + { + Widgets.DrawMenuSection(summaryRect.AtZero()); + Rect contentRect = summaryRect.AtZero().ContractedBy(8f); + int nonMinifiableCount = _nonMinifiableThings.Count; + var (obstructionCount, blockerSummary) = GetBlockerDetails(UI.MouseCell()); + string summary = + UIText.OverlayItems.Translate(DesignatedThings.Count) + "\n" + + UIText.OverlayNonMinifiable.Translate(nonMinifiableCount) + "\n" + + UIText.OverlayObstructions.Translate(obstructionCount) + "\n" + + (obstructionCount > 0 ? "Blockers: " + blockerSummary + "\n" : "") + + ( + HomeMoverMod.Setting.allowThickRoofMoves + ? UIText.OverlayThickRoofOn.TranslateSimple() + : UIText.OverlayThickRoofOff.TranslateSimple() + ); + Widgets.Label(contentRect, summary); + } + ); + } + } + + /// + /// What should be drawn when the frame is being updated.. + /// + public override void SelectedUpdate() + { + GenDraw.DrawNoBuildEdgeLines(); + if (ArchitectCategoryTab.InfoRect.Contains(UI.MousePositionOnUIInverted)) + return; + + this.DrawGhostMatrix(); + + if (_mode == Mode.Place && DesignatedThings.Any()) + { + DrawBlockedCells(UI.MouseCell()); + } + } + + /// + /// Check if can be designated by this designator. + /// + /// Thing in question. + /// Returns true is can be designated. + public override AcceptanceReport CanDesignateThing(Thing t) + { + if (_mode == Mode.Select) + { + Building building = t as Building; + if (building == null) + return false; + + if (building.def.category != ThingCategory.Building) + return false; + + if (!DebugSettings.godMode && building.Faction != Faction.OfPlayer) + { + if (building.Faction != null) + return false; + + if (!building.ClaimableBy(Faction.OfPlayer)) + return false; + } + + if (base.Map.designationManager.DesignationOn(t, Designation) != null) + return false; + + if ( + base.Map.designationManager.DesignationOn(t, DesignationDefOf.Deconstruct) + != null + ) + return false; + + return true; + } + + return false; + } + + /// + /// This method is invoked when either a click or a drag from mouse is performed. + /// + /// cell on map. + /// Data model for acceptance. + public override AcceptanceReport CanDesignateCell(IntVec3 loc) + { + if (_mode == Mode.Select) + { + // Always accept the cell during Select if we want to capture floor terrain too — + // floor-only cells (no building) must still be recorded for copyFloorTypes, + // and they must register in _selectedCells so QueueDestinationRoof has the full footprint. + if (HomeMoverMod.Setting.copyFloorTypes) + return true; + + return this.CanDesignateThing(this.TopReinstallableInCell(loc)); + } + else + return this.CanReinstallAllThings(loc); + } + + public override void DesignateSingleCell(IntVec3 c) + { + if (_mode == Mode.Select) + { + List things = this.ReinstallableInCell(c); + if (things.Any()) + { + if (!_originFound) + { + _originFound = true; + _origin = c; + } + + DesignatedThings.AddRange(things); + things.ForEach(thing => DesignateThing(thing)); + } + + _selectedCells.Add(c); + + // Establish origin from a floor-only cell if no building has been clicked yet. + // Floor recording uses _origin, so without this floor patterns from cells before + // the first building would all collapse to offset (0,0,0). + if (!_originFound && HomeMoverMod.Setting.copyFloorTypes) + { + _originFound = true; + _origin = c; + } + + // Record floor terrain if enabled + if (HomeMoverMod.Setting.copyFloorTypes && _originFound) + { + TerrainDef terrain = c.GetTerrain(base.Map); + if (terrain != null && terrain.layerable) + { + IntVec3 offset = c - _origin; + if (!_floorPattern.ContainsKey(offset)) + { + _floorPattern[offset] = terrain; + HomeMoverMod.DebugLog($"Recorded floor: {terrain.defName} at offset {offset}"); + } + } + } + } + else if (_mode == Mode.Place) + { + GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Place; + HashSet placedThings = new HashSet(); + Dictionary twinThings = new Dictionary(); + Dictionary blueprintWork = + new Dictionary(); + Dictionary siblingWork = new Dictionary(); + List<(Thing thing, string reason)> skippedItems = new List<(Thing, string)>(); + + IntVec3 mousePos = UI.MouseCell(); + int floorsPlaced = 0; + bool floorsQueued = false; + + List orderedThings = HomeMoverMod.Setting.smartDependencyPlacement + ? DesignatedThings + .OrderBy(t => t.GetPlacementTier()) + .ThenByDescending(t => t.def.altitudeLayer) + .ToList() + : DesignatedThings.ToList(); + + foreach (Thing designatedThing in orderedThings) + { + if (!floorsQueued && designatedThing.GetPlacementTier() != PlacementTier.Conduit) + { + floorsPlaced += MoveFloors(mousePos); + floorsQueued = true; + } + + IntVec3 drawCell = GetDeltaCell(designatedThing, mousePos, _ghostPos); + List things = drawCell.GetThingList(designatedThing.MapHeld); + bool foundTwin = false; + bool foundSibling = false; + foreach (Thing thingOnCell in things) + { + if (DesignatedThings.Contains(thingOnCell)) + { + if (designatedThing.IdenticalWith(_rotation, thingOnCell)) + { + if ( + blueprintWork.TryGetValue( + thingOnCell, + out Blueprint_Install install + ) + ) + { + Thing twin = this.GetTailInTwinThings( + twinThings, + designatedThing + ); + + _setBuildingToReinstall.Invoke(install, new[] { twin }); + blueprintWork[twin] = install; + blueprintWork.Remove(thingOnCell); + placedThings.Add(twin); + } + else if (siblingWork.TryGetValue(thingOnCell, out IntVec3 position)) + { + Thing twin = this.GetTailInTwinThings( + twinThings, + designatedThing + ); + _ghostPos[twin] = position; + siblingWork.Remove(thingOnCell); + siblingWork[twin] = position; + placedThings.Add(thingOnCell); + } + else + { + twinThings[thingOnCell] = designatedThing; + _ghostPos[designatedThing] = _ghostPos[thingOnCell]; + placedThings.Add(thingOnCell); + } + + this.Map.designationManager.TryRemoveDesignationOn( + thingOnCell, + HomeMoverDefOf.HomeMover + ); + + foundTwin = true; + break; + } + else if (!GenConstruct.BlocksConstruction(designatedThing, thingOnCell)) + { + continue; + } + else + { + foundSibling = true; + Thing twin = this.GetTailInTwinThings(twinThings, designatedThing); + siblingWork[twin] = _ghostPos[twin] = _ghostPos[designatedThing]; + break; + } + } + } + + if (foundTwin || foundSibling) + continue; + + Thing twin1 = this.GetTailInTwinThings(twinThings, designatedThing); + + AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt( + twin1.def, + drawCell, + GetRotation(twin1), + twin1.MapHeld, + false, + null, + twin1 + ); + + if (report.Accepted) + { + Building building = twin1 as Building; + + if (building == null) + { + skippedItems.Add((twin1, UIText.ItemNotMinifiable.Translate(twin1.LabelCap))); + continue; + } + + if (HomeMoverMod.Setting.queueDestinationRoof && building.def.holdsRoof) + { + QueueDestinationRoof(building, drawCell, building.MapHeld); + } + + blueprintWork[building] = GenConstruct.PlaceBlueprintForReinstall( + building, + drawCell, + building.MapHeld, + GetRotation(building), + Faction.OfPlayer + ); + placedThings.Add(building); + HomeMoverMod.DebugLog($"Placed {building.LabelCap} successfully"); + } + else if (HomeMoverMod.Setting.skipItemsWithErrors) + { + // Skip this item and track it + skippedItems.Add((twin1, report.Reason)); + HomeMoverMod.DebugLog($"Skipping {twin1.LabelCap}: {report.Reason}"); + } + else + { + // Old behavior: abort entire operation + Messages.Message( + UIText.PlacementError.Translate(twin1.LabelCap, report.Reason), + MessageTypeDefOf.RejectInput + ); + this.KeepDesignation = true; + _mode = Mode.Select; + Find.DesignatorManager.Deselect(); + return; + } + } + + if (!floorsQueued) + { + floorsPlaced += MoveFloors(mousePos); + floorsQueued = true; + } + + if (floorsPlaced > 0) + { + Messages.Message( + UIText.FloorsQueued.Translate(floorsPlaced), + MessageTypeDefOf.TaskCompletion + ); + } + + // Show message for skipped items if any + if (skippedItems.Any() && HomeMoverMod.Setting.showSkippedItemsMessage) + { + string itemList = string.Join("\n", skippedItems.Select(x => + $"- {x.thing.LabelCap}: {x.reason}" + )); + Messages.Message( + UIText.ItemsSkipped.Translate(itemList), + MessageTypeDefOf.CautionInput + ); + } + + RemoveRoofModel model = InitModel( + DesignatedThings, + DesignatedThings.Except(placedThings).ToList(), + DesignatedThings.First().MapHeld, + mousePos, + _rotation, + _ghostPos + ); + + ResolveDeadLock(model); + + this.KeepDesignation = true; + _mode = Mode.Select; + Find.DesignatorManager.Deselect(); + } + } + + /// + /// In Place mode, only act on the final cell of a drag to prevent ghost blueprints. + /// + public override void DesignateMultiCell(IEnumerable cells) + { + if (_mode == Mode.Place) + { + IntVec3 last = IntVec3.Invalid; + foreach (IntVec3 c in cells) + last = c; + if (last.IsValid) + DesignateSingleCell(last); + } + else + { + base.DesignateMultiCell(cells); + } + } + + public override void DesignateThing(Thing t) + { + if (t == null) + return; + + if (t.Faction != Faction.OfPlayer) + t.SetFaction(Faction.OfPlayer); + + if (t is Building building && !building.def.Minifiable) + _nonMinifiableThings.Add(t); + + base.Map.designationManager.AddDesignation(new Designation(t, this.Designation)); + _ghostPos[t] = t.Position - _origin; + } + + public override void DrawMouseAttachments() + { + if (_mode == Mode.Select) + base.DrawMouseAttachments(); + else + this.DrawGhostMatrix(); + } + + public override void SelectedProcessInput(Event ev) + { + base.SelectedProcessInput(ev); + if (DesignatedThings.Any() && _mode == Mode.Place) + { + HandleRotationShortcuts(); + } + } + + /// + /// Set no roof to false after roof is removed on . + /// + /// + /// When no-roof is true, pawn will try to remove roof on that cell. + public static void SetNoRoofFalse(IntVec3 cell) + { + foreach (RemoveRoofModel model in _removeRoofModels) + { + if (model.RoofToRemove?.Contains(cell) ?? false) + { + model.Map.areaManager.NoRoof[cell] = false; + model.RoofToRemove.Remove(cell); + if (!model.BuildingsToReinstall.Any() && !model.RoofToRemove.Any()) + { + _removeRoofModels.Remove(model); + } + + break; + } + } + } + + public static void AddToRoofToRemove(IntVec3 roof, Thing thing) + { + if ( + HomeMoverMod.Setting.allowThickRoofMoves + && thing?.MapHeld != null + && roof.GetRoof(thing.MapHeld)?.isThickRoof == true + ) + { + return; + } + + _removeRoofModels + .FirstOrDefault(model => model.BuildingsToReinstall.Contains(thing)) + ?.RoofToRemove.Add(roof); + } + + public static void UninstallJobCallback(Building building, Map map) + { + foreach (RemoveRoofModel model in _removeRoofModels) + { + if (model.WaitingThings.Contains(building)) + { + MinifiedThing minifiedThing = (MinifiedThing) + map + .listerThings.ThingsInGroup(ThingRequestGroup.MinifiedThing) + .FirstOrDefault(t => t.GetInnerIfMinified() == building); + model.GhostPosition[minifiedThing] = model.GhostPosition[building]; + model.WaitingThings.Remove(building); + model.WaitingThings.Add(minifiedThing); + } + } + } + + private static void ResolveDeadLock(RemoveRoofModel model) + { + foreach (Thing thing in model.WaitingThings.ToList()) + { + Thing lastFound = thing; + List foundThings = new List() { lastFound }; + + while (true) + { + IntVec3 spawnCell = GetDeltaCell( + lastFound, + model.MousePos, + model.GhostPosition + ); + List rect = GenAdj + .OccupiedRect( + spawnCell, + lastFound.Rotate(model.Rotation), + lastFound.def.size + ) + .ToList(); + if (lastFound.def.hasInteractionCell) + { + rect.Add( + Verse.ThingUtility.InteractionCellWhenAt( + lastFound.def, + spawnCell, + lastFound.Rotate(model.Rotation), + lastFound.MapHeld + ) + ); + } + IEnumerable thingsOnCell = rect.SelectMany(c => + c.GetThingList(lastFound.MapHeld) + .Where(t => t.def.blueprintDef != null && t.def.Minifiable) + ); + lastFound = + thingsOnCell.FirstOrDefault(t => + GenConstruct.BlocksConstruction(lastFound, t) + && model.WaitingThings.Contains(t) + ) + ?? ThingUtility.BlockAdjacentInteractionCell( + lastFound, + spawnCell, + lastFound.Rotate(model.Rotation) + ); + if (lastFound == null || foundThings.Contains(lastFound)) + break; + + foundThings.Add(lastFound); + } + + if (lastFound == null) + { + continue; + } + else + { + thing.Map.designationManager.AddDesignation( + new Designation(thing, DesignationDefOf.Uninstall) + ); + } + } + } + + private Thing GetTailInTwinThings(Dictionary table, Thing thing) + { + while (table.TryGetValue(thing, out Thing tail)) + thing = tail; + + return thing; + } + + private static IntVec3 GetDeltaCell( + Thing thing, + IntVec3 mousePos, + Dictionary ghostPos + ) + { + return new IntVec3( + mousePos.x + ghostPos[thing].x, + mousePos.y, + mousePos.z + ghostPos[thing].z + ); + } + + private static void RemoveEmptyCache() + { + foreach (RemoveRoofModel model in _removeRoofModels.ToList()) + { + if ( + model.BuildingsToReinstall.EnumerableNullOrEmpty() + && model.RoofToRemove.EnumerableNullOrEmpty() + ) + { + _removeRoofModels.Remove(model); + } + } + } + + private List ReinstallableInCell(IntVec3 loc) + { + List things = new List(); + + foreach ( + Thing item in from t in base.Map.thingGrid.ThingsAt(loc) + orderby t.def.altitudeLayer descending + select t + ) + { + if (this.CanDesignateThing(item).Accepted) + things.Add(item); + } + + return things; + } + + private Thing TopReinstallableInCell(IntVec3 loc) + { + foreach ( + Thing item in from t in base.Map.thingGrid.ThingsAt(loc) + orderby t.def.altitudeLayer descending + select t + ) + { + if (this.CanDesignateThing(item).Accepted) + { + return item; + } + } + + return null; + } + + private AcceptanceReport CanReinstallAllThings(IntVec3 mousePos) + { + // If obstruction handling is enabled, be lenient — let placement proceed and handle blockers during actual placement + if (HomeMoverMod.Setting.handleObstructions) + return AcceptanceReport.WasAccepted; + + AcceptanceReport result = AcceptanceReport.WasAccepted; + GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Check; + this.TraverseDesignateThings( + mousePos, + (drawCell, thing) => + { + AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt( + thing.def, + drawCell, + GetRotation(thing), + thing.MapHeld, + false, + null, + thing + ); + if (!report.Accepted) + { + if (_nonMinifiableThings.Contains(thing)) + return false; + + result = report; + return true; + } + + return false; + } + ); + + return result; + } + + private AcceptanceReport CanReinstall(Thing thing, IntVec3 drawCell) + { + if (_nonMinifiableThings.Contains(thing)) + return AcceptanceReport.WasAccepted; + + GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Check; + AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt( + thing.def, + drawCell, + GetRotation(thing), + thing.MapHeld, + false, + null, + thing + ); + + return report; + } + + private static Rot4 GetRotation(Thing thing) + { + if (thing.def.rotatable) + return new Rot4(_rotation.AsInt + thing.Rotation.AsInt); + else + return thing.Rotation; + } + + private static void QueueDestinationRoof(Building sourceBuilding, IntVec3 destination, Map map) + { + if (sourceBuilding?.MapHeld == null || map == null) + return; + + // Translate the entire selection footprint by the move offset so we + // only mark BuildRoof for cells inside the moved region — never beyond it. + IntVec3 moveOffset = destination - sourceBuilding.Position; + + foreach (IntVec3 selectedCell in _selectedCells) + { + RoofDef sourceRoofDef = selectedCell.GetRoof(sourceBuilding.MapHeld); + if (sourceRoofDef == null) + continue; + + if (sourceRoofDef.isThickRoof && HomeMoverMod.Setting.allowThickRoofMoves) + continue; + + IntVec3 targetRoof = selectedCell + moveOffset; + if (!targetRoof.InBounds(map)) + continue; + + map.areaManager.BuildRoof[targetRoof] = true; + } + } + + private void IncludeConduitsFromSelectedCells() + { + if (!_selectedCells.Any()) + return; + + foreach (IntVec3 cell in _selectedCells) + { + foreach (Thing thing in cell.GetThingList(base.Map)) + { + if ( + thing is Building + && thing.def.IsConduitLike() + && !DesignatedThings.Contains(thing) + && this.CanDesignateThing(thing).Accepted + ) + { + DesignatedThings.Add(thing); + DesignateThing(thing); + } + } + } + + // Also pick up wall-mounted items (e.g. wall lamps) that are attached to + // buildings in the selection but may occupy an adjacent cell outside the + // dragged rectangle. We identify them by isAttachment AND by their facing + // direction pointing back toward a selected cell. + foreach (IntVec3 cell in _selectedCells.ToList()) + { + foreach (IntVec3 dir in GenAdj.CardinalDirections) + { + IntVec3 adjCell = cell + dir; + if (!adjCell.InBounds(base.Map)) + continue; + + foreach (Thing thing in adjCell.GetThingList(base.Map)) + { + if ( + thing is Building attached + && attached.def.building?.isAttachment == true + && !DesignatedThings.Contains(attached) + && CanDesignateThing(attached).Accepted + && attached.Position + attached.Rotation.FacingCell == cell + ) + { + HomeMoverMod.DebugLog($"Auto-including wall-mounted item: {attached.LabelCap} at {attached.Position} (attached to wall at {cell})"); + DesignatedThings.Add(attached); + DesignateThing(attached); + } + } + } + } + } + + private bool SelectionHasThickRoof() + { + foreach (Thing thing in DesignatedThings) + { + if (!(thing is Building building) || !building.def.holdsRoof) + continue; + + foreach (IntVec3 cell in building.OccupiedRect()) + { + RoofDef roof = cell.GetRoof(base.Map); + if (roof?.isThickRoof == true) + return true; + } + } + + return false; + } + + private int MoveFloors(IntVec3 mousePos) + { + if (!HomeMoverMod.Setting.copyFloorTypes || !_floorPattern.Any()) + return 0; + + int floorsPlaced = 0; + Map map = base.Map; + + foreach (var kvp in _floorPattern) + { + IntVec3 offset = kvp.Key; + TerrainDef terrainToBuild = kvp.Value; + + IntVec3 floorPos = mousePos + offset; + + if (!floorPos.InBounds(map)) + continue; + + if (terrainToBuild.blueprintDef == null) + continue; + + TerrainDef currentTerrain = floorPos.GetTerrain(map); + if (currentTerrain == terrainToBuild) + continue; + + // Skip if there's already a build blueprint here + bool hasBlueprint = floorPos.GetThingList(map).Any(t => t is Blueprint_Build); + if (hasBlueprint) + continue; + + // Use vanilla's own placement check — it correctly allows floors under beds/tables/etc. + AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt( + terrainToBuild, + floorPos, + Rot4.North, + map + ); + + if (!report.Accepted) + { + HomeMoverMod.DebugLog($"Skipping floor {terrainToBuild.defName} at {floorPos}: {report.Reason}"); + continue; + } + + Blueprint_Build bp = GenConstruct.PlaceBlueprintForBuild( + terrainToBuild, + floorPos, + map, + Rot4.North, + Faction.OfPlayer, + GetDefaultStuffFor(terrainToBuild) + ); + + if (bp != null) + { + floorsPlaced++; + HomeMoverMod.DebugLog($"Placed floor blueprint {terrainToBuild.defName} at {floorPos}"); + + // Designate the source cell for removal — deconstruct to recover materials if + // the terrain has a costList (RimWorld's RemoveFloor job returns a fraction of + // those materials automatically), otherwise just strip. + IntVec3 srcCell = _origin + offset; + if (srcCell != floorPos && srcCell.InBounds(map)) + { + TerrainDef srcTerrain = srcCell.GetTerrain(map); + bool deconstructable = srcTerrain?.costList != null && srcTerrain.costList.Count > 0; + if (srcTerrain == terrainToBuild + && srcTerrain.layerable + && map.designationManager.DesignationAt(srcCell, DesignationDefOf.RemoveFloor) == null) + { + map.designationManager.AddDesignation( + new Designation(srcCell, DesignationDefOf.RemoveFloor) + ); + HomeMoverMod.DebugLog(deconstructable + ? $"Queued floor deconstruction (materials returned) at source {srcCell}" + : $"Queued floor strip (no materials) at source {srcCell}"); + } + } + } + } + + return floorsPlaced; + } + + /// + /// Picks a default stuff ThingDef for a terrain that requires Stuff (e.g. CarpetRed → cloth). + /// Without this, PlaceBlueprintForBuild creates an invalid blueprint that's silently destroyed. + /// + private static ThingDef GetDefaultStuffFor(BuildableDef def) + { + if (def == null || !def.MadeFromStuff) + return null; + + // Prefer the def's own default if available + ThingDef fallback = GenStuff.DefaultStuffFor(def); + if (fallback != null) + return fallback; + + // Otherwise grab any allowed stuff + if (def.stuffCategories != null) + { + foreach (var cat in def.stuffCategories) + { + var stuff = DefDatabase.AllDefsListForReading + .FirstOrDefault(t => t.IsStuff && t.stuffProps?.categories != null && t.stuffProps.categories.Contains(cat)); + if (stuff != null) + return stuff; + } + } + + return null; + } + + private (int count, string summary) GetBlockerDetails(IntVec3 mousePos) + { + HashSet blockerIds = new HashSet(); + Dictionary blockerTypes = new Dictionary(); + HashSet moveGroup = DesignatedThings.ToHashSet(); + + foreach (Thing thing in DesignatedThings) + { + Thing inner = thing.GetInnerIfMinified(); + IntVec3 drawCell = GetDeltaCell(thing, mousePos, _ghostPos); + + foreach (IntVec3 cell in GenAdj.OccupiedRect(drawCell, GetRotation(thing), inner.def.Size)) + { + foreach (Thing blocker in cell.GetThingList(base.Map)) + { + if (blocker == null || blocker.DestroyedOrNull()) + continue; + + if (moveGroup.Contains(blocker)) + continue; + + if (!GenConstruct.BlocksConstruction(inner, blocker)) + continue; + + int id = blocker.thingIDNumber; + if (blockerIds.Add(id)) + { + string typeKey = "Other"; + if (blocker is Plant) + typeKey = "Plant"; + else if (blocker.def.mineable) + typeKey = "Rock"; + else if (blocker is Building) + typeKey = "Building"; + + if (!blockerTypes.ContainsKey(typeKey)) + blockerTypes[typeKey] = 0; + blockerTypes[typeKey]++; + } + } + } + } + + string summary = string.Join(", ", blockerTypes.Select(kvp => $"{kvp.Key}({kvp.Value})")); + return (blockerIds.Count, summary); + } + + private void DrawBlockedCells(IntVec3 mousePos) + { + HashSet blockerIds = new HashSet(); + HashSet moveGroup = DesignatedThings.ToHashSet(); + + foreach (Thing thing in DesignatedThings) + { + Thing inner = thing.GetInnerIfMinified(); + IntVec3 drawCell = GetDeltaCell(thing, mousePos, _ghostPos); + + foreach (IntVec3 cell in GenAdj.OccupiedRect(drawCell, GetRotation(thing), inner.def.Size)) + { + foreach (Thing blocker in cell.GetThingList(base.Map)) + { + if (blocker == null || blocker.DestroyedOrNull()) + continue; + + if (moveGroup.Contains(blocker)) + continue; + + if (!GenConstruct.BlocksConstruction(inner, blocker)) + continue; + + int id = blocker.thingIDNumber; + if (blockerIds.Add(id)) + { + GenDraw.DrawFieldEdges(GenAdj.OccupiedRect(cell, Rot4.North, new IntVec2(1, 1)).ToList(), Color.red); + } + } + } + } + } + + private void DrawGhostMatrix() + { + IntVec3 mousePos = UI.MouseCell(); + this.TraverseDesignateThings( + mousePos, + (drawCell, thing) => + { + this.DrawGhostThing(drawCell, thing); + return false; + } + ); + } + + private void TraverseDesignateThings(IntVec3 mousePos, Func func) + { + foreach (Thing thing in DesignatedThings) + { + if (func(GetDeltaCell(thing, mousePos, _ghostPos), thing)) + break; + } + } + + private void DrawGhostThing(IntVec3 cell, Thing thing) + { + Graphic baseGraphic = thing.Graphic.ExtractInnerGraphicFor(thing); + Color color = this.CanReinstall(thing, cell).Accepted + ? Designator_Place.CanPlaceColor + : Designator_Place.CannotPlaceColor; + try + { + GhostDrawer.DrawGhostThing( + cell, + GetRotation(thing), + thing.def, + baseGraphic, + color, + AltitudeLayer.Blueprint, + thing + ); + } + catch + { + // no op. + } + } + + private IntVec3 VectorRotation( + IntVec3 cell, + RotationDirection rotationDirection, + Thing thing + ) + { + if (thing.def.rotatable || thing.def.size == IntVec2.One) + { + return Rotate(cell); + } + else + { + HomeMoverMod.DebugLog($"Position: {cell}"); + IEnumerable corners = thing + .OccupiedRect() + .Corners.Select(corner => + { + IntVec3 normalized = corner - thing.Position + cell; + HomeMoverMod.DebugLog($"Normalized: {normalized}"); + return normalized; + }) + .Select(Rotate); + + var value = GetCenter(corners.ToList()); + HomeMoverMod.DebugLog($"Return value: {value}"); + return value; + } + + IntVec3 Rotate(IntVec3 pos) + { + switch (rotationDirection) + { + case RotationDirection.Clockwise: + return new IntVec3(pos.z, pos.y, -pos.x); + case RotationDirection.Counterclockwise: + return new IntVec3(-pos.z, pos.y, pos.x); + default: + return pos; + } + } + + IntVec3 GetCenter(List cells) + { + int minX, + minZ, + maxX, + maxZ; + maxX = maxZ = int.MinValue; + minX = minZ = int.MaxValue; + + foreach (IntVec3 c in cells) + { + HomeMoverMod.DebugLog($"Rotated: {c}"); + minX = c.x < minX ? c.x : minX; + minZ = c.z < minZ ? c.z : minZ; + maxX = c.x > maxX ? c.x : maxX; + maxZ = c.z > maxZ ? c.z : maxZ; + } + + return new IntVec3((maxX - minX) / 2 + minX, cells[0].y, (maxZ - minZ) / 2 + minZ); + } + } + + private void HandleRotationShortcuts() + { + RotationDirection rotationDirection = RotationDirection.None; + if (KeyBindingDefOf.Designator_RotateRight.KeyDownEvent) + { + rotationDirection = RotationDirection.Clockwise; + } + else if (KeyBindingDefOf.Designator_RotateLeft.KeyDownEvent) + { + rotationDirection = RotationDirection.Counterclockwise; + } + + if (rotationDirection != RotationDirection.None) + { + foreach (Thing thing in DesignatedThings) + { + _ghostPos[thing] = this.VectorRotation( + _ghostPos[thing], + rotationDirection, + thing + ); + } + _rotation.Rotate(rotationDirection); + SoundDefOf.DragSlider.PlayOneShotOnCamera(); + } + } + + private static RemoveRoofModel InitModel( + List designatedThings, + List waitingThings, + Map map, + IntVec3 mousePos, + Rot4 rotation, + Dictionary ghostPos + ) + { + RemoveRoofModel newModel = new RemoveRoofModel( + designatedThings, + designatedThings + .OfType() + .Where(b => b.def.holdsRoof) + .Where( + b => + !HomeMoverMod.Setting.allowThickRoofMoves + || !b.OccupiedRect() + .Any(c => c.GetRoof(map)?.isThickRoof == true) + ) + .ToHashSet(), + waitingThings, + new HashSet(), + map, + mousePos, + rotation, + ghostPos + ); + _removeRoofModels.Add(newModel); + return newModel; + } + + private class RemoveRoofModel : IExposable + { + private List ghostThings = new List(); + private List ghostPos = new List(); + + public HashSet BuildingsToReinstall = new HashSet(); + + public HashSet BuildingsBeingReinstalled = new HashSet(); + + public List DesignatedThings = new List(); + + public HashSet WaitingThings = new HashSet(); + + public HashSet RoofToRemove = new HashSet(); + + public Dictionary GhostPosition = new Dictionary(); + + public IntVec3 MousePos; + + public Rot4 Rotation; + + public Map Map; + + public RemoveRoofModel() { } + + public RemoveRoofModel( + List designatedThings, + HashSet roofSupporterToReinstall, + IEnumerable waitingThings, + HashSet roofToRemove, + Map map, + IntVec3 mousePos, + Rot4 rotation, + Dictionary ghostPos + ) + { + this.DesignatedThings = designatedThings; + this.BuildingsToReinstall = roofSupporterToReinstall; + this.RoofToRemove = roofToRemove; + this.Map = map; + this.BuildingsBeingReinstalled = new HashSet(); + this.WaitingThings = waitingThings.ToHashSet(); + this.MousePos = mousePos; + this.Rotation = rotation; + this.GhostPosition = ghostPos; + } + + public void ExposeData() + { + if (Scribe.mode == LoadSaveMode.Saving) + { + this.CleanCache(); + } + + Scribe_Collections.Look( + ref this.BuildingsToReinstall, + nameof(this.BuildingsToReinstall), + LookMode.Reference + ); + Scribe_Collections.Look( + ref this.BuildingsBeingReinstalled, + nameof(this.BuildingsBeingReinstalled), + LookMode.Reference + ); + Scribe_Collections.Look( + ref this.RoofToRemove, + nameof(RoofToRemove), + LookMode.Value + ); + Scribe_Collections.Look( + ref this.DesignatedThings, + nameof(this.DesignatedThings), + LookMode.Reference + ); + Scribe_Collections.Look( + ref this.WaitingThings, + nameof(this.WaitingThings), + LookMode.Reference + ); + Scribe_References.Look(ref this.Map, nameof(this.Map)); + Scribe_Values.Look(ref this.MousePos, nameof(this.MousePos)); + Scribe_Values.Look(ref this.Rotation, nameof(this.Rotation)); + + Scribe_Collections.Look( + ref this.GhostPosition, + nameof(this.GhostPosition), + LookMode.Reference, + LookMode.Value, + ref ghostThings, + ref ghostPos + ); + } + + private void CleanCache() + { + RemoveDestroyedThings(this.DesignatedThings); + RemoveDestroyedThings(this.BuildingsBeingReinstalled); + RemoveDestroyedThings(this.BuildingsToReinstall); + RemoveDestroyedThings(this.WaitingThings); + + if (this.GhostPosition is null) + return; + + foreach (Thing key in this.GhostPosition.Keys.ToList()) + { + if (key.Destroyed) + { + this.GhostPosition.Remove(key); + } + } + } + + private static void RemoveDestroyedThings(ICollection things) + where T : Thing + { + if (things.EnumerableNullOrEmpty()) + return; + + foreach (T thing in new List(things)) + { + if (thing.Destroyed) + { + things.Remove(thing); + } + } + } + } + } +} diff --git a/Source/Patches/Blueprint_Destroy_Patch.cs b/Source/Patches/Blueprint_Destroy_Patch.cs new file mode 100644 index 0000000..99143cb --- /dev/null +++ b/Source/Patches/Blueprint_Destroy_Patch.cs @@ -0,0 +1,42 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace HomeMover +{ + [HarmonyPatch(typeof(ThingWithComps), nameof(ThingWithComps.Destroy))] + public static class Blueprint_Destroy_Patch + { + public static void Postfix(ThingWithComps __instance) + { + if ( + __instance is Blueprint_Install blueprint + && blueprint.MiniToInstallOrBuildingToReinstall != null + && blueprint.MiniToInstallOrBuildingToReinstall.MapHeld != null + ) + { + blueprint.MiniToInstallOrBuildingToReinstall.MapHeld.designationManager.TryRemoveDesignationOn( + blueprint.MiniToInstallOrBuildingToReinstall, + HomeMoverDefOf.HomeMover + ); + } + } + } + + [HarmonyPatch(typeof(Blueprint_Install), nameof(Blueprint_Install.TryReplaceWithSolidThing))] + public static class Blueprint_Install_TryReplace_Patch + { + public static void Postfix(Thing createdThing) + { + if ( + createdThing != null + && createdThing is Building building + && building.def != null + && building.def.holdsRoof + ) + { + DesignatorHomeMover.RemoveBuildingFromCache(building); + } + } + } +} diff --git a/Source/Patches/Designation_Notify_Removing_Patch.cs b/Source/Patches/Designation_Notify_Removing_Patch.cs new file mode 100644 index 0000000..b90311d --- /dev/null +++ b/Source/Patches/Designation_Notify_Removing_Patch.cs @@ -0,0 +1,25 @@ +// Intentionally disabled. +// +// The original mod had a Designation.Notify_Removing postfix that called +// InstallBlueprintUtility.CancelBlueprintsFor(thing) whenever a HomeMover +// designation was removed — but it was COMMENTED OUT (never registered) +// in the original source. See modcompat/OriginalMB/HarmonyPatches/Designation_Notify_Removing_Patch.cs. +// +// When we converted patches to [HarmonyPatch]/PatchAll, this one became +// active and started firing on every legitimate designation cleanup +// (notably during MakeMinified, which removes the HomeMover designation +// from the source). The Cancel call there destroyed the destination +// Blueprint_Install — causing the "pairwise blueprint disappearance" bug +// where moving any item killed its destination blueprint. +// +// User-initiated Cancel is already handled by Designator_DesignateThing_Patch +// (Designator_CancelThing_Patch / Designator_CancelSingleCell_Patch). +// This file intentionally has no patches; it remains as a breadcrumb so the +// hook is not re-added. + +namespace HomeMover +{ + internal static class Designation_Notify_Removing_Patch_Disabled + { + } +} diff --git a/Source/Patches/Designator_Deselect_Patch.cs b/Source/Patches/Designator_Deselect_Patch.cs new file mode 100644 index 0000000..7fbf8a8 --- /dev/null +++ b/Source/Patches/Designator_Deselect_Patch.cs @@ -0,0 +1,29 @@ +using System.Linq; +using HarmonyLib; +using RimWorld; +using Verse; + +namespace HomeMover +{ + [HarmonyPatch(typeof(DesignatorManager), nameof(DesignatorManager.Deselect))] + public static class Designator_Deselect_Patch + { + public static void Prefix(DesignatorManager __instance) + { + if ( + __instance.SelectedDesignator is DesignatorHomeMover moveBase + && !moveBase.KeepDesignation + && moveBase.DesignatedThings.Any() + ) + { + foreach (Thing thing in moveBase.DesignatedThings) + { + moveBase.Map.designationManager.TryRemoveDesignationOn( + thing, + HomeMoverDefOf.HomeMover + ); + } + } + } + } +} diff --git a/Source/Patches/Designator_DesignateThing_Patch.cs b/Source/Patches/Designator_DesignateThing_Patch.cs new file mode 100644 index 0000000..07b533f --- /dev/null +++ b/Source/Patches/Designator_DesignateThing_Patch.cs @@ -0,0 +1,47 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace HomeMover +{ + [HarmonyPatch(typeof(Designator_Cancel), nameof(Designator_Cancel.DesignateThing))] + public static class Designator_CancelThing_Patch + { + public static void Prefix(Designator __instance, Thing t) + { + if (__instance is Designator_Cancel cancel) + { + if (t.MapHeld.designationManager.DesignationOn(t, HomeMoverDefOf.HomeMover) != null) + { + DesignatorHomeMover.Notify_Removing_Callback(t); + InstallBlueprintUtility.CancelBlueprintsFor(t); + } + } + } + + } + + [HarmonyPatch(typeof(Designator_Cancel), nameof(Designator_Cancel.DesignateSingleCell))] + public static class Designator_CancelSingleCell_Patch + { + public static void Prefix(Designator __instance, IntVec3 c) + { + if (__instance is Designator_Cancel cancel) + { + foreach (Thing thing in c.GetThingList(__instance.Map)) + { + if ( + thing.MapHeld.designationManager.DesignationOn( + thing, + HomeMoverDefOf.HomeMover + ) != null + ) + { + DesignatorHomeMover.Notify_Removing_Callback(thing); + InstallBlueprintUtility.CancelBlueprintsFor(thing); + } + } + } + } + } +} diff --git a/src/common/HarmonyPatches/GenConstruct_CanPlaceBlueprintAt_Patch.cs b/Source/Patches/GenConstruct_CanPlaceBlueprintAt_Patch.cs similarity index 64% rename from src/common/HarmonyPatches/GenConstruct_CanPlaceBlueprintAt_Patch.cs rename to Source/Patches/GenConstruct_CanPlaceBlueprintAt_Patch.cs index f20e1ae..deaaa13 100644 --- a/src/common/HarmonyPatches/GenConstruct_CanPlaceBlueprintAt_Patch.cs +++ b/Source/Patches/GenConstruct_CanPlaceBlueprintAt_Patch.cs @@ -1,35 +1,30 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Text; -using System.Threading.Tasks; using HarmonyLib; using RimWorld; using Verse; -namespace MoveBase +namespace HomeMover { /// /// Patch CanPlaceBlueprint so players can move buildings to positions where are occupied by other buildings that are also desiganted. /// - [StaticConstructorOnStartup] + [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt))] public static class GenConstruct_CanPlaceBlueprintAt_Patch { - private static PropertyInfo _designatorDef = typeof(Designator).GetProperty("DesignationDef ", BindingFlags.NonPublic | BindingFlags.Instance); - private static MethodInfo _thingInDesignation = - typeof(GenConstruct_CanPlaceBlueprintAt_Patch) - .GetMethod( - nameof(GenConstruct_CanPlaceBlueprintAt_Patch.ThingInDesignation) - , BindingFlags.NonPublic | BindingFlags.Static); + typeof(GenConstruct_CanPlaceBlueprintAt_Patch).GetMethod( + nameof(GenConstruct_CanPlaceBlueprintAt_Patch.ThingInDesignation), + BindingFlags.NonPublic | BindingFlags.Static + ); private static MethodInfo _sameConduit = - typeof(GenConstruct_CanPlaceBlueprintAt_Patch) - .GetMethod( - nameof(GenConstruct_CanPlaceBlueprintAt_Patch.SameConduit) - , BindingFlags.NonPublic | BindingFlags.Static); + typeof(GenConstruct_CanPlaceBlueprintAt_Patch).GetMethod( + nameof(GenConstruct_CanPlaceBlueprintAt_Patch.SameConduit), + BindingFlags.NonPublic | BindingFlags.Static + ); private static object _retPos1; private static object _retPos2; @@ -85,80 +80,86 @@ public static class GenConstruct_CanPlaceBlueprintAt_Patch private static List _patternToMatch5 = new List() { new CodeInstruction(OpCodes.Ldarg_0), - new CodeInstruction(OpCodes.Callvirt, typeof(BuildableDef).GetProperty(nameof(BuildableDef.PlaceWorkers)).GetAccessors()[0]), + new CodeInstruction( + OpCodes.Callvirt, + typeof(BuildableDef).GetProperty(nameof(BuildableDef.PlaceWorkers)).GetAccessors()[ + 0 + ] + ), new CodeInstruction(OpCodes.Brfalse_S), }; + /// + /// Operation mode for . + /// /// /// Operation mode for . /// public static BlueprintMode Mode = BlueprintMode.Check; - static GenConstruct_CanPlaceBlueprintAt_Patch() - { - MethodInfo original = typeof(GenConstruct).GetMethod(nameof(GenConstruct.CanPlaceBlueprintAt), BindingFlags.Static | BindingFlags.Public); - MethodInfo transpiler = typeof(GenConstruct_CanPlaceBlueprintAt_Patch).GetMethod("Transpiler", BindingFlags.Static | BindingFlags.Public); - HarmonyUtility.Instance.Patch(original, transpiler: new HarmonyMethod(transpiler)); - } - /// /// Add a check in the method body so it would return true if thing occupies the current cell is also designated. /// /// /// Sequence of IL after patched. - public static IEnumerable Transpiler(IEnumerable codeInstructions) + public static IEnumerable Transpiler( + IEnumerable codeInstructions + ) { List instructions = codeInstructions.ToList(); for (int i = 0; i < instructions.Count; i++) { yield return instructions[i]; - if (!_matched1 + if ( + !_matched1 && HarmonyUtility.MatchPattern( - instructions - , _patternToMatch1 - , i - , () => + instructions, + _patternToMatch1, + i, + () => { _retPos1 = (Label)instructions[i + _patternToMatch1.Count - 1].operand; _matched1 = true; - })) + } + ) + ) { - //for (int j = 1; j < _patternToMatch1.Count; j++) - //{ - // yield return instructions[i + j]; - //} - - //i += (_patternToMatch1.Count - 1); - - foreach (var c in HarmonyUtility.ReturnPatternMatchedInstruction(_patternToMatch1, instructions, ref i)) + foreach ( + var c in HarmonyUtility.ReturnPatternMatchedInstruction( + _patternToMatch1, + instructions, + ref i + ) + ) yield return c; - yield return new CodeInstruction(OpCodes.Ldloc_S, 8); yield return new CodeInstruction(OpCodes.Call, _thingInDesignation); yield return new CodeInstruction(OpCodes.Brtrue, _retPos1); } - if (!_matched2 + if ( + !_matched2 && HarmonyUtility.MatchPattern( - instructions - , _patternToMatch2 - , i - , () => + instructions, + _patternToMatch2, + i, + () => { _retPos2 = (Label)instructions[i + _patternToMatch2.Count - 1].operand; _matched2 = true; - })) + } + ) + ) { - //for (int j = 1; j < _patternToMatch2.Count; j++) - //{ - // yield return instructions[i + j]; - //} - - //i += (_patternToMatch2.Count - 1); - - foreach (var c in HarmonyUtility.ReturnPatternMatchedInstruction(_patternToMatch2, instructions, ref i)) + foreach ( + var c in HarmonyUtility.ReturnPatternMatchedInstruction( + _patternToMatch2, + instructions, + ref i + ) + ) yield return c; yield return new CodeInstruction(OpCodes.Ldloc_S, 25); @@ -166,18 +167,27 @@ public static IEnumerable Transpiler(IEnumerable + instructions, + _patternToMatch3, + i, + () => { _retPos3 = (Label)instructions[i + _patternToMatch3.Count - 1].operand; _matched3 = true; - })) + } + ) + ) { - foreach (var c in HarmonyUtility.ReturnPatternMatchedInstruction(_patternToMatch3, instructions, ref i)) + foreach ( + var c in HarmonyUtility.ReturnPatternMatchedInstruction( + _patternToMatch3, + instructions, + ref i + ) + ) yield return c; yield return instructions[i - _patternToMatch3.Count + 1]; @@ -187,18 +197,27 @@ public static IEnumerable Transpiler(IEnumerable + instructions, + _patternToMatch4, + i, + () => { _retPos4 = (Label)instructions[i + _patternToMatch4.Count - 1].operand; _matched4 = true; - })) + } + ) + ) { - foreach (var c in HarmonyUtility.ReturnPatternMatchedInstruction(_patternToMatch4, instructions, ref i)) + foreach ( + var c in HarmonyUtility.ReturnPatternMatchedInstruction( + _patternToMatch4, + instructions, + ref i + ) + ) yield return c; yield return instructions[i - _patternToMatch4.Count + 1]; @@ -206,19 +225,27 @@ public static IEnumerable Transpiler(IEnumerable + instructions, + _patternToMatch5, + i, + () => { _retPos5 = (Label)instructions[i + _patternToMatch5.Count - 1].operand; _matched5 = true; - })) + } + ) + ) { - - foreach (var c in HarmonyUtility.ReturnPatternMatchedInstruction(_patternToMatch5, instructions, ref i)) + foreach ( + var c in HarmonyUtility.ReturnPatternMatchedInstruction( + _patternToMatch5, + instructions, + ref i + ) + ) yield return c; yield return new CodeInstruction(OpCodes.Ldarg_0); @@ -232,7 +259,7 @@ public static IEnumerable Transpiler(IEnumerable things = loc.GetThingList(map); if (things.Any(t => t.def == def && ThingInDesignation(t))) return true; @@ -260,6 +293,7 @@ public enum BlueprintMode /// Mode for checking if blueprint can be placed at a cell. /// Check, + /// /// Mode for placing down blueprint on a cell. /// diff --git a/Source/Patches/JobDriver_Uninstall_FinishedRemoving_Patch.cs b/Source/Patches/JobDriver_Uninstall_FinishedRemoving_Patch.cs new file mode 100644 index 0000000..45b0ed5 --- /dev/null +++ b/Source/Patches/JobDriver_Uninstall_FinishedRemoving_Patch.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using HarmonyLib; +using RimWorld; +using Verse; +using Verse.AI; + +namespace HomeMover +{ + [HarmonyPatch(typeof(JobDriver_Uninstall), "FinishedRemoving")] + public static class JobDriver_Uninstall_FinishedRemoving_Patch + { + private static readonly PropertyInfo _building = typeof(JobDriver_RemoveBuilding).GetProperty( + "Building", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + public static void Postfix(JobDriver_Uninstall __instance) + { + DesignatorHomeMover.UninstallJobCallback( + (Building)_building.GetValue(__instance), + __instance.pawn.MapHeld + ); + } + } +} diff --git a/Source/Patches/MinifyUtility_MakeMinified.cs b/Source/Patches/MinifyUtility_MakeMinified.cs new file mode 100644 index 0000000..595b90e --- /dev/null +++ b/Source/Patches/MinifyUtility_MakeMinified.cs @@ -0,0 +1,189 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using RimWorld; +using Verse; + +namespace HomeMover +{ + /// + /// Fixes tick-related exceptions when minifying buildings, particularly doors. + /// Applies early deregistration and despawn if the object has a MoveBase designation. + /// + [StaticConstructorOnStartup] + public static class MinifyUtility_MakeMinified + { + private static readonly FieldInfo _tickListNormal = typeof(TickManager).GetField( + "tickListNormal", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + private static readonly MethodInfo _tickListFor = typeof(TickManager).GetMethod( + "TickListFor", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + private static readonly MethodInfo _bucketOf = typeof(TickList).GetMethod( + "BucketOf", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + [HarmonyPatch(typeof(MinifyUtility), nameof(MinifyUtility.MakeMinified))] + public static class MinifyUtility_MakeMinified_Patch + { + public static bool Prefix(Thing thing, DestroyMode destroyMode, out bool __state) + { + __state = + thing?.MapHeld?.designationManager?.DesignationOn(thing, HomeMoverDefOf.HomeMover) + != null; + + // Mark for door-tick safety only; do NOT despawn here. + // Despawning before vanilla MakeMinified() runs nulls MapHeld and breaks + // InstallBlueprintUtility.CacheBlueprintsFor — orphaning the destination + // Blueprint_Install (causing pairwise blueprint disappearance). + if (__state && thing is Building building && building.Spawned) + { + MarkBeingMinified(building); + } + + return true; // Proceed to original method + } + + public static void Postfix( + Thing thing, + DestroyMode destroyMode, + MinifiedThing __result, + bool __state + ) + { + if (__state && __result != null) + { + ClearBeingMinified(thing); + + // Mirror original mod's approach: scrub the inner thing from any tick bucket + // it may have lingered in (door-tick NRE protection). + try + { + TickList tickList = _tickListFor?.Invoke(Find.TickManager, new object[] { thing }) as TickList; + if (tickList != null) + { + var bucket = _bucketOf?.Invoke(tickList, new object[] { thing }) as List; + bucket?.Remove(thing); + } + } + catch { /* swallow — defensive cleanup only */ } + } + } + } + + /// + /// Prevents NREs when doors are ticking but aren't fully initialized. + /// + [HarmonyPatch(typeof(Building_Door), "Tick")] + public static class Building_Door_Tick_Patch + { + public static bool Prefix(Building_Door __instance) + { + if (MinifyUtility_MakeMinified.IsBeingMinified(__instance)) + { + //MoveBaseMod.DebugLog($"Skipped ticking door {__instance} (currently being minified)."); + return false; + } + + //MoveBaseMod.DebugLog($"Ticking door {__instance} with Map={__instance.Map}, Spawned={__instance.Spawned}"); + + if (__instance.Map == null || !__instance.Spawned) + { + // MoveBaseMod.DebugLog($"Skipped ticking door {__instance} due to null map or unspawned."); + return false; + } + + return true; + } + } + + public static class HomeMover_DelayedCleanup + { + private static readonly List toCleanup = new List(); + + public static void Queue(Thing thing) + { + if (thing != null && !toCleanup.Contains(thing)) + toCleanup.Add(thing); + } + + public static void Tick() + { + if (toCleanup.Count == 0) + return; + + foreach (var thing in toCleanup) + { + if (thing is Building building) + { + MinifyUtility_MakeMinified.DeregisterFromAllTickLists(building); + //MoveBaseMod.DebugLog($"Delayed cleanup for {building}"); + } + } + + toCleanup.Clear(); + } + } + + [HarmonyPatch(typeof(TickManager), nameof(TickManager.DoSingleTick))] + public static class TickManager_DoSingleTick_Patch + { + public static void Postfix() + { + HomeMover_DelayedCleanup.Tick(); + } + } + + private static readonly HashSet beingMinified = new HashSet(); + + public static void MarkBeingMinified(Thing thing) + { + if (thing != null) + beingMinified.Add(thing); + } + + public static bool IsBeingMinified(Thing thing) + { + return thing != null && beingMinified.Contains(thing); + } + + public static void ClearBeingMinified(Thing thing) + { + if (thing != null) + beingMinified.Remove(thing); + } + + /// + /// Safely deregisters a thing from all three tick lists. + /// + public static void DeregisterFromAllTickLists(Thing thing) + { + var tickManager = Find.TickManager; + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + var fieldNames = new[] { "tickListNormal", "tickListRare", "tickListLong" }; + var tickListThingsField = typeof(TickList).GetField("things", flags); + + foreach (var fieldName in fieldNames) + { + var field = tickManager.GetType().GetField(fieldName, flags); + if (field == null) + continue; + + var tickList = field.GetValue(tickManager) as TickList; + if (tickList == null) + continue; + + var things = tickListThingsField?.GetValue(tickList) as List; + if (things != null && things.Contains(thing)) + { + tickList.DeregisterThing(thing); + } + } + } + } +} diff --git a/Source/Patches/RoofGrid_SetRoof_Patch.cs b/Source/Patches/RoofGrid_SetRoof_Patch.cs new file mode 100644 index 0000000..1b180dc --- /dev/null +++ b/Source/Patches/RoofGrid_SetRoof_Patch.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using HarmonyLib; +using RimWorld; +using Verse; + +namespace HomeMover +{ + [StaticConstructorOnStartup] + public static class RoofGrid_SetRoof_Patch + { + private static readonly Harmony harmony = new Harmony("Jellypowered.HomeMover"); + + static RoofGrid_SetRoof_Patch() + { + ApplyPatches(); + } + + private static void ApplyPatches() + { + MethodInfo original = typeof(RoofGrid).GetMethod( + "SetRoof", + BindingFlags.Instance | BindingFlags.Public + ); + MethodInfo postfix = typeof(RoofGrid_SetRoof_Patch).GetMethod( + "Postfix", + BindingFlags.Static | BindingFlags.Public + ); + + if (original != null && postfix != null) + { + harmony.Patch(original, postfix: new HarmonyMethod(postfix)); + HomeMoverMod.DebugLog("SetRoof Patch applied."); + } + else + { + HomeMoverMod.DebugLog( + "Failed to apply SetRoof Patch. Original method or postfix not found." + ); + } + } + + public static void Postfix(RoofGrid __instance, IntVec3 c, RoofDef def) + { + if (def == null && Current.ProgramState == ProgramState.Playing) + { + DesignatorHomeMover.SetNoRoofFalse(c); + } + } + } +} diff --git a/Source/Patches/WorkGiver_ConstructDeliverResourcesToBlueprints_Patch.cs b/Source/Patches/WorkGiver_ConstructDeliverResourcesToBlueprints_Patch.cs new file mode 100644 index 0000000..5f05902 --- /dev/null +++ b/Source/Patches/WorkGiver_ConstructDeliverResourcesToBlueprints_Patch.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using HarmonyLib; +using RimWorld; +using Verse; +using Verse.AI; + +namespace HomeMover +{ + /// + /// Patch the original so pawn won't start a reinstall job if the subject building is the only support for nearby roof. + /// + [StaticConstructorOnStartup] + public static class WorkGiver_ConstructDeliverResourcesToBlueprints_Patch + { + private static readonly Harmony harmony = new Harmony("Jellypowered.HomeMover"); + + static WorkGiver_ConstructDeliverResourcesToBlueprints_Patch() + { + ApplyPatches(); + } + + private static void ApplyPatches() + { + MethodInfo original = typeof(WorkGiver_ConstructDeliverResourcesToBlueprints).GetMethod( + "JobOnThing", + BindingFlags.Public | BindingFlags.Instance + ); + MethodInfo postfix = + typeof(WorkGiver_ConstructDeliverResourcesToBlueprints_Patch).GetMethod( + "Postfix", + BindingFlags.Public | BindingFlags.Static + ); + + if (original != null && postfix != null) + { + harmony.Patch(original, postfix: new HarmonyMethod(postfix)); + HomeMoverMod.DebugLog("JobOnThing Patch applied."); + } + else + { + HomeMoverMod.DebugLog( + "Failed to apply JobOnThing Patch. Original method or postfix not found." + ); + } + } + + /// + /// Postfix method. + /// + /// + /// + /// + public static void Postfix(Thing t, bool forced, ref Job __result) + { + if ( + t is Blueprint_Install install + && install.MiniToInstallOrBuildingToReinstall is Building building + && building.def.holdsRoof + ) + { + if (forced) + { + DesignatorHomeMover.AddBeingReinstalledBuilding(building); + return; + } + + if ( + building.MapHeld.designationManager.DesignationOn( + building, + HomeMoverDefOf.HomeMover + ) != null + ) + { + bool canRemove = true; + HashSet roofInRange = building.RoofInRange(); + List buildingsBeingRemoved = DesignatorHomeMover + .GetBuildingsBeingReinstalled(building) + .Concat(new[] { building }) + .ToList(); + + foreach (IntVec3 roof in roofInRange) + { + if (!roof.IsSupported(building.MapHeld, buildingsBeingRemoved)) + { + building.MapHeld.areaManager.NoRoof[roof] = true; + building.MapHeld.areaManager.BuildRoof[roof] = false; + DesignatorHomeMover.AddToRoofToRemove(roof, building); + canRemove = false; + } + } + + if (canRemove) + { + DesignatorHomeMover.AddBeingReinstalledBuilding(building); + } + else + { + __result = null; + } + } + } + } + } +} diff --git a/src/common/UI/DrawUtility.cs b/Source/Utilities/DrawUtility.cs similarity index 69% rename from src/common/UI/DrawUtility.cs rename to Source/Utilities/DrawUtility.cs index a9b94c0..52700ef 100644 --- a/src/common/UI/DrawUtility.cs +++ b/Source/Utilities/DrawUtility.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEngine; using Verse; -namespace MoveBase +namespace HomeMover { public static class DrawUtility { @@ -17,9 +12,11 @@ public static void DrawUrl(Rect labelRect, string text, string url) { Vector2 size = Text.CalcSize(text); Widgets.DrawLine( - new Vector2(labelRect.x, labelRect.y + size.y) - , new Vector2(labelRect.x + size.x, labelRect.y + size.y) - , ColorLibrary.SkyBlue, 1); + new Vector2(labelRect.x, labelRect.y + size.y), + new Vector2(labelRect.x + size.x, labelRect.y + size.y), + ColorLibrary.SkyBlue, + 1 + ); } if (Widgets.ButtonInvisible(labelRect)) @@ -27,6 +24,5 @@ public static void DrawUrl(Rect labelRect, string text, string url) Application.OpenURL(url); } } - } } diff --git a/Source/Utilities/HarmonyUtility.cs b/Source/Utilities/HarmonyUtility.cs new file mode 100644 index 0000000..e05e107 --- /dev/null +++ b/Source/Utilities/HarmonyUtility.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; +using RimWorld; +using Verse; + +namespace HomeMover +{ + public static class HarmonyUtility + { + [StaticConstructorOnStartup] + public static class HomeMover_Startup + { + static HomeMover_Startup() + { + var harmony = new Harmony("Jellypowered.HomeMover"); + harmony.PatchAll(); + HomeMoverMod.DebugLog("Harmony patches applied."); + HomeMoverMod.DebugLog( + $"Building_Door.Tick patched: {Harmony.GetPatchInfo(AccessTools.Method(typeof(Building_Door), "Tick")) != null}" + ); + } + } + + public static bool Compare(this CodeInstruction codeInstruction, CodeInstruction pattern) + { + if ( + codeInstruction.opcode != pattern.opcode + || ( + pattern.opcode == OpCodes.Ldloc_S + && (int)pattern.operand + != (codeInstruction.operand as LocalVariableInfo)?.LocalIndex + ) + || ( + pattern.opcode == OpCodes.Callvirt + && pattern.operand != null + && pattern.operand != codeInstruction.operand + ) + ) + return false; + + return true; + } + + public static bool MatchPattern( + List instructions, + List pattern, + int index, + Action action + ) + { + for (int i = index, j = 0; j < pattern.Count; i++, j++) + { + if (!instructions[i].Compare(pattern[j])) + { + return false; + } + } + + action?.Invoke(); + return true; + } + + public static List ReturnPatternMatchedInstruction( + List pattern, + List original, + ref int index + ) + { + List instructions = new List(); + for (int j = 1; j < pattern.Count; j++) + { + instructions.Add(original[index + j]); + } + + index += (pattern.Count - 1); + + return instructions; + } + } +} diff --git a/Source/Utilities/ObstructionHandler.cs b/Source/Utilities/ObstructionHandler.cs new file mode 100644 index 0000000..a152fa9 --- /dev/null +++ b/Source/Utilities/ObstructionHandler.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; + +namespace HomeMover +{ + public static class ObstructionHandler + { + private static readonly HashSet WarnedBlockers = new HashSet(); + + public static void HandleObstructionsAt( + Thing constructible, + IntVec3 pos, + Rot4 rot, + Map map, + ISet moveGroup + ) + { + if (!HomeMoverMod.Setting.handleObstructions || constructible == null || map == null) + return; + + foreach (IntVec3 cell in GenAdj.OccupiedRect(pos, rot, constructible.def.Size)) + { + foreach (Thing blocker in cell.GetThingList(map).ToList()) + { + if (blocker == null || blocker.DestroyedOrNull() || blocker == constructible) + continue; + + if (moveGroup != null && moveGroup.Contains(blocker)) + continue; + + if (!GenConstruct.BlocksConstruction(constructible, blocker)) + continue; + + if (TryHandlePlant(blocker, map)) + continue; + + if (TryHandleMineable(blocker, map)) + continue; + + if (TryHandlePlayerBuilding(blocker, map)) + continue; + + if (TryHandleCorpse(blocker, map)) + continue; + + if (TryHandleLooseItem(blocker, map)) + continue; + + WarnBlocker(blocker, UIText.BlockerCannotBeMoved.Translate(blocker.LabelCap)); + } + } + } + + private static bool TryHandlePlant(Thing blocker, Map map) + { + if (!(blocker is Plant plant)) + return false; + + DesignationDef desDef = plant.HarvestableNow + ? DesignationDefOf.HarvestPlant + : DesignationDefOf.CutPlant; + + if (map.designationManager.DesignationOn(plant, desDef) == null) + { + map.designationManager.AddDesignation(new Designation(plant, desDef)); + } + + return true; + } + + private static bool TryHandleMineable(Thing blocker, Map map) + { + if (!blocker.def.mineable) + return false; + + if (map.designationManager.DesignationOn(blocker, DesignationDefOf.Mine) == null) + { + map.designationManager.AddDesignation(new Designation(blocker, DesignationDefOf.Mine)); + } + + return true; + } + + private static bool TryHandlePlayerBuilding(Thing blocker, Map map) + { + if (!(blocker is Building building) || building.Faction != Faction.OfPlayer) + return false; + + if (building.def.Minifiable && HomeMoverMod.Setting.autoMinifyBlockers) + { + if (map.designationManager.DesignationOn(building, DesignationDefOf.Uninstall) == null) + { + map.designationManager.AddDesignation( + new Designation(building, DesignationDefOf.Uninstall) + ); + } + return true; + } + + if (HomeMoverMod.Setting.warnNonMinifiable) + { + WarnBlocker(building, UIText.BlockerNotMinifiable.Translate(building.LabelCap)); + } + + return true; + } + + private static bool TryHandleCorpse(Thing blocker, Map map) + { + if (!(blocker is Corpse corpse)) + return false; + + // Corpses can naturally be hauled by pawns; just accept them as "handled" + // (They won't actually block most construction anyway) + HomeMoverMod.DebugLog($"Corpse at {corpse.Position} will be naturally hauled out of the way"); + return true; + } + + private static bool TryHandleLooseItem(Thing blocker, Map map) + { + // Loose items (not buildings, plants, or corpses) + if (blocker is Building || blocker is Plant || blocker is Corpse || !blocker.Spawned) + return false; + + // Most loose items naturally get moved by pawns; accept them as "handled" + HomeMoverMod.DebugLog($"Item {blocker.LabelCap} at {blocker.Position} will be naturally moved out of the way"); + return true; + } + + private static void WarnBlocker(Thing blocker, string text) + { + if (blocker == null || WarnedBlockers.Contains(blocker.thingIDNumber)) + return; + + WarnedBlockers.Add(blocker.thingIDNumber); + Messages.Message(text, blocker, MessageTypeDefOf.CautionInput); + HomeMoverMod.DebugLog($"Blocking thing warning: {blocker.LabelCap}"); + } + } +} diff --git a/Source/Utilities/PlacementOrder.cs b/Source/Utilities/PlacementOrder.cs new file mode 100644 index 0000000..6a44e0d --- /dev/null +++ b/Source/Utilities/PlacementOrder.cs @@ -0,0 +1,94 @@ +using RimWorld; +using Verse; + +namespace HomeMover +{ + public enum PlacementTier + { + Conduit = 0, + Structure = 1, + Door = 2, + WallMount = 3, + Furniture = 4, + SmallItem = 5, + } + + public static class PlacementOrder + { + public static PlacementTier GetPlacementTier(this Thing thing) + { + if (thing?.def == null) + return PlacementTier.Furniture; + + ThingDef def = thing.def; + + if (IsConduitLike(def)) + return PlacementTier.Conduit; + + if (def.building != null && def.building.isAttachment) + return PlacementTier.WallMount; + + if (def.thingClass != null && typeof(Building_Door).IsAssignableFrom(def.thingClass)) + return PlacementTier.Door; + + if (def.holdsRoof && def.building != null && def.building.isEdifice) + return PlacementTier.Structure; + + if (RequiresWallSupport(def)) + return PlacementTier.WallMount; + + if (def.building != null && !def.building.isEdifice) + return PlacementTier.SmallItem; + + return PlacementTier.Furniture; + } + + public static bool IsConduitLike(this ThingDef def) + { + if (def?.building == null) + return false; + + if (def.building.isAttachment) + return false; + + if (def.building.isEdifice) + return false; + + if (def.EverTransmitsPower) + return true; + + if (def.designationCategory != null && def.designationCategory.defName == "Power") + return true; + + if (def.defName != null && def.defName.ToLowerInvariant().Contains("conduit")) + return true; + + return false; + } + + public static bool RequiresWallSupport(this ThingDef def) + { + if (def?.building == null) + return false; + + if (def.building.canPlaceOverWall) + return true; + + if (def.PlaceWorkers == null) + return false; + + foreach (PlaceWorker placeWorker in def.PlaceWorkers) + { + string workerName = placeWorker.GetType().Name; + if ( + workerName.Contains("Wall") + || workerName.Contains("Attach") + || workerName.Contains("OnWall") + ) + return true; + } + + return false; + } + } +} diff --git a/Source/Utilities/RoofUtility.cs b/Source/Utilities/RoofUtility.cs new file mode 100644 index 0000000..4c4d8ec --- /dev/null +++ b/Source/Utilities/RoofUtility.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; + +namespace HomeMover +{ + public static class RoofUtility + { + private static Dictionary _supportedRoof = + new Dictionary(); + + /// + /// Clear the roof-support cache. Must be called on game load and new game start + /// so stale references from the previous session are discarded. + /// + public static void ClearCache() + { + _supportedRoof.Clear(); + } + + /// + /// Check if roof is supported by buildings other than those in . + /// + public static bool IsSupported(this IntVec3 roof, Map map, IEnumerable exceptions) + { + if ( + _supportedRoof.TryGetValue(roof, out Building cachedBuilding) + && !exceptions.Contains(cachedBuilding) + ) + return true; + + bool supported = false; + + map.floodFiller.FloodFill( + root: roof, + passCheck: (IntVec3 cell) => + cell.Roofed(map) + && cell.InHorDistOf(roof, RoofCollapseUtility.RoofMaxSupportDistance), + processor: (IntVec3 cell) => + { + Building edifice = cell.GetEdifice(map); + if (edifice != null && edifice.def.holdsRoof && !exceptions.Contains(edifice)) + { + _supportedRoof[roof] = edifice; + supported = true; + } + }, + maxCellsToProcess: 512 + ); // Optional safety cap + + return supported; + } + } +} diff --git a/src/common/util/ThingUtility.cs b/Source/Utilities/ThingUtility.cs similarity index 75% rename from src/common/util/ThingUtility.cs rename to Source/Utilities/ThingUtility.cs index f19b574..2bbb1f2 100644 --- a/src/common/util/ThingUtility.cs +++ b/Source/Utilities/ThingUtility.cs @@ -1,14 +1,10 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using RimWorld; using Verse; -namespace MoveBase +namespace HomeMover { - [PerfProfile] public static class ThingUtility { public static HashSet RoofInRange(this Building building) @@ -16,14 +12,16 @@ public static HashSet RoofInRange(this Building building) HashSet supportedRoof = new HashSet(); Map map = building.MapHeld; map.floodFiller.FloodFill( - building.Position - , (cell) => cell.InHorDistOf(building.Position, RoofCollapseUtility.RoofMaxSupportDistance) - , (cell) => + building.Position, + (cell) => + cell.InHorDistOf(building.Position, RoofCollapseUtility.RoofMaxSupportDistance), + (cell) => { if (cell.Roofed(map)) supportedRoof.Add(cell); - } - , extraRoots: building.OccupiedRect()); + }, + extraRoots: building.OccupiedRect() + ); return supportedRoof; } @@ -73,11 +71,21 @@ public static Thing BlockAdjacentInteractionCell(this Thing thing, IntVec3 pos, List things = cell.GetThingList(thing.MapHeld); foreach (Thing neighbour in things) { - if (neighbour.def.hasInteractionCell - && (thing.def.passability == Traversability.Impassable || thing.def == neighbour.def) + if ( + neighbour.def.hasInteractionCell + && ( + thing.def.passability == Traversability.Impassable + || thing.def == neighbour.def + ) && cellRect.Contains( Verse.ThingUtility.InteractionCellWhenAt( - neighbour.def, neighbour.Position, neighbour.Rotation, neighbour.Map))) + neighbour.def, + neighbour.Position, + neighbour.Rotation, + neighbour.Map + ) + ) + ) { return neighbour; } diff --git a/Source/VersionInfo.cs b/Source/VersionInfo.cs new file mode 100644 index 0000000..655767c --- /dev/null +++ b/Source/VersionInfo.cs @@ -0,0 +1,15 @@ +// Auto-generated by update-version.ps1 - DO NOT EDIT MANUALLY +// Build timestamp: 2026-04-28 22:33:56 +// Build configuration: Release + +namespace HomeMover +{ + public static class BuildVersion + { + public const string Version = "0.002(R)"; + public const string VersionNumber = "0.002"; + public const string BuildType = "R"; + public const string BuildConfig = "Release"; + public const string BuildTimestamp = "2026-04-28 22:33:56"; + } +} diff --git a/references/Assembly-CSharp.dll b/references/Assembly-CSharp.dll deleted file mode 100644 index 6d0b70e..0000000 Binary files a/references/Assembly-CSharp.dll and /dev/null differ diff --git a/references/UnityEngine.CoreModule.dll b/references/UnityEngine.CoreModule.dll deleted file mode 100644 index 7afc930..0000000 Binary files a/references/UnityEngine.CoreModule.dll and /dev/null differ diff --git a/references/UnityEngine.IMGUIModule.dll b/references/UnityEngine.IMGUIModule.dll deleted file mode 100644 index f1d0b5f..0000000 Binary files a/references/UnityEngine.IMGUIModule.dll and /dev/null differ diff --git a/references/UnityEngine.InputLegacyModule.dll b/references/UnityEngine.InputLegacyModule.dll deleted file mode 100644 index 728614c..0000000 Binary files a/references/UnityEngine.InputLegacyModule.dll and /dev/null differ diff --git a/references/UnityEngine.TextRenderingModule.dll b/references/UnityEngine.TextRenderingModule.dll deleted file mode 100644 index a0ee03b..0000000 Binary files a/references/UnityEngine.TextRenderingModule.dll and /dev/null differ diff --git a/references/UnityEngine.dll b/references/UnityEngine.dll deleted file mode 100644 index bc73a68..0000000 Binary files a/references/UnityEngine.dll and /dev/null differ diff --git a/src/About/About.xml b/src/About/About.xml deleted file mode 100644 index 7bdb0e3..0000000 --- a/src/About/About.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - Home Mover - NotooShabby - NotooShabby.HomeMover - [v1.0.0] -A simple mod that moves buildings around. -It works better with "Minify Everything". - - https://github.com/Mhburg/AwsomeInventory - -
  • 1.1
  • -
    - -
  • - brrainz.harmony - Harmony - steam://url/CommunityFilePage/2009463077 - https://github.com/pardeike/HarmonyRimWorld/releases/latest -
  • -
    - -
  • brrainz.harmony
  • -
    -
    diff --git a/src/Languages/English/Keyed/MoveBase.xml b/src/Languages/English/Keyed/MoveBase.xml deleted file mode 100644 index b26b7b7..0000000 --- a/src/Languages/English/Keyed/MoveBase.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - Move building to another location - Home Mover - \ No newline at end of file diff --git a/src/LoadFolders.xml b/src/LoadFolders.xml deleted file mode 100644 index 62db7c5..0000000 --- a/src/LoadFolders.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
  • Common
  • -
  • v1.1
  • -
    - -
  • Common
  • -
  • v1.1
  • -
    -
    \ No newline at end of file diff --git a/src/common/Attributes/PerfProfile.cs b/src/common/Attributes/PerfProfile.cs deleted file mode 100644 index d26da0f..0000000 --- a/src/common/Attributes/PerfProfile.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using Verse; - -namespace MoveBase -{ - public class PerfProfile : Attribute - { - private static Dictionary _watches = new Dictionary(); - - private static Dictionary _totalTime = new Dictionary(); - - private static MethodInfo _prefix = typeof(PerfProfile).GetMethod(nameof(PerfProfile.Prefix), BindingFlags.Static | BindingFlags.NonPublic); - - private static MethodInfo _postfix = typeof(PerfProfile).GetMethod(nameof(PerfProfile.Postfix), BindingFlags.Static | BindingFlags.NonPublic); - - private static bool _initialized = false; - - static PerfProfile() - { - if (!_initialized) - Init(); - } - - public static void Init() - { - foreach (Type type in Assembly.GetExecutingAssembly().DefinedTypes) - { - if (type.HasAttribute()) - { - foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) - { - HarmonyUtility.Instance.Patch(methodInfo, new HarmonyMethod(_prefix), new HarmonyMethod(_postfix)); - } - } - } - - _initialized = true; - } - - [Conditional("DEBUG")] - public static void OutputLog() - { - StringBuilder stringBuilder = new StringBuilder(); - foreach (var pair in _totalTime.OrderByDescending(pair => pair.Value.TotalTime)) - stringBuilder.AppendLine($"{pair.Key}: {pair.Value}"); - - Log.Warning(stringBuilder.ToString(), true); - } - - private static bool Prefix(MethodBase __originalMethod) - { - if (!_watches.TryGetValue(__originalMethod, out Stopwatch stopwatch)) - stopwatch = _watches[__originalMethod] = new Stopwatch(); - - stopwatch.Restart(); - return true; - } - - private static void Postfix(MethodBase __originalMethod) - { - Stopwatch stopwatch = _watches[__originalMethod]; - stopwatch.Stop(); - - if (_totalTime.TryGetValue(__originalMethod, out StatModel model)) - model.Update(stopwatch.Elapsed.TotalMilliseconds); - else - (_totalTime[__originalMethod] = new StatModel()).Update(stopwatch.Elapsed.TotalMilliseconds); - } - - private class StatModel - { - public double TotalTime = 0; - public double InvokedTimes = 0; - public double LongestExecutionTime = 0; - - public void Update(double duration) - { - this.TotalTime += duration; - this.InvokedTimes++; - this.LongestExecutionTime = this.LongestExecutionTime > duration ? this.LongestExecutionTime : duration; - } - - public override string ToString() - { - return $"{nameof(TotalTime)}: {TotalTime: 0000.0000}ms - {nameof(InvokedTimes)}: {InvokedTimes} - {nameof(LongestExecutionTime)}: {LongestExecutionTime: 0000.0000}ms"; - } - } - } -} diff --git a/src/common/DesignatorMoveBase.cs b/src/common/DesignatorMoveBase.cs deleted file mode 100644 index e404d9d..0000000 --- a/src/common/DesignatorMoveBase.cs +++ /dev/null @@ -1,908 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using RimWorld; -using RimWorld.Planet; -using UnityEngine; -using Verse; -using Verse.AI; -using Verse.Sound; - -namespace MoveBase -{ - /// - /// Designator for Home Mover. - /// - [StaticConstructorOnStartup] - [PerfProfile] - public class DesignatorMoveBase : Designator - { - private static readonly MethodInfo _setBuildingToReinstall = typeof(Blueprint_Install).GetMethod("SetBuildingToReinstall", BindingFlags.NonPublic | BindingFlags.Instance); - - private static Texture2D _icon = ContentFinder.Get("UI/Designations/MoveBase"); - - private static Rot4 _rotation = Rot4.North; - private static bool _originFound = false; - private static IntVec3 _origin = IntVec3.Zero; - private static Dictionary _ghostPos = new Dictionary(); - private static List _removeRoofModels = new List(); - - private static Mode _mode = Mode.Select; - private static int _draggableDimension = 2; - - /// - /// Gets or sets whether designation should be kept when the designator is deselected. - /// - public bool KeepDesignation { get; set; } = false; - - /// - /// Gets or sets a list of designated things. - /// - public List DesignatedThings { get; set; } = new List(); - - /// - /// Dimension used by desigator when drag. - /// - public override int DraggableDimensions => _draggableDimension; - - /// - /// Gets the def for this designation. - /// - protected override DesignationDef Designation => MoveBaseDefOf.MoveBase; - - enum Mode - { - Select, - Place, - } - - /// - /// Create instance of DesignatorMovebase. - /// - public DesignatorMoveBase() - { - this.icon = _icon; - this.useMouseIcon = true; - this.defaultDesc = UIText.Description.TranslateSimple(); - this.defaultLabel = UIText.Label.TranslateSimple(); - } - - public static void PlaceWaitingBuildings() - { - GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Place; - foreach (RemoveRoofModel model in _removeRoofModels) - { - foreach (Thing thing in model.WaitingThings.ToList()) - { - if (thing.DestroyedOrNull() || thing.MapHeld != model.Map) - { - model.WaitingThings.Remove(thing); - continue; - } - - IntVec3 deltaCell = GetDeltaCell(thing, model.MousePos, model.GhostPosition); - - Thing inner = thing.GetInnerIfMinified(); - if (GenConstruct.CanPlaceBlueprintAt(inner.def, deltaCell, inner.Rotate(model.Rotation), inner.MapHeld, thing: inner).Accepted) - { - if (thing is MinifiedThing minifiedThing) - GenConstruct.PlaceBlueprintForInstall(minifiedThing, deltaCell, minifiedThing.MapHeld, inner.Rotate(model.Rotation), Faction.OfPlayer); - else - GenConstruct.PlaceBlueprintForReinstall(thing as Building, deltaCell, thing.MapHeld, thing.Rotate(model.Rotation), Faction.OfPlayer); - - model.WaitingThings.Remove(thing); - } - } - } - } - - - /// - /// Clear cache of roof to remove. - /// - public static void ClearCache() - { - _removeRoofModels.Clear(); - } - - /// - /// Save state. - /// - public static void ExposeData() - { - if (Scribe.mode == LoadSaveMode.Saving) - RemoveEmptyCache(); - - Scribe_Collections.Look(ref _removeRoofModels, nameof(_removeRoofModels), LookMode.Deep); - _removeRoofModels = _removeRoofModels ?? new List(); - } - - /// - /// It should be invoked when a building is removed from designation. - /// - /// Building is being removed from designation. - public static void Notify_Removing_Callback(Thing thing) - { - if (!(thing is Building building) || !building.def.holdsRoof || !building.Spawned) - return; - - foreach (RemoveRoofModel model in _removeRoofModels) - { - if (model.BuildingsToReinstall.Contains(building)) - { - IntVec3 pos = thing.Position; - List workingSet = new List(model.RoofToRemove); - foreach (IntVec3 roof in workingSet) - { - if (pos.InHorDistOf(roof, RoofCollapseUtility.RoofMaxSupportDistance)) - { - model.RoofToRemove.Remove(roof); - model.Map.areaManager.NoRoof[roof] = false; - } - } - - model.BuildingsToReinstall.Remove(building); - - break; - } - } - } - - /// - /// Add building to cache when a building is removed and being in transport/reinstallation by a pawn. - /// - /// Building under transportation or reinstallation. - public static void AddBeingReinstalledBuilding(Building building) - { - if (building == null) - return; - - foreach (RemoveRoofModel model in _removeRoofModels) - { - if (model.BuildingsToReinstall.Contains(building)) - { - model.BuildingsBeingReinstalled.Add(building); - break; - } - } - } - - /// - /// Get a list of building being reinstalled that are in the same designation group as . - /// - /// Building in question. - /// A list of buildings that are being reinstalled. - public static HashSet GetBuildingsBeingReinstalled(Building building) - { - if (building == null) - return new HashSet(); - - foreach (RemoveRoofModel model in _removeRoofModels) - { - if (model.BuildingsToReinstall.Contains(building)) - { - return model.BuildingsBeingReinstalled; - } - } - - return new HashSet(); - } - - /// - /// Remove building from cache. - /// - /// Building in question. - public static void RemoveBuildingFromCache(Building building) - { - if (building == null) - return; - - foreach (RemoveRoofModel model in _removeRoofModels) - { - if (model.BuildingsToReinstall.Contains(building)) - { - model.BuildingsBeingReinstalled.Remove(building); - model.BuildingsToReinstall.Remove(building); - if (!model.BuildingsToReinstall.Any() && !model.RoofToRemove.Any()) - { - _removeRoofModels.Remove(model); - } - - break; - } - } - } - - /// - /// This method is invoked if this selector is selected. - /// - public override void Selected() - { - _mode = Mode.Select; - _originFound = false; - _origin = IntVec3.Zero; - _rotation = Rot4.North; - _draggableDimension = 2; - DesignatedThings.Clear(); - _ghostPos = new Dictionary(); - this.KeepDesignation = false; - } - - /// - /// Rotation control. Code copied from vanilla. - /// - /// X position on screen. - /// Bottom Y position on screen. - public override void DoExtraGuiControls(float leftX, float bottomY) - { - Rect winRect = new Rect(leftX, bottomY - 90f, 200f, 90f); - Find.WindowStack.ImmediateWindow(Rand.Int, winRect, WindowLayer.GameUI, delegate - { - RotationDirection rotationDirection = RotationDirection.None; - Text.Anchor = TextAnchor.MiddleCenter; - Text.Font = GameFont.Medium; - Rect rect = new Rect(winRect.width / 2f - 64f - 5f, 15f, 64f, 64f); - if (Widgets.ButtonImage(rect, TexUI.RotLeftTex)) - { - SoundDefOf.DragSlider.PlayOneShotOnCamera(); - rotationDirection = RotationDirection.Counterclockwise; - Event.current.Use(); - } - Widgets.Label(rect, KeyBindingDefOf.Designator_RotateLeft.MainKeyLabel); - Rect rect2 = new Rect(winRect.width / 2f + 5f, 15f, 64f, 64f); - if (Widgets.ButtonImage(rect2, TexUI.RotRightTex)) - { - SoundDefOf.DragSlider.PlayOneShotOnCamera(); - rotationDirection = RotationDirection.Clockwise; - Event.current.Use(); - } - Widgets.Label(rect2, KeyBindingDefOf.Designator_RotateRight.MainKeyLabel); - if (rotationDirection != 0) - { - foreach (Thing thing in DesignatedThings) - { - _ghostPos[thing] = this.VectorRotation(_ghostPos[thing], rotationDirection, thing); - } - _rotation.Rotate(rotationDirection); - } - Text.Anchor = TextAnchor.UpperLeft; - Text.Font = GameFont.Small; - }); - } - - /// - /// What should be drawn when the frame is being updated.. - /// - public override void SelectedUpdate() - { - GenDraw.DrawNoBuildEdgeLines(); - if (ArchitectCategoryTab.InfoRect.Contains(UI.MousePositionOnUIInverted)) - return; - - this.DrawGhostMatrix(); - } - - /// - /// Check if can be designated by this designator. - /// - /// Thing in question. - /// Returns true is can be designated. - public override AcceptanceReport CanDesignateThing(Thing t) - { - if (_mode == Mode.Select) - { - Building building = t as Building; - if (building == null) - return false; - - if (building.def.category != ThingCategory.Building) - return false; - - if (!building.def.Minifiable) - return false; - - if (!DebugSettings.godMode && building.Faction != Faction.OfPlayer) - { - if (building.Faction != null) - return false; - - if (!building.ClaimableBy(Faction.OfPlayer)) - return false; - } - - if (base.Map.designationManager.DesignationOn(t, Designation) != null) - return false; - - if (base.Map.designationManager.DesignationOn(t, DesignationDefOf.Deconstruct) != null) - return false; - - return true; - } - - return false; - } - - /// - /// This method is invoked when either a click or a drag from mouse is performed. - /// - /// cell on map. - /// Data model for acceptance. - public override AcceptanceReport CanDesignateCell(IntVec3 loc) - { - if (_mode == Mode.Select) - return this.CanDesignateThing(this.TopReinstallableInCell(loc)); - else - return this.CanReinstallAllThings(loc); - } - - public override void DesignateSingleCell(IntVec3 c) - { - if (_mode == Mode.Select) - { - List things = this.ReinstallableInCell(c); - if (things.Any()) - { - if (_originFound == false) - { - _originFound = true; - _origin = c; - } - - DesignatedThings.AddRange(things); - things.ForEach(thing => DesignateThing(thing)); - } - } - else if (_mode == Mode.Place) - { - GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Place; - HashSet placedThings = new HashSet(); - Dictionary twinThings = new Dictionary(); - Dictionary blueprintWork = new Dictionary(); - Dictionary siblingWork = new Dictionary(); - - IntVec3 mousePos = UI.MouseCell(); - foreach (Thing designatedThing in DesignatedThings) - { - IntVec3 drawCell = GetDeltaCell(designatedThing, mousePos, _ghostPos); - List things = drawCell.GetThingList(designatedThing.MapHeld); - bool foundTwin = false; - bool foundSibling = false; - foreach (Thing thingOnCell in things) - { - if (DesignatedThings.Contains(thingOnCell)) - { - if (designatedThing.IdenticalWith(_rotation, thingOnCell)) - { - if (blueprintWork.TryGetValue(thingOnCell, out Blueprint_Install install)) - { - Thing twin = this.GetTailInTwinThings(twinThings, designatedThing); - - _setBuildingToReinstall.Invoke(install, new[] { twin }); - blueprintWork[twin] = install; - blueprintWork.Remove(thingOnCell); - placedThings.Add(twin); - } - else if (siblingWork.TryGetValue(thingOnCell, out IntVec3 position)) - { - Thing twin = this.GetTailInTwinThings(twinThings, designatedThing); - _ghostPos[twin] = position; - siblingWork.Remove(thingOnCell); - siblingWork[twin] = position; - placedThings.Add(thingOnCell); - } - else - { - twinThings[thingOnCell] = designatedThing; - _ghostPos[designatedThing] = _ghostPos[thingOnCell]; - placedThings.Add(thingOnCell); - } - - this.Map.designationManager.TryRemoveDesignationOn(thingOnCell, MoveBaseDefOf.MoveBase); - - foundTwin = true; - break; - } - else if (!GenConstruct.BlocksConstruction(designatedThing, thingOnCell)) - { - continue; - } - else - { - foundSibling = true; - Thing twin = this.GetTailInTwinThings(twinThings, designatedThing); - siblingWork[twin] = _ghostPos[twin] = _ghostPos[designatedThing]; - break; - } - } - } - - if (foundTwin || foundSibling) - continue; - - Thing twin1 = this.GetTailInTwinThings(twinThings, designatedThing); - - - AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt( - twin1.def - , drawCell - , GetRotation(twin1) - , twin1.MapHeld - , false - , null - , twin1); - - if (report.Accepted) - { - Building building = twin1 as Building; - blueprintWork[building] = GenConstruct.PlaceBlueprintForReinstall(building, drawCell, building.MapHeld, GetRotation(building), Faction.OfPlayer); - placedThings.Add(building); - } - } - - RemoveRoofModel model = - InitModel( - DesignatedThings - , DesignatedThings.Except(placedThings).ToList() - , DesignatedThings.First().MapHeld - , mousePos - , _rotation - , _ghostPos); - - ResolveDeadLock(model); - - this.KeepDesignation = true; - _mode = Mode.Select; - Find.DesignatorManager.Deselect(); - } - } - - public override void DesignateThing(Thing t) - { - if (t == null) - return; - - if (t.Faction != Faction.OfPlayer) - t.SetFaction(Faction.OfPlayer); - - Designation designation = new Designation(t, this.Designation); - base.Map.designationManager.AddDesignation(new Designation(t, this.Designation)); - _ghostPos[t] = t.Position - _origin; - } - - public override void DrawMouseAttachments() - { - if (_mode == Mode.Select) - base.DrawMouseAttachments(); - else - this.DrawGhostMatrix(); - } - - public override void SelectedProcessInput(Event ev) - { - base.SelectedProcessInput(ev); - if (DesignatedThings.Any() && _mode == Mode.Place) - { - HandleRotationShortcuts(); - } - } - - /// - /// Set no roof to false after roof is removed on . - /// - /// - /// When no-roof is true, pawn will try to remove roof on that cell. - public static void SetNoRoofFalse(IntVec3 cell) - { - foreach (RemoveRoofModel model in _removeRoofModels) - { - if (model.RoofToRemove?.Contains(cell) ?? false) - { - model.Map.areaManager.NoRoof[cell] = false; - model.RoofToRemove.Remove(cell); - if (!model.BuildingsToReinstall.Any() && !model.RoofToRemove.Any()) - { - _removeRoofModels.Remove(model); - } - - break; - } - } - } - - public static void AddToRoofToRemove(IntVec3 roof, Thing thing) - { - _removeRoofModels - .FirstOrDefault(model => model.BuildingsToReinstall.Contains(thing)) - ?.RoofToRemove - .Add(roof); - } - - public static void UninstallJobCallback(Building building, Map map) - { - foreach (RemoveRoofModel model in _removeRoofModels) - { - if (model.WaitingThings.Contains(building)) - { - MinifiedThing minifiedThing = (MinifiedThing)map.listerThings.ThingsInGroup(ThingRequestGroup.MinifiedThing).FirstOrDefault(t => t.GetInnerIfMinified() == building); - model.GhostPosition[minifiedThing] = model.GhostPosition[building]; - model.WaitingThings.Remove(building); - model.WaitingThings.Add(minifiedThing); - } - } - } - - /// - /// This method is invoked when at least one thing is selected by this selector. - /// - protected override void FinalizeDesignationSucceeded() - { - _mode = Mode.Place; - _draggableDimension = 0; - } - - private static void ResolveDeadLock(RemoveRoofModel model) - { - foreach (Thing thing in model.WaitingThings.ToList()) - { - Thing lastFound = thing; - List foundThings = new List() { lastFound }; - - while (true) - { - IntVec3 spawnCell = GetDeltaCell(lastFound, model.MousePos, model.GhostPosition); - List rect = GenAdj.OccupiedRect(spawnCell, lastFound.Rotate(model.Rotation), lastFound.def.size).ToList(); - if (lastFound.def.hasInteractionCell) - { - rect.Add(Verse.ThingUtility.InteractionCellWhenAt(lastFound.def, spawnCell, lastFound.Rotate(model.Rotation), lastFound.MapHeld)); - } - IEnumerable thingsOnCell = rect.SelectMany(c => c.GetThingList(lastFound.MapHeld).Where(t => t.def.blueprintDef != null && t.def.Minifiable)); - lastFound = thingsOnCell.FirstOrDefault(t => GenConstruct.BlocksConstruction(lastFound, t) && model.WaitingThings.Contains(t)) - ?? ThingUtility.BlockAdjacentInteractionCell(lastFound, spawnCell, lastFound.Rotate(model.Rotation)); - if (lastFound == null || foundThings.Contains(lastFound)) - break; - - foundThings.Add(lastFound); - } - - if (lastFound == null) - { - continue; - } - else - { - thing.Map.designationManager.AddDesignation(new Designation(thing, DesignationDefOf.Uninstall)); - } - } - } - - private Thing GetTailInTwinThings(Dictionary table, Thing thing) - { - while (table.TryGetValue(thing, out Thing tail)) - thing = tail; - - return thing; - } - - private static IntVec3 GetDeltaCell(Thing thing, IntVec3 mousePos, Dictionary ghostPos) - { - return new IntVec3(mousePos.x + ghostPos[thing].x, mousePos.y, mousePos.z + ghostPos[thing].z); - } - - private static void RemoveEmptyCache() - { - foreach (RemoveRoofModel model in _removeRoofModels.ToList()) - { - if (!model.BuildingsToReinstall.EnumerableNullOrEmpty() && !model.RoofToRemove.EnumerableNullOrEmpty()) - { - _removeRoofModels.Remove(model); - } - } - } - - private List ReinstallableInCell(IntVec3 loc) - { - List things = new List(); - - foreach (Thing item in from t in base.Map.thingGrid.ThingsAt(loc) - orderby t.def.altitudeLayer descending - select t) - { - if (this.CanDesignateThing(item).Accepted) - things.Add(item); - } - - return things; - } - - private Thing TopReinstallableInCell(IntVec3 loc) - { - foreach (Thing item in from t in base.Map.thingGrid.ThingsAt(loc) - orderby t.def.altitudeLayer descending - select t) - { - if (this.CanDesignateThing(item).Accepted) - { - return item; - } - } - - return null; - } - - private AcceptanceReport CanReinstallAllThings(IntVec3 mousePos) - { - AcceptanceReport result = AcceptanceReport.WasAccepted; - GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Check; - this.TraverseDesignateThings( - mousePos - , (drawCell, thing) => - { - AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt( - thing.def - , drawCell - , GetRotation(thing) - , thing.MapHeld - , false - , null - , thing); - if (!report.Accepted) - { - result = report; - return true; - } - - return false; - }); - - return result; - } - - private AcceptanceReport CanReinstall(Thing thing, IntVec3 drawCell) - { - GenConstruct_CanPlaceBlueprintAt_Patch.Mode = BlueprintMode.Check; - AcceptanceReport report = GenConstruct.CanPlaceBlueprintAt(thing.def, drawCell, GetRotation(thing), thing.MapHeld, false, null, thing); - - return report; - } - - private static Rot4 GetRotation(Thing thing) - { - if (thing.def.rotatable) - return new Rot4(_rotation.AsInt + thing.Rotation.AsInt); - else - return thing.Rotation; - } - - private void DrawGhostMatrix() - { - IntVec3 mousePos = UI.MouseCell(); - this.TraverseDesignateThings( - mousePos - , (drawCell, thing) => - { - this.DrawGhostThing(drawCell, thing); - return false; - }); - } - - private void TraverseDesignateThings(IntVec3 mousePos, Func func) - { - foreach (Thing thing in DesignatedThings) - { - if (func(GetDeltaCell(thing, mousePos, _ghostPos), thing)) - break; - } - } - - private void DrawGhostThing(IntVec3 cell, Thing thing) - { - Graphic baseGraphic = thing.Graphic.ExtractInnerGraphicFor(thing); - Color color = this.CanReinstall(thing, cell).Accepted ? Designator_Place.CanPlaceColor : Designator_Place.CannotPlaceColor; - try - { - GhostDrawer.DrawGhostThing(cell, GetRotation(thing), thing.def, baseGraphic, color, - AltitudeLayer.Blueprint, thing); - } - catch - { - // no op. - } - } - - private IntVec3 VectorRotation(IntVec3 cell, RotationDirection rotationDirection, Thing thing) - { - if (thing.def.rotatable || thing.def.size == IntVec2.One) - { - return Rotate(cell); - } - else - { - Log.Message($"Position: {cell}"); - IEnumerable corners = thing.OccupiedRect() - .Corners - .Select(corner => - { - IntVec3 normalized = corner - thing.Position + cell; - Log.Message($"Normalized: {normalized}"); - return normalized; - }) - .Select(Rotate); - - var value = GetCenter(corners.ToList()); - Log.Message($"Return value: {value}"); - return value; - } - - IntVec3 Rotate(IntVec3 pos) - { - switch (rotationDirection) - { - case RotationDirection.Clockwise: - return new IntVec3(pos.z, pos.y, -pos.x); - case RotationDirection.Counterclockwise: - return new IntVec3(-pos.z, pos.y, pos.x); - default: - return pos; - } - } - - IntVec3 GetCenter(List cells) - { - int minX, minZ, maxX, maxZ; - maxX = maxZ = int.MinValue; - minX = minZ = int.MaxValue; - - foreach (IntVec3 c in cells) - { - Log.Message($"Rotated: {c}"); - minX = c.x < minX ? c.x : minX; - minZ = c.z < minZ ? c.z : minZ; - maxX = c.x > maxX ? c.x : maxX; - maxZ = c.z > maxZ ? c.z : maxZ; - } - - return new IntVec3((maxX - minX) / 2 + minX, cells[0].y, (maxZ - minZ) / 2 + minZ); - } - } - - private void HandleRotationShortcuts() - { - RotationDirection rotationDirection = RotationDirection.None; - if (KeyBindingDefOf.Designator_RotateRight.KeyDownEvent) - { - rotationDirection = RotationDirection.Clockwise; - } - else if (KeyBindingDefOf.Designator_RotateLeft.KeyDownEvent) - { - rotationDirection = RotationDirection.Counterclockwise; - } - - if (rotationDirection != RotationDirection.None) - { - foreach (Thing thing in DesignatedThings) - { - _ghostPos[thing] = this.VectorRotation(_ghostPos[thing], rotationDirection, thing); - } - _rotation.Rotate(rotationDirection); - SoundDefOf.DragSlider.PlayOneShotOnCamera(); - } - } - - - private static RemoveRoofModel InitModel(List designatedThings, List waitingThings, Map map, IntVec3 mousePos, Rot4 rotation, Dictionary ghostPos) - { - RemoveRoofModel newModel = new RemoveRoofModel( - designatedThings - , designatedThings.OfType().Where(b => b.def.holdsRoof).ToHashSet() - , waitingThings - , new HashSet() - , map - , mousePos - , rotation - , ghostPos); - _removeRoofModels.Add(newModel); - return newModel; - } - - - - private class RemoveRoofModel : IExposable - { - - private List ghostThings = new List(); - private List ghostPos = new List(); - - public HashSet BuildingsToReinstall = new HashSet(); - - public HashSet BuildingsBeingReinstalled = new HashSet(); - - public List DesignatedThings = new List(); - - public HashSet WaitingThings = new HashSet(); - - public HashSet RoofToRemove = new HashSet(); - - public Dictionary GhostPosition = new Dictionary(); - - public IntVec3 MousePos; - - public Rot4 Rotation; - - public Map Map; - - public RemoveRoofModel() - { - } - - public RemoveRoofModel(List designatedThings, HashSet roofSupporterToReinstall, IEnumerable waitingThings, HashSet roofToRemove, Map map, IntVec3 mousePos, Rot4 rotation, Dictionary ghostPos) - { - this.DesignatedThings = designatedThings; - this.BuildingsToReinstall = roofSupporterToReinstall; - this.RoofToRemove = roofToRemove; - this.Map = map; - this.BuildingsBeingReinstalled = new HashSet(); - this.WaitingThings = waitingThings.ToHashSet(); - this.MousePos = mousePos; - this.Rotation = rotation; - this.GhostPosition = ghostPos; - } - - public void ExposeData() - { - if (Scribe.mode == LoadSaveMode.Saving) - { - this.CleanCache(); - } - - Scribe_Collections.Look(ref this.BuildingsToReinstall, nameof(this.BuildingsToReinstall), LookMode.Reference); - Scribe_Collections.Look(ref this.BuildingsBeingReinstalled, nameof(this.BuildingsBeingReinstalled), LookMode.Reference); - Scribe_Collections.Look(ref this.RoofToRemove, nameof(RoofToRemove), LookMode.Value); - Scribe_Collections.Look(ref this.DesignatedThings, nameof(this.DesignatedThings), LookMode.Reference); - Scribe_Collections.Look(ref this.WaitingThings, nameof(this.WaitingThings), LookMode.Reference); - Scribe_References.Look(ref this.Map, nameof(this.Map)); - Scribe_Values.Look(ref this.MousePos, nameof(this.MousePos)); - Scribe_Values.Look(ref this.Rotation, nameof(this.Rotation)); - - - Scribe_Collections.Look(ref this.GhostPosition, nameof(this.GhostPosition), LookMode.Reference, LookMode.Value, ref ghostThings, ref ghostPos); - } - - private void CleanCache() - { - RemoveDestroyedThings(this.DesignatedThings); - RemoveDestroyedThings(this.BuildingsBeingReinstalled); - RemoveDestroyedThings(this.BuildingsToReinstall); - RemoveDestroyedThings(this.WaitingThings); - - if (this.GhostPosition is null) - return; - - foreach (Thing key in this.GhostPosition.Keys.ToList()) - { - if (key.Destroyed) - { - this.GhostPosition.Remove(key); - } - } - } - - private static void RemoveDestroyedThings(ICollection things) where T : Thing - { - if (things.EnumerableNullOrEmpty()) - return; - - foreach (T thing in new List(things)) - { - if (thing.Destroyed) - { - things.Remove(thing); - } - } - } - } - } -} diff --git a/src/common/HarmonyPatches/Blueprint_Destroy_Patch.cs b/src/common/HarmonyPatches/Blueprint_Destroy_Patch.cs deleted file mode 100644 index 7fd3cb2..0000000 --- a/src/common/HarmonyPatches/Blueprint_Destroy_Patch.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using Verse; - -namespace MoveBase -{ - [StaticConstructorOnStartup] - public static class Blueprint_Destroy_Patch - { - static Blueprint_Destroy_Patch() - { - MethodInfo original = typeof(ThingWithComps).GetMethod("Destroy", BindingFlags.Public | BindingFlags.Instance); - MethodInfo postfix = typeof(Blueprint_Destroy_Patch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public); - HarmonyUtility.Instance.Patch(original, prefix: new HarmonyMethod(postfix)); - - MethodInfo originalTryReplaceWithSolidThing = typeof(Blueprint_Install).GetMethod("TryReplaceWithSolidThing", BindingFlags.Public | BindingFlags.Instance); - MethodInfo postfixTryReplaceWithSolidThing = typeof(Blueprint_Destroy_Patch).GetMethod("PostfixTryReplaceWithSolidThing", BindingFlags.Static | BindingFlags.Public); - HarmonyUtility.Instance.Patch(originalTryReplaceWithSolidThing, postfix: new HarmonyMethod(postfixTryReplaceWithSolidThing)); - } - - public static void Postfix(ThingWithComps __instance) - { - if (__instance is Blueprint_Install blueprint) - { - blueprint.MiniToInstallOrBuildingToReinstall?.MapHeld?.designationManager.TryRemoveDesignationOn(blueprint.MiniToInstallOrBuildingToReinstall, MoveBaseDefOf.MoveBase); - } - } - - public static void PostfixTryReplaceWithSolidThing(Thing createdThing) - { - if (createdThing is Building building && building.def.holdsRoof) - DesignatorMoveBase.RemoveBuildingFromCache(building); - } - } -} diff --git a/src/common/HarmonyPatches/Designation_Notify_Removing_Patch.cs b/src/common/HarmonyPatches/Designation_Notify_Removing_Patch.cs deleted file mode 100644 index 844b0f3..0000000 --- a/src/common/HarmonyPatches/Designation_Notify_Removing_Patch.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using Verse; - -namespace MoveBase -{ - [StaticConstructorOnStartup] - public static class Designation_Notify_Removing_Patch - { - static Designation_Notify_Removing_Patch() - { - MethodInfo original = typeof(Designation).GetMethod("Notify_Removing", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo postfix = typeof(Designation_Notify_Removing_Patch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public); - //HarmonyUtility.Instance.Patch(original, postfix: new HarmonyMethod(postfix)); - } - - public static void Postfix(Designation __instance) - { - if (__instance.def == MoveBaseDefOf.MoveBase && __instance.target.Thing != null) - { - DesignatorMoveBase.Notify_Removing_Callback(__instance.target.Thing); - InstallBlueprintUtility.CancelBlueprintsFor(__instance.target.Thing); - } - } - } -} diff --git a/src/common/HarmonyPatches/Designator_Deselect_Patch.cs b/src/common/HarmonyPatches/Designator_Deselect_Patch.cs deleted file mode 100644 index ee88948..0000000 --- a/src/common/HarmonyPatches/Designator_Deselect_Patch.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using UnityEngine; -using Verse; - -namespace MoveBase -{ - [StaticConstructorOnStartup] - public static class Designator_Deselect_Patch - { - static Designator_Deselect_Patch() - { - MethodInfo original = typeof(DesignatorManager).GetMethod("Deselect"); - MethodInfo prefix = typeof(Designator_Deselect_Patch).GetMethod("Prefix"); - HarmonyUtility.Instance.Patch(original, new HarmonyMethod(prefix)); - } - - public static void Prefix(DesignatorManager __instance) - { - if (__instance.SelectedDesignator is DesignatorMoveBase moveBase && !moveBase.KeepDesignation && moveBase.DesignatedThings.Any()) - { - foreach (Thing thing in moveBase.DesignatedThings) - { - moveBase.Map.designationManager.TryRemoveDesignationOn(thing, MoveBaseDefOf.MoveBase); - } - } - } - } -} diff --git a/src/common/HarmonyPatches/Designator_DesignateThing_Patch.cs b/src/common/HarmonyPatches/Designator_DesignateThing_Patch.cs deleted file mode 100644 index 63d2d68..0000000 --- a/src/common/HarmonyPatches/Designator_DesignateThing_Patch.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using Verse; - -namespace MoveBase -{ - [StaticConstructorOnStartup] - public static class Designator_Patch - { - static Designator_Patch() - { - MethodInfo originalDesignateThing = typeof(Designator_Cancel).GetMethod("DesignateThing", BindingFlags.Public | BindingFlags.Instance); - MethodInfo designateThingPrefix = typeof(Designator_Patch).GetMethod("DesignateThingPrefix", BindingFlags.Static | BindingFlags.Public); - HarmonyUtility.Instance.Patch(originalDesignateThing, prefix: new HarmonyMethod(designateThingPrefix)); - - MethodInfo originalDesignateSingleCell = typeof(Designator_Cancel).GetMethod("DesignateSingleCell", BindingFlags.Public | BindingFlags.Instance); - MethodInfo designteSingleCellPrefix = typeof(Designator_Patch).GetMethod("DesignateSingleCellPrefix", BindingFlags.Static | BindingFlags.Public); - HarmonyUtility.Instance.Patch(originalDesignateSingleCell, prefix: new HarmonyMethod(designteSingleCellPrefix)); - } - - public static void DesignateThingPrefix(Designator __instance, Thing t) - { - if (__instance is Designator_Cancel cancel) - { - if (t.MapHeld.designationManager.DesignationOn(t, MoveBaseDefOf.MoveBase) != null) - { - DesignatorMoveBase.Notify_Removing_Callback(t); - InstallBlueprintUtility.CancelBlueprintsFor(t); - } - } - } - - public static void DesignateSingleCellPrefix(Designator __instance, IntVec3 c) - { - if (__instance is Designator_Cancel cancel) - { - foreach (Thing thing in c.GetThingList(__instance.Map)) - { - if (thing.MapHeld.designationManager.DesignationOn(thing, MoveBaseDefOf.MoveBase) != null) - { - DesignatorMoveBase.Notify_Removing_Callback(thing); - InstallBlueprintUtility.CancelBlueprintsFor(thing); - } - } - } - } - } -} diff --git a/src/common/HarmonyPatches/JobDriver_Uninstall_FinishedRemoving_Patch.cs b/src/common/HarmonyPatches/JobDriver_Uninstall_FinishedRemoving_Patch.cs deleted file mode 100644 index dfbc747..0000000 --- a/src/common/HarmonyPatches/JobDriver_Uninstall_FinishedRemoving_Patch.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using Verse; -using Verse.AI; - -namespace MoveBase -{ - [StaticConstructorOnStartup] - public static class JobDriver_Uninstall_FinishedRemoving_Patch - { - private static PropertyInfo _building = typeof(JobDriver_RemoveBuilding).GetProperty("Building", BindingFlags.NonPublic | BindingFlags.Instance); - - static JobDriver_Uninstall_FinishedRemoving_Patch() - { - MethodInfo original = typeof(JobDriver_Uninstall).GetMethod("FinishedRemoving", BindingFlags.NonPublic | BindingFlags.Instance); - MethodInfo postfix = typeof(JobDriver_Uninstall_FinishedRemoving_Patch).GetMethod("Postfix", BindingFlags.Public | BindingFlags.Static); - HarmonyUtility.Instance.Patch(original, postfix: new HarmonyMethod(postfix)); - } - - public static void Postfix(JobDriver_Uninstall __instance) - { - DesignatorMoveBase.UninstallJobCallback((Building)_building.GetValue(__instance), __instance.pawn.MapHeld); - } - } -} diff --git a/src/common/HarmonyPatches/MinifyUtility_MakeMinified.cs b/src/common/HarmonyPatches/MinifyUtility_MakeMinified.cs deleted file mode 100644 index 5ce3755..0000000 --- a/src/common/HarmonyPatches/MinifyUtility_MakeMinified.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using Verse; - -namespace MoveBase -{ - /// - /// Fix door tick exception when a door is minified. - /// - [StaticConstructorOnStartup] - public static class MinifyUtility_MakeMinified - { - private static MethodInfo _bucketOf = typeof(TickList).GetMethod("BucketOf", BindingFlags.NonPublic | BindingFlags.Instance); - - private static MethodInfo _tickListFor = typeof(TickManager).GetMethod("TickListFor", BindingFlags.NonPublic | BindingFlags.Instance); - - static MinifyUtility_MakeMinified() - { - MethodInfo original = typeof(MinifyUtility).GetMethod(nameof(MinifyUtility.MakeMinified), BindingFlags.Public | BindingFlags.Static); - MethodInfo prefix = typeof(MinifyUtility_MakeMinified).GetMethod("Prefix", BindingFlags.Public | BindingFlags.Static); - MethodInfo postfix = typeof(MinifyUtility_MakeMinified).GetMethod("Postfix", BindingFlags.Public | BindingFlags.Static); - HarmonyUtility.Instance.Patch(original, new HarmonyMethod(prefix), new HarmonyMethod(postfix)); - } - - public static bool Prefix(out bool __state, Thing thing) - { - __state = thing?.MapHeld?.designationManager?.DesignationOn(thing, MoveBaseDefOf.MoveBase) != null; - return true; - } - - public static void Postfix(bool __state, Thing thing) - { - if (__state) - { - TickList tickList = _tickListFor.Invoke(Find.TickManager, new[] { thing }) as TickList; - if (tickList != null) - (_bucketOf.Invoke(tickList, new[] { thing }) as List).Remove(thing); - } - } - } -} diff --git a/src/common/HarmonyPatches/RoofGrid_SetRoof_Patch.cs b/src/common/HarmonyPatches/RoofGrid_SetRoof_Patch.cs deleted file mode 100644 index 9d3b667..0000000 --- a/src/common/HarmonyPatches/RoofGrid_SetRoof_Patch.cs +++ /dev/null @@ -1,29 +0,0 @@ -using RimWorld; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using Verse; - -namespace MoveBase -{ - [StaticConstructorOnStartup] - public static class RoofGrid_SetRoof_Patch - { - static RoofGrid_SetRoof_Patch() - { - MethodInfo original = typeof(RoofGrid).GetMethod("SetRoof", BindingFlags.Instance | BindingFlags.Public); - MethodInfo postfix = typeof(RoofGrid_SetRoof_Patch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public); - HarmonyUtility.Instance.Patch(original, postfix: new HarmonyMethod(postfix)); - } - - public static void Postfix(IntVec3 c, RoofDef def) - { - if (def == null && Current.ProgramState == ProgramState.Playing) - DesignatorMoveBase.SetNoRoofFalse(c); - } - } -} diff --git a/src/common/HarmonyPatches/WorkGiver_ConstructDeliverResourcesToBlueprints_Patch.cs b/src/common/HarmonyPatches/WorkGiver_ConstructDeliverResourcesToBlueprints_Patch.cs deleted file mode 100644 index 4170517..0000000 --- a/src/common/HarmonyPatches/WorkGiver_ConstructDeliverResourcesToBlueprints_Patch.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; -using RimWorld; -using Verse; -using Verse.AI; - -namespace MoveBase -{ - /// - /// Patch the original so pawn won't start a reinstall job if the subject building is the only support for nearby roof. - /// - [StaticConstructorOnStartup] - public static class WorkGiver_ConstructDeliverResourcesToBlueprints_Patch - { - static WorkGiver_ConstructDeliverResourcesToBlueprints_Patch() - { - MethodInfo original = typeof(WorkGiver_ConstructDeliverResourcesToBlueprints).GetMethod("JobOnThing", BindingFlags.Public | BindingFlags.Instance); - MethodInfo postfix = typeof(WorkGiver_ConstructDeliverResourcesToBlueprints_Patch).GetMethod("Postfix", BindingFlags.Public | BindingFlags.Static); - HarmonyUtility.Instance.Patch(original, postfix: new HarmonyMethod(postfix)); - } - - /// - /// Postfix method. - /// - /// - /// - /// - public static void Postfix(Thing t, bool forced, ref Job __result) - { - if (t is Blueprint_Install install && install.MiniToInstallOrBuildingToReinstall is Building building && building.def.holdsRoof) - { - if (forced) - { - DesignatorMoveBase.AddBeingReinstalledBuilding(building); - return; - } - - if (building.MapHeld.designationManager.DesignationOn(building, MoveBaseDefOf.MoveBase) != null) - { - bool canRemove = true; - HashSet roofInRange = building.RoofInRange(); - List buildingsBeingRemoved = DesignatorMoveBase.GetBuildingsBeingReinstalled(building).Concat(building).ToList(); - foreach (IntVec3 roof in roofInRange) - { - if (!roof.IsSupported(building.MapHeld, buildingsBeingRemoved)) - { - building.MapHeld.areaManager.NoRoof[roof] = true; - building.MapHeld.areaManager.BuildRoof[roof] = false; - DesignatorMoveBase.AddToRoofToRemove(roof, building); - canRemove = false; - } - } - - if (canRemove) - DesignatorMoveBase.AddBeingReinstalledBuilding(building); - else - __result = null; - } - } - } - } -} diff --git a/src/common/MoveBaseDefOf.cs b/src/common/MoveBaseDefOf.cs deleted file mode 100644 index 6e2c09a..0000000 --- a/src/common/MoveBaseDefOf.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using RimWorld; -using Verse; - -namespace MoveBase -{ - [DefOf] - public static class MoveBaseDefOf - { - public static DesignationDef MoveBase; - - public static LetterDef NotooShabbyFeatureUpdate; - - static MoveBaseDefOf() - { - DefOfHelper.EnsureInitializedInCtor(typeof(MoveBaseDefOf)); - } - } -} diff --git a/src/common/MoveBaseMod.cs b/src/common/MoveBaseMod.cs deleted file mode 100644 index a6aec63..0000000 --- a/src/common/MoveBaseMod.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Verse; - -namespace MoveBase -{ - public class MoveBaseMod : Mod - { - private static FieldInfo _rootdir = typeof(ModContentPack).GetField("rootDirInt", BindingFlags.NonPublic | BindingFlags.Instance); - - public static DateTime CreationTime; - - public static MoveBaseSetting Setting { get; private set; } - - public MoveBaseMod(ModContentPack content) - : base(content) - { - Setting = this.GetSettings(); - CreationTime = (_rootdir.GetValue(this.Content) as DirectoryInfo).CreationTimeUtc; - } - } -} diff --git a/src/common/MoveBaseSetting.cs b/src/common/MoveBaseSetting.cs deleted file mode 100644 index a97a175..0000000 --- a/src/common/MoveBaseSetting.cs +++ /dev/null @@ -1,33 +0,0 @@ -using RimWorldUtility; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Verse; - -namespace MoveBase -{ - public class MoveBaseSetting : ModSettings - { - public List FeatureNews = new List(); - - public MoveBaseSetting() - { - //FeatureNews news = new FeatureNews( - // "Home Mover update" - // , $"Now, Home Mover allows you to move buildings to positions that are occupied, in a different formation, by the same buildings." - // , @"https://steamcommunity.com/sharedfiles/filedetails/?id=2092552843" - // , "Check out the demo" - // , new DateTime(2020, 6, 15)); - - //FeatureNews.Add(news); - } - - public override void ExposeData() - { - //base.ExposeData(); - //Scribe_Collections.Look(ref FeatureNews, nameof(FeatureNews), LookMode.Deep); - } - } -} diff --git a/src/common/UI/FeatureUpdateLetter.cs b/src/common/UI/FeatureUpdateLetter.cs deleted file mode 100644 index 8a51879..0000000 --- a/src/common/UI/FeatureUpdateLetter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using RimWorld; -using RimWorldUtility; -using RimWorldUtility.UI; -using Verse; - -namespace MoveBase -{ - public class FeatureUpdateLetter : ChoiceLetter - { - private FeatureNews _news; - - private Mod _mod; - - public FeatureUpdateLetter() - { - this.def = MoveBaseDefOf.NotooShabbyFeatureUpdate; - } - - public FeatureUpdateLetter(FeatureNews featureNews, Mod mod) - : this() - { - _news = featureNews; - _mod = mod; - this.label = _news.Label; - this.ID = Find.UniqueIDsManager.GetNextLetterID(); - } - - - public override bool CanDismissWithRightClick - { - get - { - this.UpdateModSetting(); - return _news.Received = true; - } - } - - public override IEnumerable Choices => new List(); - - public override void OpenLetter() - { - Find.WindowStack.Add(new Dialog_Feature(_news)); - _news.Received = true; - this.UpdateModSetting(); - } - - protected override string GetMouseoverText() - { - return _news.Description; - } - - private void UpdateModSetting() - { - _mod.WriteSettings(); - } - } -} diff --git a/src/common/UIText.cs b/src/common/UIText.cs deleted file mode 100644 index 9d86eec..0000000 --- a/src/common/UIText.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MoveBase -{ - public static class UIText - { - public const string Description = "MoveBase_Description"; - - public const string Label = "MoveBase_Label"; - } -} diff --git a/src/common/util/HarmonyUtility.cs b/src/common/util/HarmonyUtility.cs deleted file mode 100644 index 52c46f6..0000000 --- a/src/common/util/HarmonyUtility.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; - -namespace MoveBase -{ - public static class HarmonyUtility - { - public static Harmony Instance = new Harmony("NotooShabby.MoveBase"); - - public static bool Compare(this CodeInstruction codeInstruction, CodeInstruction pattern) - { - if (codeInstruction.opcode != pattern.opcode - || (pattern.opcode == OpCodes.Ldloc_S - && (int)pattern.operand != (codeInstruction.operand as LocalVariableInfo)?.LocalIndex) - || (pattern.opcode == OpCodes.Callvirt - && pattern.operand != null - && pattern.operand != codeInstruction.operand)) - return false; - - return true; - } - - public static bool MatchPattern(List instructions, List pattern, int index, Action action) - { - for (int i = index, j = 0; j < pattern.Count; i++, j++) - { - if (!instructions[i].Compare(pattern[j])) - { - return false; - } - } - - action?.Invoke(); - return true; - } - - public static List ReturnPatternMatchedInstruction(List pattern, List original, ref int index) - { - List instructions = new List(); - for (int j = 1; j < pattern.Count; j++) - { - instructions.Add(original[index + j]); - } - - index += (pattern.Count - 1); - - return instructions; - } - } -} diff --git a/src/common/util/RoofUtility.cs b/src/common/util/RoofUtility.cs deleted file mode 100644 index e997bbd..0000000 --- a/src/common/util/RoofUtility.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Verse; - -namespace MoveBase -{ - /// - /// Utility for roof. - /// - [PerfProfile] - public static class RoofUtility - { - private static Dictionary _supportedRoof = new Dictionary(); - - /// - /// Check if roof is supported by buildings other than those in . - /// - /// - /// - /// - /// - /// (141, 144) to (138, 138) - public static bool IsSupported(this IntVec3 roof, Map map, IEnumerable exceptions) - { - if (_supportedRoof.TryGetValue(roof, out Building building1) && !exceptions.Contains(building1)) - return true; - - bool supported = false; - - map.floodFiller.FloodFill( - roof - , (cell) => cell.Roofed(map) && cell.InHorDistOf(roof, RoofCollapseUtility.RoofMaxSupportDistance) - , (cell) => - { - if (cell.GetEdifice(map) is Building building && building.def.holdsRoof && !exceptions.Contains(building)) - { - _supportedRoof[roof] = building; - return supported = true; - } - - return false; - }); - - return supported; - } - } -} diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..2a82278 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.002