Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4be97e0de | |||
| 2b74c9e70c | |||
| 77d405687c | |||
| 4c1831762b | |||
| 6d967bf2bf | |||
| e87a228dd2 | |||
| e8ff01e097 | |||
| 66a71067cc | |||
| 7a938b245e | |||
| 52ebc68a02 | |||
| 2a604fb06a | |||
| 1f904cdb13 | |||
| db93e8f68e | |||
| 4c302be16b | |||
| 013545af8a | |||
| 9f5769bb76 | |||
| 8a7ad9d418 | |||
| f589ef8dec | |||
| 55a12ec7cd | |||
| fad528faa1 | |||
| cd519e528f | |||
| b6e8d0b590 | |||
| 03f72f3715 | |||
| 15ab9edab1 | |||
| 0e412c2a42 | |||
| 8a3faff7d0 | |||
| 3525f0e3eb | |||
| cc973b9f0d | |||
| 8a552f7993 | |||
| 93841bc85d | |||
| 9ba8847626 | |||
| 51b7328310 | |||
| fdc352596d | |||
| 89ba5cc985 | |||
| fdc79166a0 | |||
| b84487336b | |||
| e4ab103c4d | |||
| 3b6cf0252b | |||
| e908cd3085 | |||
| 6b23fdbd26 | |||
| ea6258ff19 | |||
| 5e54f0f83b | |||
| f00439a430 | |||
| 02ec230b3f | |||
| 867554b835 | |||
| a4835eeb3c | |||
| 55b877226e | |||
| d7d33d0dac | |||
| f92c6d282f | |||
| 2d41523668 | |||
| 34b04a365a | |||
| d0ac644e14 | |||
| 510a3200d1 | |||
| bef601941c | |||
| b6605d6293 | |||
| 1b6742ea45 | |||
| 405e487881 | |||
| af1f6da98d | |||
| 119850a7b4 | |||
| b198aba09b | |||
| 37165d1562 | |||
| 5684561b66 | |||
| 2678cac0e6 | |||
| 230b409abe | |||
| a6c80206c9 | |||
| 1a73f23670 | |||
| 38e62dcbb3 | |||
| 19bdc143c1 | |||
| 1709554e72 | |||
| 5c2e9408c5 | |||
| caeae26a09 | |||
| 056a68b0ad | |||
| f1f0febf29 | |||
| 916a6e7153 | |||
| ddf1bd019b | |||
| 2fdc9c7ca8 | |||
| d79ca44972 | |||
| 148aea9bb4 | |||
| bdce8b969c | |||
| 4095f818f6 | |||
| 72bf3d4cc5 | |||
| 51907a1f01 | |||
| c5c4ceb032 | |||
| 64957334de | |||
| 7323b6e814 | |||
| 24f9801093 | |||
| bcc8af76c2 | |||
| 2923407e90 | |||
| ee5844f603 | |||
| 5ab1589609 | |||
| bed7437893 | |||
| 05219b536e | |||
| 05c4ce1c43 | |||
| c66e929d8b | |||
| 5e193ede88 | |||
| c28d97de2d | |||
| 39d6ab1c5f | |||
| bf06955c06 | |||
| 370e015cc5 | |||
| 5ce2824f8b | |||
| b73c1a6dd5 | |||
| e50e2c9918 | |||
| 5cb2d2beb5 | |||
| cb2d7e35ce | |||
| 58bb1d9ca5 | |||
| cf7591b413 | |||
| 92cc4f0264 | |||
| 18c8b741dd | |||
| b84b7e4dd5 | |||
| 4d419b9010 | |||
| 8b2bf3e32e | |||
| db49703326 | |||
| fb78add739 | |||
| 04121f18a4 | |||
| fa029b9e53 | |||
| 494f0cb9ca | |||
| c1ca0bf27b | |||
| 8d1e7ebb4f | |||
| a257306999 | |||
| 2e5fcb6a75 | |||
| 87a9fad005 | |||
| 837b3d7705 | |||
| 4224333963 | |||
| 4dd48bed70 | |||
| abe6f42a3b | |||
| 27c67dbdd9 | |||
| 98ed361546 | |||
| eb1c7f78fa | |||
| 35b9ea383c | |||
| 9690280cd7 | |||
| 65538495c4 | |||
| 561e026834 | |||
| 7c74b8b5e5 | |||
| f7705a6d57 | |||
| 0dcf4a3f99 | |||
| 4ccdbc0ee6 | |||
| 0436053c62 | |||
| 6b97c226f1 | |||
| b1e78df6c7 | |||
| 5908494977 | |||
| 63529a11ae | |||
| 255b87f991 | |||
| fd3eb35782 | |||
| 9e75193731 | |||
| 609078c584 | |||
| 893126ef78 | |||
| 6737668391 | |||
| ca77579168 | |||
| c6559d593a | |||
| e32dac9e6e | |||
| 30b4d1a2eb | |||
| 2fa4ce68e7 | |||
| 80e533d98e | |||
| 0e3e258fd3 | |||
| c7991198ea | |||
| 1a4b2f4c19 | |||
| 52a9c3f120 | |||
| 2301884418 | |||
| 04054cfeae | |||
| 66be7838bb | |||
| 1eb65d1520 | |||
| f2a39316ba | |||
| fffd8c947b |
42
.gitea/workflows/dev-branch.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Create tag and build when new code gets to main
|
||||||
|
run-name: Create tag and build when new code gets to main
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**' # matches every branch
|
||||||
|
- '!main' # except main
|
||||||
|
- '!release/*' # except release branches
|
||||||
|
tags-ignore:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GODOT_VERSION: 4.6
|
||||||
|
GAME_NAME: MovementTests
|
||||||
|
ITCHIO_USERNAME: Minimata
|
||||||
|
ITCHIO_GAMEID: MovementTests
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Export:
|
||||||
|
runs-on: godot
|
||||||
|
steps:
|
||||||
|
- name: Checkout with LFS
|
||||||
|
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
uses: godot-gdunit-labs/gdUnit4-action@v1
|
||||||
|
with:
|
||||||
|
godot-version: ${GODOT_VERSION}
|
||||||
|
godot-net: true
|
||||||
|
godot-force-mono: true
|
||||||
|
dotnet-version: 'net9.0'
|
||||||
|
paths: |
|
||||||
|
res://tests/
|
||||||
|
timeout: 1
|
||||||
|
publish-report: false
|
||||||
|
upload-report: false
|
||||||
|
|
||||||
|
- name: Upload test report
|
||||||
|
uses: actions/upload-artifact@v3-node20
|
||||||
|
with:
|
||||||
|
name: Test Report
|
||||||
|
path: ${{ github.workspace }}/reports/test-result.html
|
||||||
@@ -8,6 +8,7 @@ on:
|
|||||||
- "**"
|
- "**"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
GODOT_VERSION: 4.6
|
||||||
GAME_NAME: MovementTests
|
GAME_NAME: MovementTests
|
||||||
ITCHIO_USERNAME: Minimata
|
ITCHIO_USERNAME: Minimata
|
||||||
ITCHIO_GAMEID: MovementTests
|
ITCHIO_GAMEID: MovementTests
|
||||||
@@ -21,6 +22,7 @@ jobs:
|
|||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
token: ${{ secrets.TOKEN }}
|
||||||
lfs: false
|
lfs: false
|
||||||
- name: Remove buggy pre-push hook
|
- name: Remove buggy pre-push hook
|
||||||
run: |
|
run: |
|
||||||
@@ -36,81 +38,79 @@ jobs:
|
|||||||
INITIAL_VERSION: 0.1.0
|
INITIAL_VERSION: 0.1.0
|
||||||
DEFAULT_BUMP: patch
|
DEFAULT_BUMP: patch
|
||||||
|
|
||||||
Export:
|
Test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: godot
|
||||||
needs: BumpTag
|
# env:
|
||||||
container:
|
# RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
|
||||||
image: barichello/godot-ci:mono-4.5
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install node, curl and zip
|
|
||||||
run: |
|
|
||||||
apt update && apt -y install curl zip nodejs
|
|
||||||
- name: Checkout with LFS
|
- name: Checkout with LFS
|
||||||
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
|
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
|
||||||
with:
|
|
||||||
checkout-version: 3
|
|
||||||
|
|
||||||
- name: Import resources and build solution
|
- name: Run tests
|
||||||
|
uses: godot-gdunit-labs/gdUnit4-action@v1
|
||||||
|
with:
|
||||||
|
godot-version: ${GODOT_VERSION}
|
||||||
|
godot-net: true
|
||||||
|
godot-force-mono: true
|
||||||
|
dotnet-version: 'net9.0'
|
||||||
|
paths: |
|
||||||
|
res://tests/
|
||||||
|
timeout: 1
|
||||||
|
publish-report: false
|
||||||
|
upload-report: false
|
||||||
|
|
||||||
|
- name: Upload test report
|
||||||
|
uses: actions/upload-artifact@v3-node20
|
||||||
|
with:
|
||||||
|
name: Test Report
|
||||||
|
path: ${{ github.workspace }}/reports/test-result.html
|
||||||
|
|
||||||
|
Export:
|
||||||
|
runs-on: godot
|
||||||
|
needs:
|
||||||
|
- BumpTag
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout with LFS
|
||||||
|
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
id: setup-godot
|
||||||
|
uses: https://git.game-dev.space/minimata/setup-godot.git@main
|
||||||
|
with:
|
||||||
|
godot-version: '4.6'
|
||||||
|
dotnet-version: 'net9.0'
|
||||||
|
|
||||||
|
- name: Remove GDUnit addon
|
||||||
run: |
|
run: |
|
||||||
godot --headless --editor --build-solutions --quit --import --path $PWD
|
rm -rf ${{ gitea.workspace }}/addons/gdUnit4
|
||||||
|
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
run: |
|
run: |
|
||||||
mkdir -v -p build/windows
|
mkdir -v -p build/windows
|
||||||
godot --headless --verbose --build-solutions --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
|
${{ steps.setup-godot.outputs.godot_bin }} --headless --verbose --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
|
||||||
zip -r Windows.zip build/windows
|
zip -r Windows.zip build/windows
|
||||||
- name: Upload to Itch
|
|
||||||
uses: KikimoraGames/itch-publish@v0.0.3
|
|
||||||
with:
|
|
||||||
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
|
|
||||||
itchUsername: ${{ env.ITCHIO_USERNAME }}
|
|
||||||
itchGameId: ${{ env.ITCHIO_GAMEID }}
|
|
||||||
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
|
|
||||||
gameData: Windows.zip
|
|
||||||
buildChannel: windows
|
|
||||||
|
|
||||||
- name: Build Windows ARM
|
- name: Setup Butler
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -v -p build/windowsArm
|
mkdir ./tools 2>/dev/null || true
|
||||||
godot --headless --verbose --build-solutions --export-release "Windows ARM" build/windowsArm/${{ env.GAME_NAME }}.exe
|
pushd tools
|
||||||
zip -r WindowsArm.zip build/windowsArm
|
curl -sSLfo ./butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
|
||||||
- name: Upload to Itch
|
unzip butler.zip
|
||||||
uses: KikimoraGames/itch-publish@v0.0.3
|
chmod +x ./butler
|
||||||
with:
|
popd
|
||||||
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
|
./tools/butler -V
|
||||||
itchUsername: ${{ env.ITCHIO_USERNAME }}
|
|
||||||
itchGameId: ${{ env.ITCHIO_GAMEID }}
|
|
||||||
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
|
|
||||||
gameData: WindowsArm.zip
|
|
||||||
buildChannel: windows-arm
|
|
||||||
|
|
||||||
- name: Linux Build
|
- name: Upload to itch.io
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -v -p build/linux
|
versionArgument="--userversion ${{ needs.BumpTag.outputs.tag_name }}"
|
||||||
godot --headless --verbose --export-release "Linux/X11" build/linux/${{ env.GAME_NAME }}.x86_64
|
./tools/butler push \
|
||||||
zip -r Linux.zip build/linux
|
"Windows.zip" \
|
||||||
- name: Upload to Itch
|
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows ${versionArgument}
|
||||||
uses: KikimoraGames/itch-publish@v0.0.3
|
|
||||||
with:
|
|
||||||
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
|
|
||||||
itchUsername: ${{ env.ITCHIO_USERNAME }}
|
|
||||||
itchGameId: ${{ env.ITCHIO_GAMEID }}
|
|
||||||
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
|
|
||||||
gameData: Linux.zip
|
|
||||||
buildChannel: linux
|
|
||||||
|
|
||||||
- name: Mac Build
|
|
||||||
run: |
|
|
||||||
mkdir -v -p build/mac
|
|
||||||
godot --headless --verbose --export-release "macOS" build/mac/${{ env.GAME_NAME }}.zip
|
|
||||||
zip -r Mac.zip build/mac
|
|
||||||
- name: Upload to Itch
|
|
||||||
uses: KikimoraGames/itch-publish@v0.0.3
|
|
||||||
with:
|
|
||||||
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
|
|
||||||
itchUsername: ${{ env.ITCHIO_USERNAME }}
|
|
||||||
itchGameId: ${{ env.ITCHIO_GAMEID }}
|
|
||||||
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
|
|
||||||
gameData: Mac.zip
|
|
||||||
buildChannel: mac
|
|
||||||
|
|||||||
116
.gitea/workflows/release-branch.yaml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
name: Create tag and build when new code gets to main
|
||||||
|
run-name: Create tag and build when new code gets to main
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'release/*' # only release branches
|
||||||
|
tags-ignore:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GODOT_VERSION: 4.6
|
||||||
|
GAME_NAME: MovementTests
|
||||||
|
ITCHIO_USERNAME: Minimata
|
||||||
|
ITCHIO_GAMEID: MovementTests
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ReleaseName:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ contains(gitea.ref_name, 'release/') }}
|
||||||
|
outputs:
|
||||||
|
release_name: ${{ steps.split.outputs._1 }}
|
||||||
|
steps:
|
||||||
|
- uses: winterjung/split@v2
|
||||||
|
id: split
|
||||||
|
with:
|
||||||
|
msg: ${{ gitea.ref_name }}
|
||||||
|
separator: '/'
|
||||||
|
|
||||||
|
Release:
|
||||||
|
runs-on: godot
|
||||||
|
if: ${{ contains(gitea.ref_name, 'release/') }}
|
||||||
|
needs: ReleaseName
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout with LFS
|
||||||
|
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
|
||||||
|
|
||||||
|
- name: Setup Godot
|
||||||
|
id: setup-godot
|
||||||
|
uses: https://git.game-dev.space/minimata/setup-godot.git@main
|
||||||
|
with:
|
||||||
|
godot-version: '4.6'
|
||||||
|
dotnet-version: 'net9.0'
|
||||||
|
|
||||||
|
- name: Setup Butler
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
|
run: |
|
||||||
|
mkdir ./tools 2>/dev/null || true
|
||||||
|
pushd tools
|
||||||
|
curl -sSLfo ./butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
|
||||||
|
unzip butler.zip
|
||||||
|
chmod +x ./butler
|
||||||
|
popd
|
||||||
|
./tools/butler -V
|
||||||
|
|
||||||
|
- name: Build Windows
|
||||||
|
run: |
|
||||||
|
mkdir -v -p build/windows
|
||||||
|
godot --headless --verbose --build-solutions --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
|
||||||
|
zip -r Windows.zip build/windows
|
||||||
|
- name: Upload Windows to itch.io
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
|
run: |
|
||||||
|
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
|
||||||
|
./tools/butler push \
|
||||||
|
"Windows.zip" \
|
||||||
|
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows ${versionArgument}
|
||||||
|
|
||||||
|
- name: Build Windows ARM
|
||||||
|
run: |
|
||||||
|
mkdir -v -p build/windowsArm
|
||||||
|
godot --headless --verbose --build-solutions --export-release "Windows ARM" build/windowsArm/${{ env.GAME_NAME }}.exe
|
||||||
|
zip -r WindowsArm.zip build/windowsArm
|
||||||
|
- name: Upload Windows to itch.io
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
|
run: |
|
||||||
|
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
|
||||||
|
./tools/butler push \
|
||||||
|
"WindowsArm.zip" \
|
||||||
|
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows-arm ${versionArgument}
|
||||||
|
|
||||||
|
- name: Linux Build
|
||||||
|
run: |
|
||||||
|
mkdir -v -p build/linux
|
||||||
|
godot --headless --verbose --export-release "Linux/X11" build/linux/${{ env.GAME_NAME }}.x86_64
|
||||||
|
zip -r Linux.zip build/linux
|
||||||
|
- name: Upload Windows to itch.io
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
|
run: |
|
||||||
|
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
|
||||||
|
./tools/butler push \
|
||||||
|
"Linux.zip" \
|
||||||
|
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:linux ${versionArgument}
|
||||||
|
|
||||||
|
- name: Mac Build
|
||||||
|
run: |
|
||||||
|
mkdir -v -p build/mac
|
||||||
|
godot --headless --verbose --export-release "macOS" build/mac/${{ env.GAME_NAME }}.zip
|
||||||
|
zip -r Mac.zip build/mac
|
||||||
|
- name: Upload Windows to itch.io
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
|
||||||
|
run: |
|
||||||
|
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
|
||||||
|
./tools/butler push \
|
||||||
|
"Mac.zip" \
|
||||||
|
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:mac ${versionArgument}
|
||||||
1
.gitignore
vendored
@@ -10,6 +10,7 @@
|
|||||||
.import/
|
.import/
|
||||||
|
|
||||||
/builds
|
/builds
|
||||||
|
/communication
|
||||||
|
|
||||||
# Imported translations (automatically generated from CSV files)
|
# Imported translations (automatically generated from CSV files)
|
||||||
*.translation
|
*.translation
|
||||||
55
.runsettings
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RunSettings>
|
||||||
|
<RunConfiguration>
|
||||||
|
<MaxCpuCount>1</MaxCpuCount>
|
||||||
|
<ResultsDirectory>./TestResults</ResultsDirectory>
|
||||||
|
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||||
|
<TestSessionTimeout>180000</TestSessionTimeout>
|
||||||
|
<TreatNoTestsAsError>true</TreatNoTestsAsError>
|
||||||
|
<EnvironmentVariables>
|
||||||
|
<GODOT_BIN>d:\development\Godot_v4.5-stable_mono_win64\Godot_v4.5-stable_mono_win64.exe</GODOT_BIN>
|
||||||
|
</EnvironmentVariables>
|
||||||
|
</RunConfiguration>
|
||||||
|
|
||||||
|
<LoggerRunSettings>
|
||||||
|
<Loggers>
|
||||||
|
<Logger friendlyName="console" enabled="True">
|
||||||
|
<Configuration>
|
||||||
|
<Verbosity>detailed</Verbosity>
|
||||||
|
</Configuration>
|
||||||
|
</Logger>
|
||||||
|
<Logger friendlyName="html" enabled="True">
|
||||||
|
<Configuration>
|
||||||
|
<LogFileName>test-result.html</LogFileName>
|
||||||
|
</Configuration>
|
||||||
|
</Logger>
|
||||||
|
<Logger friendlyName="trx" enabled="True">
|
||||||
|
<Configuration>
|
||||||
|
<LogFileName>test-result.trx</LogFileName>
|
||||||
|
</Configuration>
|
||||||
|
</Logger>
|
||||||
|
</Loggers>
|
||||||
|
</LoggerRunSettings>
|
||||||
|
|
||||||
|
<GdUnit4>
|
||||||
|
<!-- Additional Godot runtime parameters. These are passed to the Godot executable when running tests.-->
|
||||||
|
<Parameters>"--verbose"</Parameters>
|
||||||
|
|
||||||
|
<!-- Controls the display name format of test cases in the test results.
|
||||||
|
Allowed values:
|
||||||
|
- SimpleName: Uses only the method name (e.g., "TestMethod")
|
||||||
|
- FullyQualifiedName: Uses the full path including class and method name (e.g., "MyNamespace.MyClass.TestMethod")
|
||||||
|
Default: SimpleName -->
|
||||||
|
<DisplayName>FullyQualifiedName</DisplayName>
|
||||||
|
|
||||||
|
<!-- When set to true, standard output (stdout) from test cases is captured
|
||||||
|
and included in the test result. This can be useful for debugging. -->
|
||||||
|
<CaptureStdOut>true</CaptureStdOut>
|
||||||
|
|
||||||
|
<!-- The maximum duration allowed for a Godot project compilation process in milliseconds.
|
||||||
|
After this timeout period expires, the compilation process is forcefully terminated.
|
||||||
|
For large or complex Godot projects, you may need to increase this value.
|
||||||
|
Default: 20000 (20 seconds) -->
|
||||||
|
<CompileProcessTimeout>20000</CompileProcessTimeout>
|
||||||
|
</GdUnit4>
|
||||||
|
</RunSettings>
|
||||||
@@ -1,131 +1,143 @@
|
|||||||
<Project Sdk="Godot.NET.Sdk/4.5.0">
|
<Project Sdk="Godot.NET.Sdk/4.6.0">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
<RootNamespace>Movementtests</RootNamespace>
|
<RootNamespace>Movementtests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="export_presets.cfg" />
|
<Content Include=".runsettings"/>
|
||||||
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png" />
|
<Content Include="export_presets.cfg"/>
|
||||||
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import" />
|
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png"/>
|
||||||
<Content Include="menus\assets\git_logo\LICENSE.txt" />
|
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import"/>
|
||||||
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt" />
|
<Content Include="menus\assets\git_logo\LICENSE.txt"/>
|
||||||
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png" />
|
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt"/>
|
||||||
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import" />
|
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png"/>
|
||||||
<Content Include="menus\assets\icon.png" />
|
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import"/>
|
||||||
<Content Include="menus\assets\icon.png.import" />
|
<Content Include="menus\assets\icon.png"/>
|
||||||
<Content Include="menus\ATTRIBUTION.md" />
|
<Content Include="menus\assets\icon.png.import"/>
|
||||||
<Content Include="menus\resources\themes\expedition.tres" />
|
<Content Include="menus\ATTRIBUTION.md"/>
|
||||||
<Content Include="menus\resources\themes\gravity.tres" />
|
<Content Include="menus\resources\themes\expedition.tres"/>
|
||||||
<Content Include="menus\resources\themes\grow.tres" />
|
<Content Include="menus\resources\themes\gravity.tres"/>
|
||||||
<Content Include="menus\resources\themes\lab.tres" />
|
<Content Include="menus\resources\themes\grow.tres"/>
|
||||||
<Content Include="menus\resources\themes\lore.tres" />
|
<Content Include="menus\resources\themes\lab.tres"/>
|
||||||
<Content Include="menus\resources\themes\steal_this_theme.tres" />
|
<Content Include="menus\resources\themes\lore.tres"/>
|
||||||
<Content Include="menus\scenes\credits\scrollable_credits.gd" />
|
<Content Include="menus\resources\themes\steal_this_theme.tres"/>
|
||||||
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid" />
|
<Content Include="menus\scenes\credits\scrollable_credits.gd"/>
|
||||||
<Content Include="menus\scenes\credits\scrollable_credits.tscn" />
|
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid"/>
|
||||||
<Content Include="menus\scenes\credits\scrolling_credits.gd" />
|
<Content Include="menus\scenes\credits\scrollable_credits.tscn"/>
|
||||||
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid" />
|
<Content Include="menus\scenes\credits\scrolling_credits.gd"/>
|
||||||
<Content Include="menus\scenes\credits\scrolling_credits.tscn" />
|
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid"/>
|
||||||
<Content Include="menus\scenes\end_credits\end_credits.gd" />
|
<Content Include="menus\scenes\credits\scrolling_credits.tscn"/>
|
||||||
<Content Include="menus\scenes\end_credits\end_credits.gd.uid" />
|
<Content Include="menus\scenes\end_credits\end_credits.gd"/>
|
||||||
<Content Include="menus\scenes\end_credits\end_credits.tscn" />
|
<Content Include="menus\scenes\end_credits\end_credits.gd.uid"/>
|
||||||
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd" />
|
<Content Include="menus\scenes\end_credits\end_credits.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid" />
|
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd"/>
|
||||||
<Content Include="menus\scenes\game_scene\game_ui.tscn" />
|
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid"/>
|
||||||
<Content Include="menus\scenes\game_scene\input_display_label.gd" />
|
<Content Include="menus\scenes\game_scene\game_ui.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid" />
|
<Content Include="menus\scenes\game_scene\input_display_label.gd"/>
|
||||||
<Content Include="menus\scenes\game_scene\levels\level.gd" />
|
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid"/>
|
||||||
<Content Include="menus\scenes\game_scene\levels\level.gd.uid" />
|
<Content Include="menus\scenes\game_scene\levels\level.gd"/>
|
||||||
<Content Include="menus\scenes\game_scene\levels\level_1.tscn" />
|
<Content Include="menus\scenes\game_scene\levels\level.gd.uid"/>
|
||||||
<Content Include="menus\scenes\game_scene\levels\level_2.tscn" />
|
<Content Include="menus\scenes\game_scene\levels\level_1.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\levels\level_3.tscn" />
|
<Content Include="menus\scenes\game_scene\levels\level_2.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn" />
|
<Content Include="menus\scenes\game_scene\levels\level_3.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn" />
|
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn" />
|
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\tutorial_manager.gd" />
|
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn"/>
|
||||||
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid" />
|
<Content Include="menus\scenes\game_scene\tutorial_manager.gd"/>
|
||||||
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn" />
|
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid"/>
|
||||||
<Content Include="menus\scenes\loading_screen\loading_screen.gd" />
|
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn"/>
|
||||||
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid" />
|
<Content Include="menus\scenes\loading_screen\loading_screen.gd"/>
|
||||||
<Content Include="menus\scenes\loading_screen\loading_screen.tscn" />
|
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid"/>
|
||||||
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd" />
|
<Content Include="menus\scenes\loading_screen\loading_screen.tscn"/>
|
||||||
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid" />
|
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd"/>
|
||||||
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn" />
|
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd" />
|
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn" />
|
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\main_menu\main_menu.gd" />
|
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\main_menu\main_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn" />
|
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd" />
|
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid" />
|
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd"/>
|
||||||
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn" />
|
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd" />
|
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd" />
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd" />
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd" />
|
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd" />
|
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd" />
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd" />
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd" />
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd" />
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid" />
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn" />
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\opening\opening.gd" />
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn"/>
|
||||||
<Content Include="menus\scenes\opening\opening.gd.uid" />
|
<Content Include="menus\scenes\opening\opening.gd"/>
|
||||||
<Content Include="menus\scenes\opening\opening.tscn" />
|
<Content Include="menus\scenes\opening\opening.gd.uid"/>
|
||||||
<Content Include="menus\scenes\opening\opening_with_logo.tscn" />
|
<Content Include="menus\scenes\opening\opening.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd" />
|
<Content Include="menus\scenes\opening\opening_with_logo.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid" />
|
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd" />
|
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid" />
|
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd" />
|
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid" />
|
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd" />
|
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid" />
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd" />
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid" />
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd" />
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid" />
|
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd"/>
|
||||||
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn" />
|
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid"/>
|
||||||
<Content Include="menus\scripts\game_state.gd" />
|
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn"/>
|
||||||
<Content Include="menus\scripts\game_state.gd.uid" />
|
<Content Include="menus\scripts\game_state.gd"/>
|
||||||
<Content Include="menus\scripts\level_list_and_state_manager.gd" />
|
<Content Include="menus\scripts\game_state.gd.uid"/>
|
||||||
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid" />
|
<Content Include="menus\scripts\level_list_and_state_manager.gd"/>
|
||||||
<Content Include="menus\scripts\level_state.gd" />
|
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid"/>
|
||||||
<Content Include="menus\scripts\level_state.gd.uid" />
|
<Content Include="menus\scripts\level_state.gd"/>
|
||||||
|
<Content Include="menus\scripts\level_state.gd.uid"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="addons\" />
|
<Folder Include="addons\"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RustyOptions" Version="0.10.1" />
|
<PackageReference Include="RustyOptions" Version="0.10.1"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<!-- gdUnit4 package dependencies -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
|
||||||
|
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
|
||||||
|
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
|
||||||
|
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
|
||||||
|
<PrivateAssets>none</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="addons/forge/Forge.props"/>
|
||||||
</Project>
|
</Project>
|
||||||
142
Movement tests.csproj.old.1
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<Project Sdk="Godot.NET.Sdk/4.5.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
|
<RootNamespace>Movementtests</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include=".runsettings" />
|
||||||
|
<Content Include="export_presets.cfg" />
|
||||||
|
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png" />
|
||||||
|
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import" />
|
||||||
|
<Content Include="menus\assets\git_logo\LICENSE.txt" />
|
||||||
|
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt" />
|
||||||
|
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png" />
|
||||||
|
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import" />
|
||||||
|
<Content Include="menus\assets\icon.png" />
|
||||||
|
<Content Include="menus\assets\icon.png.import" />
|
||||||
|
<Content Include="menus\ATTRIBUTION.md" />
|
||||||
|
<Content Include="menus\resources\themes\expedition.tres" />
|
||||||
|
<Content Include="menus\resources\themes\gravity.tres" />
|
||||||
|
<Content Include="menus\resources\themes\grow.tres" />
|
||||||
|
<Content Include="menus\resources\themes\lab.tres" />
|
||||||
|
<Content Include="menus\resources\themes\lore.tres" />
|
||||||
|
<Content Include="menus\resources\themes\steal_this_theme.tres" />
|
||||||
|
<Content Include="menus\scenes\credits\scrollable_credits.gd" />
|
||||||
|
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\credits\scrollable_credits.tscn" />
|
||||||
|
<Content Include="menus\scenes\credits\scrolling_credits.gd" />
|
||||||
|
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\credits\scrolling_credits.tscn" />
|
||||||
|
<Content Include="menus\scenes\end_credits\end_credits.gd" />
|
||||||
|
<Content Include="menus\scenes\end_credits\end_credits.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\end_credits\end_credits.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd" />
|
||||||
|
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\game_scene\game_ui.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\input_display_label.gd" />
|
||||||
|
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\game_scene\levels\level.gd" />
|
||||||
|
<Content Include="menus\scenes\game_scene\levels\level.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\game_scene\levels\level_1.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\levels\level_2.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\levels\level_3.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn" />
|
||||||
|
<Content Include="menus\scenes\game_scene\tutorial_manager.gd" />
|
||||||
|
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\loading_screen.gd" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\loading_screen.tscn" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\main_menu\main_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn" />
|
||||||
|
<Content Include="menus\scenes\opening\opening.gd" />
|
||||||
|
<Content Include="menus\scenes\opening\opening.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\opening\opening.tscn" />
|
||||||
|
<Content Include="menus\scenes\opening\opening_with_logo.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid" />
|
||||||
|
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn" />
|
||||||
|
<Content Include="menus\scripts\game_state.gd" />
|
||||||
|
<Content Include="menus\scripts\game_state.gd.uid" />
|
||||||
|
<Content Include="menus\scripts\level_list_and_state_manager.gd" />
|
||||||
|
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid" />
|
||||||
|
<Content Include="menus\scripts\level_state.gd" />
|
||||||
|
<Content Include="menus\scripts\level_state.gd.uid" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="addons\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="RustyOptions" Version="0.10.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- gdUnit4 package dependencies -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
|
||||||
|
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
|
||||||
|
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
|
||||||
|
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
|
||||||
|
<PrivateAssets>none</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -1,4 +1,12 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAction_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7c0f83388bfc4d2c9d09befcec9dd79bc90908_003Fb8_003F4d300c4d_003FAction_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAction_00602_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7c0f83388bfc4d2c9d09befcec9dd79bc90908_003F87_003Fded27e2d_003FAction_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnemy_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8e71dc81611862c01a2cb998a1f327de14747655_003FEnemy_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F716d154fef5cbe863cd637bd32beda6e3cec5f12e8fed2dc5b2d8149a0d558ab_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fdf73a4db74df89d59655c5fb6326406f47fbfa9af1fa81518fe0a07c49d34133_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fdf73a4db74df89d59655c5fb6326406f47fbfa9af1fa81518fe0a07c49d34133_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneTree_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8d6960554e939a669841b1ece03d27df4ab42f92bb80be3767eaec8cdaccf84b_003FSceneTree_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneTree_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8d6960554e939a669841b1ece03d27df4ab42f92bb80be3767eaec8cdaccf84b_003FSceneTree_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=dd9a7ac6_002Dbb9b_002D4001_002Db145_002D15e6509b7e78/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
|
<Solution />
|
||||||
|
</SessionState></s:String>
|
||||||
|
<s:String x:Key="/Default/Housekeeping/UnitTestingMru/UnitTestRunner/RunConfigurationFilename/@EntryValue">D:\Godot\Projects\movement-tests\.runsettings</s:String>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=floorplane/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=floorplane/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
42
addons/csg_toolkit/SETTINGS.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# CSG Toolkit Settings
|
||||||
|
|
||||||
|
The CSG Toolkit now uses Godot's built-in **ProjectSettings** system instead of a custom configuration file.
|
||||||
|
|
||||||
|
## Accessing Settings
|
||||||
|
|
||||||
|
Settings can be accessed in two ways:
|
||||||
|
|
||||||
|
1. **Through the CSG Toolkit config window** (recommended for users)
|
||||||
|
- Click the config button in the CSG Toolkit sidebar
|
||||||
|
- Modify settings using the UI
|
||||||
|
- Click "Save" to persist changes
|
||||||
|
|
||||||
|
2. **Directly in Project Settings** (for advanced users)
|
||||||
|
- Go to Project → Project Settings
|
||||||
|
- Navigate to the "Addons" section
|
||||||
|
- Look for `addons/csg_toolkit/*` settings
|
||||||
|
|
||||||
|
## Available Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `addons/csg_toolkit/default_behavior` | Enum | Sibling | Default insertion behavior (Sibling or Child) |
|
||||||
|
| `addons/csg_toolkit/action_key` | Key | Shift | Primary action key for shortcuts |
|
||||||
|
| `addons/csg_toolkit/secondary_action_key` | Key | Alt | Secondary action key for behavior inversion |
|
||||||
|
| `addons/csg_toolkit/auto_hide` | Boolean | true | Auto-hide sidebar when no CSG nodes selected |
|
||||||
|
|
||||||
|
## Migration from Old Config
|
||||||
|
|
||||||
|
If you were using an older version with `csg_toolkit_config.cfg`:
|
||||||
|
- The old config file is no longer used
|
||||||
|
- Settings are now stored in `project.godot`
|
||||||
|
- Settings will be initialized with defaults on first run
|
||||||
|
- You'll need to reconfigure your preferences if migrating
|
||||||
|
|
||||||
|
## Advantages of ProjectSettings
|
||||||
|
|
||||||
|
- Settings are version-controlled with your project
|
||||||
|
- Visible and editable in Project Settings editor
|
||||||
|
- Better integration with Godot's editor
|
||||||
|
- No separate config file to manage
|
||||||
|
- Settings persist per-project automatically
|
||||||
57
addons/csg_toolkit/csg_toolkit.gd
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
@tool
|
||||||
|
class_name CsgToolkit extends EditorPlugin
|
||||||
|
@onready var config: CsgTkConfig:
|
||||||
|
get:
|
||||||
|
return get_tree().root.get_node_or_null(AUTOLOAD_NAME) as CsgTkConfig
|
||||||
|
|
||||||
|
var sidebar: CSGSideToolkitBar
|
||||||
|
var topbar: CSGTopToolkitBar
|
||||||
|
var shortcut_manager: CsgShortcutManager
|
||||||
|
|
||||||
|
const AUTOLOAD_NAME = "CsgToolkitAutoload"
|
||||||
|
static var csg_plugin_path
|
||||||
|
static var undo_manager: EditorUndoRedoManager
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
# Config
|
||||||
|
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/csg_toolkit/scripts/csg_toolkit_config.gd")
|
||||||
|
csg_plugin_path = get_path()
|
||||||
|
undo_manager = get_undo_redo()
|
||||||
|
|
||||||
|
# Nodes
|
||||||
|
add_custom_type("CSGRepeater3D", "CSGCombiner3D", preload("res://addons/csg_toolkit/scripts/csg_repeater_3d.gd"), null)
|
||||||
|
add_custom_type("CSGSpreader3D", "CSGCombiner3D", preload("res://addons/csg_toolkit/scripts/csg_spreader_3d.gd"), null)
|
||||||
|
|
||||||
|
# Sidebar
|
||||||
|
var sidebarScene = preload("res://addons/csg_toolkit/scenes/csg_side_toolkit_bar.tscn")
|
||||||
|
sidebar = sidebarScene.instantiate()
|
||||||
|
add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, sidebar)
|
||||||
|
# Ensure sidebar can be found by shortcut manager if needed
|
||||||
|
sidebar.add_to_group("CSGSideToolkit")
|
||||||
|
|
||||||
|
# Topbar
|
||||||
|
var topbarScene = preload("res://addons/csg_toolkit/scenes/csg_top_toolkit_bar.tscn")
|
||||||
|
topbar = topbarScene.instantiate()
|
||||||
|
add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, topbar)
|
||||||
|
|
||||||
|
# Shortcut Manager
|
||||||
|
shortcut_manager = CsgShortcutManager.new()
|
||||||
|
get_tree().root.add_child(shortcut_manager)
|
||||||
|
shortcut_manager.sidebar = sidebar
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
remove_custom_type("CSGRepeater3D")
|
||||||
|
remove_custom_type("CSGSpreader3D")
|
||||||
|
undo_manager = null
|
||||||
|
|
||||||
|
remove_autoload_singleton(AUTOLOAD_NAME)
|
||||||
|
|
||||||
|
# Note: ProjectSettings for CSG Toolkit are preserved in project.godot
|
||||||
|
# They can be manually removed from Project Settings if desired
|
||||||
|
|
||||||
|
remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, sidebar)
|
||||||
|
sidebar.free()
|
||||||
|
remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, topbar)
|
||||||
|
topbar.free()
|
||||||
|
if shortcut_manager and is_instance_valid(shortcut_manager):
|
||||||
|
shortcut_manager.queue_free()
|
||||||
1
addons/csg_toolkit/csg_toolkit.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cmwnf4lkck6mm
|
||||||
71
addons/csg_toolkit/demo/demo.tscn
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
[gd_scene format=3 uid="uid://bltlelosbn4ky"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://c68dxahp0v5xg" path="res://addons/csg_toolkit/scripts/csg_repeater_3d.gd" id="1_mnwru"]
|
||||||
|
[ext_resource type="Script" uid="uid://3il6xs7cr7gj" path="res://addons/csg_toolkit/scripts/patterns/noise_pattern.gd" id="2_mnwru"]
|
||||||
|
|
||||||
|
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_i7qq0"]
|
||||||
|
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
|
||||||
|
ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
|
||||||
|
|
||||||
|
[sub_resource type="Sky" id="Sky_lhy2n"]
|
||||||
|
sky_material = SubResource("ProceduralSkyMaterial_i7qq0")
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_mnwru"]
|
||||||
|
background_mode = 2
|
||||||
|
sky = SubResource("Sky_lhy2n")
|
||||||
|
tonemap_mode = 2
|
||||||
|
glow_enabled = true
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Resource_fg620"]
|
||||||
|
script = ExtResource("2_mnwru")
|
||||||
|
bounds = Vector3(10, 1, 10)
|
||||||
|
noise_threshold = 0.9
|
||||||
|
noise_frequency = 16.0
|
||||||
|
use_template_size = true
|
||||||
|
metadata/_custom_type_script = "uid://3il6xs7cr7gj"
|
||||||
|
|
||||||
|
[node name="Node" type="Node3D" unique_id=1351203367]
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=190667961]
|
||||||
|
environment = SubResource("Environment_mnwru")
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=656509290]
|
||||||
|
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="." unique_id=1763227161]
|
||||||
|
transform = Transform3D(-0.93186235, 0, -0.36281216, 0, 1, 0, 0.36281216, 0, -0.9318623, -5.236788, 3.9650297, -6.404826)
|
||||||
|
current = true
|
||||||
|
|
||||||
|
[node name="CSGRepeater3D" type="CSGCombiner3D" parent="." unique_id=873109275]
|
||||||
|
script = ExtResource("1_mnwru")
|
||||||
|
template_node_path = NodePath("CSGCombiner3D2")
|
||||||
|
estimated_instances = 27
|
||||||
|
pattern = SubResource("Resource_fg620")
|
||||||
|
randomize_rotation = false
|
||||||
|
randomize_rot_x = false
|
||||||
|
randomize_rot_y = false
|
||||||
|
randomize_rot_z = false
|
||||||
|
randomize_scale = false
|
||||||
|
scale_variance = 0.15
|
||||||
|
randomize_scale_x = false
|
||||||
|
randomize_scale_y = false
|
||||||
|
randomize_scale_z = false
|
||||||
|
metadata/_custom_type_script = "uid://c68dxahp0v5xg"
|
||||||
|
|
||||||
|
[node name="CSGCombiner3D2" type="CSGCombiner3D" parent="CSGRepeater3D" unique_id=109113911]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.31901503, -0.3383956, -7.8776984)
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="CSGBox3D" type="CSGBox3D" parent="CSGRepeater3D/CSGCombiner3D2" unique_id=110356410]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.48632813)
|
||||||
|
size = Vector3(1, 1, 1.9726563)
|
||||||
|
|
||||||
|
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGRepeater3D/CSGCombiner3D2/CSGBox3D" unique_id=194604460]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.51814646, 0.37408447, -0.24641752)
|
||||||
|
size = Vector3(1, 1.748169, 1)
|
||||||
|
|
||||||
|
[node name="CSGBox3D" type="CSGBox3D" parent="CSGRepeater3D/CSGCombiner3D2/CSGBox3D" unique_id=763032662]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4555664, 0.08105469)
|
||||||
|
operation = 2
|
||||||
|
size = Vector3(1, 1.9111328, 0.8378906)
|
||||||
3
addons/csg_toolkit/demo/new_shader_material.tres
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[gd_resource type="ShaderMaterial" format=3 uid="uid://bvlrolerfilhd"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
BIN
addons/csg_toolkit/demo/red.material
Normal file
7
addons/csg_toolkit/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="CSG Toolkit - Enhance Your Blockout Speed"
|
||||||
|
description="Enhance your blockout workflow with the CSG Toolkit. This tool adds quick access buttons to the left toolbar for easy addition of CSG nodes. By pressing SHIFT, you can efficiently add the selected CSG as a child node. The toolkit also automatically preselects operations, streamlining your process. Additionally, shortcuts are available for CSG nodes or children of CSG combiners, further boosting your productivity."
|
||||||
|
author="LuckyTepot"
|
||||||
|
version="1.7.0"
|
||||||
|
script="csg_toolkit.gd"
|
||||||
BIN
addons/csg_toolkit/res/demo-image.png
(Stored with Git LFS)
Normal file
40
addons/csg_toolkit/res/demo-image.png.import
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d3biubbsy5rmr"
|
||||||
|
path="res://.godot/imported/demo-image.png-239231e5e25f0563a6646f0443acfa4e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/demo-image.png"
|
||||||
|
dest_files=["res://.godot/imported/demo-image.png-239231e5e25f0563a6646f0443acfa4e.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
addons/csg_toolkit/res/icon.png
(Stored with Git LFS)
Normal file
40
addons/csg_toolkit/res/icon.png.import
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c10bvkyvb2xdd"
|
||||||
|
path="res://.godot/imported/icon.png-4ba3e0a5d510aafd8e055683d8699174.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icon.png"
|
||||||
|
dest_files=["res://.godot/imported/icon.png-4ba3e0a5d510aafd8e055683d8699174.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
1
addons/csg_toolkit/res/icons/box.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg>
|
||||||
|
After Width: | Height: | Size: 467 B |
44
addons/csg_toolkit/res/icons/box.svg.import
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cjxx30pcamj36"
|
||||||
|
path="res://.godot/imported/box.svg-24afd61c89464af4af9f4d845ca57b9a.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/box.svg"
|
||||||
|
dest_files=["res://.godot/imported/box.svg-24afd61c89464af4af9f4d845ca57b9a.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
4
addons/csg_toolkit/res/icons/config.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="24" cy="24" r="10" stroke="#fc7f7f" stroke-width="4"/>
|
||||||
|
<circle cx="24" cy="24" r="14" stroke="#fc7f7f" stroke-width="4" stroke-dasharray="8 2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 260 B |
43
addons/csg_toolkit/res/icons/config.svg.import
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://clgooji83dl4u"
|
||||||
|
path="res://.godot/imported/config.svg-dd635575de604576026d414163f67266.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/config.svg"
|
||||||
|
dest_files=["res://.godot/imported/config.svg-dd635575de604576026d414163f67266.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
1
addons/csg_toolkit/res/icons/cyliner.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" stroke-width="2" fill="none" stroke="#fc7f7f" mask="url(#a)"/></svg>
|
||||||
|
After Width: | Height: | Size: 478 B |
44
addons/csg_toolkit/res/icons/cyliner.svg.import
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dioxt3oaqvsi3"
|
||||||
|
path="res://.godot/imported/cyliner.svg-50489df51cf1e8dde56b511c63cd2437.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/cyliner.svg"
|
||||||
|
dest_files=["res://.godot/imported/cyliner.svg-50489df51cf1e8dde56b511c63cd2437.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
1
addons/csg_toolkit/res/icons/empty-material.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><linearGradient x2="0" y2="16" gradientUnits="userSpaceOnUse" id="a"><stop offset=".1875" stop-color="#ff4545"/><stop stop-color="#ffe345"/><stop offset=".3125" stop-color="#ffe345"/><stop stop-color="#80ff45"/><stop offset=".4375" stop-color="#80ff45"/><stop stop-color="#45ffa2"/><stop offset=".5625" stop-color="#45ffa2"/><stop stop-color="#45d7ff"/><stop offset=".6875" stop-color="#45d7ff"/><stop stop-color="#8045ff"/><stop offset=".8125" stop-color="#8045ff"/><stop stop-color="#ff4596"/></linearGradient><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke-linejoin="round" stroke="url(#a)"/></svg>
|
||||||
|
After Width: | Height: | Size: 724 B |
43
addons/csg_toolkit/res/icons/empty-material.svg.import
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cg5jbylrlg00x"
|
||||||
|
path="res://.godot/imported/empty-material.svg-3f6f61e9606d6bae9d37867d9ac3ad0b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/empty-material.svg"
|
||||||
|
dest_files=["res://.godot/imported/empty-material.svg-3f6f61e9606d6bae9d37867d9ac3ad0b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
4
addons/csg_toolkit/res/icons/intersection.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796C25.1901 30.2398 27 27.3313 27 24C27 20.6687 25.1901 17.7602 22.5 16.204Z" fill="#fc7f7f"/>
|
||||||
|
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796M22.5 16.204C23.8238 15.4383 25.3607 15 27 15C31.9706 15 36 19.0294 36 24C36 28.9706 31.9706 33 27 33C25.3607 33 23.8238 32.5617 22.5 31.796M22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796M22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796" stroke="#fc7f7f"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 763 B |
43
addons/csg_toolkit/res/icons/intersection.svg.import
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://2kohi3tb3c70"
|
||||||
|
path="res://.godot/imported/intersection.svg-a7d56bc571616c4c8343b7fe260d4607.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/intersection.svg"
|
||||||
|
dest_files=["res://.godot/imported/intersection.svg-a7d56bc571616c4c8343b7fe260d4607.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
1
addons/csg_toolkit/res/icons/mesh.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.73 2A2 2 0 1 0 2 4.73v6.541A2 2 0 1 0 4.729 14H8v-2H4.729A2 2 0 0 0 4 11.271V5.415l4.914 4.916A2 2 0 0 1 9.998 10a2 2 0 0 1 .33-1.084L5.414 4h5.856a2 2 0 0 0 .73.729V8h2V4.729A2 2 0 1 0 11.27 2z" fill="#fc7f7f"/><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/></svg>
|
||||||
|
After Width: | Height: | Size: 459 B |
44
addons/csg_toolkit/res/icons/mesh.svg.import
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bnpu878eanspj"
|
||||||
|
path="res://.godot/imported/mesh.svg-156308ec8458ed846eae996bc130ccbc.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/mesh.svg"
|
||||||
|
dest_files=["res://.godot/imported/mesh.svg-156308ec8458ed846eae996bc130ccbc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
1
addons/csg_toolkit/res/icons/polygon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" fill="none" stroke-linejoin="round" stroke-width="2" stroke="#fc7f7f"/></svg>
|
||||||
|
After Width: | Height: | Size: 223 B |
44
addons/csg_toolkit/res/icons/polygon.svg.import
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cjps2pofcsfc0"
|
||||||
|
path="res://.godot/imported/polygon.svg-9526fe3ac0cb0791e6339d67ae6afbcf.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/polygon.svg"
|
||||||
|
dest_files=["res://.godot/imported/polygon.svg-9526fe3ac0cb0791e6339d67ae6afbcf.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
1
addons/csg_toolkit/res/icons/sphere.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg>
|
||||||
|
After Width: | Height: | Size: 488 B |
44
addons/csg_toolkit/res/icons/sphere.svg.import
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://kpld1ou63wlf"
|
||||||
|
path="res://.godot/imported/sphere.svg-c7c8c1311207415ed880ee6c5e1e06f9.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/sphere.svg"
|
||||||
|
dest_files=["res://.godot/imported/sphere.svg-c7c8c1311207415ed880ee6c5e1e06f9.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
4
addons/csg_toolkit/res/icons/subtraction.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796C19.8099 30.2398 18 27.3313 18 24C18 20.6687 19.8099 17.7602 22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24Z" fill="#fc7f7f"/>
|
||||||
|
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796M22.5 16.204C23.8238 15.4383 25.3607 15 27 15C31.9706 15 36 19.0294 36 24C36 28.9706 31.9706 33 27 33C25.3607 33 23.8238 32.5617 22.5 31.796M22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796M22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796" stroke="#fc7f7f" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 826 B |
43
addons/csg_toolkit/res/icons/subtraction.svg.import
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://snbhkpq6sh4j"
|
||||||
|
path="res://.godot/imported/subtraction.svg-9afd3882eada513c9c6754b2b38f150f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/subtraction.svg"
|
||||||
|
dest_files=["res://.godot/imported/subtraction.svg-9afd3882eada513c9c6754b2b38f150f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
1
addons/csg_toolkit/res/icons/torus.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><ellipse cx="8" cy="7.5" fill="none" rx="6" ry="3.5" stroke="#fc7f7f" stroke-width="2" mask="url(#a)"/></svg>
|
||||||
|
After Width: | Height: | Size: 456 B |
44
addons/csg_toolkit/res/icons/torus.svg.import
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://gbgmyv4bknqo"
|
||||||
|
path="res://.godot/imported/torus.svg-73f1780e51211292acb9e9d40bb4276f.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/torus.svg"
|
||||||
|
dest_files=["res://.godot/imported/torus.svg-73f1780e51211292acb9e9d40bb4276f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
6
addons/csg_toolkit/res/icons/union.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796C19.8099 30.2398 18 27.3313 18 24C18 20.6687 19.8099 17.7602 22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24Z" fill="#fc7f7f"/>
|
||||||
|
<path d="M27 33C31.9706 33 36 28.9706 36 24C36 19.0294 31.9706 15 27 15C25.3607 15 23.8238 15.4383 22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796C23.8238 32.5617 25.3607 33 27 33Z" fill="#fc7f7f"/>
|
||||||
|
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796C25.1901 30.2398 27 27.3313 27 24C27 20.6687 25.1901 17.7602 22.5 16.204Z" fill="#fc7f7f"/>
|
||||||
|
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796M22.5 16.204C23.8238 15.4383 25.3607 15 27 15C31.9706 15 36 19.0294 36 24C36 28.9706 31.9706 33 27 33C25.3607 33 23.8238 32.5617 22.5 31.796M22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796M22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796" fill="#fc7f7f" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
43
addons/csg_toolkit/res/icons/union.svg.import
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://sc7itwu80oi3"
|
||||||
|
path="res://.godot/imported/union.svg-330af8601d6493b573ab68d43d4b23f3.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/icons/union.svg"
|
||||||
|
dest_files=["res://.godot/imported/union.svg-330af8601d6493b573ab68d43d4b23f3.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
34
addons/csg_toolkit/res/image.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d0dy7wteea7l5"
|
||||||
|
path="res://.godot/imported/image.png-e18db3df6f24d50b719c17a226c337bd.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/csg_toolkit/res/image.png"
|
||||||
|
dest_files=["res://.godot/imported/image.png-e18db3df6f24d50b719c17a226c337bd.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
3
addons/csg_toolkit/res/operation_group.tres
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[gd_resource type="ButtonGroup" format=3 uid="uid://wxtkg1wlxka8"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
125
addons/csg_toolkit/scenes/config_window.tscn
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://dts41g6camqwq"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://b7isqiq2asnu6" path="res://addons/csg_toolkit/scripts/config_window.gd" id="1_pigko"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ng5lh"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_vj6tm"]
|
||||||
|
|
||||||
|
[node name="ConfigWindow" type="Window"]
|
||||||
|
oversampling_override = 1.0
|
||||||
|
title = "CSG Toolit Configuration"
|
||||||
|
initial_position = 1
|
||||||
|
size = Vector2i(480, 360)
|
||||||
|
popup_window = true
|
||||||
|
script = ExtResource("1_pigko")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 8
|
||||||
|
theme_override_constants/margin_top = 8
|
||||||
|
theme_override_constants/margin_right = 8
|
||||||
|
theme_override_constants/margin_bottom = 8
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Default Behavior"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
|
||||||
|
|
||||||
|
[node name="OptionButton" type="OptionButton" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
selected = 0
|
||||||
|
item_count = 2
|
||||||
|
popup/item_0/text = "Sibling"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Child"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
|
||||||
|
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Action Key"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Shift"
|
||||||
|
|
||||||
|
[node name="HBoxContainer4" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer4"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Behvaior Toggle"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer4"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer4"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Shift"
|
||||||
|
|
||||||
|
[node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Auto Hide"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
|
||||||
|
|
||||||
|
[node name="CheckButton" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
button_pressed = true
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/separator = SubResource("StyleBoxEmpty_vj6tm")
|
||||||
|
|
||||||
|
[node name="Save" type="Button" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Save"
|
||||||
|
|
||||||
|
[node name="HSeparator2" type="HSeparator" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Ko-Fi" type="LinkButton" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Support me on Ko-Fi"
|
||||||
|
underline = 1
|
||||||
|
uri = "https://ko-fi.com/luckyteapot"
|
||||||
|
|
||||||
|
[connection signal="item_selected" from="MarginContainer/VBoxContainer/HBoxContainer/OptionButton" to="." method="_on_option_button_item_selected"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/Button" to="." method="_on_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer4/Button" to="." method="_on_second_button_pressed"]
|
||||||
|
[connection signal="toggled" from="MarginContainer/VBoxContainer/HBoxContainer3/CheckButton" to="." method="_on_check_box_toggled"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/VBoxContainer/Save" to="." method="_on_save_pressed"]
|
||||||
20
addons/csg_toolkit/scenes/csg_quick_actions.tscn
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://dhpjubljm0tf0"]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cjxx30pcamj36" path="res://addons/csg_toolkit/res/icons/box.svg" id="1_n1dqv"]
|
||||||
|
|
||||||
|
[node name="CsgQuickActions" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_right = 40.0
|
||||||
|
offset_bottom = 40.0
|
||||||
|
|
||||||
|
[node name="Box" type="TextureButton" parent="CenterContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
texture_normal = ExtResource("1_n1dqv")
|
||||||
179
addons/csg_toolkit/scenes/csg_side_toolkit_bar.tscn
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
[gd_scene load_steps=15 format=3 uid="uid://cdjxmp0p1bbup"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dr5f1egll7hdq" path="res://addons/csg_toolkit/scripts/csg_side_toolkit_bar.gd" id="1_awo4p"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cjxx30pcamj36" path="res://addons/csg_toolkit/res/icons/box.svg" id="2_p4f7r"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dioxt3oaqvsi3" path="res://addons/csg_toolkit/res/icons/cyliner.svg" id="3_j7s68"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bnpu878eanspj" path="res://addons/csg_toolkit/res/icons/mesh.svg" id="4_328ee"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cjps2pofcsfc0" path="res://addons/csg_toolkit/res/icons/polygon.svg" id="5_0os5t"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://kpld1ou63wlf" path="res://addons/csg_toolkit/res/icons/sphere.svg" id="6_ju1pi"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://gbgmyv4bknqo" path="res://addons/csg_toolkit/res/icons/torus.svg" id="7_6hoxg"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://sc7itwu80oi3" path="res://addons/csg_toolkit/res/icons/union.svg" id="8_gmt87"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://2kohi3tb3c70" path="res://addons/csg_toolkit/res/icons/intersection.svg" id="9_55ipi"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://snbhkpq6sh4j" path="res://addons/csg_toolkit/res/icons/subtraction.svg" id="10_8u5xb"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cg5jbylrlg00x" path="res://addons/csg_toolkit/res/icons/empty-material.svg" id="11_ten1m"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://clgooji83dl4u" path="res://addons/csg_toolkit/res/icons/config.svg" id="12_3gawg"]
|
||||||
|
|
||||||
|
[sub_resource type="ButtonGroup" id="ButtonGroup_2ipgb"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ixko7"]
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer"]
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(52, 0)
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = -2.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_top = 8
|
||||||
|
theme_override_constants/margin_bottom = 8
|
||||||
|
script = ExtResource("1_awo4p")
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 120)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 6
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="CSG" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Box" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Box"
|
||||||
|
icon = ExtResource("2_p4f7r")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Cylinder" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Cylinder"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
icon = ExtResource("3_j7s68")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Mesh" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Mesh"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
icon = ExtResource("4_328ee")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Polygon" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Polygon"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
icon = ExtResource("5_0os5t")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Sphere" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Sphere"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
icon = ExtResource("6_ju1pi")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Torus" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Torus"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
icon = ExtResource("7_6hoxg")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Operation" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Union" type="Button" parent="ScrollContainer/HBoxContainer/Operation"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Union"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
toggle_mode = true
|
||||||
|
button_group = SubResource("ButtonGroup_2ipgb")
|
||||||
|
icon = ExtResource("8_gmt87")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Intersection" type="Button" parent="ScrollContainer/HBoxContainer/Operation"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Intersection"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
toggle_mode = true
|
||||||
|
button_group = SubResource("ButtonGroup_2ipgb")
|
||||||
|
icon = ExtResource("9_55ipi")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Subtraction" type="Button" parent="ScrollContainer/HBoxContainer/Operation"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Subtraction"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
toggle_mode = true
|
||||||
|
button_group = SubResource("ButtonGroup_2ipgb")
|
||||||
|
icon = ExtResource("10_8u5xb")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="Material" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="MaterialPicker" type="Button" parent="ScrollContainer/HBoxContainer/Material"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Material"
|
||||||
|
icon = ExtResource("11_ten1m")
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="ScrollContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/separator = SubResource("StyleBoxEmpty_ixko7")
|
||||||
|
|
||||||
|
[node name="Options" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Config" type="Button" parent="ScrollContainer/HBoxContainer/Options"]
|
||||||
|
custom_minimum_size = Vector2(42, 42)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Config"
|
||||||
|
theme_override_constants/icon_max_width = 24
|
||||||
|
icon = ExtResource("12_3gawg")
|
||||||
|
icon_alignment = 1
|
||||||
|
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Box" to="." method="_on_box_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Cylinder" to="." method="_on_cylinder_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Mesh" to="." method="_on_mesh_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Polygon" to="." method="_on_polygon_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Sphere" to="." method="_on_sphere_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Torus" to="." method="_on_torus_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Operation/Union" to="." method="_on_operation_pressed" binds= [0]]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Operation/Intersection" to="." method="_on_operation_pressed" binds= [1]]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Operation/Subtraction" to="." method="_on_operation_pressed" binds= [2]]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Material/MaterialPicker" to="." method="_on_material_picker_pressed"]
|
||||||
|
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Options/Config" to="." method="_on_config_pressed"]
|
||||||
16
addons/csg_toolkit/scenes/csg_top_toolkit_bar.tscn
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://dgldacs362p7g"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dk6dt8fk1s43s" path="res://addons/csg_toolkit/scripts/csg_top_toolkit_bar.gd" id="1_154hl"]
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
offset_right = 40.0
|
||||||
|
offset_bottom = 40.0
|
||||||
|
script = ExtResource("1_154hl")
|
||||||
|
|
||||||
|
[node name="Refresh" type="Button" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Refresh"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[connection signal="pressed" from="Refresh" to="." method="_on_refresh_pressed"]
|
||||||
5
addons/csg_toolkit/scenes/demo.tscn
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b6adrnqobv5fi"]
|
||||||
|
|
||||||
|
[node name="Demo" type="Node3D"]
|
||||||
|
|
||||||
|
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||||
46
addons/csg_toolkit/scripts/config_window.gd
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
@tool
|
||||||
|
extends Window
|
||||||
|
|
||||||
|
@onready var config: CsgTkConfig:
|
||||||
|
get: return get_tree().root.get_node(CsgToolkit.AUTOLOAD_NAME) as CsgTkConfig
|
||||||
|
|
||||||
|
@onready var default_behavior_option: OptionButton = $MarginContainer/VBoxContainer/HBoxContainer/OptionButton
|
||||||
|
@onready var action_key_button: Button = $MarginContainer/VBoxContainer/HBoxContainer2/Button
|
||||||
|
@onready var behvaior_toogle_button: Button = $MarginContainer/VBoxContainer/HBoxContainer4/Button
|
||||||
|
@onready var auto_hide_switch: CheckBox = $MarginContainer/VBoxContainer/HBoxContainer3/CheckButton
|
||||||
|
|
||||||
|
signal key_press(key: InputEventKey)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
default_behavior_option.select(config.default_behavior)
|
||||||
|
action_key_button.text = OS.get_keycode_string(config.action_key)
|
||||||
|
behvaior_toogle_button.text = OS.get_keycode_string(config.secondary_action_key)
|
||||||
|
auto_hide_switch.button_pressed = config.auto_hide
|
||||||
|
|
||||||
|
func _on_option_button_item_selected(index):
|
||||||
|
match index:
|
||||||
|
0: config.default_behavior = CsgTkConfig.CSGBehavior.SIBLING
|
||||||
|
1: config.default_behavior = CsgTkConfig.CSGBehavior.CHILD
|
||||||
|
|
||||||
|
func _unhandled_input(event):
|
||||||
|
if event is InputEventKey:
|
||||||
|
if event.pressed:
|
||||||
|
key_press.emit(event)
|
||||||
|
|
||||||
|
func _on_save_pressed():
|
||||||
|
config.save_config()
|
||||||
|
hide()
|
||||||
|
|
||||||
|
func _on_button_pressed():
|
||||||
|
var key_event: InputEventKey = await key_press
|
||||||
|
config.action_key = key_event.keycode
|
||||||
|
action_key_button.text = key_event.as_text_key_label()
|
||||||
|
|
||||||
|
func _on_second_button_pressed():
|
||||||
|
var key_event: InputEventKey = await key_press
|
||||||
|
config.secondary_action_key = key_event.keycode
|
||||||
|
behvaior_toogle_button.text = key_event.as_text_key_label()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_check_box_toggled(toggled_on):
|
||||||
|
config.auto_hide = toggled_on
|
||||||
1
addons/csg_toolkit/scripts/config_window.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b7isqiq2asnu6
|
||||||
522
addons/csg_toolkit/scripts/csg_repeater_3d.gd
Normal file
@@ -0,0 +1,522 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGRepeater3D extends CSGCombiner3D
|
||||||
|
|
||||||
|
# NOTE: Registered as custom type in plugin (csg_toolkit.gd) inheriting CSGCombiner3D.
|
||||||
|
# Ensure pattern resource scripts are loaded (Godot should handle via class_name, but we force references for safety):
|
||||||
|
const _REF_GRID = preload("res://addons/csg_toolkit/scripts/patterns/grid_pattern.gd") # ensure subclass scripts loaded
|
||||||
|
const _REF_CIRC = preload("res://addons/csg_toolkit/scripts/patterns/circular_pattern.gd")
|
||||||
|
const _REF_SPIRAL = preload("res://addons/csg_toolkit/scripts/patterns/spiral_pattern.gd")
|
||||||
|
const _REF_NOISE = preload("res://addons/csg_toolkit/scripts/patterns/noise_pattern.gd")
|
||||||
|
|
||||||
|
const REPEATER_NODE_META = "REPEATED_NODE_META"
|
||||||
|
const MAX_INSTANCES = 20000
|
||||||
|
|
||||||
|
var _dirty: bool = false
|
||||||
|
var _template_node_path: NodePath
|
||||||
|
@export var template_node_path: NodePath:
|
||||||
|
get: return _template_node_path
|
||||||
|
set(value):
|
||||||
|
_template_node_path = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _template_node_scene: PackedScene
|
||||||
|
@export var template_node_scene: PackedScene:
|
||||||
|
get: return _template_node_scene
|
||||||
|
set(value):
|
||||||
|
_template_node_scene = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _hide_template: bool = true
|
||||||
|
@export var hide_template: bool = true:
|
||||||
|
get: return _hide_template
|
||||||
|
set(value):
|
||||||
|
_hide_template = value
|
||||||
|
_update_template_visibility()
|
||||||
|
|
||||||
|
## repeat & spacing removed (migrated into pattern resources)
|
||||||
|
|
||||||
|
@export_group("Pattern Options")
|
||||||
|
# A single exported pattern resource (`pattern`) defines generation behavior.
|
||||||
|
|
||||||
|
|
||||||
|
@export_group("Variation Options")
|
||||||
|
# Rotation variation properties now managed via custom property list for collapsible enable group.
|
||||||
|
var _randomize_rotation: bool = false
|
||||||
|
var randomize_rotation: bool:
|
||||||
|
get: return _randomize_rotation
|
||||||
|
set(value):
|
||||||
|
_randomize_rotation = value
|
||||||
|
_mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _randomize_rot_x: bool = false
|
||||||
|
var randomize_rot_x: bool:
|
||||||
|
get: return _randomize_rot_x
|
||||||
|
set(value):
|
||||||
|
_randomize_rot_x = value
|
||||||
|
if _randomize_rotation: _mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _randomize_rot_y: bool = false
|
||||||
|
var randomize_rot_y: bool:
|
||||||
|
get: return _randomize_rot_y
|
||||||
|
set(value):
|
||||||
|
_randomize_rot_y = value
|
||||||
|
if _randomize_rotation: _mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _randomize_rot_z: bool = false
|
||||||
|
var randomize_rot_z: bool:
|
||||||
|
get: return _randomize_rot_z
|
||||||
|
set(value):
|
||||||
|
_randomize_rot_z = value
|
||||||
|
if _randomize_rotation: _mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
# Per-axis rotation variance in degrees (0 = full 0..360 random for that axis; >0 jitters around original)
|
||||||
|
var _rotation_variance_x_deg: float = 0.0
|
||||||
|
var rotation_variance_x_deg: float:
|
||||||
|
get: return _rotation_variance_x_deg
|
||||||
|
set(value):
|
||||||
|
_rotation_variance_x_deg = clamp(value, 0.0, 360.0)
|
||||||
|
if _randomize_rotation and _randomize_rot_x: _mark_dirty()
|
||||||
|
|
||||||
|
var _rotation_variance_y_deg: float = 0.0
|
||||||
|
var rotation_variance_y_deg: float:
|
||||||
|
get: return _rotation_variance_y_deg
|
||||||
|
set(value):
|
||||||
|
_rotation_variance_y_deg = clamp(value, 0.0, 360.0)
|
||||||
|
if _randomize_rotation and _randomize_rot_y: _mark_dirty()
|
||||||
|
|
||||||
|
var _rotation_variance_z_deg: float = 0.0
|
||||||
|
var rotation_variance_z_deg: float:
|
||||||
|
get: return _rotation_variance_z_deg
|
||||||
|
set(value):
|
||||||
|
_rotation_variance_z_deg = clamp(value, 0.0, 360.0)
|
||||||
|
if _randomize_rotation and _randomize_rot_z: _mark_dirty()
|
||||||
|
|
||||||
|
var _randomize_scale: bool = false
|
||||||
|
var randomize_scale: bool:
|
||||||
|
get: return _randomize_scale
|
||||||
|
set(value):
|
||||||
|
_randomize_scale = value
|
||||||
|
_mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _scale_variance: float = 0.0
|
||||||
|
var scale_variance: float:
|
||||||
|
get: return _scale_variance
|
||||||
|
set(value):
|
||||||
|
_scale_variance = clamp(value, 0.0, 1.0)
|
||||||
|
if _randomize_scale:
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
# Per-axis scale variance (if zero => use global variance when axis toggle active)
|
||||||
|
var _scale_variance_x: float = 0.0
|
||||||
|
var scale_variance_x: float:
|
||||||
|
get: return _scale_variance_x
|
||||||
|
set(value):
|
||||||
|
_scale_variance_x = clamp(value, 0.0, 1.0)
|
||||||
|
if _randomize_scale and _randomize_scale_x: _mark_dirty()
|
||||||
|
|
||||||
|
var _scale_variance_y: float = 0.0
|
||||||
|
var scale_variance_y: float:
|
||||||
|
get: return _scale_variance_y
|
||||||
|
set(value):
|
||||||
|
_scale_variance_y = clamp(value, 0.0, 1.0)
|
||||||
|
if _randomize_scale and _randomize_scale_y: _mark_dirty()
|
||||||
|
|
||||||
|
var _scale_variance_z: float = 0.0
|
||||||
|
var scale_variance_z: float:
|
||||||
|
get: return _scale_variance_z
|
||||||
|
set(value):
|
||||||
|
_scale_variance_z = clamp(value, 0.0, 1.0)
|
||||||
|
if _randomize_scale and _randomize_scale_z: _mark_dirty()
|
||||||
|
|
||||||
|
# Per-axis scale randomization toggles (optional – if none enabled acts as uniform variance on all axes)
|
||||||
|
var _randomize_scale_x: bool = false
|
||||||
|
var randomize_scale_x: bool:
|
||||||
|
get: return _randomize_scale_x
|
||||||
|
set(value):
|
||||||
|
_randomize_scale_x = value
|
||||||
|
if _randomize_scale: _mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _randomize_scale_y: bool = false
|
||||||
|
var randomize_scale_y: bool:
|
||||||
|
get: return _randomize_scale_y
|
||||||
|
set(value):
|
||||||
|
_randomize_scale_y = value
|
||||||
|
if _randomize_scale: _mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _randomize_scale_z: bool = false
|
||||||
|
var randomize_scale_z: bool:
|
||||||
|
get: return _randomize_scale_z
|
||||||
|
set(value):
|
||||||
|
_randomize_scale_z = value
|
||||||
|
if _randomize_scale: _mark_dirty()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
var _position_jitter: float = 0.0
|
||||||
|
@export var position_jitter: float = 0.0:
|
||||||
|
get: return _position_jitter
|
||||||
|
set(value):
|
||||||
|
_position_jitter = max(0.0, value)
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _random_seed: int = 0
|
||||||
|
@export var random_seed: int = 0:
|
||||||
|
get: return _random_seed
|
||||||
|
set(value):
|
||||||
|
_random_seed = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
# Estimated instance count (read-only in inspector; updated internally)
|
||||||
|
@export var estimated_instances: int = 0
|
||||||
|
|
||||||
|
var rng: RandomNumberGenerator
|
||||||
|
var _generation_in_progress := false
|
||||||
|
var _pattern: CSGPattern
|
||||||
|
@export var pattern: CSGPattern:
|
||||||
|
get: return _pattern
|
||||||
|
set(value):
|
||||||
|
if value == _pattern:
|
||||||
|
return
|
||||||
|
# Reject non-CSGPattern resources
|
||||||
|
if value != null and not (value is CSGPattern):
|
||||||
|
push_warning("Assigned pattern is not a CSGPattern-derived resource; ignoring.")
|
||||||
|
return
|
||||||
|
# Prevent assigning the abstract base directly (must use subclass)
|
||||||
|
if value != null and value.get_class() == "CSGPattern":
|
||||||
|
push_warning("Cannot assign base CSGPattern directly. Please use a concrete pattern (Grid, Circular, Spiral...).")
|
||||||
|
return
|
||||||
|
# Disconnect old
|
||||||
|
if _pattern and _pattern.is_connected("changed", Callable(self, "_on_pattern_changed")):
|
||||||
|
_pattern.disconnect("changed", Callable(self, "_on_pattern_changed"))
|
||||||
|
_pattern = value
|
||||||
|
if _pattern and not _pattern.is_connected("changed", Callable(self, "_on_pattern_changed")):
|
||||||
|
_pattern.connect("changed", Callable(self, "_on_pattern_changed"))
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
rng = RandomNumberGenerator.new()
|
||||||
|
# Provide a default pattern if none assigned (through setter for signal wiring).
|
||||||
|
if pattern == null:
|
||||||
|
pattern = CSGGridPattern.new()
|
||||||
|
_mark_dirty()
|
||||||
|
# Generate instances in-game on ready
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
call_deferred("repeat_template")
|
||||||
|
|
||||||
|
func _on_pattern_changed():
|
||||||
|
# Called when the assigned pattern resource's exported properties are edited in inspector.
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
if not Engine.is_editor_hint(): return
|
||||||
|
if _dirty and not _generation_in_progress:
|
||||||
|
_dirty = false
|
||||||
|
call_deferred("repeat_template")
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
# Clean up any remaining repeated nodes
|
||||||
|
clear_children()
|
||||||
|
|
||||||
|
func _mark_dirty():
|
||||||
|
_dirty = true
|
||||||
|
|
||||||
|
func _update_template_visibility():
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
var template_node = get_node_or_null(template_node_path)
|
||||||
|
if template_node and template_node is Node3D:
|
||||||
|
template_node.visible = not _hide_template
|
||||||
|
|
||||||
|
func clear_children():
|
||||||
|
# Clear existing children except the template node
|
||||||
|
var children_to_remove = []
|
||||||
|
for child in get_children(true):
|
||||||
|
if child.has_meta(REPEATER_NODE_META):
|
||||||
|
children_to_remove.append(child)
|
||||||
|
# Remove children immediately for better performance
|
||||||
|
for child in children_to_remove:
|
||||||
|
remove_child(child)
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
func repeat_template():
|
||||||
|
if _generation_in_progress:
|
||||||
|
return
|
||||||
|
_generation_in_progress = true
|
||||||
|
clear_children()
|
||||||
|
|
||||||
|
var template_node = get_node_or_null(template_node_path)
|
||||||
|
var using_scene = false
|
||||||
|
# Determine template source
|
||||||
|
if not template_node:
|
||||||
|
if not template_node_scene or not template_node_scene.can_instantiate():
|
||||||
|
_generation_in_progress = false
|
||||||
|
return
|
||||||
|
template_node = template_node_scene.instantiate()
|
||||||
|
using_scene = true
|
||||||
|
add_child(template_node)
|
||||||
|
|
||||||
|
# Use pattern estimation for cap check
|
||||||
|
var template_size := _get_template_size(template_node)
|
||||||
|
var ctx_cap := {"template_size": template_size, "rng": rng, "position_jitter": _position_jitter}
|
||||||
|
var estimate := 0
|
||||||
|
if pattern:
|
||||||
|
estimate = pattern.get_estimated_count(ctx_cap)
|
||||||
|
if estimate <= 1:
|
||||||
|
if using_scene:
|
||||||
|
remove_child(template_node)
|
||||||
|
template_node.queue_free()
|
||||||
|
_generation_in_progress = false
|
||||||
|
return
|
||||||
|
if estimate > MAX_INSTANCES:
|
||||||
|
push_warning("CSGRepeater3D: Estimated count %s exceeds cap %s. Aborting generation." % [estimate, MAX_INSTANCES])
|
||||||
|
_generation_in_progress = false
|
||||||
|
return
|
||||||
|
|
||||||
|
rng.seed = _random_seed
|
||||||
|
# template_size already computed earlier (template_size variable)
|
||||||
|
var positions = _generate_positions(template_size)
|
||||||
|
estimated_instances = positions.size() - 1
|
||||||
|
|
||||||
|
for i in range(positions.size()):
|
||||||
|
var position = positions[i]
|
||||||
|
if i == 0 and position.is_zero_approx():
|
||||||
|
continue
|
||||||
|
var instance = template_node.duplicate()
|
||||||
|
if instance == null:
|
||||||
|
continue
|
||||||
|
instance.set_meta(REPEATER_NODE_META, true)
|
||||||
|
instance.transform.origin = position
|
||||||
|
# Ensure instance is visible regardless of template visibility
|
||||||
|
if instance is Node3D:
|
||||||
|
instance.visible = true
|
||||||
|
_apply_variations(instance)
|
||||||
|
add_child(instance)
|
||||||
|
|
||||||
|
if using_scene:
|
||||||
|
remove_child(template_node)
|
||||||
|
template_node.queue_free()
|
||||||
|
else:
|
||||||
|
_update_template_visibility()
|
||||||
|
_generation_in_progress = false
|
||||||
|
|
||||||
|
func _generate_positions(template_size: Vector3) -> Array:
|
||||||
|
var ctx: Dictionary = {"template_size": template_size, "rng": rng, "position_jitter": _position_jitter}
|
||||||
|
if pattern == null:
|
||||||
|
return []
|
||||||
|
return pattern.generate(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
# -- Geometry-based spacing helpers -------------------------------------------------
|
||||||
|
|
||||||
|
func _get_template_size(template_node: Node) -> Vector3:
|
||||||
|
if template_node == null or not (template_node is Node3D):
|
||||||
|
return Vector3.ONE
|
||||||
|
var aabb := _get_combined_aabb(template_node)
|
||||||
|
var size: Vector3 = aabb.size
|
||||||
|
if size.x <= 0.0001: size.x = 1.0
|
||||||
|
if size.y <= 0.0001: size.y = 1.0
|
||||||
|
if size.z <= 0.0001: size.z = 1.0
|
||||||
|
return size
|
||||||
|
|
||||||
|
func _get_combined_aabb(node: Node) -> AABB:
|
||||||
|
var found := false
|
||||||
|
var combined := AABB()
|
||||||
|
if node is Node3D and node.has_method("get_aabb"):
|
||||||
|
var aabb = node.get_aabb()
|
||||||
|
combined = aabb
|
||||||
|
found = true
|
||||||
|
for child in node.get_children():
|
||||||
|
if child is Node3D:
|
||||||
|
var child_aabb = _get_combined_aabb(child)
|
||||||
|
if child_aabb.size != Vector3.ZERO:
|
||||||
|
if not found:
|
||||||
|
combined = child_aabb
|
||||||
|
found = true
|
||||||
|
else:
|
||||||
|
combined = combined.merge(child_aabb)
|
||||||
|
return combined if found else AABB(Vector3.ZERO, Vector3.ZERO)
|
||||||
|
|
||||||
|
func _apply_material_recursive(node: Node, material: Material):
|
||||||
|
if node is CSGShape3D:
|
||||||
|
node.material_override = material
|
||||||
|
for child in node.get_children():
|
||||||
|
_apply_material_recursive(child, material)
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_variations(instance: Node3D):
|
||||||
|
if _randomize_rotation:
|
||||||
|
var final_rot := instance.rotation
|
||||||
|
if _randomize_rot_x:
|
||||||
|
if _rotation_variance_x_deg > 0.0:
|
||||||
|
final_rot.x += rng.randf_range(-deg_to_rad(_rotation_variance_x_deg), deg_to_rad(_rotation_variance_x_deg))
|
||||||
|
else:
|
||||||
|
final_rot.x = rng.randf() * TAU
|
||||||
|
if _randomize_rot_y:
|
||||||
|
if _rotation_variance_y_deg > 0.0:
|
||||||
|
final_rot.y += rng.randf_range(-deg_to_rad(_rotation_variance_y_deg), deg_to_rad(_rotation_variance_y_deg))
|
||||||
|
else:
|
||||||
|
final_rot.y = rng.randf() * TAU
|
||||||
|
if _randomize_rot_z:
|
||||||
|
if _rotation_variance_z_deg > 0.0:
|
||||||
|
final_rot.z += rng.randf_range(-deg_to_rad(_rotation_variance_z_deg), deg_to_rad(_rotation_variance_z_deg))
|
||||||
|
else:
|
||||||
|
final_rot.z = rng.randf() * TAU
|
||||||
|
instance.rotation = final_rot
|
||||||
|
if _randomize_scale:
|
||||||
|
# If any axis toggles are on, apply independent variance per axis; else uniform.
|
||||||
|
var use_axes = _randomize_scale_x or _randomize_scale_y or _randomize_scale_z
|
||||||
|
if use_axes:
|
||||||
|
var sx = instance.scale.x
|
||||||
|
var sy = instance.scale.y
|
||||||
|
var sz = instance.scale.z
|
||||||
|
if _randomize_scale_x:
|
||||||
|
var vx = (_scale_variance_x if _scale_variance_x > 0.0 else _scale_variance)
|
||||||
|
sx *= max(0.1, 1.0 + rng.randf_range(-vx, vx))
|
||||||
|
if _randomize_scale_y:
|
||||||
|
var vy = (_scale_variance_y if _scale_variance_y > 0.0 else _scale_variance)
|
||||||
|
sy *= max(0.1, 1.0 + rng.randf_range(-vy, vy))
|
||||||
|
if _randomize_scale_z:
|
||||||
|
var vz = (_scale_variance_z if _scale_variance_z > 0.0 else _scale_variance)
|
||||||
|
sz *= max(0.1, 1.0 + rng.randf_range(-vz, vz))
|
||||||
|
instance.scale = Vector3(sx, sy, sz)
|
||||||
|
else:
|
||||||
|
var scale_factor = max(0.1, 1.0 + rng.randf_range(-_scale_variance, _scale_variance))
|
||||||
|
instance.scale *= scale_factor
|
||||||
|
|
||||||
|
func regenerate():
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
# -- Custom property list (Godot 4.5 group enable support) -------------------------
|
||||||
|
func _get_property_list() -> Array:
|
||||||
|
var props: Array = []
|
||||||
|
|
||||||
|
# Keep default exported properties (engine already exposes them). Only inject
|
||||||
|
# the rotation variation cluster with group enable + subgroup organization.
|
||||||
|
|
||||||
|
# Group header for random rotation feature.
|
||||||
|
# Variation Options parent group
|
||||||
|
props.append({
|
||||||
|
"name": "Variation Options",
|
||||||
|
"type": TYPE_NIL,
|
||||||
|
"usage": PROPERTY_USAGE_GROUP
|
||||||
|
})
|
||||||
|
# Rotation subgroup under Variation Options
|
||||||
|
props.append({
|
||||||
|
"name": "Rotation Randomization",
|
||||||
|
"type": TYPE_NIL,
|
||||||
|
"usage": PROPERTY_USAGE_SUBGROUP
|
||||||
|
})
|
||||||
|
# Enabling checkbox on group header via PROPERTY_HINT_GROUP_ENABLE.
|
||||||
|
props.append({
|
||||||
|
"name": "randomize_rotation",
|
||||||
|
"type": TYPE_BOOL,
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
|
||||||
|
"hint": PROPERTY_HINT_GROUP_ENABLE
|
||||||
|
})
|
||||||
|
# Per-axis random toggles
|
||||||
|
props.append(_prop_bool("randomize_rot_x"))
|
||||||
|
if _randomize_rotation and _randomize_rot_x:
|
||||||
|
props.append({
|
||||||
|
"name": "rotation_variance_x_deg",
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "0,360,0.1,degrees",
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
|
||||||
|
})
|
||||||
|
props.append(_prop_bool("randomize_rot_y"))
|
||||||
|
if _randomize_rotation and _randomize_rot_y:
|
||||||
|
props.append({
|
||||||
|
"name": "rotation_variance_y_deg",
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "0,360,0.1,degrees",
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
|
||||||
|
})
|
||||||
|
props.append(_prop_bool("randomize_rot_z"))
|
||||||
|
if _randomize_rotation and _randomize_rot_z:
|
||||||
|
props.append({
|
||||||
|
"name": "rotation_variance_z_deg",
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "0,360,0.1,degrees",
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
|
||||||
|
})
|
||||||
|
|
||||||
|
# Subgroup for locked rotations (should reside inside Rotation Randomization group)
|
||||||
|
# (Locked rotations removed as per user request)
|
||||||
|
|
||||||
|
# Scale variation subgroup under Variation Options
|
||||||
|
props.append({
|
||||||
|
"name": "Scale Variation",
|
||||||
|
"type": TYPE_NIL,
|
||||||
|
"usage": PROPERTY_USAGE_SUBGROUP
|
||||||
|
})
|
||||||
|
props.append({
|
||||||
|
"name": "randomize_scale",
|
||||||
|
"type": TYPE_BOOL,
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
|
||||||
|
"hint": PROPERTY_HINT_GROUP_ENABLE
|
||||||
|
})
|
||||||
|
props.append(_prop_float_range("scale_variance", "0,1,0.01"))
|
||||||
|
props.append(_prop_bool("randomize_scale_x"))
|
||||||
|
if _randomize_scale and _randomize_scale_x:
|
||||||
|
props.append(_prop_float_range("scale_variance_x", "0,1,0.01"))
|
||||||
|
props.append(_prop_bool("randomize_scale_y"))
|
||||||
|
if _randomize_scale and _randomize_scale_y:
|
||||||
|
props.append(_prop_float_range("scale_variance_y", "0,1,0.01"))
|
||||||
|
props.append(_prop_bool("randomize_scale_z"))
|
||||||
|
if _randomize_scale and _randomize_scale_z:
|
||||||
|
props.append(_prop_float_range("scale_variance_z", "0,1,0.01"))
|
||||||
|
|
||||||
|
return props
|
||||||
|
|
||||||
|
func _prop_bool(name: String) -> Dictionary:
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"type": TYPE_BOOL,
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
|
||||||
|
}
|
||||||
|
|
||||||
|
func _prop_float_deg(name: String, value: float) -> Dictionary:
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "-360,360,0.1,degrees",
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
|
||||||
|
}
|
||||||
|
|
||||||
|
func _prop_float_range(name: String, hint_str: String) -> Dictionary:
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": hint_str,
|
||||||
|
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_instance_count() -> int:
|
||||||
|
if pattern == null:
|
||||||
|
return 0
|
||||||
|
var ctx := {"template_size": Vector3.ONE, "rng": rng, "position_jitter": _position_jitter}
|
||||||
|
return max(0, pattern.get_estimated_count(ctx) - 1)
|
||||||
|
|
||||||
|
func apply_template():
|
||||||
|
if get_child_count() == 0:
|
||||||
|
return
|
||||||
|
var stack = []
|
||||||
|
stack.append_array(get_children())
|
||||||
|
while stack.size() > 0:
|
||||||
|
var node = stack.pop_back()
|
||||||
|
node.set_owner(owner)
|
||||||
|
stack.append_array(node.get_children())
|
||||||
|
|
||||||
|
# Alias for clarity in UI
|
||||||
|
func bake_instances():
|
||||||
|
apply_template()
|
||||||
1
addons/csg_toolkit/scripts/csg_repeater_3d.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c68dxahp0v5xg
|
||||||
86
addons/csg_toolkit/scripts/csg_shortcut_manager.gd
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
@tool
|
||||||
|
extends Node
|
||||||
|
class_name CsgShortcutManager
|
||||||
|
|
||||||
|
# Provides global key handling for quick CSG creation & operation switching (Layers 1 & 2)
|
||||||
|
# Delegates actual creation to the sidebar instance to reuse UndoRedo + material logic.
|
||||||
|
|
||||||
|
var sidebar: CSGSideToolkitBar
|
||||||
|
var config: CsgTkConfig
|
||||||
|
|
||||||
|
# Mapping shape keycode -> factory id (string used for log / optional future use)
|
||||||
|
var _shape_key_map: Dictionary = {
|
||||||
|
KEY_B: CSGBox3D,
|
||||||
|
KEY_S: CSGSphere3D,
|
||||||
|
KEY_C: CSGCylinder3D,
|
||||||
|
KEY_T: CSGTorus3D,
|
||||||
|
KEY_M: CSGMesh3D,
|
||||||
|
KEY_P: CSGPolygon3D,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Layer 2 operation selection numbers
|
||||||
|
var _op_number_map: Dictionary = {
|
||||||
|
KEY_1: 0, # Union
|
||||||
|
KEY_2: 1, # Intersection
|
||||||
|
KEY_3: 2, # Subtraction
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional cycle order
|
||||||
|
var _op_cycle: Array = [0,1,2]
|
||||||
|
var _cycle_index := 0
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
set_process_unhandled_key_input(true)
|
||||||
|
|
||||||
|
func _unhandled_key_input(event: InputEvent):
|
||||||
|
if not event is InputEventKey: return
|
||||||
|
var ev := event as InputEventKey
|
||||||
|
if not ev.pressed or ev.echo: return
|
||||||
|
if config == null:
|
||||||
|
config = get_tree().root.get_node_or_null(CsgToolkit.AUTOLOAD_NAME) as CsgTkConfig
|
||||||
|
if sidebar == null:
|
||||||
|
# Try to find existing sidebar if not explicitly set
|
||||||
|
var candidates = get_tree().get_nodes_in_group("CSGSideToolkit")
|
||||||
|
if candidates.size() > 0:
|
||||||
|
sidebar = candidates[0]
|
||||||
|
# Prevent interfering with text input fields
|
||||||
|
var focus_owner = get_viewport().gui_get_focus_owner()
|
||||||
|
if focus_owner and (focus_owner is LineEdit or focus_owner is TextEdit):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Operation & shape shortcuts only trigger when primary action key is held (secondary key reserved for behavior inversion in creation)
|
||||||
|
if Input.is_key_pressed(config.action_key):
|
||||||
|
if ev.physical_keycode in _op_number_map:
|
||||||
|
var op_val = _op_number_map[ev.physical_keycode]
|
||||||
|
sidebar.set_operation(op_val)
|
||||||
|
_print_feedback("Op -> %s" % _op_label(op_val))
|
||||||
|
return
|
||||||
|
# Cycle operation with backtick (`) or TAB
|
||||||
|
if ev.physical_keycode in [KEY_APOSTROPHE, KEY_QUOTELEFT, KEY_TAB]:
|
||||||
|
_cycle_index = (_cycle_index + 1) % _op_cycle.size()
|
||||||
|
var cyc_op = _op_cycle[_cycle_index]
|
||||||
|
sidebar.set_operation(cyc_op)
|
||||||
|
_print_feedback("Op Cycle -> %s" % _op_label(cyc_op))
|
||||||
|
return
|
||||||
|
# Direct shape create (Layer 1)
|
||||||
|
if ev.physical_keycode in _shape_key_map:
|
||||||
|
_create_shape(_shape_key_map[ev.physical_keycode])
|
||||||
|
return
|
||||||
|
|
||||||
|
func _create_shape(type_ref: Variant):
|
||||||
|
if sidebar == null:
|
||||||
|
_print_feedback("No sidebar found for creation")
|
||||||
|
return
|
||||||
|
# Delegates to sidebar logic (handles operation, insertion mode, UndoRedo, materials)
|
||||||
|
sidebar.create_csg(type_ref)
|
||||||
|
_print_feedback("Create %s (%s)" % [type_ref, _op_label(sidebar.operation)])
|
||||||
|
|
||||||
|
func _op_label(op: int) -> String:
|
||||||
|
match op:
|
||||||
|
0: return "Union"
|
||||||
|
1: return "Intersect"
|
||||||
|
2: return "Subtract"
|
||||||
|
_: return str(op)
|
||||||
|
|
||||||
|
func _print_feedback(msg: String):
|
||||||
|
print("CSG Toolkit: %s" % msg)
|
||||||
1
addons/csg_toolkit/scripts/csg_shortcut_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://1bqvx7teqnrl
|
||||||
236
addons/csg_toolkit/scripts/csg_side_toolkit_bar.gd
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGSideToolkitBar extends Control
|
||||||
|
|
||||||
|
@onready var config: CsgTkConfig:
|
||||||
|
get:
|
||||||
|
return get_tree().root.get_node_or_null(CsgToolkit.AUTOLOAD_NAME) as CsgTkConfig
|
||||||
|
|
||||||
|
var operation: CSGShape3D.Operation = CSGShape3D.OPERATION_UNION
|
||||||
|
var selected_material: BaseMaterial3D
|
||||||
|
var selected_shader: ShaderMaterial
|
||||||
|
|
||||||
|
@onready var picker_button: Button = $ScrollContainer/HBoxContainer/Material/MaterialPicker
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
EditorInterface.get_selection().selection_changed.disconnect(_on_selection_changed)
|
||||||
|
|
||||||
|
func _on_selection_changed():
|
||||||
|
if not config.auto_hide:
|
||||||
|
return
|
||||||
|
var selection = EditorInterface.get_selection().get_selected_nodes()
|
||||||
|
if selection.any(func (node): return node is CSGShape3D):
|
||||||
|
show()
|
||||||
|
else:
|
||||||
|
hide()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
picker_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
# Connect material picker button if not connected via scene
|
||||||
|
if not picker_button.pressed.is_connected(_on_material_picker_pressed):
|
||||||
|
picker_button.pressed.connect(_on_material_picker_pressed)
|
||||||
|
set_process_unhandled_key_input(true)
|
||||||
|
|
||||||
|
func _unhandled_key_input(event: InputEvent):
|
||||||
|
# Shortcut: action_key + 1/2/3 to set operation (Union / Intersection / Subtraction)
|
||||||
|
if not (event is InputEventKey):
|
||||||
|
return
|
||||||
|
var ev := event as InputEventKey
|
||||||
|
if ev.pressed and not ev.echo:
|
||||||
|
# Ensure action key is held (config.action_key)
|
||||||
|
if Input.is_key_pressed(config.action_key):
|
||||||
|
match ev.physical_keycode:
|
||||||
|
KEY_1, KEY_KP_1:
|
||||||
|
set_operation(0)
|
||||||
|
_accept_shortcut_feedback("Union")
|
||||||
|
KEY_2, KEY_KP_2:
|
||||||
|
set_operation(1)
|
||||||
|
_accept_shortcut_feedback("Intersection")
|
||||||
|
KEY_3, KEY_KP_3:
|
||||||
|
set_operation(2)
|
||||||
|
_accept_shortcut_feedback("Subtraction")
|
||||||
|
|
||||||
|
func _accept_shortcut_feedback(label: String):
|
||||||
|
# Provide lightweight visual/editor feedback. Avoid static call to non-existent get_status_bar in Godot 4.
|
||||||
|
# Fallback: print to output.
|
||||||
|
var ei = EditorInterface
|
||||||
|
if ei:
|
||||||
|
# Some editor builds expose status bar via base control's children - skip deep search for now.
|
||||||
|
print("CSG Operation: %s" % label)
|
||||||
|
else:
|
||||||
|
print("CSG Operation: %s" % label)
|
||||||
|
|
||||||
|
func _on_box_pressed():
|
||||||
|
create_csg(CSGBox3D)
|
||||||
|
|
||||||
|
func _on_cylinder_pressed():
|
||||||
|
create_csg(CSGCylinder3D)
|
||||||
|
|
||||||
|
func _on_mesh_pressed():
|
||||||
|
create_csg(CSGMesh3D)
|
||||||
|
|
||||||
|
func _on_polygon_pressed():
|
||||||
|
create_csg(CSGPolygon3D)
|
||||||
|
|
||||||
|
func _on_sphere_pressed():
|
||||||
|
create_csg(CSGSphere3D)
|
||||||
|
|
||||||
|
func _on_torus_pressed():
|
||||||
|
create_csg(CSGTorus3D)
|
||||||
|
|
||||||
|
# Operation Toggle (accept optional arg for signal variations)
|
||||||
|
func _on_operation_pressed(val := 0):
|
||||||
|
set_operation(val)
|
||||||
|
|
||||||
|
func _on_config_pressed():
|
||||||
|
var config_view_scene = preload("res://addons/csg_toolkit/scenes/config_window.tscn")
|
||||||
|
var config_view = config_view_scene.instantiate()
|
||||||
|
config_view.close_requested.connect(func ():
|
||||||
|
get_tree().root.remove_child(config_view)
|
||||||
|
config_view.queue_free()
|
||||||
|
)
|
||||||
|
get_tree().root.add_child(config_view)
|
||||||
|
|
||||||
|
func _request_material():
|
||||||
|
var dialog = EditorFileDialog.new()
|
||||||
|
dialog.title = "Select Material"
|
||||||
|
dialog.display_mode = EditorFileDialog.DISPLAY_LIST
|
||||||
|
dialog.filters = ["*.tres, *.material, *.res"]
|
||||||
|
dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
|
||||||
|
dialog.position = ((EditorInterface.get_base_control().size / 2) as Vector2i) - dialog.size
|
||||||
|
dialog.close_requested.connect(func ():
|
||||||
|
get_tree().root.remove_child(dialog)
|
||||||
|
dialog.queue_free()
|
||||||
|
)
|
||||||
|
get_tree().root.add_child(dialog)
|
||||||
|
dialog.show()
|
||||||
|
var res_path = await dialog.file_selected
|
||||||
|
var res = ResourceLoader.load(res_path)
|
||||||
|
if res == null:
|
||||||
|
return
|
||||||
|
if res is BaseMaterial3D:
|
||||||
|
update_material(res)
|
||||||
|
elif res is ShaderMaterial:
|
||||||
|
update_shader(res)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
var previewer = EditorInterface.get_resource_previewer()
|
||||||
|
previewer.queue_edited_resource_preview(res, self, "_update_picker_icon", null)
|
||||||
|
|
||||||
|
func _update_picker_icon(path, preview, thumbnail, userdata):
|
||||||
|
if preview:
|
||||||
|
picker_button.icon = preview
|
||||||
|
|
||||||
|
func set_operation(val: int):
|
||||||
|
match val:
|
||||||
|
0: operation = CSGShape3D.OPERATION_UNION
|
||||||
|
1: operation = CSGShape3D.OPERATION_INTERSECTION
|
||||||
|
2: operation = CSGShape3D.OPERATION_SUBTRACTION
|
||||||
|
_: operation = CSGShape3D.OPERATION_UNION
|
||||||
|
|
||||||
|
func update_material(material: BaseMaterial3D):
|
||||||
|
selected_material = material
|
||||||
|
selected_shader = null
|
||||||
|
|
||||||
|
func update_shader(shader: ShaderMaterial):
|
||||||
|
selected_material = null
|
||||||
|
selected_shader = shader
|
||||||
|
|
||||||
|
func create_csg(type: Variant):
|
||||||
|
var selection = EditorInterface.get_selection()
|
||||||
|
var selected_nodes = selection.get_selected_nodes()
|
||||||
|
if selected_nodes.is_empty() or !(selected_nodes[0] is CSGShape3D):
|
||||||
|
push_warning("Select a CSGShape3D to add a new CSG node")
|
||||||
|
return
|
||||||
|
var selected_node: CSGShape3D = selected_nodes[0]
|
||||||
|
var csg: CSGShape3D
|
||||||
|
match type:
|
||||||
|
CSGBox3D: csg = CSGBox3D.new()
|
||||||
|
CSGCylinder3D: csg = CSGCylinder3D.new()
|
||||||
|
CSGSphere3D: csg = CSGSphere3D.new()
|
||||||
|
CSGMesh3D: csg = CSGMesh3D.new()
|
||||||
|
CSGPolygon3D: csg = CSGPolygon3D.new()
|
||||||
|
CSGTorus3D: csg = CSGTorus3D.new()
|
||||||
|
|
||||||
|
csg.operation = operation
|
||||||
|
if selected_material:
|
||||||
|
csg.material = selected_material
|
||||||
|
elif selected_shader:
|
||||||
|
csg.material = selected_shader
|
||||||
|
|
||||||
|
if (selected_node.get_owner() == null):
|
||||||
|
return
|
||||||
|
|
||||||
|
var parent: Node
|
||||||
|
var add_as_child := false
|
||||||
|
|
||||||
|
# Behavior inversion now uses secondary_action_key (e.g. Alt) instead of primary action key
|
||||||
|
var invert := Input.is_key_pressed(config.secondary_action_key)
|
||||||
|
if config.default_behavior == CsgTkConfig.CSGBehavior.SIBLING:
|
||||||
|
add_as_child = invert
|
||||||
|
else:
|
||||||
|
add_as_child = !invert
|
||||||
|
|
||||||
|
parent = selected_node if add_as_child else selected_node.get_parent()
|
||||||
|
if parent == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Try undo manager path if plugin provided one
|
||||||
|
if CsgToolkit.undo_manager:
|
||||||
|
var insert_index := parent.get_child_count()
|
||||||
|
CsgToolkit.undo_manager.create_action("Add %s" % csg.get_class())
|
||||||
|
# DO methods
|
||||||
|
CsgToolkit.undo_manager.add_do_method(self, "_undoable_add_csg", parent, csg, selected_node.get_owner(), selected_node.global_position, insert_index)
|
||||||
|
CsgToolkit.undo_manager.add_do_method(self, "_select_created_csg", csg)
|
||||||
|
# UNDO methods
|
||||||
|
CsgToolkit.undo_manager.add_undo_method(self, "_undoable_remove_csg", parent, csg)
|
||||||
|
CsgToolkit.undo_manager.add_undo_method(self, "_clear_selection_if", csg)
|
||||||
|
CsgToolkit.undo_manager.commit_action()
|
||||||
|
else:
|
||||||
|
parent.add_child(csg, true)
|
||||||
|
csg.owner = selected_node.get_owner()
|
||||||
|
csg.global_position = selected_node.global_position
|
||||||
|
call_deferred("_select_created_csg", csg)
|
||||||
|
|
||||||
|
func _deferred_select(csg: Node):
|
||||||
|
call_deferred("_select_created_csg", csg)
|
||||||
|
|
||||||
|
func _undoable_add_csg(parent: Node, csg: CSGShape3D, owner_ref: Node, global_pos: Vector3, insert_index: int):
|
||||||
|
if csg.get_parent() != parent:
|
||||||
|
parent.add_child(csg, true)
|
||||||
|
if insert_index >= 0 and insert_index < parent.get_child_count():
|
||||||
|
parent.move_child(csg, insert_index)
|
||||||
|
csg.owner = owner_ref
|
||||||
|
csg.global_position = global_pos
|
||||||
|
|
||||||
|
func _undoable_remove_csg(parent: Node, csg: CSGShape3D):
|
||||||
|
if csg.get_parent() == parent:
|
||||||
|
parent.remove_child(csg)
|
||||||
|
# Intentionally do NOT free node so redo can re-add it. If you need memory, implement a recreate pattern instead.
|
||||||
|
|
||||||
|
func _clear_selection_if(csg: Node):
|
||||||
|
var selection = EditorInterface.get_selection()
|
||||||
|
if selection:
|
||||||
|
var nodes: Array = selection.get_selected_nodes()
|
||||||
|
if csg in nodes:
|
||||||
|
selection.remove_node(csg)
|
||||||
|
|
||||||
|
func _select_created_csg(csg: Node):
|
||||||
|
var selection = EditorInterface.get_selection()
|
||||||
|
selection.clear()
|
||||||
|
selection.add_node(csg)
|
||||||
|
|
||||||
|
func _add_as_child(selected_node: CSGShape3D, csg: CSGShape3D):
|
||||||
|
selected_node.add_child(csg, true)
|
||||||
|
csg.owner = selected_node.get_owner()
|
||||||
|
csg.global_position = selected_node.global_position
|
||||||
|
|
||||||
|
func _add_as_sibling(selected_node: CSGShape3D, csg: CSGShape3D):
|
||||||
|
selected_node.get_parent().add_child(csg, true)
|
||||||
|
csg.owner = selected_node.get_owner()
|
||||||
|
csg.global_position = selected_node.global_position
|
||||||
|
|
||||||
|
func _on_material_picker_pressed() -> void:
|
||||||
|
_request_material()
|
||||||
1
addons/csg_toolkit/scripts/csg_side_toolkit_bar.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dr5f1egll7hdq
|
||||||
280
addons/csg_toolkit/scripts/csg_spreader_3d.gd
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGSpreader3D extends CSGCombiner3D
|
||||||
|
|
||||||
|
const SPREADER_NODE_META = "SPREADER_NODE_META"
|
||||||
|
const MAX_INSTANCES = 20000
|
||||||
|
|
||||||
|
var _dirty: bool = false
|
||||||
|
var _generation_in_progress := false
|
||||||
|
var _template_node_path: NodePath
|
||||||
|
@export var template_node_path: NodePath:
|
||||||
|
get: return _template_node_path
|
||||||
|
set(value):
|
||||||
|
_template_node_path = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _hide_template: bool = true
|
||||||
|
@export var hide_template: bool = true:
|
||||||
|
get: return _hide_template
|
||||||
|
set(value):
|
||||||
|
_hide_template = value
|
||||||
|
_update_template_visibility()
|
||||||
|
|
||||||
|
var _spread_area_3d: Shape3D = null
|
||||||
|
@export var spread_area_3d: Shape3D = null:
|
||||||
|
get: return _spread_area_3d
|
||||||
|
set(value):
|
||||||
|
_spread_area_3d = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _max_count: int = 10
|
||||||
|
@export var max_count: int = 10:
|
||||||
|
get: return _max_count
|
||||||
|
set(value):
|
||||||
|
_max_count = clamp(value, 1, 100000)
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
@export_group("Spread Options")
|
||||||
|
var _noise_threshold: float = 0.5
|
||||||
|
@export var noise_threshold: float = 0.5:
|
||||||
|
get: return _noise_threshold
|
||||||
|
set(value):
|
||||||
|
_noise_threshold = clamp(value, 0.0, 1.0)
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _seed: int = 0
|
||||||
|
@export var seed: int = 0:
|
||||||
|
get: return _seed
|
||||||
|
set(value):
|
||||||
|
_seed = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _allow_rotation: bool = false
|
||||||
|
@export var allow_rotation: bool = false:
|
||||||
|
get: return _allow_rotation
|
||||||
|
set(value):
|
||||||
|
_allow_rotation = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _allow_scale: bool = false
|
||||||
|
@export var allow_scale: bool = false:
|
||||||
|
get: return _allow_scale
|
||||||
|
set(value):
|
||||||
|
_allow_scale = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _snap_distance = 0
|
||||||
|
@export var snap_distance = 0:
|
||||||
|
get: return _snap_distance
|
||||||
|
set(value):
|
||||||
|
_snap_distance = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
@export_group("Collision Options")
|
||||||
|
var _avoid_overlaps: bool = false
|
||||||
|
@export var avoid_overlaps: bool = false:
|
||||||
|
get: return _avoid_overlaps
|
||||||
|
set(value):
|
||||||
|
_avoid_overlaps = value
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _min_distance: float = 1.0
|
||||||
|
@export var min_distance: float = 1.0:
|
||||||
|
get: return _min_distance
|
||||||
|
set(value):
|
||||||
|
_min_distance = max(0.0, value)
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
var _max_placement_attempts: int = 100
|
||||||
|
@export var max_placement_attempts: int = 100:
|
||||||
|
get: return _max_placement_attempts
|
||||||
|
set(value):
|
||||||
|
_max_placement_attempts = clamp(value, 10, 1000)
|
||||||
|
_mark_dirty()
|
||||||
|
|
||||||
|
@export var estimated_instances: int = 0
|
||||||
|
|
||||||
|
var rng: RandomNumberGenerator
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
rng = RandomNumberGenerator.new()
|
||||||
|
_mark_dirty()
|
||||||
|
# Generate instances in-game on ready
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
call_deferred("spread_template")
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
if not Engine.is_editor_hint(): return
|
||||||
|
if _dirty and not _generation_in_progress:
|
||||||
|
_dirty = false
|
||||||
|
call_deferred("spread_template")
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
clear_children()
|
||||||
|
|
||||||
|
func _mark_dirty():
|
||||||
|
_dirty = true
|
||||||
|
|
||||||
|
func _update_template_visibility():
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
var template_node = get_node_or_null(template_node_path)
|
||||||
|
if template_node and template_node is Node3D:
|
||||||
|
template_node.visible = not _hide_template
|
||||||
|
|
||||||
|
func clear_children():
|
||||||
|
var children_to_remove = []
|
||||||
|
for child in get_children(true):
|
||||||
|
if child.has_meta(SPREADER_NODE_META):
|
||||||
|
children_to_remove.append(child)
|
||||||
|
for child in children_to_remove:
|
||||||
|
remove_child(child)
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
func get_random_position_in_area() -> Vector3:
|
||||||
|
if spread_area_3d is SphereShape3D:
|
||||||
|
var radius = spread_area_3d.get_radius()
|
||||||
|
var u = rng.randf()
|
||||||
|
var v = rng.randf()
|
||||||
|
var theta = u * TAU
|
||||||
|
var phi = acos(2.0 * v - 1.0)
|
||||||
|
var r = radius * pow(rng.randf(), 1.0/3.0)
|
||||||
|
return Vector3(r * sin(phi) * cos(theta), r * sin(phi) * sin(theta), r * cos(phi))
|
||||||
|
if spread_area_3d is BoxShape3D:
|
||||||
|
var size = spread_area_3d.size
|
||||||
|
return Vector3(
|
||||||
|
rng.randf_range(-size.x * 0.5, size.x * 0.5),
|
||||||
|
rng.randf_range(-size.y * 0.5, size.y * 0.5),
|
||||||
|
rng.randf_range(-size.z * 0.5, size.z * 0.5)
|
||||||
|
)
|
||||||
|
if spread_area_3d is CapsuleShape3D:
|
||||||
|
var radius = spread_area_3d.get_radius()
|
||||||
|
var height = spread_area_3d.get_height() * 0.5
|
||||||
|
if rng.randf() < noise_threshold:
|
||||||
|
var angle = rng.randf() * TAU
|
||||||
|
var r = radius * sqrt(rng.randf())
|
||||||
|
return Vector3(r * cos(angle), rng.randf_range(-height, height), r * sin(angle))
|
||||||
|
else:
|
||||||
|
var hemisphere_y = height if rng.randf() < noise_threshold else -height
|
||||||
|
var u = rng.randf()
|
||||||
|
var v = rng.randf()
|
||||||
|
var theta = u * TAU
|
||||||
|
var phi = acos(1.0 - v)
|
||||||
|
var r = radius * pow(rng.randf(), 1.0/3.0)
|
||||||
|
return Vector3(
|
||||||
|
r * sin(phi) * cos(theta),
|
||||||
|
hemisphere_y + r * cos(phi) * (1 if hemisphere_y > 0 else -1),
|
||||||
|
r * sin(phi) * sin(theta)
|
||||||
|
)
|
||||||
|
if spread_area_3d is CylinderShape3D:
|
||||||
|
var radius = spread_area_3d.get_radius()
|
||||||
|
var height = spread_area_3d.get_height() * 0.5
|
||||||
|
var angle = rng.randf() * TAU
|
||||||
|
var r = radius * sqrt(rng.randf())
|
||||||
|
return Vector3(r * cos(angle), rng.randf_range(-height, height), r * sin(angle))
|
||||||
|
if spread_area_3d is HeightMapShape3D:
|
||||||
|
var width = spread_area_3d.map_width
|
||||||
|
var depth = spread_area_3d.map_depth
|
||||||
|
if width <= 0 or depth <= 0 or spread_area_3d.map_data.size() == 0:
|
||||||
|
return Vector3.ZERO
|
||||||
|
var x = rng.randi_range(0, width - 1)
|
||||||
|
var z = rng.randi_range(0, depth - 1)
|
||||||
|
var index = x + z * width
|
||||||
|
if index < spread_area_3d.map_data.size():
|
||||||
|
return Vector3(x, spread_area_3d.map_data[index], z)
|
||||||
|
return Vector3.ZERO
|
||||||
|
if spread_area_3d is WorldBoundaryShape3D:
|
||||||
|
var bound = 100.0
|
||||||
|
return Vector3(rng.randf_range(-bound, bound), 0, rng.randf_range(-bound, bound))
|
||||||
|
if spread_area_3d is ConvexPolygonShape3D or spread_area_3d is ConcavePolygonShape3D:
|
||||||
|
var pts = spread_area_3d.points if spread_area_3d.has_method("get_points") else []
|
||||||
|
if pts.size() == 0:
|
||||||
|
return Vector3.ZERO
|
||||||
|
var min_point = pts[0]
|
||||||
|
var max_point = pts[0]
|
||||||
|
for p in pts:
|
||||||
|
min_point = min_point.min(p)
|
||||||
|
max_point = max_point.max(p)
|
||||||
|
return Vector3(
|
||||||
|
rng.randf_range(min_point.x, max_point.x),
|
||||||
|
rng.randf_range(min_point.y, max_point.y),
|
||||||
|
rng.randf_range(min_point.z, max_point.z)
|
||||||
|
)
|
||||||
|
push_warning("CSGSpreader3D: Shape type not supported")
|
||||||
|
return Vector3.ZERO
|
||||||
|
|
||||||
|
func spread_template():
|
||||||
|
if _generation_in_progress:
|
||||||
|
return
|
||||||
|
_generation_in_progress = true
|
||||||
|
if not spread_area_3d:
|
||||||
|
_generation_in_progress = false
|
||||||
|
return
|
||||||
|
clear_children()
|
||||||
|
var template_node = get_node_or_null(template_node_path)
|
||||||
|
if not template_node:
|
||||||
|
_generation_in_progress = false
|
||||||
|
return
|
||||||
|
|
||||||
|
rng.seed = _seed
|
||||||
|
var instances_created = 0
|
||||||
|
var placed_positions = []
|
||||||
|
var budget = min(_max_count, MAX_INSTANCES)
|
||||||
|
if _max_count > MAX_INSTANCES:
|
||||||
|
push_warning("CSGSpreader3D: max_count %s exceeds cap %s. Limiting." % [_max_count, MAX_INSTANCES])
|
||||||
|
for i in range(budget):
|
||||||
|
var noise_value = rng.randf()
|
||||||
|
if noise_value <= _noise_threshold:
|
||||||
|
continue
|
||||||
|
var position_found = false
|
||||||
|
var final_position = Vector3.ZERO
|
||||||
|
var attempts = _max_placement_attempts if _avoid_overlaps else 1
|
||||||
|
for attempt in range(attempts):
|
||||||
|
var test_position = get_random_position_in_area()
|
||||||
|
if not _avoid_overlaps:
|
||||||
|
final_position = test_position
|
||||||
|
position_found = true
|
||||||
|
break
|
||||||
|
var overlap = false
|
||||||
|
for existing_pos in placed_positions:
|
||||||
|
if test_position.distance_to(existing_pos) < _min_distance:
|
||||||
|
overlap = true
|
||||||
|
break
|
||||||
|
if not overlap:
|
||||||
|
final_position = test_position
|
||||||
|
position_found = true
|
||||||
|
break
|
||||||
|
if not position_found:
|
||||||
|
continue
|
||||||
|
var instance = template_node.duplicate()
|
||||||
|
if instance == null:
|
||||||
|
continue
|
||||||
|
instance.set_meta(SPREADER_NODE_META, true)
|
||||||
|
instance.transform.origin = final_position
|
||||||
|
# Ensure instance is visible regardless of template visibility
|
||||||
|
if instance is Node3D:
|
||||||
|
instance.visible = true
|
||||||
|
placed_positions.append(final_position)
|
||||||
|
if _allow_rotation:
|
||||||
|
var rotation_y = rng.randf_range(0, TAU)
|
||||||
|
instance.rotate_y(rotation_y)
|
||||||
|
if _allow_scale:
|
||||||
|
var scale_factor = rng.randf_range(0.5, 2.0)
|
||||||
|
instance.scale *= scale_factor
|
||||||
|
add_child(instance)
|
||||||
|
instances_created += 1
|
||||||
|
estimated_instances = instances_created
|
||||||
|
_update_template_visibility()
|
||||||
|
_generation_in_progress = false
|
||||||
|
|
||||||
|
func bake_instances():
|
||||||
|
if get_child_count() == 0:
|
||||||
|
return
|
||||||
|
var stack = []
|
||||||
|
stack.append_array(get_children())
|
||||||
|
while stack.size() > 0:
|
||||||
|
var node = stack.pop_back()
|
||||||
|
node.set_owner(owner)
|
||||||
|
stack.append_array(node.get_children())
|
||||||
1
addons/csg_toolkit/scripts/csg_spreader_3d.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://rgfomqnhbhpk
|
||||||
99
addons/csg_toolkit/scripts/csg_toolkit_config.gd
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
@tool
|
||||||
|
extends Node
|
||||||
|
class_name CsgTkConfig
|
||||||
|
|
||||||
|
# ProjectSettings paths
|
||||||
|
const SETTING_DEFAULT_BEHAVIOR = "addons/csg_toolkit/default_behavior"
|
||||||
|
const SETTING_ACTION_KEY = "addons/csg_toolkit/action_key"
|
||||||
|
const SETTING_SECONDARY_ACTION_KEY = "addons/csg_toolkit/secondary_action_key"
|
||||||
|
const SETTING_AUTO_HIDE = "addons/csg_toolkit/auto_hide"
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
const DEFAULT_DEFAULT_BEHAVIOR = CSGBehavior.SIBLING
|
||||||
|
const DEFAULT_ACTION_KEY = KEY_SHIFT
|
||||||
|
const DEFAULT_SECONDARY_ACTION_KEY = KEY_ALT
|
||||||
|
const DEFAULT_AUTO_HIDE = true
|
||||||
|
|
||||||
|
# Configurable properties
|
||||||
|
|
||||||
|
## Default behavior when adding new CSG nodes
|
||||||
|
var default_behavior: CSGBehavior = CSGBehavior.SIBLING:
|
||||||
|
get: return _get_setting(SETTING_DEFAULT_BEHAVIOR, DEFAULT_DEFAULT_BEHAVIOR)
|
||||||
|
set(value): _set_setting(SETTING_DEFAULT_BEHAVIOR, value)
|
||||||
|
|
||||||
|
## Key to hold for primary action (e.g., adding CSG nodes)
|
||||||
|
var action_key: Key = KEY_SHIFT:
|
||||||
|
get: return _get_setting(SETTING_ACTION_KEY, DEFAULT_ACTION_KEY)
|
||||||
|
set(value): _set_setting(SETTING_ACTION_KEY, value)
|
||||||
|
|
||||||
|
## Key to hold for secondary action (e.g., alternative CSG operations)
|
||||||
|
var secondary_action_key: Key = KEY_ALT:
|
||||||
|
get: return _get_setting(SETTING_SECONDARY_ACTION_KEY, DEFAULT_SECONDARY_ACTION_KEY)
|
||||||
|
set(value): _set_setting(SETTING_SECONDARY_ACTION_KEY, value)
|
||||||
|
|
||||||
|
## Whether to auto-hide the CSG toolkit UI when not in use
|
||||||
|
var auto_hide: bool = true:
|
||||||
|
get: return _get_setting(SETTING_AUTO_HIDE, DEFAULT_AUTO_HIDE)
|
||||||
|
set(value): _set_setting(SETTING_AUTO_HIDE, value)
|
||||||
|
|
||||||
|
signal config_saved()
|
||||||
|
|
||||||
|
enum CSGBehavior { SIBLING, CHILD }
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
_ensure_settings_exist()
|
||||||
|
|
||||||
|
func _ensure_settings_exist():
|
||||||
|
"""Register settings in ProjectSettings if they don't exist."""
|
||||||
|
if not ProjectSettings.has_setting(SETTING_DEFAULT_BEHAVIOR):
|
||||||
|
ProjectSettings.set_setting(SETTING_DEFAULT_BEHAVIOR, DEFAULT_DEFAULT_BEHAVIOR)
|
||||||
|
ProjectSettings.set_initial_value(SETTING_DEFAULT_BEHAVIOR, DEFAULT_DEFAULT_BEHAVIOR)
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
"name": SETTING_DEFAULT_BEHAVIOR,
|
||||||
|
"type": TYPE_INT,
|
||||||
|
"hint": PROPERTY_HINT_ENUM,
|
||||||
|
"hint_string": "Sibling,Child"
|
||||||
|
})
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting(SETTING_ACTION_KEY):
|
||||||
|
ProjectSettings.set_setting(SETTING_ACTION_KEY, DEFAULT_ACTION_KEY)
|
||||||
|
ProjectSettings.set_initial_value(SETTING_ACTION_KEY, DEFAULT_ACTION_KEY)
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
"name": SETTING_ACTION_KEY,
|
||||||
|
"type": TYPE_INT,
|
||||||
|
"hint": PROPERTY_HINT_NONE
|
||||||
|
})
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting(SETTING_SECONDARY_ACTION_KEY):
|
||||||
|
ProjectSettings.set_setting(SETTING_SECONDARY_ACTION_KEY, DEFAULT_SECONDARY_ACTION_KEY)
|
||||||
|
ProjectSettings.set_initial_value(SETTING_SECONDARY_ACTION_KEY, DEFAULT_SECONDARY_ACTION_KEY)
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
"name": SETTING_SECONDARY_ACTION_KEY,
|
||||||
|
"type": TYPE_INT,
|
||||||
|
"hint": PROPERTY_HINT_NONE
|
||||||
|
})
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting(SETTING_AUTO_HIDE):
|
||||||
|
ProjectSettings.set_setting(SETTING_AUTO_HIDE, DEFAULT_AUTO_HIDE)
|
||||||
|
ProjectSettings.set_initial_value(SETTING_AUTO_HIDE, DEFAULT_AUTO_HIDE)
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
"name": SETTING_AUTO_HIDE,
|
||||||
|
"type": TYPE_BOOL
|
||||||
|
})
|
||||||
|
|
||||||
|
func _get_setting(path: String, default_value: Variant) -> Variant:
|
||||||
|
"""Get a setting from ProjectSettings."""
|
||||||
|
return ProjectSettings.get_setting(path, default_value)
|
||||||
|
|
||||||
|
func _set_setting(path: String, value: Variant):
|
||||||
|
"""Set a setting in ProjectSettings."""
|
||||||
|
ProjectSettings.set_setting(path, value)
|
||||||
|
|
||||||
|
func save_config():
|
||||||
|
"""Save settings to project.godot file."""
|
||||||
|
var err = ProjectSettings.save()
|
||||||
|
if err == OK:
|
||||||
|
print("CsgToolkit: Saved Config to ProjectSettings")
|
||||||
|
config_saved.emit()
|
||||||
|
else:
|
||||||
|
push_error("CsgToolkit: Failed to save config - error code %d" % err)
|
||||||
1
addons/csg_toolkit/scripts/csg_toolkit_config.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://w8ad8q4lneis
|
||||||
41
addons/csg_toolkit/scripts/csg_top_toolkit_bar.gd
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGTopToolkitBar extends Control
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
|
||||||
|
_on_selection_changed()
|
||||||
|
# Attempt to find buttons and add tooltips if present
|
||||||
|
var refresh_btn = find_child("Refresh", true, false)
|
||||||
|
if refresh_btn and refresh_btn is Button:
|
||||||
|
refresh_btn.tooltip_text = "Regenerate preview instances"
|
||||||
|
var bake_btn = find_child("Bake", true, false)
|
||||||
|
if bake_btn and bake_btn is Button:
|
||||||
|
bake_btn.tooltip_text = "Bake generated instances into the scene (makes them persistent)"
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
EditorInterface.get_selection().selection_changed.disconnect(_on_selection_changed)
|
||||||
|
|
||||||
|
func _on_selection_changed():
|
||||||
|
var selection = EditorInterface.get_selection().get_selected_nodes()
|
||||||
|
if selection.is_empty():
|
||||||
|
hide()
|
||||||
|
elif selection[0] is CSGRepeater3D or selection[0] is CSGSpreader3D:
|
||||||
|
show()
|
||||||
|
else:
|
||||||
|
hide()
|
||||||
|
|
||||||
|
func _on_refresh_pressed():
|
||||||
|
var selection = EditorInterface.get_selection().get_selected_nodes()
|
||||||
|
if (selection.is_empty()):
|
||||||
|
return
|
||||||
|
if selection[0] is CSGRepeater3D:
|
||||||
|
selection[0].call("repeat_template")
|
||||||
|
elif selection[0] is CSGSpreader3D:
|
||||||
|
selection[0].call("spread_template")
|
||||||
|
|
||||||
|
func _on_bake_pressed():
|
||||||
|
var selection = EditorInterface.get_selection().get_selected_nodes()
|
||||||
|
if selection.is_empty():
|
||||||
|
return
|
||||||
|
if selection[0] is CSGRepeater3D or selection[0] is CSGSpreader3D:
|
||||||
|
selection[0].call("bake_instances")
|
||||||
1
addons/csg_toolkit/scripts/csg_top_toolkit_bar.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dk6dt8fk1s43s
|
||||||
31
addons/csg_toolkit/scripts/patterns/circular_pattern.gd
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGCircularPattern
|
||||||
|
extends CSGPattern
|
||||||
|
|
||||||
|
@export var radius: float = 5.0
|
||||||
|
@export var points: int = 8
|
||||||
|
@export var layers: int = 1
|
||||||
|
## If 0 use template_size.y
|
||||||
|
@export var layer_height: float = 0.0
|
||||||
|
## Additional gap added per layer beyond base height
|
||||||
|
@export var layer_spacing: float = 0.0
|
||||||
|
|
||||||
|
func _generate(ctx: Dictionary) -> Array:
|
||||||
|
var positions: Array = []
|
||||||
|
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
|
||||||
|
var rad: float = max(0.0, radius)
|
||||||
|
var count: int = max(1, points)
|
||||||
|
if count <= 1:
|
||||||
|
return [Vector3.ZERO]
|
||||||
|
var lyr_count = max(1, layers)
|
||||||
|
var base_y = layer_height if layer_height > 0.0 else template_size.y
|
||||||
|
var step_y = base_y + max(0.0, layer_spacing)
|
||||||
|
for i in range(count):
|
||||||
|
var angle = (i * TAU) / count
|
||||||
|
var base_pos = Vector3(cos(angle) * rad, 0, sin(angle) * rad)
|
||||||
|
for layer in range(lyr_count):
|
||||||
|
positions.append(base_pos + Vector3(0, layer * step_y, 0))
|
||||||
|
return positions
|
||||||
|
|
||||||
|
func get_estimated_count(ctx: Dictionary) -> int:
|
||||||
|
return max(1, points) * max(1, layers)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b3ws8vwtsqmjt
|
||||||
19
addons/csg_toolkit/scripts/patterns/csg_pattern.gd
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@tool
|
||||||
|
@abstract
|
||||||
|
class_name CSGPattern
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
## Base pattern interface. Subclasses implement _generate(RepeaterContext) returning Array[Vector3].
|
||||||
|
|
||||||
|
# Common interface call
|
||||||
|
func generate(ctx: Dictionary) -> Array:
|
||||||
|
# ctx expected keys: repeat: Vector3i, spacing: Vector3, rng: RandomNumberGenerator, step_spacing: Vector3, user: Node (repeater)
|
||||||
|
return _generate(ctx)
|
||||||
|
|
||||||
|
func _generate(_ctx: Dictionary) -> Array:
|
||||||
|
return []
|
||||||
|
|
||||||
|
func get_estimated_count(ctx: Dictionary) -> int:
|
||||||
|
# Default: fallback to generating (may be overridden for performance/accuracy)
|
||||||
|
var arr = _generate(ctx)
|
||||||
|
return arr.size()
|
||||||
1
addons/csg_toolkit/scripts/patterns/csg_pattern.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dbavf0pl65chb
|
||||||
33
addons/csg_toolkit/scripts/patterns/grid_pattern.gd
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGGridPattern
|
||||||
|
extends CSGPattern
|
||||||
|
|
||||||
|
@export var count_x: int = 2
|
||||||
|
@export var count_y: int = 1
|
||||||
|
@export var count_z: int = 1
|
||||||
|
@export var spacing: Vector3 = Vector3.ZERO
|
||||||
|
|
||||||
|
## If true, automatically adds template AABB size to spacing for proper object separation
|
||||||
|
@export var use_template_size: bool = true
|
||||||
|
|
||||||
|
func _generate(ctx: Dictionary) -> Array:
|
||||||
|
var positions: Array = []
|
||||||
|
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
|
||||||
|
var jitter: float = ctx.get("position_jitter", 0.0)
|
||||||
|
var rng: RandomNumberGenerator = ctx.rng
|
||||||
|
var cx = max(1, count_x)
|
||||||
|
var cy = max(1, count_y)
|
||||||
|
var cz = max(1, count_z)
|
||||||
|
var base_step: Vector3 = (template_size if use_template_size else Vector3.ZERO) + spacing
|
||||||
|
for x in range(cx):
|
||||||
|
for y in range(cy):
|
||||||
|
for z in range(cz):
|
||||||
|
var position = Vector3(x * base_step.x, y * base_step.y, z * base_step.z)
|
||||||
|
if jitter > 0.0:
|
||||||
|
position += Vector3(
|
||||||
|
rng.randf_range(-jitter, jitter),
|
||||||
|
rng.randf_range(-jitter, jitter),
|
||||||
|
rng.randf_range(-jitter, jitter)
|
||||||
|
)
|
||||||
|
positions.append(position)
|
||||||
|
return positions
|
||||||
1
addons/csg_toolkit/scripts/patterns/grid_pattern.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bc1sxg4vy464o
|
||||||
124
addons/csg_toolkit/scripts/patterns/noise_pattern.gd
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGNoisePattern
|
||||||
|
extends CSGPattern
|
||||||
|
|
||||||
|
## Generates instance positions based on noise sampling in a 3D volume
|
||||||
|
## Instances are placed where noise value exceeds the threshold
|
||||||
|
|
||||||
|
##
|
||||||
|
@export var bounds: Vector3 = Vector3(10, 10, 10)
|
||||||
|
|
||||||
|
##
|
||||||
|
@export var sample_density: Vector3i = Vector3i(20, 1, 20)
|
||||||
|
|
||||||
|
##
|
||||||
|
@export_range(0.0, 1.0) var noise_threshold: float = 0.5
|
||||||
|
|
||||||
|
##
|
||||||
|
@export var noise_seed: int = 0
|
||||||
|
|
||||||
|
##
|
||||||
|
@export_range(0.01, 100) var noise_frequency: float = 0.1
|
||||||
|
|
||||||
|
##
|
||||||
|
@export_enum("Simplex", "Simplex Smooth", "Cellular", "Perlin", "Value Cubic", "Value") var noise_type: int = 0
|
||||||
|
|
||||||
|
##
|
||||||
|
@export_enum("None", "OpenSimplex2", "OpenSimplex2S", "Cellular", "Perlin", "Value Cubic", "Value") var fractal_type: int = 0
|
||||||
|
|
||||||
|
##
|
||||||
|
@export_range(1, 8) var fractal_octaves: int = 3
|
||||||
|
|
||||||
|
##
|
||||||
|
@export var use_template_size: bool = false
|
||||||
|
|
||||||
|
var noise: FastNoiseLite
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
noise = FastNoiseLite.new()
|
||||||
|
_update_noise()
|
||||||
|
|
||||||
|
func _update_noise():
|
||||||
|
if not noise:
|
||||||
|
noise = FastNoiseLite.new()
|
||||||
|
|
||||||
|
noise.seed = noise_seed
|
||||||
|
noise.frequency = noise_frequency
|
||||||
|
noise.fractal_octaves = fractal_octaves
|
||||||
|
|
||||||
|
# Map noise_type enum to FastNoiseLite types
|
||||||
|
match noise_type:
|
||||||
|
0: noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
|
||||||
|
1: noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||||
|
2: noise.noise_type = FastNoiseLite.TYPE_CELLULAR
|
||||||
|
3: noise.noise_type = FastNoiseLite.TYPE_PERLIN
|
||||||
|
4: noise.noise_type = FastNoiseLite.TYPE_VALUE_CUBIC
|
||||||
|
5: noise.noise_type = FastNoiseLite.TYPE_VALUE
|
||||||
|
|
||||||
|
# Map fractal_type enum to FastNoiseLite fractal types
|
||||||
|
match fractal_type:
|
||||||
|
0: noise.fractal_type = FastNoiseLite.FRACTAL_NONE
|
||||||
|
1: noise.fractal_type = FastNoiseLite.FRACTAL_FBM
|
||||||
|
2: noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
|
||||||
|
3: noise.fractal_type = FastNoiseLite.FRACTAL_PING_PONG
|
||||||
|
|
||||||
|
func _generate(ctx: Dictionary) -> Array:
|
||||||
|
_update_noise()
|
||||||
|
|
||||||
|
var positions: Array = []
|
||||||
|
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE) if use_template_size else Vector3.ZERO
|
||||||
|
var jitter: float = ctx.get("position_jitter", 0.0)
|
||||||
|
var rng: RandomNumberGenerator = ctx.get("rng", RandomNumberGenerator.new())
|
||||||
|
|
||||||
|
var effective_bounds = bounds
|
||||||
|
var sample_count = sample_density
|
||||||
|
|
||||||
|
# Calculate step size for sampling
|
||||||
|
var step = Vector3(
|
||||||
|
effective_bounds.x / max(1, sample_count.x),
|
||||||
|
effective_bounds.y / max(1, sample_count.y),
|
||||||
|
effective_bounds.z / max(1, sample_count.z)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start from negative half to center the pattern around origin
|
||||||
|
var start_pos = -effective_bounds * 0.5
|
||||||
|
|
||||||
|
# Sample noise at regular intervals
|
||||||
|
for x in range(sample_count.x):
|
||||||
|
for y in range(sample_count.y):
|
||||||
|
for z in range(sample_count.z):
|
||||||
|
var sample_pos = start_pos + Vector3(
|
||||||
|
x * step.x + step.x * 0.5,
|
||||||
|
y * step.y + step.y * 0.5,
|
||||||
|
z * step.z + step.z * 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get noise value at this position (normalized to 0-1)
|
||||||
|
var noise_value = (noise.get_noise_3d(sample_pos.x, sample_pos.y, sample_pos.z) + 1.0) * 0.5
|
||||||
|
|
||||||
|
# Only place instance if noise exceeds threshold
|
||||||
|
if noise_value >= noise_threshold:
|
||||||
|
var final_pos = sample_pos
|
||||||
|
|
||||||
|
# Apply template size offset if enabled
|
||||||
|
if use_template_size:
|
||||||
|
final_pos += template_size * Vector3(x, y, z)
|
||||||
|
|
||||||
|
# Apply jitter
|
||||||
|
if jitter > 0.0:
|
||||||
|
final_pos += Vector3(
|
||||||
|
rng.randf_range(-jitter, jitter),
|
||||||
|
rng.randf_range(-jitter, jitter),
|
||||||
|
rng.randf_range(-jitter, jitter)
|
||||||
|
)
|
||||||
|
|
||||||
|
positions.append(final_pos)
|
||||||
|
|
||||||
|
return positions
|
||||||
|
|
||||||
|
func get_estimated_count(ctx: Dictionary) -> int:
|
||||||
|
# Rough estimate: total samples * (1 - threshold)
|
||||||
|
# Higher threshold = fewer instances
|
||||||
|
var total_samples = max(1, sample_density.x) * max(1, sample_density.y) * max(1, sample_density.z)
|
||||||
|
var estimated = int(total_samples * (1.0 - noise_threshold))
|
||||||
|
return max(1, estimated)
|
||||||
1
addons/csg_toolkit/scripts/patterns/noise_pattern.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://3il6xs7cr7gj
|
||||||
39
addons/csg_toolkit/scripts/patterns/spiral_pattern.gd
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
@tool
|
||||||
|
class_name CSGSpiralPattern
|
||||||
|
extends CSGPattern
|
||||||
|
|
||||||
|
@export var turns: float = 2.0
|
||||||
|
@export var start_radius: float = 0.5
|
||||||
|
@export var end_radius: float = 5.0
|
||||||
|
## If > 0 overrides vertical spread based on repeat & step
|
||||||
|
@export var total_height: float = 0.0
|
||||||
|
@export var use_radius_curve: bool = false
|
||||||
|
@export var radius_curve: Curve
|
||||||
|
@export var points: int = 32
|
||||||
|
|
||||||
|
func _generate(ctx: Dictionary) -> Array:
|
||||||
|
var positions: Array = []
|
||||||
|
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
|
||||||
|
var t_turns: float = max(0.1, turns)
|
||||||
|
var r_start: float = max(0.0, start_radius)
|
||||||
|
var r_end: float = max(r_start, end_radius)
|
||||||
|
var total: int = max(2, points)
|
||||||
|
if total <= 1:
|
||||||
|
return [Vector3.ZERO]
|
||||||
|
for i in range(total):
|
||||||
|
var t: float = float(i) / float(total - 1)
|
||||||
|
var angle = t * t_turns * TAU
|
||||||
|
var curve_t = t
|
||||||
|
if use_radius_curve and radius_curve and radius_curve.get_point_count() > 0:
|
||||||
|
curve_t = clamp(radius_curve.sample(t), 0.0, 1.0)
|
||||||
|
var radius = lerp(r_start, r_end, curve_t)
|
||||||
|
var y_pos: float = t * (total_height if total_height > 0.0 else template_size.y * 1.0)
|
||||||
|
positions.append(Vector3(
|
||||||
|
cos(angle) * radius,
|
||||||
|
y_pos,
|
||||||
|
sin(angle) * radius
|
||||||
|
))
|
||||||
|
return positions
|
||||||
|
|
||||||
|
func get_estimated_count(ctx: Dictionary) -> int:
|
||||||
|
return max(2, points)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://belgcjd0ys212
|
||||||
8
addons/forge/Forge.props
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Gamesmiths.Forge" Version="0.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
109
addons/forge/ForgePluginLoader.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
#if TOOLS
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Gamesmiths.Forge.Godot.Editor;
|
||||||
|
using Gamesmiths.Forge.Godot.Editor.Attributes;
|
||||||
|
using Gamesmiths.Forge.Godot.Editor.Cues;
|
||||||
|
using Gamesmiths.Forge.Godot.Editor.Tags;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class ForgePluginLoader : EditorPlugin
|
||||||
|
{
|
||||||
|
private const string AutoloadPath = "uid://ba8fquhtwu5mu";
|
||||||
|
private const string PluginScenePath = "uid://pjscvogl6jak";
|
||||||
|
|
||||||
|
private EditorDock? _editorDock;
|
||||||
|
private PanelContainer? _dockedScene;
|
||||||
|
private TagContainerInspectorPlugin? _tagContainerInspectorPlugin;
|
||||||
|
private TagInspectorPlugin? _tagInspectorPlugin;
|
||||||
|
private AttributeSetInspectorPlugin? _attributeSetInspectorPlugin;
|
||||||
|
private CueHandlerInspectorPlugin? _cueHandlerInspectorPlugin;
|
||||||
|
private AttributeEditorPlugin? _attributeEditorPlugin;
|
||||||
|
|
||||||
|
public override void _EnterTree()
|
||||||
|
{
|
||||||
|
PackedScene pluginScene = ResourceLoader.Load<PackedScene>(PluginScenePath);
|
||||||
|
|
||||||
|
_editorDock = new EditorDock
|
||||||
|
{
|
||||||
|
Title = "Forge",
|
||||||
|
DockIcon = GD.Load<Texture2D>("uid://cu6ncpuumjo20"),
|
||||||
|
DefaultSlot = EditorDock.DockSlot.RightUl,
|
||||||
|
};
|
||||||
|
|
||||||
|
_dockedScene = (PanelContainer)pluginScene.Instantiate();
|
||||||
|
_dockedScene.GetNode<TagsEditor>("%Tags").IsPluginInstance = true;
|
||||||
|
|
||||||
|
_editorDock.AddChild(_dockedScene);
|
||||||
|
AddDock(_editorDock);
|
||||||
|
|
||||||
|
_tagContainerInspectorPlugin = new TagContainerInspectorPlugin();
|
||||||
|
AddInspectorPlugin(_tagContainerInspectorPlugin);
|
||||||
|
_tagInspectorPlugin = new TagInspectorPlugin();
|
||||||
|
AddInspectorPlugin(_tagInspectorPlugin);
|
||||||
|
_attributeSetInspectorPlugin = new AttributeSetInspectorPlugin();
|
||||||
|
AddInspectorPlugin(_attributeSetInspectorPlugin);
|
||||||
|
_cueHandlerInspectorPlugin = new CueHandlerInspectorPlugin();
|
||||||
|
AddInspectorPlugin(_cueHandlerInspectorPlugin);
|
||||||
|
_attributeEditorPlugin = new AttributeEditorPlugin();
|
||||||
|
AddInspectorPlugin(_attributeEditorPlugin);
|
||||||
|
|
||||||
|
AddToolMenuItem("Repair assets tags", new Callable(this, MethodName.CallAssetRepairTool));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _ExitTree()
|
||||||
|
{
|
||||||
|
Debug.Assert(_editorDock is not null, $"{nameof(_editorDock)} should have been initialized on _Ready().");
|
||||||
|
Debug.Assert(_dockedScene is not null, $"{nameof(_dockedScene)} should have been initialized on _Ready().");
|
||||||
|
|
||||||
|
RemoveDock(_editorDock);
|
||||||
|
_editorDock.QueueFree();
|
||||||
|
_dockedScene.Free();
|
||||||
|
|
||||||
|
RemoveInspectorPlugin(_tagContainerInspectorPlugin);
|
||||||
|
RemoveInspectorPlugin(_tagInspectorPlugin);
|
||||||
|
RemoveInspectorPlugin(_attributeSetInspectorPlugin);
|
||||||
|
RemoveInspectorPlugin(_cueHandlerInspectorPlugin);
|
||||||
|
RemoveInspectorPlugin(_attributeEditorPlugin);
|
||||||
|
|
||||||
|
RemoveToolMenuItem("Repair assets tags");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _EnablePlugin()
|
||||||
|
{
|
||||||
|
base._EnablePlugin();
|
||||||
|
|
||||||
|
var config = ProjectSettings.LoadResourcePack(AutoloadPath);
|
||||||
|
|
||||||
|
if (config)
|
||||||
|
{
|
||||||
|
GD.PrintErr("Failed to load script at res://addons/forge/core/ForgeBootstrap.cs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ProjectSettings.HasSetting("autoload/Forge Bootstrap"))
|
||||||
|
{
|
||||||
|
ProjectSettings.SetSetting("autoload/Forge Bootstrap", AutoloadPath);
|
||||||
|
ProjectSettings.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _DisablePlugin()
|
||||||
|
{
|
||||||
|
if (ProjectSettings.HasSetting("autoload/Forge Bootstrap"))
|
||||||
|
{
|
||||||
|
ProjectSettings.Clear("autoload/Forge Bootstrap");
|
||||||
|
ProjectSettings.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CallAssetRepairTool()
|
||||||
|
{
|
||||||
|
AssetRepairTool.RepairAllAssetsTags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
1
addons/forge/ForgePluginLoader.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://686m2ah4as6w
|
||||||
21
addons/forge/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Gamesmiths Guild
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
58
addons/forge/README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Forge for Godot
|
||||||
|
|
||||||
|
Forge for Godot is an Unreal GAS-like gameplay framework for the Godot Engine.
|
||||||
|
|
||||||
|
It integrates the [Forge Gameplay System](https://github.com/gamesmiths-guild/forge) into Godot, providing a robust, data-driven foundation for gameplay features such as attributes, effects, gameplay tags, abilities, events, and cues, fully aligned with Godot’s node, resource, and editor workflows.
|
||||||
|
|
||||||
|
This plugin enables you to:
|
||||||
|
|
||||||
|
- Use **ForgeEntity** nodes or implement `IForgeEntity` to integrate core Forge systems like attributes, effects, abilities, events and tags.
|
||||||
|
- Define attributes, effects, abilities, cues, and tags directly in the Godot editor.
|
||||||
|
- Apply and manage gameplay effects with area or raycasting nodes.
|
||||||
|
- Create hierarchical gameplay tags using the built-in Tags Editor.
|
||||||
|
- Trigger visual and audio feedback with the Cues system.
|
||||||
|
- Create player skills, attacks, or behaviors, with support for custom logic, costs, cooldowns, and triggers.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Effects System**: Comprehensive effect application and management, including stacking, periodic, instant, and infinite effects.
|
||||||
|
- **Attributes System**: Attribute management, supporting sets, modifiers, and configuration.
|
||||||
|
- **Tags System**: Full hierarchical tag system with Godot editor integration.
|
||||||
|
- **Abilities System**: Feature-complete ability system, supporting grant/removal, custom behaviors, triggers, cooldowns, and costs.
|
||||||
|
- **Events System**: Gameplay event bus supporting event-driven logic, subscriptions, and triggers.
|
||||||
|
- **Cues System**: Visual/audio feedback layer; decouples presentation from game logic.
|
||||||
|
- **Editor Extensions**: Custom inspector elements and tag editor with Godot integration.
|
||||||
|
- **Custom Nodes**: Includes nodes like `ForgeEntity`, `ForgeAttributeSet`, `EffectArea2D`, and more.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Godot 4.6 or later with .NET support.
|
||||||
|
- .NET SDK 8.0 or later.
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
1. Install the plugin via the Godot Asset Library or manually by copying the `addons` folder.
|
||||||
|
- [Godot Asset Library](https://godotengine.org/asset-library/asset/4239)
|
||||||
|
- [Manual installation guide](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/installing_plugins.html)
|
||||||
|
2. Add the following line in your `.csproj` file (before the closing `</Project>` tag). The `.csproj` file can be created through Godot by navigating to `Project > Tools > C# > Create C# solution`:
|
||||||
|
```xml
|
||||||
|
<Import Project="addons/forge/Forge.props" />
|
||||||
|
```
|
||||||
|
3. Back in the Godot editor, build your project by clicking `Build` in the top-right corner of the script editor.
|
||||||
|
4. Enable **Forge Gameplay System** in `Project > Project Settings > Plugins`.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
- See the [Quick Start Guide](https://github.com/gamesmiths-guild/forge-godot/blob/main/docs/quick-start.md) for a basic setup.
|
||||||
|
- Explore [sample scenes](https://github.com/gamesmiths-guild/forge-godot/tree/main/examples) by cloning the full repo.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full documentation, examples, and advanced usage are available in the [Forge for Godot GitHub repository](https://github.com/gamesmiths-guild/forge-godot).
|
||||||
|
For technical details about core systems, see the [Forge Gameplay System documentation](https://github.com/gamesmiths-guild/forge/blob/main/docs/README.md).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This plugin is licensed under the same terms as the core [Forge Gameplay System](https://github.com/gamesmiths-guild/forge).
|
||||||
312
addons/forge/core/EffectApplier.cs
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Gamesmiths.Forge.Core;
|
||||||
|
using Gamesmiths.Forge.Effects;
|
||||||
|
using Gamesmiths.Forge.Godot.Nodes;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Core;
|
||||||
|
|
||||||
|
internal sealed class EffectApplier
|
||||||
|
{
|
||||||
|
private record struct EffectKey(EffectData EffectData, EffectOwnership EffectOwnership, int Level);
|
||||||
|
|
||||||
|
private readonly List<EffectData> _effects = [];
|
||||||
|
|
||||||
|
private readonly Dictionary<IForgeEntity, List<ActiveEffectHandle>> _effectInstances = [];
|
||||||
|
private readonly Dictionary<EffectKey, Effect> _effectsCache = [];
|
||||||
|
|
||||||
|
public EffectApplier(Node node)
|
||||||
|
{
|
||||||
|
foreach (Node child in node.GetChildren())
|
||||||
|
{
|
||||||
|
if (child is ForgeEffect effectNode && effectNode.EffectData is not null)
|
||||||
|
{
|
||||||
|
_effects.Add(effectNode.EffectData.GetEffectData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyEffects(
|
||||||
|
Node node,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level = 1)
|
||||||
|
{
|
||||||
|
if (node is IForgeEntity forgeEntity)
|
||||||
|
{
|
||||||
|
ApplyEffects(forgeEntity, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Node? child in node.GetChildren())
|
||||||
|
{
|
||||||
|
if (child is IForgeEntity forgeEntityChild)
|
||||||
|
{
|
||||||
|
ApplyEffects(forgeEntityChild, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyEffects<TData>(
|
||||||
|
Node node,
|
||||||
|
TData contextData,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level = 1)
|
||||||
|
{
|
||||||
|
if (node is IForgeEntity forgeEntity)
|
||||||
|
{
|
||||||
|
ApplyEffects(forgeEntity, contextData, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Node? child in node.GetChildren())
|
||||||
|
{
|
||||||
|
if (child is IForgeEntity forgeEntityChild)
|
||||||
|
{
|
||||||
|
ApplyEffects(forgeEntityChild, contextData, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEffects(
|
||||||
|
Node node,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
if (node is IForgeEntity forgeEntity)
|
||||||
|
{
|
||||||
|
AddEffects(forgeEntity, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Node? child in node.GetChildren())
|
||||||
|
{
|
||||||
|
if (child is IForgeEntity forgeEntityChild)
|
||||||
|
{
|
||||||
|
AddEffects(forgeEntityChild, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEffects<TData>(
|
||||||
|
Node node,
|
||||||
|
TData contextData,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
if (node is IForgeEntity forgeEntity)
|
||||||
|
{
|
||||||
|
AddEffects(forgeEntity, contextData, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Node? child in node.GetChildren())
|
||||||
|
{
|
||||||
|
if (child is IForgeEntity forgeEntityChild)
|
||||||
|
{
|
||||||
|
AddEffects(forgeEntityChild, contextData, effectOwner, effectSource, level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEffects(Node node)
|
||||||
|
{
|
||||||
|
if (node is IForgeEntity forgeEntity)
|
||||||
|
{
|
||||||
|
RemoveEffects(forgeEntity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Node? child in node.GetChildren())
|
||||||
|
{
|
||||||
|
if (child is IForgeEntity forgeEntityChild)
|
||||||
|
{
|
||||||
|
RemoveEffects(forgeEntityChild);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyEffects(
|
||||||
|
IForgeEntity forgeEntity,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
|
||||||
|
|
||||||
|
foreach (EffectData effectData in _effects)
|
||||||
|
{
|
||||||
|
var key = new EffectKey(effectData, effectOwnership, level);
|
||||||
|
|
||||||
|
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
|
||||||
|
{
|
||||||
|
forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effect = new Effect(
|
||||||
|
effectData,
|
||||||
|
new EffectOwnership(effectOwner, effectSource),
|
||||||
|
level);
|
||||||
|
|
||||||
|
_effectsCache[key] = effect;
|
||||||
|
|
||||||
|
forgeEntity.EffectsManager.ApplyEffect(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyEffects<TData>(
|
||||||
|
IForgeEntity forgeEntity,
|
||||||
|
TData contextData,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
|
||||||
|
|
||||||
|
foreach (EffectData effectData in _effects)
|
||||||
|
{
|
||||||
|
var key = new EffectKey(effectData, effectOwnership, level);
|
||||||
|
|
||||||
|
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
|
||||||
|
{
|
||||||
|
forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effect = new Effect(
|
||||||
|
effectData,
|
||||||
|
new EffectOwnership(effectOwner, effectSource),
|
||||||
|
level);
|
||||||
|
|
||||||
|
_effectsCache[key] = effect;
|
||||||
|
|
||||||
|
forgeEntity.EffectsManager.ApplyEffect(effect, contextData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEffects(
|
||||||
|
IForgeEntity forgeEntity,
|
||||||
|
IForgeEntity?
|
||||||
|
effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
var instanceEffects = new List<ActiveEffectHandle>();
|
||||||
|
if (!_effectInstances.TryAdd(forgeEntity, instanceEffects))
|
||||||
|
{
|
||||||
|
instanceEffects = _effectInstances[forgeEntity];
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
|
||||||
|
|
||||||
|
foreach (EffectData effectData in _effects)
|
||||||
|
{
|
||||||
|
var key = new EffectKey(effectData, effectOwnership, level);
|
||||||
|
|
||||||
|
ActiveEffectHandle? handle;
|
||||||
|
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
|
||||||
|
{
|
||||||
|
handle = forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
|
||||||
|
|
||||||
|
if (handle is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceEffects.Add(handle);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effect = new Effect(
|
||||||
|
effectData,
|
||||||
|
new EffectOwnership(effectOwner, effectSource),
|
||||||
|
level);
|
||||||
|
|
||||||
|
handle = forgeEntity.EffectsManager.ApplyEffect(effect);
|
||||||
|
|
||||||
|
if (handle is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceEffects.Add(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEffects<TData>(
|
||||||
|
IForgeEntity forgeEntity,
|
||||||
|
TData contextData,
|
||||||
|
IForgeEntity? effectOwner,
|
||||||
|
IForgeEntity? effectSource,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
var instanceEffects = new List<ActiveEffectHandle>();
|
||||||
|
if (!_effectInstances.TryAdd(forgeEntity, instanceEffects))
|
||||||
|
{
|
||||||
|
instanceEffects = _effectInstances[forgeEntity];
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
|
||||||
|
|
||||||
|
foreach (EffectData effectData in _effects)
|
||||||
|
{
|
||||||
|
var key = new EffectKey(effectData, effectOwnership, level);
|
||||||
|
|
||||||
|
ActiveEffectHandle? handle;
|
||||||
|
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
|
||||||
|
{
|
||||||
|
handle = forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
|
||||||
|
|
||||||
|
if (handle is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceEffects.Add(handle);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effect = new Effect(
|
||||||
|
effectData,
|
||||||
|
new EffectOwnership(effectOwner, effectSource),
|
||||||
|
level);
|
||||||
|
|
||||||
|
handle = forgeEntity.EffectsManager.ApplyEffect(effect, contextData);
|
||||||
|
|
||||||
|
if (handle is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceEffects.Add(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveEffects(IForgeEntity forgeEntity)
|
||||||
|
{
|
||||||
|
if (!_effectInstances.TryGetValue(forgeEntity, out List<ActiveEffectHandle>? value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ActiveEffectHandle handle in value)
|
||||||
|
{
|
||||||
|
forgeEntity.EffectsManager.RemoveEffect(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
_effectInstances[forgeEntity] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
1
addons/forge/core/EffectApplier.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bs52uo5esaiu2
|
||||||
14
addons/forge/core/ForgeBootstrap.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Core;
|
||||||
|
|
||||||
|
public partial class ForgeBootstrap : Node
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
ForgeData pluginData = ResourceLoader.Load<ForgeData>("uid://8j4xg16o3qnl");
|
||||||
|
_ = new ForgeManagers(pluginData);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
addons/forge/core/ForgeBootstrap.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ba8fquhtwu5mu
|
||||||
21
addons/forge/core/ForgeCurve.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
using Gamesmiths.Forge.Core;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Core;
|
||||||
|
|
||||||
|
public readonly struct ForgeCurve(Curve? curve) : ICurve
|
||||||
|
{
|
||||||
|
private readonly Curve? _curve = curve;
|
||||||
|
|
||||||
|
public float Evaluate(float value)
|
||||||
|
{
|
||||||
|
if (_curve is null)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _curve.Sample(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
addons/forge/core/ForgeCurve.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cei2cgvy84iy6
|
||||||
13
addons/forge/core/ForgeData.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using Godot.Collections;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Core;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class ForgeData : Resource
|
||||||
|
{
|
||||||
|
[Export]
|
||||||
|
public Array<string> RegisteredTags { get; set; } = [];
|
||||||
|
}
|
||||||
1
addons/forge/core/ForgeData.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bq4vlbfx00hea
|
||||||
30
addons/forge/core/ForgeManagers.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
using Gamesmiths.Forge.Core;
|
||||||
|
using Gamesmiths.Forge.Cues;
|
||||||
|
using Gamesmiths.Forge.Tags;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Core;
|
||||||
|
|
||||||
|
public class ForgeManagers
|
||||||
|
{
|
||||||
|
public static ForgeManagers Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
public TagsManager TagsManager { get; private set; }
|
||||||
|
|
||||||
|
public CuesManager CuesManager { get; private set; }
|
||||||
|
|
||||||
|
public ForgeManagers(ForgeData pluginData)
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Validation.Enabled = true;
|
||||||
|
#else
|
||||||
|
Validation.Enabled = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TagsManager = new TagsManager([.. pluginData.RegisteredTags]);
|
||||||
|
CuesManager = new CuesManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
addons/forge/core/ForgeManagers.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://djeiinm1gclh4
|
||||||
98
addons/forge/core/ForgeRandom.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Gamesmiths.Forge.Core;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Core;
|
||||||
|
|
||||||
|
public class ForgeRandom : IRandom, IDisposable
|
||||||
|
{
|
||||||
|
private readonly RandomNumberGenerator _randomNumberGenerator;
|
||||||
|
|
||||||
|
public ForgeRandom()
|
||||||
|
{
|
||||||
|
_randomNumberGenerator = new RandomNumberGenerator();
|
||||||
|
_randomNumberGenerator.Randomize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NextBytes(byte[] buffer)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < buffer.Length; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NextBytes(Span<byte> buffer)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < buffer.Length; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double NextDouble()
|
||||||
|
{
|
||||||
|
return _randomNumberGenerator.Randf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NextInt()
|
||||||
|
{
|
||||||
|
return (int)_randomNumberGenerator.Randi();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NextInt(int maxValue)
|
||||||
|
{
|
||||||
|
return _randomNumberGenerator.RandiRange(0, maxValue - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NextInt(int minValue, int maxValue)
|
||||||
|
{
|
||||||
|
return _randomNumberGenerator.RandiRange(minValue, maxValue - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NextInt64()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
var high = _randomNumberGenerator.Randi();
|
||||||
|
var low = _randomNumberGenerator.Randi();
|
||||||
|
return ((long)high << 32) | low;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NextInt64(long maxValue)
|
||||||
|
{
|
||||||
|
return NextInt64(0, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NextInt64(long minValue, long maxValue)
|
||||||
|
{
|
||||||
|
if (minValue >= maxValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than maxValue.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var range = (ulong)(maxValue - minValue);
|
||||||
|
var rand = (ulong)NextInt64();
|
||||||
|
|
||||||
|
return (long)(rand % range) + minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float NextSingle()
|
||||||
|
{
|
||||||
|
return _randomNumberGenerator.Randf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
_randomNumberGenerator.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
addons/forge/core/ForgeRandom.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dap6x2ddf6baj
|
||||||
7
addons/forge/core/forge_data.tres
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://8j4xg16o3qnl"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bq4vlbfx00hea" path="res://addons/forge/core/ForgeData.cs" id="1_x0pne"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_x0pne")
|
||||||
|
RegisteredTags = Array[String](["effect.fire", "effect.wet", "cue.floating.text", "cue.vfx.fire", "cue.vfx.wet", "cue.vfx.regen", "cooldown.enemy.attack", "set_by_caller.damage", "event.damage", "cooldown", "cooldown.skill.projectile", "cooldown.skill.shield", "cooldown.skill.dash", "movement.block", "immunity.damage", "effect.mana_shield", "cue.vfx.shield", "event.damage.taken", "event.damage.dealt", "event", "set_by_caller", "trait.flammable", "trait.healable", "trait.damageable", "trait.wettable", "cue.vfx.reflect", "cue.vfx", "cooldown.skill", "cooldown.skill.reflect"])
|
||||||
210
addons/forge/editor/AssetRepairTool.cs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
#if TOOLS
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Gamesmiths.Forge.Godot.Core;
|
||||||
|
using Gamesmiths.Forge.Godot.Resources;
|
||||||
|
using Gamesmiths.Forge.Tags;
|
||||||
|
using Godot;
|
||||||
|
using Godot.Collections;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Editor;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class AssetRepairTool : EditorPlugin
|
||||||
|
{
|
||||||
|
public static void RepairAllAssetsTags()
|
||||||
|
{
|
||||||
|
ForgeData pluginData = ResourceLoader.Load<ForgeData>("uid://8j4xg16o3qnl");
|
||||||
|
var tagsManager = new TagsManager([.. pluginData.RegisteredTags]);
|
||||||
|
|
||||||
|
List<string> scenes = GetScenePaths("res://");
|
||||||
|
GD.Print($"Found {scenes.Count} scene(s) to process.");
|
||||||
|
|
||||||
|
var openedScenes = EditorInterface.Singleton.GetOpenScenes();
|
||||||
|
|
||||||
|
foreach (var originalScenePath in scenes)
|
||||||
|
{
|
||||||
|
// For some weird reason scenes from the GetScenePath are coming with 3 slashes instead of just two.
|
||||||
|
var scenePath = originalScenePath.Replace("res:///", "res://");
|
||||||
|
|
||||||
|
GD.Print($"Processing scene: {scenePath}.");
|
||||||
|
PackedScene? packedScene = ResourceLoader.Load<PackedScene>(scenePath);
|
||||||
|
|
||||||
|
if (packedScene is null)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"Failed to load scene: {scenePath}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node sceneInstance = packedScene.Instantiate();
|
||||||
|
var modified = ProcessNode(sceneInstance, tagsManager);
|
||||||
|
|
||||||
|
if (!modified)
|
||||||
|
{
|
||||||
|
GD.Print($"No changes needed for {scenePath}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'sceneInstance' is the modified scene instance in memory, need to save to disk and reload if needed.
|
||||||
|
var newScene = new PackedScene();
|
||||||
|
Error error = newScene.Pack(sceneInstance);
|
||||||
|
if (error != Error.Ok)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"Failed to pack scene: {error}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = ResourceSaver.Save(newScene, scenePath);
|
||||||
|
if (error != Error.Ok)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"Failed to save scene: {error}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openedScenes.Contains(scenePath))
|
||||||
|
{
|
||||||
|
GD.Print($"Scene was opened, reloading background scene: {scenePath}.");
|
||||||
|
EditorInterface.Singleton.ReloadSceneFromPath(scenePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively get scene files from a folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="basePath">Current path iteration.</param>
|
||||||
|
/// <returns>List of scenes found.</returns>
|
||||||
|
private static List<string> GetScenePaths(string basePath)
|
||||||
|
{
|
||||||
|
var scenePaths = new List<string>();
|
||||||
|
var dir = DirAccess.Open(basePath);
|
||||||
|
|
||||||
|
if (dir is null)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"Failed to open directory: {basePath}");
|
||||||
|
return scenePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listing directory entries; skip navigational and hidden files.
|
||||||
|
dir.ListDirBegin();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var fileName = dir.GetNext();
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath = $"{basePath}/{fileName}";
|
||||||
|
if (dir.CurrentIsDir())
|
||||||
|
{
|
||||||
|
// Recursively scan subdirectories.
|
||||||
|
scenePaths.AddRange(GetScenePaths(filePath));
|
||||||
|
}
|
||||||
|
else if (fileName.EndsWith(".tscn", StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
|| fileName.EndsWith(".scn", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
scenePaths.Add(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.ListDirEnd();
|
||||||
|
return scenePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively process nodes; returns true if any ForgeEntity was modified.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Current node iteration.</param>
|
||||||
|
/// <param name="tagsManager">The tags manager used to validate tags.</param>
|
||||||
|
/// <returns><see langword="true"/> if any ForgeEntity was modified.</returns>
|
||||||
|
private static bool ProcessNode(Node node, TagsManager tagsManager)
|
||||||
|
{
|
||||||
|
var modified = ValidateNode(node, tagsManager);
|
||||||
|
|
||||||
|
foreach (Node child in node.GetChildren())
|
||||||
|
{
|
||||||
|
modified |= ProcessNode(child, tagsManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ValidateNode(Node node, TagsManager tagsManager)
|
||||||
|
{
|
||||||
|
var modified = false;
|
||||||
|
foreach (Dictionary propertyInfo in node.GetPropertyList())
|
||||||
|
{
|
||||||
|
if (!propertyInfo.TryGetValue("class_name", out Variant className))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (className.AsString() != "TagContainer")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!propertyInfo.TryGetValue("name", out Variant nameObj))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyName = nameObj.AsString();
|
||||||
|
Variant value = node.Get(propertyName);
|
||||||
|
|
||||||
|
if (value.VariantType != Variant.Type.Object)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.As<Resource>() is ForgeTagContainer tagContainer)
|
||||||
|
{
|
||||||
|
modified |= ValidateTagContainerProperty(tagContainer, node.Name, tagsManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ValidateTagContainerProperty(
|
||||||
|
ForgeTagContainer container,
|
||||||
|
string nodeName,
|
||||||
|
TagsManager tagsManager)
|
||||||
|
{
|
||||||
|
if (container.ContainerTags is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<string> originalTags = container.ContainerTags;
|
||||||
|
var newTags = new Array<string>();
|
||||||
|
var modified = false;
|
||||||
|
|
||||||
|
foreach (var tag in originalTags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Tag.RequestTag(tagsManager, tag);
|
||||||
|
newTags.Add(tag);
|
||||||
|
}
|
||||||
|
catch (TagNotRegisteredException)
|
||||||
|
{
|
||||||
|
GD.PrintRich(
|
||||||
|
$"[color=LIGHT_STEEL_BLUE][RepairTool] Removing invalid tag [{tag}] from node {nodeName}.");
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified)
|
||||||
|
{
|
||||||
|
container.ContainerTags = newTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
1
addons/forge/editor/AssetRepairTool.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://1runivyr5don
|
||||||
62
addons/forge/editor/EditorUtils.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
#if TOOLS
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Gamesmiths.Forge.Attributes;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Editor;
|
||||||
|
|
||||||
|
internal static class EditorUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Uses reflection to gather all classes inheriting from AttributeSet and their fields of type Attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An array with the available attributes.</returns>
|
||||||
|
public static string[] GetAttributeSetOptions()
|
||||||
|
{
|
||||||
|
var options = new List<string>();
|
||||||
|
|
||||||
|
// Get all types in the current assembly
|
||||||
|
Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes();
|
||||||
|
|
||||||
|
// Find all types that subclass AttributeSet
|
||||||
|
foreach (Type attributeSetType in allTypes.Where(x => x.IsSubclassOf(typeof(AttributeSet))))
|
||||||
|
{
|
||||||
|
options.Add(attributeSetType.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. options];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses reflection to gather all classes inheriting from AttributeSet and their fields of type Attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributeSet">The attribute set used to search for the attributes.</param>
|
||||||
|
/// <returns>An array with the available attributes.</returns>
|
||||||
|
public static string[] GetAttributeOptions(string? attributeSet)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(attributeSet))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var asm = Assembly.GetExecutingAssembly();
|
||||||
|
Type? type = Array.Find(
|
||||||
|
asm.GetTypes(),
|
||||||
|
x => x.IsSubclassOf(typeof(AttributeSet)) && x.Name == attributeSet);
|
||||||
|
|
||||||
|
if (type is null)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<PropertyInfo> properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(x => x.PropertyType == typeof(EntityAttribute));
|
||||||
|
|
||||||
|
return [.. properties.Select(x => $"{x.Name}")];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
1
addons/forge/editor/EditorUtils.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dcvmf0r1f43m6
|
||||||
14
addons/forge/editor/ForgeEditor.tscn
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[gd_scene format=3 uid="uid://pjscvogl6jak"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c17f812by5x23" path="res://addons/forge/editor/tags/TagsEditor.tscn" id="1_bxwfw"]
|
||||||
|
|
||||||
|
[node name="Forge" type="PanelContainer" unique_id=249446352]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Tags" parent="." unique_id=654228508 instance=ExtResource("1_bxwfw")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
34
addons/forge/editor/attributes/AttributeEditorPlugin.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
#if TOOLS
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Editor.Attributes;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class AttributeEditorPlugin : EditorInspectorPlugin
|
||||||
|
{
|
||||||
|
public override bool _CanHandle(GodotObject @object)
|
||||||
|
{
|
||||||
|
return @object is Resources.ForgeModifier || @object is Resources.ForgeCue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool _ParseProperty(
|
||||||
|
GodotObject @object,
|
||||||
|
Variant.Type type,
|
||||||
|
string name,
|
||||||
|
PropertyHint hintType,
|
||||||
|
string hintString,
|
||||||
|
PropertyUsageFlags usageFlags,
|
||||||
|
bool wide)
|
||||||
|
{
|
||||||
|
if (name == "Attribute" || name == "CapturedAttribute" || name == "MagnitudeAttribute")
|
||||||
|
{
|
||||||
|
AddPropertyEditor(name, new AttributeEditorProperty());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bl2w0vp6b5p8k
|
||||||
101
addons/forge/editor/attributes/AttributeEditorProperty.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
#if TOOLS
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Editor.Attributes;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class AttributeEditorProperty : EditorProperty
|
||||||
|
{
|
||||||
|
private const int ButtonSize = 26;
|
||||||
|
private const int PopupSize = 300;
|
||||||
|
|
||||||
|
private Label _label = null!;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
Texture2D dropdownIcon = EditorInterface.Singleton
|
||||||
|
.GetEditorTheme()
|
||||||
|
.GetIcon("GuiDropdown", "EditorIcons");
|
||||||
|
|
||||||
|
var hbox = new HBoxContainer();
|
||||||
|
_label = new Label { Text = "None", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||||
|
var button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) };
|
||||||
|
|
||||||
|
hbox.AddChild(_label);
|
||||||
|
hbox.AddChild(button);
|
||||||
|
AddChild(hbox);
|
||||||
|
|
||||||
|
var popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) };
|
||||||
|
var tree = new Tree
|
||||||
|
{
|
||||||
|
HideRoot = true,
|
||||||
|
AnchorRight = 1,
|
||||||
|
AnchorBottom = 1,
|
||||||
|
};
|
||||||
|
popup.AddChild(tree);
|
||||||
|
|
||||||
|
var bg = new StyleBoxFlat
|
||||||
|
{
|
||||||
|
BgColor = EditorInterface.Singleton
|
||||||
|
.GetEditorTheme()
|
||||||
|
.GetColor("dark_color_2", "Editor"),
|
||||||
|
};
|
||||||
|
tree.AddThemeStyleboxOverride("panel", bg);
|
||||||
|
|
||||||
|
AddChild(popup);
|
||||||
|
|
||||||
|
BuildAttributeTree(tree);
|
||||||
|
|
||||||
|
button.Pressed += () =>
|
||||||
|
{
|
||||||
|
Window win = GetWindow();
|
||||||
|
popup.Position = (Vector2I)button.GlobalPosition
|
||||||
|
+ win.Position
|
||||||
|
- new Vector2I(PopupSize - ButtonSize, -30);
|
||||||
|
popup.Popup();
|
||||||
|
};
|
||||||
|
|
||||||
|
tree.ItemActivated += () =>
|
||||||
|
{
|
||||||
|
TreeItem item = tree.GetSelected();
|
||||||
|
if (item?.HasMeta("attribute_path") != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullPath = item.GetMeta("attribute_path").AsString();
|
||||||
|
_label.Text = fullPath;
|
||||||
|
EmitChanged(GetEditedProperty(), fullPath);
|
||||||
|
popup.Hide();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _UpdateProperty()
|
||||||
|
{
|
||||||
|
var value = GetEditedObject().Get(GetEditedProperty()).AsString();
|
||||||
|
_label.Text = string.IsNullOrEmpty(value) ? "None" : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildAttributeTree(Tree tree)
|
||||||
|
{
|
||||||
|
TreeItem root = tree.CreateItem();
|
||||||
|
|
||||||
|
foreach (var attributeSet in EditorUtils.GetAttributeSetOptions())
|
||||||
|
{
|
||||||
|
TreeItem setItem = tree.CreateItem(root);
|
||||||
|
setItem.SetText(0, attributeSet);
|
||||||
|
setItem.Collapsed = true;
|
||||||
|
|
||||||
|
foreach (var attribute in EditorUtils.GetAttributeOptions(attributeSet))
|
||||||
|
{
|
||||||
|
TreeItem attributeItem = tree.CreateItem(setItem);
|
||||||
|
var attributePath = $"{attributeSet}.{attribute}";
|
||||||
|
attributeItem.SetText(0, attribute);
|
||||||
|
attributeItem.SetMeta("attribute_path", attributePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dvjqj637kfav
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright © Gamesmiths Guild.
|
||||||
|
|
||||||
|
#if TOOLS
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Gamesmiths.Forge.Attributes;
|
||||||
|
using Gamesmiths.Forge.Godot.Nodes;
|
||||||
|
using Godot;
|
||||||
|
using Godot.Collections;
|
||||||
|
|
||||||
|
namespace Gamesmiths.Forge.Godot.Editor.Attributes;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class AttributeSetClassEditorProperty : EditorProperty
|
||||||
|
{
|
||||||
|
private OptionButton _optionButton = null!;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
_optionButton = new OptionButton();
|
||||||
|
AddChild(_optionButton);
|
||||||
|
|
||||||
|
_optionButton.AddItem("Select AttributeSet Class");
|
||||||
|
foreach (var option in EditorUtils.GetAttributeSetOptions())
|
||||||
|
{
|
||||||
|
_optionButton.AddItem(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
_optionButton.ItemSelected += x =>
|
||||||
|
{
|
||||||
|
var className = _optionButton.GetItemText((int)x);
|
||||||
|
EmitChanged(GetEditedProperty(), className);
|
||||||
|
|
||||||
|
GodotObject obj = GetEditedObject();
|
||||||
|
if (obj is not null)
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, AttributeValues>();
|
||||||
|
|
||||||
|
var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet));
|
||||||
|
Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className);
|
||||||
|
if (targetType is not null)
|
||||||
|
{
|
||||||
|
System.Collections.Generic.IEnumerable<PropertyInfo> attrProps = targetType
|
||||||
|
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(x => x.PropertyType == typeof(EntityAttribute));
|
||||||
|
|
||||||
|
foreach (PropertyInfo? pi in attrProps)
|
||||||
|
{
|
||||||
|
dict[pi.Name] = new AttributeValues(0, 0, int.MaxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EmitChanged("InitialAttributeValues", dict);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _UpdateProperty()
|
||||||
|
{
|
||||||
|
GodotObject obj = GetEditedObject();
|
||||||
|
StringName property = GetEditedProperty();
|
||||||
|
var val = obj.Get(property).AsString();
|
||||||
|
for (var i = 0; i < _optionButton.GetItemCount(); i++)
|
||||||
|
{
|
||||||
|
if (_optionButton.GetItemText(i) == val)
|
||||||
|
{
|
||||||
|
_optionButton.Selected = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||