mirror of
https://github.com/litruv/PNGTuber-Plus.git
synced 2026-07-25 02:56:02 +10:00
Compare commits
27 Commits
v2025.07.2
...
v2025.07.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ef8ecb3732 | |||
| 803e867e20 | |||
| cebef2f06b | |||
| e0420a6583 | |||
| 56e76c61e6 | |||
| 5dfe68cb81 | |||
| 6e02a67114 | |||
| 32a67a6617 | |||
| fb0c7a9e3b | |||
| f0ce00b23b | |||
| d4a9f5a0b0 | |||
| 085d6a21a0 | |||
| 99d2d3f508 | |||
| dad22ece22 | |||
| 52496df10e | |||
| 29b903fadd | |||
| e8191e2de8 | |||
| b7b7d7cdec | |||
| fc6e4a1028 | |||
| 1dfe21363b | |||
| fde3f23ed5 | |||
| 79e54c28da | |||
| a8f327ae5a | |||
| 1295bcbcba | |||
| 758f2ca885 | |||
| 524920d140 | |||
| f226c14c00 |
46
.github/workflows/build-windows.yml
vendored
46
.github/workflows/build-windows.yml
vendored
@@ -16,6 +16,7 @@ jobs:
|
|||||||
export-windows:
|
export-windows:
|
||||||
name: Windows Export
|
name: Windows Export
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
|
if: contains(github.event.head_commit.message, '!build')
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -124,11 +125,49 @@ jobs:
|
|||||||
Write-Host "Generated tag: $tag"
|
Write-Host "Generated tag: $tag"
|
||||||
Write-Host "Generated release name: $releaseName"
|
Write-Host "Generated release name: $releaseName"
|
||||||
|
|
||||||
|
# Get the latest release tag to compare against
|
||||||
|
$latestTag = ""
|
||||||
|
try {
|
||||||
|
$latestTag = & git describe --tags --abbrev=0 2>$null
|
||||||
|
Write-Host "Latest tag found: $latestTag"
|
||||||
|
} catch {
|
||||||
|
Write-Host "No previous tags found, showing all commits"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate commit history since last tag (or all commits if no tag)
|
||||||
|
$commitRange = if ($latestTag) { "$latestTag..HEAD" } else { "HEAD" }
|
||||||
|
|
||||||
|
# Get commits with format: short hash, subject, author
|
||||||
|
$commits = & git log $commitRange --pretty=format:"%h|%s|%an" --no-merges
|
||||||
|
|
||||||
|
# Build commit history as a single string
|
||||||
|
$commitHistory = ""
|
||||||
|
|
||||||
|
if ($commits) {
|
||||||
|
$commitHistory += "`n`n## Changes Since Last Release`n`n"
|
||||||
|
|
||||||
|
$commits | ForEach-Object {
|
||||||
|
$parts = $_ -split '\|', 3
|
||||||
|
$hash = $parts[0]
|
||||||
|
$subject = $parts[1] -replace '"', '""'
|
||||||
|
$author = $parts[2]
|
||||||
|
$commitUrl = "https://github.com/${{ github.repository }}/commit/$hash"
|
||||||
|
$commitHistory += "- [$hash]($commitUrl) $subject`n"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$commitHistory += "`n`n## Changes Since Last Release`n`nNo new commits since last release.`n"
|
||||||
|
}
|
||||||
|
|
||||||
# Set outputs for use in next steps
|
# Set outputs for use in next steps
|
||||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||||
echo "release_name=$releaseName" >> $env:GITHUB_OUTPUT
|
echo "release_name=$releaseName" >> $env:GITHUB_OUTPUT
|
||||||
echo "short_sha=$shortSha" >> $env:GITHUB_OUTPUT
|
echo "short_sha=$shortSha" >> $env:GITHUB_OUTPUT
|
||||||
echo "build_number=$buildNumber" >> $env:GITHUB_OUTPUT
|
echo "build_number=$buildNumber" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Use heredoc syntax for multiline commit history
|
||||||
|
Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_history<<EOF"
|
||||||
|
Add-Content -Path $env:GITHUB_OUTPUT -Value $commitHistory
|
||||||
|
Add-Content -Path $env:GITHUB_OUTPUT -Value "EOF"
|
||||||
- name: Download Windows Build
|
- name: Download Windows Build
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -145,15 +184,14 @@ jobs:
|
|||||||
release_name: ${{ steps.release_info.outputs.release_name }}
|
release_name: ${{ steps.release_info.outputs.release_name }}
|
||||||
body: |
|
body: |
|
||||||
Automated build of PNGTuber Plus
|
Automated build of PNGTuber Plus
|
||||||
|
|
||||||
|
${{ steps.release_info.outputs.commit_history }}
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
1. Download PNGTuber-Plus.exe from the assets below
|
1. Download PNGTuber-Plus.exe from the assets below
|
||||||
2. Run the executable - no installation required!
|
2. Run the executable - no installation required!
|
||||||
3. For StreamDeck integration, download the plugin from [here](https://github.com/BoyneGames/streamdeck-godot-plugin/releases/tag/1.0.0)
|
3. For StreamDeck integration, download the plugin from [here](https://github.com/BoyneGames/streamdeck-godot-plugin/releases/tag/1.0.0)
|
||||||
|
|
||||||
## Credits
|
|
||||||
- Original project by [kaiakairos](https://github.com/kaiakairos/PNGTuber-Plus)
|
|
||||||
- Microphone improvements by [k0ffinz](https://github.com/k0ffinz/PNGTuber-Plus)
|
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
|
|||||||
33
.vscode/launch.json
vendored
33
.vscode/launch.json
vendored
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Launch PNGTuber Plus (Godot)",
|
|
||||||
"type": "godot",
|
|
||||||
"request": "launch",
|
|
||||||
"port": 6007,
|
|
||||||
"address": "127.0.0.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Attach to Running Godot",
|
|
||||||
"type": "godot",
|
|
||||||
"request": "attach",
|
|
||||||
"port": 6007,
|
|
||||||
"address": "127.0.0.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Launch Built Executable",
|
|
||||||
"type": "PowerShell",
|
|
||||||
"request": "launch",
|
|
||||||
"script": "if (Test-Path '${workspaceFolder}\\build\\windows\\PNGTuber-Plus.exe') { Start-Process '${workspaceFolder}\\build\\windows\\PNGTuber-Plus.exe' } else { Write-Host 'Executable not found. Build the project first.' }",
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Build and Launch",
|
|
||||||
"type": "PowerShell",
|
|
||||||
"request": "launch",
|
|
||||||
"script": ".\\build-windows.bat; if (Test-Path '.\\build\\windows\\PNGTuber-Plus.exe') { Start-Process '.\\build\\windows\\PNGTuber-Plus.exe' }",
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
116
.vscode/tasks.json
vendored
116
.vscode/tasks.json
vendored
@@ -2,120 +2,10 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Build Windows (Local)",
|
"label": "Build PNGTuber-Plus",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": ".\\build-windows.bat",
|
"command": "cd c:\\DEV\\Sync\\PNGTuber-Plus; .\\build-windows.bat",
|
||||||
"group": {
|
"group": "build"
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"args": [],
|
|
||||||
"isBackground": false,
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false
|
|
||||||
},
|
|
||||||
"detail": "Build Windows executable using local build script with template management"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Build Windows (CI)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": ".\\build-windows-ci.bat",
|
|
||||||
"group": "build",
|
|
||||||
"args": [],
|
|
||||||
"isBackground": false,
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false
|
|
||||||
},
|
|
||||||
"detail": "Build Windows executable using CI build script (no template download)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Export Windows (Godot Direct)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "D:\\Godot\\Godot_v4.4.1-stable_win64.exe",
|
|
||||||
"group": "build",
|
|
||||||
"args": [
|
|
||||||
"--headless",
|
|
||||||
"--export-release",
|
|
||||||
"\"Windows Desktop\"",
|
|
||||||
"\"build/windows/PNGTuber-Plus.exe\""
|
|
||||||
],
|
|
||||||
"isBackground": false,
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false
|
|
||||||
},
|
|
||||||
"detail": "Export Windows build directly using Godot (requires templates already installed)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean Build Directory",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "if (Test-Path 'build') { Remove-Item -Recurse -Force 'build' }; Write-Host 'Build directory cleaned'",
|
|
||||||
"group": "build",
|
|
||||||
"args": [],
|
|
||||||
"isBackground": false,
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false
|
|
||||||
},
|
|
||||||
"detail": "Clean the build output directory"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Open Build Directory",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "if (Test-Path 'build\\windows') { explorer 'build\\windows' } else { Write-Host 'Build directory does not exist. Run a build first.' }",
|
|
||||||
"group": "build",
|
|
||||||
"args": [],
|
|
||||||
"isBackground": false,
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false
|
|
||||||
},
|
|
||||||
"detail": "Open the Windows build output directory in Explorer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Run Built Executable",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "if (Test-Path 'build\\windows\\PNGTuber-Plus.exe') { Start-Process 'build\\windows\\PNGTuber-Plus.exe' } else { Write-Host 'Executable not found. Build the project first.' }",
|
|
||||||
"group": "test",
|
|
||||||
"args": [],
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false
|
|
||||||
},
|
|
||||||
"detail": "Run the built Windows executable for testing"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
91
README.md
91
README.md
@@ -2,16 +2,85 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Recent Updates by Litruv
|
A fork of [kaiakairos/PNGTuber-Plus](https://github.com/kaiakairos/PNGTuber-Plus) with some quality-of-life upgrades, editor polish, and a few power-user toys.
|
||||||
- **GitHub Actions**: Automated Windows builds on every commit
|
|
||||||
- **StreamDeck Integration**: Add buttons to enable the streamdeck plugin that was built in
|
|
||||||
- **Rotation Wobble**: New sprite rotation effects with frequency and amplitude controls
|
|
||||||
- **Enhanced Modifier Keys**: Support for complex key combinations (Ctrl+Shift+Alt+Cmd)
|
|
||||||
- **Godot 4.4.1 Support**: Updated for latest Godot stable release
|
|
||||||
- **Microphone Improvements**: Audio delay fix & more *from [k0ffinz/PNGTuber-Plus](https://github.com/k0ffinz/PNGTuber-Plus)*
|
|
||||||
|
|
||||||
## 🙏 Credits
|
---
|
||||||
|
|
||||||
- **Original Project**: [PNGTuber Plus by kaiakairos](https://github.com/kaiakairos/PNGTuber-Plus)
|
## 🔧 Fork Changes (Litruv Edition)
|
||||||
- **StreamDeck Plugin**: Based on [BoyneGames StreamDeck Godot Plugin](https://github.com/BoyneGames/streamdeck-godot-plugin)
|
|
||||||
- **Microphone Improvements**: k0ffinz - Experimental mic loudness detection, improved audio initialization, and audio delay fixes from [k0ffinz/PNGTuber-Plus](https://github.com/k0ffinz/PNGTuber-Plus)
|
### 🎛️ Opacity Controls
|
||||||
|
|
||||||
|
Added a proper opacity slider for sprites, with an "affect children" toggle. Clip masks still behave like they should.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 🌀 Rotation Wobble
|
||||||
|
|
||||||
|
Sprites can now do a subtle (or chaotic) rotational wobble. Frequency + amplitude sliders included.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 🔗 Wobble Sync Groups
|
||||||
|
|
||||||
|
Keep multiple sprites in sync with wobble sync groups. Create groups, assign sprites, and all wobble parameters (X/Y/Rotation frequency, amplitude) automatically sync across group members. Perfect for ears, hair, accessories, or any elements that should move together.
|
||||||
|
|
||||||
|
* Create custom sync groups with descriptive names
|
||||||
|
* Assign multiple sprites to the same group
|
||||||
|
* Edit any group member's wobble settings to update the entire group
|
||||||
|
* Visual indicators show which sprites are synced
|
||||||
|
* Groups persist in save files
|
||||||
|
|
||||||
|
|
||||||
|
### ⌨️ Enhanced Modifier Keys
|
||||||
|
|
||||||
|
Supports combos like Ctrl+Shift+Alt+Cmd. For people with more keybinds than fingers.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### ↗️ Editor Middle Mouse Button Panning
|
||||||
|
|
||||||
|
Hold MMB in the editor to pan around the scene, it'll reset when you go back to live.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### ↗️ View Mode MMB Shake
|
||||||
|
|
||||||
|
Hold MMB in view mode to shake your character, Show off them physics!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 🎞️ Delta Time Physics
|
||||||
|
|
||||||
|
Physics are now framerate-independent. Low FPS won’t make your character move in slow motion anymore.
|
||||||
|
Just smooth, consistent jank if that’s your thing. Great for stop-motion vibes.
|
||||||
|
|
||||||
|
|
||||||
|
### 🎤 Microphone Fixes
|
||||||
|
|
||||||
|
Merged in [k0ffinz’s](https://github.com/k0ffinz/PNGTuber-Plus) audio tweaks:
|
||||||
|
|
||||||
|
* Reduced mic startup delay
|
||||||
|
* Better loudness detection
|
||||||
|
* General stability improvements
|
||||||
|
|
||||||
|
|
||||||
|
### 🧱 Engine Update
|
||||||
|
|
||||||
|
Updated to **Godot 4.4.1** — no more editor nags or compatibility weirdness.
|
||||||
|
|
||||||
|
|
||||||
|
### 🎚️ StreamDeck Integration
|
||||||
|
|
||||||
|
StreamDeck plugin was already in, this just wires up the buttons.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧾 Credits
|
||||||
|
|
||||||
|
* Original project by [kaiakairos](https://github.com/kaiakairos/PNGTuber-Plus)
|
||||||
|
* StreamDeck plugin from [BoyneGames](https://github.com/BoyneGames/streamdeck-godot-plugin)
|
||||||
|
* Mic improvements by [k0ffinz](https://github.com/k0ffinz/PNGTuber-Plus)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -106,7 +106,7 @@ func _process(delta):
|
|||||||
heldSprite.z += 1
|
heldSprite.z += 1
|
||||||
heldSprite.setZIndex()
|
heldSprite.setZIndex()
|
||||||
pushUpdate("Moved sprite layer.")
|
pushUpdate("Moved sprite layer.")
|
||||||
if main.editMode:
|
if main and main.editMode:
|
||||||
if Input.is_action_just_pressed("reparent"):
|
if Input.is_action_just_pressed("reparent"):
|
||||||
reparentMode = !reparentMode
|
reparentMode = !reparentMode
|
||||||
Global.chain.enable(reparentMode)
|
Global.chain.enable(reparentMode)
|
||||||
@@ -115,7 +115,7 @@ func _process(delta):
|
|||||||
reparentMode = false
|
reparentMode = false
|
||||||
Global.chain.enable(reparentMode)
|
Global.chain.enable(reparentMode)
|
||||||
|
|
||||||
if main.editMode:
|
if main and main.editMode:
|
||||||
if reparentMode:
|
if reparentMode:
|
||||||
RenderingServer.set_default_clear_color(Color.POWDER_BLUE)
|
RenderingServer.set_default_clear_color(Color.POWDER_BLUE)
|
||||||
else:
|
else:
|
||||||
@@ -126,7 +126,7 @@ func _process(delta):
|
|||||||
scrollSprites()
|
scrollSprites()
|
||||||
|
|
||||||
|
|
||||||
if !main.fileSystemOpen:
|
if !main or main.fileSystemOpen:
|
||||||
|
|
||||||
if Input.is_action_just_pressed("refresh"):
|
if Input.is_action_just_pressed("refresh"):
|
||||||
refresh()
|
refresh()
|
||||||
@@ -222,7 +222,7 @@ func scrollSprites():
|
|||||||
if Input.is_action_pressed("control"):
|
if Input.is_action_pressed("control"):
|
||||||
return
|
return
|
||||||
|
|
||||||
if !main.editMode:
|
if !main or !main.editMode:
|
||||||
return
|
return
|
||||||
|
|
||||||
if main.fileSystemOpen:
|
if main.fileSystemOpen:
|
||||||
|
|||||||
452
autoload/wobble_sync_manager.gd
Normal file
452
autoload/wobble_sync_manager.gd
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
## WobbleSyncGroupManager - Manages synchronized wobble groups for sprites
|
||||||
|
## Follows class-based OOP pattern as requested
|
||||||
|
|
||||||
|
var syncGroups: Dictionary = {} # groupName: WobbleSyncGroup
|
||||||
|
var spriteGroupMembership: Dictionary = {} # spriteId: groupName
|
||||||
|
|
||||||
|
signal group_created(group_name: String)
|
||||||
|
signal group_deleted(group_name: String)
|
||||||
|
signal sprite_joined_group(sprite_id: int, group_name: String)
|
||||||
|
signal sprite_left_group(sprite_id: int, group_name: String)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
print("DEBUG: WobbleSyncManager._ready() called")
|
||||||
|
print("DEBUG: WobbleSyncManager autoload initialized successfully")
|
||||||
|
print("DEBUG: Initial syncGroups: ", syncGroups)
|
||||||
|
print("DEBUG: Initial spriteGroupMembership: ", spriteGroupMembership)
|
||||||
|
|
||||||
|
class WobbleSyncGroup:
|
||||||
|
var name: String
|
||||||
|
var xFrq: float = 0.0
|
||||||
|
var xAmp: float = 0.0
|
||||||
|
var yFrq: float = 0.0
|
||||||
|
var yAmp: float = 0.0
|
||||||
|
var rFrq: float = 0.0
|
||||||
|
var rAmp: float = 0.0
|
||||||
|
var memberSprites: Array = []
|
||||||
|
|
||||||
|
func _init(group_name: String):
|
||||||
|
name = group_name
|
||||||
|
|
||||||
|
## Set wobble parameters for the entire group
|
||||||
|
func setWobbleParameters(x_frq: float, x_amp: float, y_frq: float, y_amp: float, r_frq: float, r_amp: float, manager_ref):
|
||||||
|
print("DEBUG: WobbleSyncGroup.setWobbleParameters called for group '", name, "' with ", memberSprites.size(), " members")
|
||||||
|
print("DEBUG: Member sprite IDs: ", memberSprites)
|
||||||
|
xFrq = x_frq
|
||||||
|
xAmp = x_amp
|
||||||
|
yFrq = y_frq
|
||||||
|
yAmp = y_amp
|
||||||
|
rFrq = r_frq
|
||||||
|
rAmp = r_amp
|
||||||
|
|
||||||
|
# Update all member sprites using the manager reference
|
||||||
|
var sprites = manager_ref.get_tree().get_nodes_in_group("saved")
|
||||||
|
print("DEBUG: Found ", sprites.size(), " total sprites in 'saved' group")
|
||||||
|
|
||||||
|
var updated_count = 0
|
||||||
|
for sprite in sprites:
|
||||||
|
print("DEBUG: Checking sprite ID ", sprite.id, " (type: ", typeof(sprite.id), ") against member list")
|
||||||
|
if sprite.id in memberSprites:
|
||||||
|
print("DEBUG: Updating sprite ID ", sprite.id, " with new wobble values (no scaling - values stored as-is)")
|
||||||
|
# Preserve the sprite's group membership
|
||||||
|
var current_group = sprite.wobbleSyncGroup
|
||||||
|
sprite.xFrq = xFrq
|
||||||
|
sprite.xAmp = xAmp
|
||||||
|
sprite.yFrq = yFrq
|
||||||
|
sprite.yAmp = yAmp
|
||||||
|
sprite.rFrq = rFrq
|
||||||
|
sprite.rAmp = rAmp
|
||||||
|
# Restore group membership in case it got cleared
|
||||||
|
sprite.wobbleSyncGroup = current_group
|
||||||
|
updated_count += 1
|
||||||
|
else:
|
||||||
|
print("DEBUG: Sprite ID ", sprite.id, " not in member list")
|
||||||
|
|
||||||
|
print("DEBUG: Updated ", updated_count, " sprites in group '", name, "'")
|
||||||
|
|
||||||
|
## Add sprite to this group
|
||||||
|
func addSprite(sprite_id): # Remove type constraint to handle float IDs
|
||||||
|
print("DEBUG: WobbleSyncGroup.addSprite called with sprite_id: ", sprite_id, " (type: ", typeof(sprite_id), ")")
|
||||||
|
if sprite_id not in memberSprites:
|
||||||
|
memberSprites.append(sprite_id)
|
||||||
|
print("DEBUG: Added sprite to memberSprites. Current members: ", memberSprites)
|
||||||
|
else:
|
||||||
|
print("DEBUG: Sprite already in memberSprites")
|
||||||
|
|
||||||
|
## Remove sprite from this group
|
||||||
|
func removeSprite(sprite_id): # Remove type constraint
|
||||||
|
memberSprites.erase(sprite_id)
|
||||||
|
|
||||||
|
## Check if group is empty
|
||||||
|
func isEmpty() -> bool:
|
||||||
|
return memberSprites.size() == 0
|
||||||
|
|
||||||
|
## Get group data for saving
|
||||||
|
func toSaveData() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"xFrq": xFrq,
|
||||||
|
"xAmp": xAmp,
|
||||||
|
"yFrq": yFrq,
|
||||||
|
"yAmp": yAmp,
|
||||||
|
"rFrq": rFrq,
|
||||||
|
"rAmp": rAmp,
|
||||||
|
"memberSprites": memberSprites
|
||||||
|
}
|
||||||
|
|
||||||
|
## Load group data from save
|
||||||
|
func fromSaveData(data: Dictionary):
|
||||||
|
name = data.get("name", "")
|
||||||
|
xFrq = data.get("xFrq", 0.0)
|
||||||
|
xAmp = data.get("xAmp", 0.0)
|
||||||
|
yFrq = data.get("yFrq", 0.0)
|
||||||
|
yAmp = data.get("yAmp", 0.0)
|
||||||
|
rFrq = data.get("rFrq", 0.0)
|
||||||
|
rAmp = data.get("rAmp", 0.0)
|
||||||
|
memberSprites = data.get("memberSprites", [])
|
||||||
|
|
||||||
|
## Create a new wobble sync group
|
||||||
|
func createGroup(group_name: String, sprite_id = -1) -> bool: # Remove type constraint
|
||||||
|
if group_name.strip_edges() == "" or syncGroups.has(group_name):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var group = WobbleSyncGroup.new(group_name)
|
||||||
|
syncGroups[group_name] = group
|
||||||
|
|
||||||
|
# If a sprite ID is provided, add it and copy its wobble values
|
||||||
|
if sprite_id != -1:
|
||||||
|
var sprite = getSpriteById(sprite_id)
|
||||||
|
if sprite:
|
||||||
|
# Store the sprite's raw wobble values directly
|
||||||
|
group.setWobbleParameters(sprite.xFrq, sprite.xAmp, sprite.yFrq, sprite.yAmp, sprite.rFrq, sprite.rAmp, self)
|
||||||
|
addSpriteToGroup(sprite_id, group_name)
|
||||||
|
|
||||||
|
group_created.emit(group_name)
|
||||||
|
Global.pushUpdate("Created wobble sync group: " + group_name)
|
||||||
|
return true
|
||||||
|
|
||||||
|
## Delete a wobble sync group
|
||||||
|
func deleteGroup(group_name: String):
|
||||||
|
if not syncGroups.has(group_name):
|
||||||
|
return
|
||||||
|
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
|
||||||
|
# Remove all sprites from the group
|
||||||
|
for sprite_id in group.memberSprites.duplicate():
|
||||||
|
removeSpriteFromGroup(sprite_id, group_name)
|
||||||
|
|
||||||
|
syncGroups.erase(group_name)
|
||||||
|
group_deleted.emit(group_name)
|
||||||
|
Global.pushUpdate("Deleted wobble sync group: " + group_name)
|
||||||
|
|
||||||
|
## Add sprite to a group
|
||||||
|
func addSpriteToGroup(sprite_id, group_name: String) -> bool: # Remove type constraint
|
||||||
|
print("DEBUG: addSpriteToGroup called with sprite_id: ", sprite_id, " (type: ", typeof(sprite_id), "), group: '", group_name, "'")
|
||||||
|
|
||||||
|
if not syncGroups.has(group_name):
|
||||||
|
print("DEBUG: Group '", group_name, "' not found")
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Remove sprite from any existing group first
|
||||||
|
removeSpriteFromAllGroups(sprite_id)
|
||||||
|
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
group.addSprite(sprite_id)
|
||||||
|
spriteGroupMembership[sprite_id] = group_name
|
||||||
|
print("DEBUG: Added sprite ", sprite_id, " to group '", group_name, "'. Group now has ", group.memberSprites.size(), " members")
|
||||||
|
|
||||||
|
# Sync the sprite's wobble parameters to the group
|
||||||
|
var sprite = getSpriteById(sprite_id)
|
||||||
|
if sprite:
|
||||||
|
print("DEBUG: Found sprite object, syncing wobble parameters to group values")
|
||||||
|
sprite.wobbleSyncGroup = group_name
|
||||||
|
sprite.xFrq = group.xFrq
|
||||||
|
sprite.xAmp = group.xAmp
|
||||||
|
sprite.yFrq = group.yFrq
|
||||||
|
sprite.yAmp = group.yAmp
|
||||||
|
sprite.rFrq = group.rFrq
|
||||||
|
sprite.rAmp = group.rAmp
|
||||||
|
|
||||||
|
sprite_joined_group.emit(sprite_id, group_name)
|
||||||
|
Global.pushUpdate("Added sprite to wobble sync group: " + group_name)
|
||||||
|
return true
|
||||||
|
|
||||||
|
## Remove sprite from a specific group
|
||||||
|
func removeSpriteFromGroup(sprite_id, group_name: String): # Remove type constraint
|
||||||
|
print("DEBUG: removeSpriteFromGroup called for sprite ID: ", sprite_id, " (type: ", typeof(sprite_id), "), group: '", group_name, "'")
|
||||||
|
if not syncGroups.has(group_name):
|
||||||
|
print("DEBUG: Group '", group_name, "' not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
print("DEBUG: Group before removal has ", group.memberSprites.size(), " members: ", group.memberSprites)
|
||||||
|
group.removeSprite(sprite_id)
|
||||||
|
spriteGroupMembership.erase(sprite_id)
|
||||||
|
print("DEBUG: Group after removal has ", group.memberSprites.size(), " members: ", group.memberSprites)
|
||||||
|
|
||||||
|
# Clear the sprite's sync group reference
|
||||||
|
var sprite = getSpriteById(sprite_id)
|
||||||
|
if sprite:
|
||||||
|
print("DEBUG: Clearing sprite's wobbleSyncGroup property")
|
||||||
|
sprite.wobbleSyncGroup = ""
|
||||||
|
else:
|
||||||
|
print("DEBUG: Could not find sprite object for ID: ", sprite_id)
|
||||||
|
|
||||||
|
sprite_left_group.emit(sprite_id, group_name)
|
||||||
|
|
||||||
|
# Auto-delete empty groups
|
||||||
|
if group.isEmpty():
|
||||||
|
deleteGroup(group_name)
|
||||||
|
else:
|
||||||
|
Global.pushUpdate("Removed sprite from wobble sync group: " + group_name)
|
||||||
|
|
||||||
|
## Remove sprite from all groups
|
||||||
|
func removeSpriteFromAllGroups(sprite_id): # Remove type constraint
|
||||||
|
print("DEBUG: removeSpriteFromAllGroups called for sprite ID: ", sprite_id)
|
||||||
|
if spriteGroupMembership.has(sprite_id):
|
||||||
|
var group_name = spriteGroupMembership[sprite_id]
|
||||||
|
print("DEBUG: Found sprite in group '", group_name, "', removing...")
|
||||||
|
removeSpriteFromGroup(sprite_id, group_name)
|
||||||
|
else:
|
||||||
|
print("DEBUG: Sprite not found in any group membership")
|
||||||
|
|
||||||
|
## Update wobble parameters for a group
|
||||||
|
func updateGroupWobble(group_name: String, x_frq: float, x_amp: float, y_frq: float, y_amp: float, r_frq: float, r_amp: float):
|
||||||
|
print("DEBUG: updateGroupWobble called for group '", group_name, "' with values - xFrq:", x_frq, " xAmp:", x_amp, " yFrq:", y_frq, " yAmp:", y_amp, " rFrq:", r_frq, " rAmp:", r_amp)
|
||||||
|
|
||||||
|
if not syncGroups.has(group_name):
|
||||||
|
print("DEBUG: Group '", group_name, "' not found in syncGroups!")
|
||||||
|
print("DEBUG: Available groups: ", syncGroups.keys())
|
||||||
|
print("DEBUG: This suggests the avatar's wobble sync groups were never loaded from the save file!")
|
||||||
|
print("DEBUG: Either the save file doesn't contain wobbleSyncGroups, or the loading process didn't run.")
|
||||||
|
return
|
||||||
|
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
print("DEBUG: Found group, calling setWobbleParameters on group with ", group.memberSprites.size(), " members")
|
||||||
|
group.setWobbleParameters(x_frq, x_amp, y_frq, y_amp, r_frq, r_amp, self)
|
||||||
|
|
||||||
|
## Get sprite's current group name
|
||||||
|
func getSpriteGroupName(sprite_id: int) -> String:
|
||||||
|
return spriteGroupMembership.get(sprite_id, "")
|
||||||
|
|
||||||
|
## Get all group names
|
||||||
|
func getGroupNames() -> Array:
|
||||||
|
print("DEBUG: getGroupNames called. syncGroups has ", syncGroups.size(), " groups: ", syncGroups.keys())
|
||||||
|
print("DEBUG: getGroupNames - syncGroups reference: ", syncGroups)
|
||||||
|
print("DEBUG: getGroupNames - self reference: ", self)
|
||||||
|
return syncGroups.keys()
|
||||||
|
|
||||||
|
## Test function to manually load wobble sync groups
|
||||||
|
func testManualLoad():
|
||||||
|
print("DEBUG: testManualLoad called - creating test data...")
|
||||||
|
var test_data = {
|
||||||
|
"ears": {
|
||||||
|
"name": "ears",
|
||||||
|
"xFrq": 0.0,
|
||||||
|
"xAmp": 0.0,
|
||||||
|
"yFrq": 0.019,
|
||||||
|
"yAmp": 3.0,
|
||||||
|
"rFrq": 0.399,
|
||||||
|
"rAmp": 47.0,
|
||||||
|
"memberSprites": [3291286625.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("DEBUG: Calling loadSaveData with test data...")
|
||||||
|
await loadSaveData(test_data)
|
||||||
|
print("DEBUG: testManualLoad completed. syncGroups now has: ", syncGroups.size(), " groups")
|
||||||
|
|
||||||
|
## Check if a group exists
|
||||||
|
func hasGroup(group_name: String) -> bool:
|
||||||
|
return syncGroups.has(group_name)
|
||||||
|
|
||||||
|
## Get sprite by ID
|
||||||
|
func getSpriteById(sprite_id): # Remove type constraint to handle float IDs
|
||||||
|
var sprites = get_tree().get_nodes_in_group("saved")
|
||||||
|
for sprite in sprites:
|
||||||
|
if sprite.id == sprite_id:
|
||||||
|
return sprite
|
||||||
|
return null
|
||||||
|
|
||||||
|
## Clean up orphaned sprites (sprites that no longer exist)
|
||||||
|
func cleanupOrphanedSprites():
|
||||||
|
var existing_sprite_ids = []
|
||||||
|
var sprites = get_tree().get_nodes_in_group("saved")
|
||||||
|
for sprite in sprites:
|
||||||
|
existing_sprite_ids.append(sprite.id)
|
||||||
|
|
||||||
|
# Check each group for orphaned sprite IDs
|
||||||
|
for group_name in syncGroups.keys():
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
var orphaned_ids = []
|
||||||
|
|
||||||
|
for sprite_id in group.memberSprites:
|
||||||
|
if sprite_id not in existing_sprite_ids:
|
||||||
|
orphaned_ids.append(sprite_id)
|
||||||
|
|
||||||
|
# Remove orphaned IDs
|
||||||
|
for orphaned_id in orphaned_ids:
|
||||||
|
group.removeSprite(orphaned_id)
|
||||||
|
spriteGroupMembership.erase(orphaned_id)
|
||||||
|
|
||||||
|
# Delete empty groups
|
||||||
|
if group.isEmpty():
|
||||||
|
deleteGroup(group_name)
|
||||||
|
|
||||||
|
## Get save data for all groups
|
||||||
|
func getSaveData() -> Dictionary:
|
||||||
|
var save_data = {}
|
||||||
|
for group_name in syncGroups.keys():
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
save_data[group_name] = group.toSaveData()
|
||||||
|
return save_data
|
||||||
|
|
||||||
|
## Load save data for all groups
|
||||||
|
func loadSaveData(save_data: Dictionary):
|
||||||
|
print("DEBUG: ========================")
|
||||||
|
print("DEBUG: WobbleSyncManager.loadSaveData called with save_data: ", save_data)
|
||||||
|
print("DEBUG: save_data type: ", typeof(save_data))
|
||||||
|
print("DEBUG: save_data.keys(): ", save_data.keys())
|
||||||
|
print("DEBUG: save_data.size(): ", save_data.size())
|
||||||
|
print("DEBUG: self reference: ", self)
|
||||||
|
print("DEBUG: syncGroups before clear: ", syncGroups)
|
||||||
|
|
||||||
|
# Check if save_data is valid
|
||||||
|
if save_data == null or typeof(save_data) != TYPE_DICTIONARY:
|
||||||
|
print("DEBUG: ERROR - Invalid save_data provided")
|
||||||
|
return
|
||||||
|
|
||||||
|
if save_data.size() == 0:
|
||||||
|
print("DEBUG: WARNING - Empty save_data provided")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clear existing groups
|
||||||
|
print("DEBUG: Clearing existing syncGroups (had ", syncGroups.size(), " groups)")
|
||||||
|
syncGroups.clear()
|
||||||
|
spriteGroupMembership.clear()
|
||||||
|
print("DEBUG: Cleared existing data. syncGroups now has ", syncGroups.size(), " groups")
|
||||||
|
|
||||||
|
# Load groups from save data
|
||||||
|
var loaded_groups = 0
|
||||||
|
print("DEBUG: Starting group loading loop...")
|
||||||
|
for group_name in save_data.keys():
|
||||||
|
print("DEBUG: ---- Processing group '", group_name, "' (attempt ", loaded_groups + 1, ") ----")
|
||||||
|
var group_data = save_data[group_name]
|
||||||
|
print("DEBUG: Group data type: ", typeof(group_data))
|
||||||
|
print("DEBUG: Group data: ", group_data)
|
||||||
|
|
||||||
|
if typeof(group_data) == TYPE_DICTIONARY:
|
||||||
|
print("DEBUG: Creating WobbleSyncGroup instance...")
|
||||||
|
var group = WobbleSyncGroup.new(group_name)
|
||||||
|
print("DEBUG: Created new WobbleSyncGroup instance for '", group_name, "'")
|
||||||
|
print("DEBUG: Calling fromSaveData...")
|
||||||
|
group.fromSaveData(group_data)
|
||||||
|
print("DEBUG: Called fromSaveData on group '", group_name, "'")
|
||||||
|
print("DEBUG: Adding to syncGroups dictionary...")
|
||||||
|
syncGroups[group_name] = group
|
||||||
|
loaded_groups += 1
|
||||||
|
print("DEBUG: Added group '", group_name, "' to syncGroups. Total groups now: ", syncGroups.size())
|
||||||
|
print("DEBUG: syncGroups.keys() now: ", syncGroups.keys())
|
||||||
|
print("DEBUG: Group memberSprites: ", group.memberSprites)
|
||||||
|
|
||||||
|
# Update sprite group membership
|
||||||
|
print("DEBUG: Updating sprite group membership...")
|
||||||
|
for sprite_id in group.memberSprites:
|
||||||
|
spriteGroupMembership[sprite_id] = group_name
|
||||||
|
print("DEBUG: Added sprite ", sprite_id, " to spriteGroupMembership for group '", group_name, "'")
|
||||||
|
print("DEBUG: ---- Finished processing group '", group_name, "' ----")
|
||||||
|
else:
|
||||||
|
print("DEBUG: ERROR - Group data for '", group_name, "' is not a Dictionary, skipping")
|
||||||
|
|
||||||
|
print("DEBUG: ========================")
|
||||||
|
print("DEBUG: GROUP LOADING COMPLETE")
|
||||||
|
print("DEBUG: Loaded ", loaded_groups, " groups total")
|
||||||
|
print("DEBUG: Final syncGroups after loading: ", syncGroups.keys())
|
||||||
|
print("DEBUG: Final syncGroups size: ", syncGroups.size())
|
||||||
|
print("DEBUG: Final syncGroups contents: ", syncGroups)
|
||||||
|
print("DEBUG: Final spriteGroupMembership: ", spriteGroupMembership)
|
||||||
|
print("DEBUG: ========================")
|
||||||
|
|
||||||
|
# If no groups were loaded, clear any orphaned group references on sprites
|
||||||
|
if syncGroups.size() == 0:
|
||||||
|
print("DEBUG: No groups loaded, clearing orphaned sprite group references")
|
||||||
|
var sprites = get_tree().get_nodes_in_group("saved")
|
||||||
|
for sprite in sprites:
|
||||||
|
if sprite.wobbleSyncGroup != "":
|
||||||
|
print("DEBUG: Clearing orphaned group reference '", sprite.wobbleSyncGroup, "' from sprite ID ", sprite.id)
|
||||||
|
sprite.wobbleSyncGroup = ""
|
||||||
|
else:
|
||||||
|
print("DEBUG: Groups loaded successfully, syncing sprite parameters...")
|
||||||
|
# Apply wobble parameters to sprites with better timing
|
||||||
|
await get_tree().process_frame # Wait for sprites to be loaded
|
||||||
|
await get_tree().process_frame # Extra frame to ensure everything is ready
|
||||||
|
|
||||||
|
for group_name in syncGroups.keys():
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
print("DEBUG: Syncing parameters for group '", group_name, "' with ", group.memberSprites.size(), " members")
|
||||||
|
for sprite_id in group.memberSprites:
|
||||||
|
var sprite = getSpriteById(sprite_id)
|
||||||
|
if sprite:
|
||||||
|
print("DEBUG: Syncing sprite ID ", sprite_id, " to group '", group_name, "'")
|
||||||
|
sprite.wobbleSyncGroup = group_name
|
||||||
|
sprite.xFrq = group.xFrq
|
||||||
|
sprite.xAmp = group.xAmp
|
||||||
|
sprite.yFrq = group.yFrq
|
||||||
|
sprite.yAmp = group.yAmp
|
||||||
|
sprite.rFrq = group.rFrq
|
||||||
|
sprite.rAmp = group.rAmp
|
||||||
|
# Force a complete opacity update after modifying sprite properties
|
||||||
|
if sprite.has_method("updateOpacity"):
|
||||||
|
sprite.updateOpacity()
|
||||||
|
if sprite.has_method("updateShaderOpacity"):
|
||||||
|
sprite.updateShaderOpacity()
|
||||||
|
else:
|
||||||
|
print("DEBUG: ERROR - Could not find sprite with ID ", sprite_id)
|
||||||
|
|
||||||
|
print("DEBUG: ========================")
|
||||||
|
print("DEBUG: loadSaveData FUNCTION COMPLETE")
|
||||||
|
print("DEBUG: Final verification - syncGroups has ", syncGroups.size(), " groups: ", syncGroups.keys())
|
||||||
|
print("DEBUG: ========================")
|
||||||
|
|
||||||
|
## Called when a sprite is deleted to clean up references
|
||||||
|
func onSpriteDeleted(sprite_id): # Remove type constraint
|
||||||
|
removeSpriteFromAllGroups(sprite_id)
|
||||||
|
|
||||||
|
## Debug function to check data consistency
|
||||||
|
func checkDataConsistency():
|
||||||
|
print("=== WOBBLE SYNC DATA CONSISTENCY CHECK ===")
|
||||||
|
|
||||||
|
# Check each group's member sprites
|
||||||
|
for group_name in syncGroups.keys():
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
print("Group '", group_name, "' has ", group.memberSprites.size(), " members: ", group.memberSprites)
|
||||||
|
|
||||||
|
for sprite_id in group.memberSprites:
|
||||||
|
# Check if sprite exists
|
||||||
|
var sprite = getSpriteById(sprite_id)
|
||||||
|
if sprite == null:
|
||||||
|
print(" ERROR: Sprite ID ", sprite_id, " in group but sprite object not found!")
|
||||||
|
else:
|
||||||
|
# Check if sprite's wobbleSyncGroup matches
|
||||||
|
if sprite.wobbleSyncGroup != group_name:
|
||||||
|
print(" ERROR: Sprite ID ", sprite_id, " in group '", group_name, "' but sprite.wobbleSyncGroup = '", sprite.wobbleSyncGroup, "'")
|
||||||
|
else:
|
||||||
|
print(" OK: Sprite ID ", sprite_id, " consistent")
|
||||||
|
|
||||||
|
# Check if membership mapping is correct
|
||||||
|
if not spriteGroupMembership.has(sprite_id) or spriteGroupMembership[sprite_id] != group_name:
|
||||||
|
print(" ERROR: Sprite ID ", sprite_id, " in group but spriteGroupMembership is wrong!")
|
||||||
|
|
||||||
|
# Check membership mapping for orphaned entries
|
||||||
|
for sprite_id in spriteGroupMembership.keys():
|
||||||
|
var group_name = spriteGroupMembership[sprite_id]
|
||||||
|
if not syncGroups.has(group_name):
|
||||||
|
print("ERROR: Sprite ID ", sprite_id, " mapped to non-existent group '", group_name, "'")
|
||||||
|
else:
|
||||||
|
var group = syncGroups[group_name]
|
||||||
|
if sprite_id not in group.memberSprites:
|
||||||
|
print("ERROR: Sprite ID ", sprite_id, " mapped to group '", group_name, "' but not in group's member list")
|
||||||
|
|
||||||
|
print("=== END CONSISTENCY CHECK ===")
|
||||||
1
autoload/wobble_sync_manager.gd.uid
Normal file
1
autoload/wobble_sync_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ccl0r2eov68sy
|
||||||
BIN
bin/~libgdexample.windows.template_debug.x86_64.dll
Normal file
BIN
bin/~libgdexample.windows.template_debug.x86_64.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -47,3 +47,4 @@ if %ERRORLEVEL% EQU 0 (
|
|||||||
echo.
|
echo.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pause
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ extends Node2D
|
|||||||
@onready var editButton = $Edit/Button
|
@onready var editButton = $Edit/Button
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
|
var uiAnimSpeed = Global.main.physicsTimeMultiplier * 20.0 if Global.main else 120.0
|
||||||
if Rect2(editButton.get_parent().position-Vector2(24,24),editButton.size).has_point(get_local_mouse_position()):
|
if Rect2(editButton.get_parent().position-Vector2(24,24),editButton.size).has_point(get_local_mouse_position()):
|
||||||
editSprite.scale = lerp(editSprite.scale,Vector2(1.2,1.2),0.2)
|
editSprite.scale = lerp(editSprite.scale, Vector2(1.2,1.2), delta * uiAnimSpeed)
|
||||||
else:
|
else:
|
||||||
editSprite.scale = lerp(editSprite.scale,Vector2(1.0,1.0),0.2)
|
editSprite.scale = lerp(editSprite.scale, Vector2(1.0,1.0), delta * uiAnimSpeed)
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ func _process(delta):
|
|||||||
if buttons[b] == null:
|
if buttons[b] == null:
|
||||||
continue
|
continue
|
||||||
if Rect2(buttons[b].get_parent().position-Vector2(24,24),buttons[b].size).has_point(get_local_mouse_position()):
|
if Rect2(buttons[b].get_parent().position-Vector2(24,24),buttons[b].size).has_point(get_local_mouse_position()):
|
||||||
sprites[s].scale = lerp(sprites[s].scale,Vector2(1.2,1.2),0.2)
|
var uiAnimSpeed = Global.main.physicsTimeMultiplier * 20.0 if Global.main else 120.0
|
||||||
|
sprites[s].scale = lerp(sprites[s].scale, Vector2(1.2,1.2), delta * uiAnimSpeed)
|
||||||
if Input.is_action_pressed("mouse_left"):
|
if Input.is_action_pressed("mouse_left"):
|
||||||
sprites[s].scale = Vector2(0.6,0.6)
|
sprites[s].scale = Vector2(0.6,0.6)
|
||||||
match b:
|
match b:
|
||||||
@@ -50,7 +51,8 @@ func _process(delta):
|
|||||||
Global.mouse.text = "Duplicate sprite"
|
Global.mouse.text = "Duplicate sprite"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
sprites[s].scale = lerp(sprites[s].scale,Vector2(1.0,1.0),0.2)
|
var uiAnimSpeed = Global.main.physicsTimeMultiplier * 20.0 if Global.main else 120.0
|
||||||
|
sprites[s].scale = lerp(sprites[s].scale, Vector2(1.0,1.0), delta * uiAnimSpeed)
|
||||||
s += 1
|
s += 1
|
||||||
|
|
||||||
var newColor = Color.DARK_SLATE_GRAY if Global.heldSprite == null else Color.WHITE
|
var newColor = Color.DARK_SLATE_GRAY if Global.heldSprite == null else Color.WHITE
|
||||||
|
|||||||
@@ -30,13 +30,16 @@ var editMode = true
|
|||||||
@onready var spriteObject = preload("res://ui_scenes/selectedSprite/spriteObject.tscn")
|
@onready var spriteObject = preload("res://ui_scenes/selectedSprite/spriteObject.tscn")
|
||||||
|
|
||||||
var saveLoaded = false
|
var saveLoaded = false
|
||||||
var isSelectingMask = false
|
|
||||||
|
|
||||||
#Motion
|
#Motion
|
||||||
var yVel = 0
|
var yVel = 0
|
||||||
var bounceSlider = 250
|
var bounceSlider = 250
|
||||||
var bounceGravity = 1000
|
var bounceGravity = 1000
|
||||||
|
|
||||||
|
# Physics timing multiplier - adjust this to fine-tune physics speed
|
||||||
|
# 1.0 = very slow, 10.0 = reasonable, 60.0 = old frame-based speed
|
||||||
|
var physicsTimeMultiplier = 6.0
|
||||||
|
|
||||||
#Costumes
|
#Costumes
|
||||||
var costume = 1
|
var costume = 1
|
||||||
var bounceOnCostumeChange = false
|
var bounceOnCostumeChange = false
|
||||||
@@ -44,6 +47,18 @@ var bounceOnCostumeChange = false
|
|||||||
#Zooming
|
#Zooming
|
||||||
var scaleOverall = 100
|
var scaleOverall = 100
|
||||||
|
|
||||||
|
#Camera Panning
|
||||||
|
var isPanning = false
|
||||||
|
var initialPanPosition = Vector2.ZERO
|
||||||
|
var initialCameraOffset = Vector2.ZERO
|
||||||
|
var cameraOffset = Vector2.ZERO
|
||||||
|
|
||||||
|
#View Mode Panning
|
||||||
|
var viewModePanning = false
|
||||||
|
var initialViewPanPosition = Vector2.ZERO
|
||||||
|
var viewPanOffset = Vector2.ZERO
|
||||||
|
var viewPanLerpSpeed = 3.0
|
||||||
|
|
||||||
var bounceChange = 0.0
|
var bounceChange = 0.0
|
||||||
|
|
||||||
#IMPORTANT
|
#IMPORTANT
|
||||||
@@ -63,16 +78,27 @@ func _ready():
|
|||||||
|
|
||||||
Global.connect("startSpeaking",onSpeak)
|
Global.connect("startSpeaking",onSpeak)
|
||||||
|
|
||||||
|
# Connect to StreamDeck if available
|
||||||
var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck")
|
var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck")
|
||||||
if streamdeck_node:
|
if streamdeck_node != null:
|
||||||
streamdeck_node.on_key_down.connect(changeCostumeStreamDeck)
|
streamdeck_node.on_key_down.connect(changeCostumeStreamDeck)
|
||||||
|
|
||||||
if Saving.settings["newUser"]:
|
if Saving.settings["newUser"]:
|
||||||
|
print("DEBUG: MAIN._ready() - New user detected, loading default avatar")
|
||||||
_on_load_dialog_file_selected("default")
|
_on_load_dialog_file_selected("default")
|
||||||
Saving.settings["newUser"] = false
|
Saving.settings["newUser"] = false
|
||||||
saveLoaded = true
|
saveLoaded = true
|
||||||
else:
|
else:
|
||||||
_on_load_dialog_file_selected(Saving.settings["lastAvatar"])
|
print("DEBUG: MAIN._ready() - Existing user, loading last avatar")
|
||||||
|
var lastAvatar = Saving.settings["lastAvatar"]
|
||||||
|
print("DEBUG: lastAvatar setting: ", lastAvatar, " (type: ", typeof(lastAvatar), ")")
|
||||||
|
# Ensure lastAvatar is a string, not a Dictionary
|
||||||
|
if typeof(lastAvatar) == TYPE_STRING and lastAvatar != "":
|
||||||
|
print("DEBUG: Loading avatar from: ", lastAvatar)
|
||||||
|
_on_load_dialog_file_selected(lastAvatar)
|
||||||
|
else:
|
||||||
|
print("WARNING: lastAvatar setting is invalid, loading default instead")
|
||||||
|
_on_load_dialog_file_selected("default")
|
||||||
|
|
||||||
$ControlPanel/volumeSlider.value = Saving.settings["volume"]
|
$ControlPanel/volumeSlider.value = Saving.settings["volume"]
|
||||||
$ControlPanel/sensitiveSlider.value = Saving.settings["sense"]
|
$ControlPanel/sensitiveSlider.value = Saving.settings["sense"]
|
||||||
@@ -133,43 +159,96 @@ func _ready():
|
|||||||
|
|
||||||
var s = get_viewport().get_visible_rect().size
|
var s = get_viewport().get_visible_rect().size
|
||||||
origin.position = s*0.5
|
origin.position = s*0.5
|
||||||
camera.position = origin.position
|
updateCameraPosition()
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
var hold = origin.get_parent().position.y
|
var hold = origin.get_parent().position.y
|
||||||
|
|
||||||
origin.get_parent().position.y += yVel * 0.0166
|
origin.get_parent().position.y += yVel * delta * physicsTimeMultiplier
|
||||||
if origin.get_parent().position.y > 0:
|
if origin.get_parent().position.y > 0:
|
||||||
origin.get_parent().position.y = 0
|
origin.get_parent().position.y = 0
|
||||||
bounceChange = hold - origin.get_parent().position.y
|
bounceChange = hold - origin.get_parent().position.y
|
||||||
|
|
||||||
yVel += bounceGravity*0.0166
|
yVel += bounceGravity * delta * physicsTimeMultiplier
|
||||||
|
|
||||||
|
# Handle view mode panning lerp back to center
|
||||||
|
if !editMode:
|
||||||
|
if !viewModePanning:
|
||||||
|
# Lerp back to center when not actively panning
|
||||||
|
viewPanOffset = viewPanOffset.lerp(Vector2.ZERO, viewPanLerpSpeed * delta)
|
||||||
|
|
||||||
|
# Apply view pan offset to the root character (affects physics)
|
||||||
|
origin.position = (get_viewport().get_visible_rect().size * 0.5) + viewPanOffset
|
||||||
|
updateCameraPosition()
|
||||||
|
|
||||||
if Input.is_action_just_pressed("openFolder"):
|
if Input.is_action_just_pressed("openFolder"):
|
||||||
OS.shell_open(ProjectSettings.globalize_path("user://"))
|
OS.shell_open(ProjectSettings.globalize_path("user://"))
|
||||||
|
|
||||||
moveSpriteMenu(delta)
|
moveSpriteMenu(delta)
|
||||||
zoomScene()
|
zoomScene(delta)
|
||||||
|
|
||||||
fileSystemOpen = isFileSystemOpen()
|
fileSystemOpen = isFileSystemOpen()
|
||||||
|
|
||||||
followShadow()
|
followShadow()
|
||||||
|
|
||||||
func followShadow():
|
## Handle input events, especially for camera panning
|
||||||
shadow.visible = false # Disabled drop shadow
|
func _input(event):
|
||||||
return
|
# Handle middle mouse button for camera panning/reset
|
||||||
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_MIDDLE:
|
||||||
|
if editMode:
|
||||||
|
if event.pressed:
|
||||||
|
# Start panning in edit mode
|
||||||
|
isPanning = true
|
||||||
|
initialPanPosition = event.global_position
|
||||||
|
initialCameraOffset = cameraOffset
|
||||||
|
else:
|
||||||
|
# Stop panning
|
||||||
|
isPanning = false
|
||||||
|
else:
|
||||||
|
if event.pressed:
|
||||||
|
# Start view mode panning
|
||||||
|
viewModePanning = true
|
||||||
|
initialViewPanPosition = event.global_position
|
||||||
|
else:
|
||||||
|
# Stop view mode panning
|
||||||
|
viewModePanning = false
|
||||||
|
|
||||||
shadow.visible = is_instance_valid(Global.heldSprite)
|
# Handle mouse movement during edit mode panning
|
||||||
if !shadow.visible:
|
elif editMode and event is InputEventMouseMotion and isPanning:
|
||||||
return
|
var totalDelta = event.global_position - initialPanPosition
|
||||||
|
|
||||||
shadow.global_position = Global.heldSprite.sprite.global_position + Vector2(6,6)
|
|
||||||
shadow.global_rotation = Global.heldSprite.sprite.global_rotation
|
|
||||||
shadow.offset = Global.heldSprite.sprite.offset
|
|
||||||
|
|
||||||
shadow.texture = Global.heldSprite.sprite.texture
|
# Scale pan distance based on zoom level for more intuitive panning
|
||||||
shadow.hframes = Global.heldSprite.sprite.hframes
|
# Higher zoom (closer) = less movement, lower zoom (farther) = more movement
|
||||||
shadow.frame = Global.heldSprite.sprite.frame
|
var zoomScale = 1.0 / camera.zoom.x
|
||||||
|
var scaledDelta = totalDelta * zoomScale
|
||||||
|
|
||||||
|
# Calculate new camera offset from initial position plus scaled movement
|
||||||
|
cameraOffset = initialCameraOffset - scaledDelta
|
||||||
|
|
||||||
|
# Update camera position immediately
|
||||||
|
var s = get_viewport().get_visible_rect().size
|
||||||
|
camera.position = origin.position + cameraOffset
|
||||||
|
|
||||||
|
# Update UI positions
|
||||||
|
controlPanel.position = camera.position + (s/(camera.zoom*2.0))
|
||||||
|
tutorial.position = controlPanel.position
|
||||||
|
editControls.position = camera.position - (s/(camera.zoom*2.0))
|
||||||
|
viewerArrows.position = editControls.position
|
||||||
|
pushUpdates.position.y = controlPanel.position.y
|
||||||
|
pushUpdates.position.x = editControls.position.x
|
||||||
|
|
||||||
|
# Handle mouse movement during view mode panning
|
||||||
|
elif !editMode and event is InputEventMouseMotion and viewModePanning:
|
||||||
|
var totalDelta = event.global_position - initialViewPanPosition
|
||||||
|
|
||||||
|
# Scale pan distance for view mode (less sensitive than edit mode)
|
||||||
|
# Invert the movement so dragging right moves character right (not left)
|
||||||
|
var panSensitivity = 0.5
|
||||||
|
viewPanOffset = -totalDelta * panSensitivity
|
||||||
|
|
||||||
|
func followShadow():
|
||||||
|
# Shadow disabled for selected sprites
|
||||||
|
shadow.visible = false
|
||||||
|
|
||||||
|
|
||||||
func isFileSystemOpen():
|
func isFileSystemOpen():
|
||||||
@@ -177,11 +256,13 @@ func isFileSystemOpen():
|
|||||||
if obj.visible:
|
if obj.visible:
|
||||||
if obj == replaceDialog:
|
if obj == replaceDialog:
|
||||||
return true
|
return true
|
||||||
# Don't clear held sprite when selecting mask
|
|
||||||
if obj == fileDialog and isSelectingMask:
|
|
||||||
return true
|
|
||||||
Global.heldSprite = null
|
Global.heldSprite = null
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
# Check wobble sync group dialog
|
||||||
|
if Global.spriteEdit and Global.spriteEdit.wobbleSyncControl and Global.spriteEdit.wobbleSyncControl.createGroupDialog and Global.spriteEdit.wobbleSyncControl.createGroupDialog.visible:
|
||||||
|
return true
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
#Displays control panel whether or not application is focused
|
#Displays control panel whether or not application is focused
|
||||||
@@ -207,16 +288,10 @@ func onWindowSizeChange():
|
|||||||
lines.position = s*0.5
|
lines.position = s*0.5
|
||||||
lines.drawLine()
|
lines.drawLine()
|
||||||
|
|
||||||
camera.position = origin.position
|
updateCameraPosition()
|
||||||
controlPanel.position = camera.position + (s/(camera.zoom*2.0))
|
|
||||||
tutorial.position = controlPanel.position
|
|
||||||
editControls.position = camera.position - (s/(camera.zoom*2.0))
|
|
||||||
viewerArrows.position = editControls.position
|
|
||||||
spriteList.position.x = s.x - 233
|
spriteList.position.x = s.x - 233
|
||||||
pushUpdates.position.y = controlPanel.position.y
|
|
||||||
pushUpdates.position.x = editControls.position.x
|
|
||||||
|
|
||||||
func zoomScene():
|
func zoomScene(delta):
|
||||||
#Handles Zooming
|
#Handles Zooming
|
||||||
if Input.is_action_pressed("control"):
|
if Input.is_action_pressed("control"):
|
||||||
if Input.is_action_just_pressed("scrollUp"):
|
if Input.is_action_just_pressed("scrollUp"):
|
||||||
@@ -230,7 +305,7 @@ func zoomScene():
|
|||||||
scaleOverall -= 10
|
scaleOverall -= 10
|
||||||
changeZoom()
|
changeZoom()
|
||||||
|
|
||||||
$ControlPanel/ZoomLabel.modulate.a = lerp($ControlPanel/ZoomLabel.modulate.a,0.0,0.02)
|
$ControlPanel/ZoomLabel.modulate.a = lerp($ControlPanel/ZoomLabel.modulate.a, 0.0, delta * (physicsTimeMultiplier * 1.2))
|
||||||
|
|
||||||
func changeZoom():
|
func changeZoom():
|
||||||
var newZoom = Vector2(1.0,1.0) / camera.zoom
|
var newZoom = Vector2(1.0,1.0) / camera.zoom
|
||||||
@@ -242,11 +317,45 @@ func changeZoom():
|
|||||||
pushUpdates.scale = newZoom
|
pushUpdates.scale = newZoom
|
||||||
Global.mouse.scale = newZoom
|
Global.mouse.scale = newZoom
|
||||||
|
|
||||||
|
# Update UI positions to account for new zoom level
|
||||||
|
# Force update even during panning since zoom affects UI positioning calculations
|
||||||
|
var s = get_viewport().get_visible_rect().size
|
||||||
|
controlPanel.position = camera.position + (s/(camera.zoom*2.0))
|
||||||
|
tutorial.position = controlPanel.position
|
||||||
|
editControls.position = camera.position - (s/(camera.zoom*2.0))
|
||||||
|
viewerArrows.position = editControls.position
|
||||||
|
pushUpdates.position.y = controlPanel.position.y
|
||||||
|
pushUpdates.position.x = editControls.position.x
|
||||||
|
|
||||||
$ControlPanel/ZoomLabel.modulate.a = 6.0
|
$ControlPanel/ZoomLabel.modulate.a = 6.0
|
||||||
$ControlPanel/ZoomLabel.text = "Zoom : " + str(scaleOverall) + "%"
|
$ControlPanel/ZoomLabel.text = "Zoom : " + str(scaleOverall) + "%"
|
||||||
|
|
||||||
Global.pushUpdate("Set zoom to " + str(scaleOverall) + "%")
|
Global.pushUpdate("Set zoom to " + str(scaleOverall) + "%")
|
||||||
onWindowSizeChange()
|
|
||||||
|
## Update camera position with current offset
|
||||||
|
func updateCameraPosition():
|
||||||
|
var s = get_viewport().get_visible_rect().size
|
||||||
|
|
||||||
|
# Only update if not currently panning to avoid conflicts
|
||||||
|
if !isPanning:
|
||||||
|
camera.position = origin.position + cameraOffset
|
||||||
|
|
||||||
|
# Update UI element positions relative to new camera position
|
||||||
|
controlPanel.position = camera.position + (s/(camera.zoom*2.0))
|
||||||
|
tutorial.position = controlPanel.position
|
||||||
|
editControls.position = camera.position - (s/(camera.zoom*2.0))
|
||||||
|
viewerArrows.position = editControls.position
|
||||||
|
pushUpdates.position.y = controlPanel.position.y
|
||||||
|
pushUpdates.position.x = editControls.position.x
|
||||||
|
|
||||||
|
## Reset camera position to center
|
||||||
|
func resetCameraPosition():
|
||||||
|
cameraOffset = Vector2.ZERO
|
||||||
|
isPanning = false
|
||||||
|
viewPanOffset = Vector2.ZERO
|
||||||
|
viewModePanning = false
|
||||||
|
updateCameraPosition()
|
||||||
|
Global.pushUpdate("Camera reset to center.")
|
||||||
|
|
||||||
#When the user speaks!
|
#When the user speaks!
|
||||||
func onSpeak():
|
func onSpeak():
|
||||||
@@ -261,6 +370,14 @@ func swapMode():
|
|||||||
editMode = !editMode
|
editMode = !editMode
|
||||||
Global.pushUpdate("Toggled editing mode.")
|
Global.pushUpdate("Toggled editing mode.")
|
||||||
|
|
||||||
|
# Reset camera when entering view mode
|
||||||
|
if !editMode:
|
||||||
|
resetCameraPosition()
|
||||||
|
else:
|
||||||
|
# Reset view mode panning when entering edit mode
|
||||||
|
viewPanOffset = Vector2.ZERO
|
||||||
|
viewModePanning = false
|
||||||
|
|
||||||
get_viewport().transparent_bg = !editMode
|
get_viewport().transparent_bg = !editMode
|
||||||
if Global.backgroundColor.a != 0.0:
|
if Global.backgroundColor.a != 0.0:
|
||||||
get_viewport().transparent_bg = false
|
get_viewport().transparent_bg = false
|
||||||
@@ -297,20 +414,7 @@ func _on_add_button_pressed():
|
|||||||
|
|
||||||
#Runs when selecting image in File Dialog
|
#Runs when selecting image in File Dialog
|
||||||
func _on_file_dialog_file_selected(path):
|
func _on_file_dialog_file_selected(path):
|
||||||
if isSelectingMask:
|
add_image(path)
|
||||||
# Handle mask selection
|
|
||||||
if Global.heldSprite != null:
|
|
||||||
Global.heldSprite.setMaskTexture(path)
|
|
||||||
Global.spriteEdit.updateMaskDisplay(path)
|
|
||||||
isSelectingMask = false
|
|
||||||
else:
|
|
||||||
# Handle normal sprite addition
|
|
||||||
add_image(path)
|
|
||||||
|
|
||||||
#Opens File Dialog for mask selection
|
|
||||||
func openMaskSelection():
|
|
||||||
isSelectingMask = true
|
|
||||||
fileDialog.visible = true
|
|
||||||
|
|
||||||
func _on_save_button_pressed():
|
func _on_save_button_pressed():
|
||||||
$SaveDialog.visible = true
|
$SaveDialog.visible = true
|
||||||
@@ -321,17 +425,52 @@ func _on_load_button_pressed():
|
|||||||
|
|
||||||
#LOAD AVATAR
|
#LOAD AVATAR
|
||||||
func _on_load_dialog_file_selected(path):
|
func _on_load_dialog_file_selected(path):
|
||||||
|
print("DEBUG: _on_load_dialog_file_selected called with path: ", path, " (type: ", typeof(path), ")")
|
||||||
|
|
||||||
|
# Handle case where path might be a Dictionary instead of string
|
||||||
|
if typeof(path) == TYPE_DICTIONARY:
|
||||||
|
print("ERROR: Expected string path but got Dictionary: ", path)
|
||||||
|
return
|
||||||
|
|
||||||
|
print("DEBUG: About to read save data from: ", path)
|
||||||
var data = Saving.read_save(path)
|
var data = Saving.read_save(path)
|
||||||
|
print("DEBUG: Save data read. Data type: ", typeof(data), ", null check: ", data == null)
|
||||||
|
|
||||||
if data == null:
|
if data == null:
|
||||||
|
print("DEBUG: Save data is null, returning")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
print("DEBUG: Save data keys: ", data.keys() if typeof(data) == TYPE_DICTIONARY else "not a dictionary")
|
||||||
|
print("DEBUG: Save data size: ", data.size() if typeof(data) == TYPE_DICTIONARY else "not a dictionary")
|
||||||
|
|
||||||
|
# Check the structure of the first few items
|
||||||
|
if typeof(data) == TYPE_DICTIONARY:
|
||||||
|
var count = 0
|
||||||
|
for key in data.keys():
|
||||||
|
if count < 3: # Just show first 3 items for debugging
|
||||||
|
print("DEBUG: data[", key, "] = ", data[key])
|
||||||
|
print("DEBUG: data[", key, "] type = ", typeof(data[key]))
|
||||||
|
if typeof(data[key]) == TYPE_DICTIONARY and data[key].has("path"):
|
||||||
|
print("DEBUG: data[", key, "][\"path\"] = ", data[key]["path"])
|
||||||
|
else:
|
||||||
|
print("DEBUG: data[", key, "] doesn't have 'path' key or isn't a dictionary")
|
||||||
|
count += 1
|
||||||
|
|
||||||
origin.queue_free()
|
origin.queue_free()
|
||||||
var new = Node2D.new()
|
var new = Node2D.new()
|
||||||
$OriginMotion.add_child(new)
|
$OriginMotion.add_child(new)
|
||||||
origin = new
|
origin = new
|
||||||
|
|
||||||
for item in data:
|
for item in data:
|
||||||
|
print("DEBUG: Processing item: ", item, ", data type: ", typeof(data[item]))
|
||||||
|
if typeof(data[item]) != TYPE_DICTIONARY:
|
||||||
|
print("DEBUG: Skipping item ", item, " - not a dictionary")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not data[item].has("path"):
|
||||||
|
print("DEBUG: Skipping item ", item, " - no 'path' key. Keys: ", data[item].keys())
|
||||||
|
continue
|
||||||
|
|
||||||
var sprite = spriteObject.instantiate()
|
var sprite = spriteObject.instantiate()
|
||||||
sprite.path = data[item]["path"]
|
sprite.path = data[item]["path"]
|
||||||
sprite.id = data[item]["identification"]
|
sprite.id = data[item]["identification"]
|
||||||
@@ -384,14 +523,14 @@ func _on_load_dialog_file_selected(path):
|
|||||||
sprite.clipped = data[item]["clipped"]
|
sprite.clipped = data[item]["clipped"]
|
||||||
if data[item].has("toggle"):
|
if data[item].has("toggle"):
|
||||||
sprite.toggle = data[item]["toggle"]
|
sprite.toggle = data[item]["toggle"]
|
||||||
|
if data[item].has("spriteOpacity"):
|
||||||
|
sprite.spriteOpacity = data[item]["spriteOpacity"]
|
||||||
|
if data[item].has("affectChildrenOpacity"):
|
||||||
|
sprite.affectChildrenOpacity = data[item]["affectChildrenOpacity"]
|
||||||
|
|
||||||
# Load mask properties
|
# Load wobble sync group (with backward compatibility)
|
||||||
if data[item].has("mask_texture_path"):
|
if data[item].has("wobbleSyncGroup"):
|
||||||
sprite.mask_texture_path = data[item]["mask_texture_path"]
|
sprite.wobbleSyncGroup = data[item]["wobbleSyncGroup"]
|
||||||
if data[item].has("use_mask"):
|
|
||||||
sprite.use_mask = data[item]["use_mask"]
|
|
||||||
if data[item].has("mask_opacity"):
|
|
||||||
sprite.mask_opacity = data[item]["mask_opacity"]
|
|
||||||
|
|
||||||
origin.add_child(sprite)
|
origin.add_child(sprite)
|
||||||
sprite.position = str_to_var(data[item]["pos"])
|
sprite.position = str_to_var(data[item]["pos"])
|
||||||
@@ -400,6 +539,49 @@ func _on_load_dialog_file_selected(path):
|
|||||||
Saving.settings["lastAvatar"] = path
|
Saving.settings["lastAvatar"] = path
|
||||||
Global.spriteList.updateData()
|
Global.spriteList.updateData()
|
||||||
|
|
||||||
|
# Load wobble sync groups
|
||||||
|
print("DEBUG: About to check for wobble sync groups...")
|
||||||
|
print("DEBUG: data type: ", typeof(data), ", data.keys(): ", data.keys() if typeof(data) == TYPE_DICTIONARY else "not a dictionary")
|
||||||
|
print("DEBUG: data.has('wobbleSyncGroups'): ", data.has("wobbleSyncGroups"))
|
||||||
|
print("DEBUG: WobbleSyncManager exists: ", WobbleSyncManager != null)
|
||||||
|
if WobbleSyncManager != null:
|
||||||
|
print("DEBUG: WobbleSyncManager type: ", typeof(WobbleSyncManager))
|
||||||
|
|
||||||
|
# Show save data content related to wobble sync
|
||||||
|
if data.has("wobbleSyncGroups"):
|
||||||
|
print("DEBUG: wobbleSyncGroups data found: ", data["wobbleSyncGroups"])
|
||||||
|
else:
|
||||||
|
print("DEBUG: No wobbleSyncGroups found in save data")
|
||||||
|
|
||||||
|
if data.has("wobbleSyncGroups") and WobbleSyncManager:
|
||||||
|
print("DEBUG: Loading wobble sync groups. Data: ", data["wobbleSyncGroups"])
|
||||||
|
await WobbleSyncManager.loadSaveData(data["wobbleSyncGroups"])
|
||||||
|
print("DEBUG: Finished loading wobble sync groups")
|
||||||
|
# Refresh wobble sync UI after groups are loaded
|
||||||
|
if Global.spriteEdit and Global.spriteEdit.wobbleSyncControl:
|
||||||
|
Global.spriteEdit.wobbleSyncControl.updateUI()
|
||||||
|
# Force refresh in case timing issues
|
||||||
|
await get_tree().process_frame
|
||||||
|
Global.spriteEdit.wobbleSyncControl.forceRefresh()
|
||||||
|
# Force refresh in case timing issues
|
||||||
|
await get_tree().process_frame
|
||||||
|
Global.spriteEdit.wobbleSyncControl.forceRefresh()
|
||||||
|
else:
|
||||||
|
print("DEBUG: Not loading wobble sync groups - missing data or manager")
|
||||||
|
|
||||||
|
# Final debug check
|
||||||
|
if WobbleSyncManager:
|
||||||
|
print("DEBUG: Final check - WobbleSyncManager has ", WobbleSyncManager.getGroupNames().size(), " groups: ", WobbleSyncManager.getGroupNames())
|
||||||
|
else:
|
||||||
|
print("DEBUG: No wobble sync groups to load. has key: ", data.has("wobbleSyncGroups"), ", WobbleSyncManager: ", WobbleSyncManager != null)
|
||||||
|
|
||||||
|
# Update opacity shaders for all loaded sprites after wobble sync data is applied
|
||||||
|
await get_tree().process_frame # Wait for all sprites to be fully initialized
|
||||||
|
var allSprites = get_tree().get_nodes_in_group("saved")
|
||||||
|
for sprite in allSprites:
|
||||||
|
if sprite.has_method("updateOpacity"):
|
||||||
|
sprite.updateOpacity()
|
||||||
|
|
||||||
Global.pushUpdate("Loaded avatar at: " + path)
|
Global.pushUpdate("Loaded avatar at: " + path)
|
||||||
|
|
||||||
onWindowSizeChange()
|
onWindowSizeChange()
|
||||||
@@ -454,13 +636,18 @@ func _on_save_dialog_file_selected(path):
|
|||||||
|
|
||||||
data[id]["toggle"] = child.toggle
|
data[id]["toggle"] = child.toggle
|
||||||
|
|
||||||
# Save mask properties
|
data[id]["spriteOpacity"] = child.spriteOpacity
|
||||||
data[id]["mask_texture_path"] = child.mask_texture_path
|
data[id]["affectChildrenOpacity"] = child.affectChildrenOpacity
|
||||||
data[id]["use_mask"] = child.use_mask
|
|
||||||
data[id]["mask_opacity"] = child.mask_opacity
|
# Save wobble sync group
|
||||||
|
data[id]["wobbleSyncGroup"] = child.wobbleSyncGroup
|
||||||
|
|
||||||
id += 1
|
id += 1
|
||||||
|
|
||||||
|
# Save wobble sync groups
|
||||||
|
if WobbleSyncManager:
|
||||||
|
data["wobbleSyncGroups"] = WobbleSyncManager.getSaveData()
|
||||||
|
|
||||||
Saving.settings["lastAvatar"] = path
|
Saving.settings["lastAvatar"] = path
|
||||||
|
|
||||||
Saving.data = data.duplicate()
|
Saving.data = data.duplicate()
|
||||||
@@ -535,9 +722,19 @@ func _on_duplicate_button_pressed():
|
|||||||
|
|
||||||
sprite.costumeLayers = Global.heldSprite.costumeLayers
|
sprite.costumeLayers = Global.heldSprite.costumeLayers
|
||||||
|
|
||||||
|
sprite.spriteOpacity = Global.heldSprite.spriteOpacity
|
||||||
|
sprite.affectChildrenOpacity = Global.heldSprite.affectChildrenOpacity
|
||||||
|
|
||||||
|
# Copy wobble sync group
|
||||||
|
sprite.wobbleSyncGroup = Global.heldSprite.wobbleSyncGroup
|
||||||
|
|
||||||
origin.add_child(sprite)
|
origin.add_child(sprite)
|
||||||
sprite.position = Global.heldSprite.position + Vector2(16,16)
|
sprite.position = Global.heldSprite.position + Vector2(16,16)
|
||||||
|
|
||||||
|
# Update opacity shader for the duplicated sprite
|
||||||
|
await get_tree().process_frame # Wait for sprite to be fully initialized
|
||||||
|
sprite.updateOpacity()
|
||||||
|
|
||||||
Global.heldSprite = sprite
|
Global.heldSprite = sprite
|
||||||
|
|
||||||
Global.spriteList.updateData()
|
Global.spriteList.updateData()
|
||||||
@@ -610,9 +807,9 @@ func moveSpriteMenu(delta):
|
|||||||
|
|
||||||
|
|
||||||
if $EditControls/MoveMenuUp.overlaps_area(Global.mouse.area):
|
if $EditControls/MoveMenuUp.overlaps_area(Global.mouse.area):
|
||||||
Global.spriteEdit.position.y += (delta*432.0)
|
Global.spriteEdit.position.y += (delta * 400.0)
|
||||||
elif $EditControls/MoveMenuDown.overlaps_area(Global.mouse.area):
|
elif $EditControls/MoveMenuDown.overlaps_area(Global.mouse.area):
|
||||||
Global.spriteEdit.position.y -= (delta*432.0)
|
Global.spriteEdit.position.y -= (delta * 400.0)
|
||||||
|
|
||||||
if Global.spriteEdit.position.y > 66:
|
if Global.spriteEdit.position.y > 66:
|
||||||
Global.spriteEdit.position.y = 66
|
Global.spriteEdit.position.y = 66
|
||||||
@@ -639,7 +836,7 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed):
|
|||||||
var has_ctrl = keys_pressed.has(KEY_CTRL) and keys_pressed[KEY_CTRL]
|
var has_ctrl = keys_pressed.has(KEY_CTRL) and keys_pressed[KEY_CTRL]
|
||||||
var has_shift = keys_pressed.has(KEY_SHIFT) and keys_pressed[KEY_SHIFT]
|
var has_shift = keys_pressed.has(KEY_SHIFT) and keys_pressed[KEY_SHIFT]
|
||||||
var has_alt = keys_pressed.has(KEY_ALT) and keys_pressed[KEY_ALT]
|
var has_alt = keys_pressed.has(KEY_ALT) and keys_pressed[KEY_ALT]
|
||||||
var meta_pressed = keys_pressed.has(KEY_META) and keys_pressed[KEY_META] # Command key on macOS
|
var has_meta = keys_pressed.has(KEY_META) and keys_pressed[KEY_META] # Command key on macOS
|
||||||
|
|
||||||
# Build modifier prefix once
|
# Build modifier prefix once
|
||||||
if has_ctrl:
|
if has_ctrl:
|
||||||
@@ -648,7 +845,7 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed):
|
|||||||
modifiers.append("Shift")
|
modifiers.append("Shift")
|
||||||
if has_alt:
|
if has_alt:
|
||||||
modifiers.append("Alt")
|
modifiers.append("Alt")
|
||||||
if meta_pressed:
|
if has_meta:
|
||||||
modifiers.append("Cmd") # Command key on macOS
|
modifiers.append("Cmd") # Command key on macOS
|
||||||
|
|
||||||
var modifier_prefix = ""
|
var modifier_prefix = ""
|
||||||
@@ -662,14 +859,27 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed):
|
|||||||
if i == KEY_CTRL or i == KEY_SHIFT or i == KEY_ALT or i == KEY_META:
|
if i == KEY_CTRL or i == KEY_SHIFT or i == KEY_ALT or i == KEY_META:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
var key_name = OS.get_keycode_string(i) if !OS.get_keycode_string(i).strip_edges().is_empty() else "Keycode" + str(i)
|
# Skip mouse button keycodes (common source of phantom inputs)
|
||||||
keyStrings.append(modifier_prefix + key_name)
|
if i == MOUSE_BUTTON_LEFT or i == MOUSE_BUTTON_RIGHT or i == MOUSE_BUTTON_MIDDLE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip phantom keypad inputs if no actual keypad connected
|
||||||
|
var key_name = OS.get_keycode_string(i)
|
||||||
|
if key_name.begins_with("kp ") or key_name.begins_with("Kp "):
|
||||||
|
# Skip keypad inputs unless they're from actual key presses
|
||||||
|
continue
|
||||||
|
|
||||||
|
if !key_name.strip_edges().is_empty():
|
||||||
|
keyStrings.append(modifier_prefix + key_name)
|
||||||
|
else:
|
||||||
|
keyStrings.append(modifier_prefix + "Keycode" + str(i))
|
||||||
|
|
||||||
if fileSystemOpen:
|
if fileSystemOpen:
|
||||||
return
|
return
|
||||||
|
|
||||||
if keyStrings.size() <= 0:
|
if keyStrings.size() <= 0:
|
||||||
emit_signal("emptiedCapture")
|
emit_signal("emptiedCapture")
|
||||||
|
emit_signal("fatfuckingballs")
|
||||||
return
|
return
|
||||||
|
|
||||||
if settingsMenu.awaitingCostumeInput >= 0:
|
if settingsMenu.awaitingCostumeInput >= 0:
|
||||||
@@ -685,25 +895,13 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed):
|
|||||||
Saving.settings["costumeKeys"] = costumeKeys
|
Saving.settings["costumeKeys"] = costumeKeys
|
||||||
Global.pushUpdate("Changed costume " + str(settingsMenu.awaitingCostumeInput+1) + " hotkey from \"" + currentButton + "\" to \"" + keyStrings[0] + "\"")
|
Global.pushUpdate("Changed costume " + str(settingsMenu.awaitingCostumeInput+1) + " hotkey from \"" + currentButton + "\" to \"" + keyStrings[0] + "\"")
|
||||||
emit_signal("pressedKey")
|
emit_signal("pressedKey")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle sprite visibility toggles if not waiting for costume input
|
||||||
|
spriteVisToggles.emit(keyStrings)
|
||||||
|
|
||||||
for key in keyStrings:
|
for key in keyStrings:
|
||||||
var i = costumeKeys.find(key)
|
var i = costumeKeys.find(key)
|
||||||
if i >= 0:
|
if i >= 0:
|
||||||
changeCostume(i+1)
|
changeCostume(i+1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func bgInputSprite(node, keys_pressed):
|
|
||||||
if fileSystemOpen:
|
|
||||||
return
|
|
||||||
var keyStrings = []
|
|
||||||
|
|
||||||
for i in keys_pressed:
|
|
||||||
if keys_pressed[i]:
|
|
||||||
keyStrings.append(OS.get_keycode_string(i) if !OS.get_keycode_string(i).strip_edges().is_empty() else "Keycode" + str(i))
|
|
||||||
|
|
||||||
if keyStrings.size() <= 0:
|
|
||||||
emit_signal("fatfuckingballs")
|
|
||||||
return
|
|
||||||
|
|
||||||
spriteVisToggles.emit(keyStrings)
|
|
||||||
|
|||||||
@@ -14358,6 +14358,7 @@ text = "Zoom : 100%"
|
|||||||
horizontal_alignment = 2
|
horizontal_alignment = 2
|
||||||
|
|
||||||
[node name="VersionLabels" type="Node2D" parent="ControlPanel"]
|
[node name="VersionLabels" type="Node2D" parent="ControlPanel"]
|
||||||
|
position = Vector2(-284, 59)
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="ControlPanel/VersionLabels"]
|
[node name="Label" type="Label" parent="ControlPanel/VersionLabels"]
|
||||||
offset_left = -713.0
|
offset_left = -713.0
|
||||||
@@ -14377,17 +14378,39 @@ label_settings = SubResource("LabelSettings_qg0do")
|
|||||||
|
|
||||||
[node name="versionNo" type="Label" parent="ControlPanel/VersionLabels/Label2"]
|
[node name="versionNo" type="Label" parent="ControlPanel/VersionLabels/Label2"]
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
offset_left = 144.0
|
offset_left = 104.0
|
||||||
offset_top = 40.0
|
offset_top = 11.0
|
||||||
offset_right = 282.0
|
offset_right = 242.0
|
||||||
offset_bottom = 74.0
|
offset_bottom = 54.0
|
||||||
text = "v1.4.5"
|
text = "v1.4.5"
|
||||||
label_settings = SubResource("LabelSettings_xvf50")
|
label_settings = SubResource("LabelSettings_xvf50")
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="ControlPanel/VersionLabels"]
|
||||||
|
offset_left = -566.0
|
||||||
|
offset_top = -93.0
|
||||||
|
offset_right = -428.0
|
||||||
|
offset_bottom = -59.0
|
||||||
|
text = "Litruv Edition"
|
||||||
|
label_settings = SubResource("LabelSettings_qg0do")
|
||||||
|
|
||||||
[node name="EditControls" type="Node2D" parent="."]
|
[node name="EditControls" type="Node2D" parent="."]
|
||||||
z_index = 4090
|
z_index = 4090
|
||||||
script = ExtResource("17_bf1ic")
|
script = ExtResource("17_bf1ic")
|
||||||
|
|
||||||
|
[node name="Container" type="Container" parent="EditControls"]
|
||||||
|
clip_contents = true
|
||||||
|
anchors_preset = 9
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_top = 70.0
|
||||||
|
offset_right = 250.0
|
||||||
|
offset_bottom = 20070.0
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="SpriteViewer" parent="EditControls/Container" instance=ExtResource("9_uqv8p")]
|
||||||
|
z_index = 4094
|
||||||
|
position = Vector2(9, 5)
|
||||||
|
|
||||||
[node name="Add" type="Sprite2D" parent="EditControls"]
|
[node name="Add" type="Sprite2D" parent="EditControls"]
|
||||||
position = Vector2(104, 32)
|
position = Vector2(104, 32)
|
||||||
texture = ExtResource("4_xenui")
|
texture = ExtResource("4_xenui")
|
||||||
@@ -14549,11 +14572,6 @@ position = Vector2(2132, 928.5)
|
|||||||
shape = SubResource("RectangleShape2D_ebmai")
|
shape = SubResource("RectangleShape2D_ebmai")
|
||||||
disabled = true
|
disabled = true
|
||||||
|
|
||||||
[node name="SpriteViewer" parent="EditControls" instance=ExtResource("9_uqv8p")]
|
|
||||||
visible = false
|
|
||||||
z_index = 4094
|
|
||||||
position = Vector2(9, 66)
|
|
||||||
|
|
||||||
[node name="MoveMenuUp" type="Area2D" parent="EditControls"]
|
[node name="MoveMenuUp" type="Area2D" parent="EditControls"]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 2048
|
collision_mask = 2048
|
||||||
@@ -14745,4 +14763,3 @@ z_index = -4096
|
|||||||
[connection signal="file_selected" from="SaveDialog" to="." method="_on_save_dialog_file_selected"]
|
[connection signal="file_selected" from="SaveDialog" to="." method="_on_save_dialog_file_selected"]
|
||||||
[connection signal="file_selected" from="LoadDialog" to="." method="_on_load_dialog_file_selected"]
|
[connection signal="file_selected" from="LoadDialog" to="." method="_on_load_dialog_file_selected"]
|
||||||
[connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="_on_background_input_capture_bg_key_pressed"]
|
[connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="_on_background_input_capture_bg_key_pressed"]
|
||||||
[connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="bgInputSprite"]
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ buses/channel_disable_time=0.0
|
|||||||
|
|
||||||
Saving="*res://autoload/saving.gd"
|
Saving="*res://autoload/saving.gd"
|
||||||
Global="*res://autoload/global.gd"
|
Global="*res://autoload/global.gd"
|
||||||
|
WobbleSyncManager="*res://autoload/wobble_sync_manager.gd"
|
||||||
ElgatoStreamDeck="*res://addons/godot-streamdeck-addon/singleton.gd"
|
ElgatoStreamDeck="*res://addons/godot-streamdeck-addon/singleton.gd"
|
||||||
DefaultAvatarData="*res://autoload/defaultAvatarData.gd"
|
DefaultAvatarData="*res://autoload/defaultAvatarData.gd"
|
||||||
|
|
||||||
@@ -136,6 +137,11 @@ control={
|
|||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
mouse_middle={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":3,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
saveImages={
|
saveImages={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
|
||||||
@@ -148,6 +154,4 @@ textures/canvas_textures/default_texture_filter=0
|
|||||||
textures/canvas_textures/default_texture_repeat=1
|
textures/canvas_textures/default_texture_repeat=1
|
||||||
renderer/rendering_method="gl_compatibility"
|
renderer/rendering_method="gl_compatibility"
|
||||||
renderer/rendering_method.mobile="gl_compatibility"
|
renderer/rendering_method.mobile="gl_compatibility"
|
||||||
shading/overrides/force_vertex_shading=true
|
|
||||||
shading/overrides/force_lambert_over_burley=true
|
|
||||||
environment/defaults/default_clear_color=Color(0.376471, 0.376471, 0.376471, 1)
|
environment/defaults/default_clear_color=Color(0.376471, 0.376471, 0.376471, 1)
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
shader_type canvas_item;
|
shader_type canvas_item;
|
||||||
|
|
||||||
uniform sampler2D noise_texture: source_color, repeat_enable;
|
uniform sampler2D noise_texture:repeat_enable;
|
||||||
uniform float distortion_strengh: hint_range(0, 0.1) = 0.01;
|
uniform float distortion_strengh: hint_range(0, 0.1) = 1.0;
|
||||||
uniform float speed: hint_range(0.1, 10) = 1.0;
|
uniform float speed: hint_range(0.1, 10) = 1.0;
|
||||||
uniform bool use_noise_texture: hint_default_true = false;
|
|
||||||
|
|
||||||
void fragment() {
|
void fragment() {
|
||||||
vec2 uv_offset = vec2(0.0);
|
vec4 noise_pixel = texture(noise_texture, UV + floor(TIME*speed)/3.0);
|
||||||
|
vec2 uv_offset = (noise_pixel.rg * 2.0 - 1.0) * distortion_strengh;
|
||||||
if (use_noise_texture) {
|
|
||||||
vec4 noise_pixel = texture(noise_texture, UV + floor(TIME * speed) / 3.0);
|
|
||||||
uv_offset = (noise_pixel.rg * 2.0 - 1.0) * distortion_strengh;
|
|
||||||
} else {
|
|
||||||
// Fallback procedural noise when no texture is provided
|
|
||||||
float noise = sin(UV.x * 20.0 + TIME * speed) * cos(UV.y * 20.0 + TIME * speed * 0.7);
|
|
||||||
uv_offset = vec2(noise) * distortion_strengh;
|
|
||||||
}
|
|
||||||
|
|
||||||
COLOR = texture(TEXTURE, UV + uv_offset);
|
COLOR = texture(TEXTURE, UV + uv_offset);
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
uid://4a1uhohwy43l
|
uid://b342og0l0k1p
|
||||||
|
|||||||
30
test_wobble_sync.gd
Normal file
30
test_wobble_sync.gd
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
## Test script for wobble sync groups - run this to verify the system is working
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Wait a frame for everything to initialize
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
print("=== Wobble Sync Groups Test ===")
|
||||||
|
|
||||||
|
# Test creating a group
|
||||||
|
var success = WobbleSyncManager.createGroup("TestGroup")
|
||||||
|
print("Create group 'TestGroup': ", success)
|
||||||
|
|
||||||
|
# Test getting group names
|
||||||
|
var groups = WobbleSyncManager.getGroupNames()
|
||||||
|
print("Available groups: ", groups)
|
||||||
|
|
||||||
|
# Test group existence
|
||||||
|
print("Group 'TestGroup' exists: ", WobbleSyncManager.hasGroup("TestGroup"))
|
||||||
|
print("Group 'NonExistent' exists: ", WobbleSyncManager.hasGroup("NonExistent"))
|
||||||
|
|
||||||
|
# Clean up test group
|
||||||
|
WobbleSyncManager.deleteGroup("TestGroup")
|
||||||
|
print("Deleted test group")
|
||||||
|
|
||||||
|
groups = WobbleSyncManager.getGroupNames()
|
||||||
|
print("Groups after deletion: ", groups)
|
||||||
|
|
||||||
|
print("=== Test Complete ===")
|
||||||
1
test_wobble_sync.gd.uid
Normal file
1
test_wobble_sync.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ca66ai4v1k0sw
|
||||||
@@ -9,7 +9,7 @@ func _ready():
|
|||||||
Global.mouse = self
|
Global.mouse = self
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
if Global.main.editMode:
|
if Global.main and Global.main.editMode:
|
||||||
if text != "":
|
if text != "":
|
||||||
label.text = text
|
label.text = text
|
||||||
visible = true
|
visible = true
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
shader_type canvas_item;
|
|
||||||
|
|
||||||
uniform sampler2D mask_texture : source_color;
|
|
||||||
uniform bool use_mask = false;
|
|
||||||
uniform float mask_opacity : hint_range(0.0, 1.0) = 1.0;
|
|
||||||
|
|
||||||
void fragment() {
|
|
||||||
// Get the original sprite color
|
|
||||||
vec4 sprite_color = texture(TEXTURE, UV);
|
|
||||||
|
|
||||||
if (use_mask) {
|
|
||||||
// Sample the mask texture
|
|
||||||
vec4 mask_sample = texture(mask_texture, UV);
|
|
||||||
|
|
||||||
// Use the mask's alpha channel to determine visibility
|
|
||||||
float mask_alpha = mask_sample.a * mask_opacity;
|
|
||||||
|
|
||||||
// Apply the mask by multiplying the sprite's alpha with the mask alpha
|
|
||||||
sprite_color.a *= mask_alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
COLOR = sprite_color;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://btd72itp0cy6b
|
|
||||||
8
ui_scenes/selectedSprite/opacity.gdshader
Normal file
8
ui_scenes/selectedSprite/opacity.gdshader
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
shader_type canvas_item;
|
||||||
|
|
||||||
|
uniform float opacity : hint_range(0.0, 1.0) = 1.0;
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
COLOR = texture(TEXTURE, UV);
|
||||||
|
COLOR.a *= opacity;
|
||||||
|
}
|
||||||
1
ui_scenes/selectedSprite/opacity.gdshader.uid
Normal file
1
ui_scenes/selectedSprite/opacity.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://byxs44vljsg2f
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
shader_type canvas_item;
|
shader_type canvas_item;
|
||||||
|
|
||||||
uniform vec4 outline_color : source_color = vec4(1.0, 0.0, 0.0, 0.3);
|
uniform float speed;
|
||||||
|
|
||||||
|
uniform sampler2D backImg: repeat_enable;
|
||||||
|
|
||||||
|
uniform sampler2D palette;
|
||||||
|
uniform float palette_speed = 0.1;
|
||||||
|
|
||||||
void fragment(){
|
void fragment(){
|
||||||
vec4 tex = texture(TEXTURE, UV);
|
|
||||||
COLOR = vec4(outline_color.rgb, tex.a * outline_color.a);
|
vec2 scroll = vec2(1.0,0.0) * TIME * speed;
|
||||||
|
vec4 tex = texture(TEXTURE, (UV));
|
||||||
|
float palette_swap = mod(tex.r - TIME * palette_speed, 1.0);
|
||||||
|
COLOR = vec4(texture(palette, vec2(palette_swap, 0)).rgb, tex.a);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,9 @@ var imageSize = Vector2.ZERO
|
|||||||
@onready var wob = $WobbleOrigin
|
@onready var wob = $WobbleOrigin
|
||||||
|
|
||||||
@onready var outlineScene = preload("res://ui_scenes/selectedSprite/outline.tscn")
|
@onready var outlineScene = preload("res://ui_scenes/selectedSprite/outline.tscn")
|
||||||
|
@onready var opacityShader = preload("res://ui_scenes/selectedSprite/opacity.gdshader")
|
||||||
|
|
||||||
|
var opacityMaterial = null
|
||||||
|
|
||||||
#Visuals
|
#Visuals
|
||||||
var mouseOffset = Vector2.ZERO
|
var mouseOffset = Vector2.ZERO
|
||||||
@@ -76,22 +79,24 @@ var ignoreBounce = false
|
|||||||
#Animation
|
#Animation
|
||||||
var frames = 1
|
var frames = 1
|
||||||
var animSpeed = 0
|
var animSpeed = 0
|
||||||
|
var animTime = 0.0 # Time accumulator for animations
|
||||||
#Masking
|
|
||||||
var mask_texture_path = ""
|
|
||||||
var mask_texture = null
|
|
||||||
var use_mask = false
|
|
||||||
var mask_opacity = 1.0
|
|
||||||
|
|
||||||
var remadePolygon = false
|
var remadePolygon = false
|
||||||
|
|
||||||
var clipped = false
|
var clipped = false
|
||||||
|
|
||||||
var tick = 0
|
var time = 0.0 # Time accumulator for physics calculations
|
||||||
|
|
||||||
|
#Opacity
|
||||||
|
var spriteOpacity = 1.0
|
||||||
|
var affectChildrenOpacity = false
|
||||||
|
|
||||||
#Vis toggle
|
#Vis toggle
|
||||||
var toggle = "null"
|
var toggle = "null"
|
||||||
|
|
||||||
|
#Wobble Sync Group
|
||||||
|
var wobbleSyncGroup = ""
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
|
||||||
Global.main.spriteVisToggles.connect(visToggle)
|
Global.main.spriteVisToggles.connect(visToggle)
|
||||||
@@ -125,9 +130,6 @@ func _ready():
|
|||||||
|
|
||||||
sprite.texture = tex
|
sprite.texture = tex
|
||||||
|
|
||||||
# Initialize masking
|
|
||||||
setupMasking()
|
|
||||||
|
|
||||||
var bitmap = BitMap.new()
|
var bitmap = BitMap.new()
|
||||||
bitmap.create_from_image_alpha(imageData)
|
bitmap.create_from_image_alpha(imageData)
|
||||||
|
|
||||||
@@ -172,6 +174,8 @@ func _ready():
|
|||||||
|
|
||||||
setClip(clipped)
|
setClip(clipped)
|
||||||
|
|
||||||
|
# Always initialize opacity shader
|
||||||
|
updateShaderOpacity()
|
||||||
|
|
||||||
if Global.filtering:
|
if Global.filtering:
|
||||||
sprite.texture_filter = 2
|
sprite.texture_filter = 2
|
||||||
@@ -226,7 +230,7 @@ func replaceSprite(pathNew):
|
|||||||
remakePolygon()
|
remakePolygon()
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
tick += 1
|
time += delta
|
||||||
if Global.heldSprite == self:
|
if Global.heldSprite == self:
|
||||||
|
|
||||||
grabArea.visible = true
|
grabArea.visible = true
|
||||||
@@ -236,6 +240,16 @@ func _process(delta):
|
|||||||
grabArea.visible = false
|
grabArea.visible = false
|
||||||
originSprite.visible = false
|
originSprite.visible = false
|
||||||
|
|
||||||
|
# Disable grabArea input and detection when dialogs are open
|
||||||
|
if Global.main and Global.main.fileSystemOpen:
|
||||||
|
grabArea.input_pickable = false
|
||||||
|
grabArea.monitoring = false
|
||||||
|
grabArea.monitorable = false
|
||||||
|
else:
|
||||||
|
grabArea.input_pickable = true
|
||||||
|
grabArea.monitoring = true
|
||||||
|
grabArea.monitorable = true
|
||||||
|
|
||||||
var glob = dragger.global_position
|
var glob = dragger.global_position
|
||||||
if ignoreBounce:
|
if ignoreBounce:
|
||||||
glob.y -= Global.main.bounceChange
|
glob.y -= Global.main.bounceChange
|
||||||
@@ -253,13 +267,17 @@ func _process(delta):
|
|||||||
|
|
||||||
talkBlink()
|
talkBlink()
|
||||||
|
|
||||||
animation()
|
animation(delta)
|
||||||
|
|
||||||
func animation():
|
func animation(delta):
|
||||||
|
|
||||||
var speed = max(float(animSpeed),Engine.max_fps*6.0)
|
|
||||||
if animSpeed > 0 and frames > 1:
|
if animSpeed > 0 and frames > 1:
|
||||||
if Global.animationTick % int((speed)/float(animSpeed)) == 0:
|
animTime += delta
|
||||||
|
# Convert animSpeed to frames per second - no additional scaling needed since this controls frame rate
|
||||||
|
var frameRate = animSpeed / 60.0 # Normalize from old frame-based system
|
||||||
|
var frameInterval = 1.0 / frameRate
|
||||||
|
|
||||||
|
if animTime >= frameInterval:
|
||||||
|
animTime -= frameInterval
|
||||||
if sprite.frame == frames - 1:
|
if sprite.frame == frames - 1:
|
||||||
sprite.frame = 0
|
sprite.frame = 0
|
||||||
else:
|
else:
|
||||||
@@ -271,12 +289,67 @@ func setZIndex():
|
|||||||
sprite.z_index = z
|
sprite.z_index = z
|
||||||
|
|
||||||
func talkBlink():
|
func talkBlink():
|
||||||
var faded = 0.2 * int(Global.main.editMode)
|
var faded = 0.2 * int(Global.main and Global.main.editMode)
|
||||||
var value = (showOnTalk + (showOnBlink*3)) + (int(Global.speaking)*10) + (int(Global.blink)*20)
|
var value = (showOnTalk + (showOnBlink*3)) + (int(Global.speaking)*10) + (int(Global.blink)*20)
|
||||||
var yes = [0,10,20,30,1,21,12,32,3,13,4,15,26,36,27,38].has(int(value))
|
var yes = [0,10,20,30,1,21,12,32,3,13,4,15,26,36,27,38].has(int(value))
|
||||||
sprite.self_modulate.a = max(int(yes),faded)
|
var baseAlpha = max(int(yes), faded)
|
||||||
|
|
||||||
|
# Only handle talk/blink/edit visibility with modulate
|
||||||
|
# Opacity slider effects are handled by shader
|
||||||
|
sprite.self_modulate.a = 1.0 # Always keep original texture alpha for clipping
|
||||||
|
var previousAlpha = sprite.modulate.a
|
||||||
|
sprite.modulate.a = baseAlpha # Only apply talk/blink/edit visibility
|
||||||
|
|
||||||
|
# Update shader if modulate alpha changed (for editor dimming)
|
||||||
|
if previousAlpha != baseAlpha:
|
||||||
|
updateShaderOpacity()
|
||||||
|
|
||||||
|
func calculateParentOpacityMultiplier():
|
||||||
|
var multiplier = 1.0
|
||||||
|
var currentParent = parentSprite
|
||||||
|
|
||||||
|
# Traverse up the entire parent hierarchy
|
||||||
|
while currentParent != null:
|
||||||
|
if currentParent.affectChildrenOpacity:
|
||||||
|
multiplier *= currentParent.spriteOpacity
|
||||||
|
currentParent = currentParent.parentSprite
|
||||||
|
|
||||||
|
return multiplier
|
||||||
|
|
||||||
|
## Update wobble parameters and sync to group if needed
|
||||||
|
func updateWobbleParameter(parameter: String, value: float):
|
||||||
|
print("DEBUG: updateWobbleParameter called - parameter: ", parameter, ", value: ", value, ", sprite ID: ", id)
|
||||||
|
print("DEBUG: Current wobbleSyncGroup: '", wobbleSyncGroup, "'")
|
||||||
|
|
||||||
|
# Update this sprite's parameter first
|
||||||
|
match parameter:
|
||||||
|
"xFrq": xFrq = value
|
||||||
|
"xAmp": xAmp = value
|
||||||
|
"yFrq": yFrq = value
|
||||||
|
"yAmp": yAmp = value
|
||||||
|
"rFrq": rFrq = value
|
||||||
|
"rAmp": rAmp = value
|
||||||
|
|
||||||
|
print("DEBUG: Parameter updated on sprite. New values - xFrq:", xFrq, " xAmp:", xAmp, " yFrq:", yFrq, " yAmp:", yAmp, " rFrq:", rFrq, " rAmp:", rAmp)
|
||||||
|
|
||||||
|
# If this sprite is in a sync group, update the entire group
|
||||||
|
if wobbleSyncGroup != "" and WobbleSyncManager:
|
||||||
|
print("DEBUG: Sprite is in sync group '", wobbleSyncGroup, "' - updating group wobble parameters")
|
||||||
|
WobbleSyncManager.updateGroupWobble(wobbleSyncGroup, xFrq, xAmp, yFrq, yAmp, rFrq, rAmp)
|
||||||
|
|
||||||
|
# Don't refresh UI immediately - let the sync process complete first
|
||||||
|
# The wobble sync control will handle UI updates when needed
|
||||||
|
else:
|
||||||
|
print("DEBUG: Sprite not in sync group or WobbleSyncManager not available")
|
||||||
|
|
||||||
|
## Check if this sprite is synced to a group
|
||||||
|
func isSynced() -> bool:
|
||||||
|
return wobbleSyncGroup != ""
|
||||||
|
|
||||||
func delete():
|
func delete():
|
||||||
|
# Clean up from wobble sync groups
|
||||||
|
if wobbleSyncGroup != "" and WobbleSyncManager:
|
||||||
|
WobbleSyncManager.onSpriteDeleted(id)
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
||||||
func _physics_process(delta):
|
func _physics_process(delta):
|
||||||
@@ -333,31 +406,46 @@ func drag(delta):
|
|||||||
if dragSpeed == 0:
|
if dragSpeed == 0:
|
||||||
dragger.global_position = wob.global_position
|
dragger.global_position = wob.global_position
|
||||||
else:
|
else:
|
||||||
dragger.global_position = lerp(dragger.global_position,wob.global_position,1/dragSpeed)
|
# Convert dragSpeed to a proper delta-time lerp factor
|
||||||
|
# Higher dragSpeed = slower movement, so we invert it
|
||||||
|
# Use Global.main.physicsTimeMultiplier for consistent physics timing
|
||||||
|
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||||
|
var lerpFactor = clamp(delta * (physicsMultiplier * 10.0 / max(dragSpeed, 0.1)), 0.0, 1.0)
|
||||||
|
dragger.global_position = lerp(dragger.global_position, wob.global_position, lerpFactor)
|
||||||
dragOrigin.global_position = dragger.global_position
|
dragOrigin.global_position = dragger.global_position
|
||||||
|
|
||||||
func wobble():
|
func wobble():
|
||||||
wob.position.x = sin(tick*xFrq)*xAmp
|
# Use Global.main.physicsTimeMultiplier for consistent physics timing
|
||||||
wob.position.y = sin(tick*yFrq)*yAmp
|
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||||
|
var scaledTime = time * physicsMultiplier
|
||||||
|
wob.position.x = sin(scaledTime * xFrq) * xAmp
|
||||||
|
wob.position.y = sin(scaledTime * yFrq) * yAmp
|
||||||
|
|
||||||
func rotationalDrag(length,delta):
|
func rotationalDrag(length, delta):
|
||||||
var yvel = (length * rdragStr)
|
var yvel = (length * rdragStr)
|
||||||
|
|
||||||
#Calculate Max angle
|
#Calculate Max angle
|
||||||
yvel = clamp(yvel,rLimitMin,rLimitMax)
|
yvel = clamp(yvel, rLimitMin, rLimitMax)
|
||||||
|
|
||||||
# Add rotation wobble as percentage of rotational limits range
|
# Add rotation wobble as percentage of rotational limits range
|
||||||
var rotationRange = rLimitMax - rLimitMin
|
var rotationRange = rLimitMax - rLimitMin
|
||||||
var rotationWobble = sin(tick*rFrq) * (rotationRange * rAmp * 0.01)
|
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||||
|
var scaledTime = time * physicsMultiplier
|
||||||
|
var rotationWobble = sin(scaledTime * rFrq) * (rotationRange * rAmp * 0.01)
|
||||||
var totalRotation = yvel + rotationWobble
|
var totalRotation = yvel + rotationWobble
|
||||||
|
|
||||||
sprite.rotation = lerp_angle(sprite.rotation,deg_to_rad(totalRotation),0.25)
|
# Use delta-based lerp for smooth rotation with slower speed
|
||||||
|
var lerpFactor = clamp(delta * (physicsMultiplier * 1.0), 0.0, 1.0) # Reduced from 15.0 to 4.0
|
||||||
|
sprite.rotation = lerp_angle(sprite.rotation, deg_to_rad(totalRotation), lerpFactor)
|
||||||
|
|
||||||
func stretch(length,delta):
|
func stretch(length, delta):
|
||||||
var yvel = (length * stretchAmount * 0.01)
|
var yvel = (length * stretchAmount * 0.01)
|
||||||
var target = Vector2(1.0-yvel,1.0+yvel)
|
var target = Vector2(1.0 - yvel, 1.0 + yvel)
|
||||||
|
|
||||||
sprite.scale = lerp(sprite.scale,target,0.5)
|
# Use delta-based lerp for smooth stretching
|
||||||
|
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||||
|
var lerpFactor = clamp(delta * (physicsMultiplier * 30.0), 0.0, 1.0)
|
||||||
|
sprite.scale = lerp(sprite.scale, target, lerpFactor)
|
||||||
|
|
||||||
func changeCollision(enable):
|
func changeCollision(enable):
|
||||||
grabArea.monitorable = enable
|
grabArea.monitorable = enable
|
||||||
@@ -412,59 +500,42 @@ func getAllLinkedSprites():
|
|||||||
linkedSprites.append(node)
|
linkedSprites.append(node)
|
||||||
return linkedSprites
|
return linkedSprites
|
||||||
|
|
||||||
|
func updateOpacity():
|
||||||
|
# Force a refresh of opacity for this sprite and its children
|
||||||
|
talkBlink()
|
||||||
|
updateShaderOpacity()
|
||||||
|
|
||||||
|
# Update all child sprites in the entire hierarchy if this sprite affects children
|
||||||
|
if affectChildrenOpacity:
|
||||||
|
updateChildrenOpacityRecursively()
|
||||||
|
|
||||||
|
func updateShaderOpacity():
|
||||||
|
# Calculate total opacity from user settings and parent hierarchy
|
||||||
|
var totalOpacity = spriteOpacity * calculateParentOpacityMultiplier()
|
||||||
|
|
||||||
|
# Also factor in the modulate alpha for editor dimming/onion skinning
|
||||||
|
var editorAlpha = sprite.modulate.a
|
||||||
|
var finalOpacity = totalOpacity * editorAlpha
|
||||||
|
|
||||||
|
# Always apply the opacity shader to ensure it's loaded
|
||||||
|
if opacityMaterial == null:
|
||||||
|
opacityMaterial = ShaderMaterial.new()
|
||||||
|
opacityMaterial.shader = opacityShader
|
||||||
|
|
||||||
|
opacityMaterial.set_shader_parameter("opacity", finalOpacity)
|
||||||
|
sprite.material = opacityMaterial
|
||||||
|
|
||||||
|
func updateChildrenOpacityRecursively():
|
||||||
|
var linkedSprites = getAllLinkedSprites()
|
||||||
|
for child in linkedSprites:
|
||||||
|
child.talkBlink()
|
||||||
|
child.updateShaderOpacity()
|
||||||
|
# Always update the entire hierarchy below this child
|
||||||
|
child.updateChildrenOpacityRecursively()
|
||||||
|
|
||||||
func visToggle(keys):
|
func visToggle(keys):
|
||||||
if keys.has(toggle):
|
if keys.has(toggle):
|
||||||
$WobbleOrigin/DragOrigin.visible = !$WobbleOrigin/DragOrigin.visible
|
$WobbleOrigin/DragOrigin.visible = !$WobbleOrigin/DragOrigin.visible
|
||||||
|
|
||||||
func makeVis():
|
func makeVis():
|
||||||
$WobbleOrigin/DragOrigin.visible = true
|
$WobbleOrigin/DragOrigin.visible = true
|
||||||
|
|
||||||
## Setup masking system
|
|
||||||
func setupMasking():
|
|
||||||
if use_mask and mask_texture_path != "":
|
|
||||||
loadMaskTexture()
|
|
||||||
updateMaskShader()
|
|
||||||
|
|
||||||
## Load mask texture from file path
|
|
||||||
func loadMaskTexture():
|
|
||||||
if mask_texture_path == "":
|
|
||||||
return
|
|
||||||
|
|
||||||
var img = Image.new()
|
|
||||||
var err = img.load(mask_texture_path)
|
|
||||||
if err != OK:
|
|
||||||
print_debug("Failed to load mask texture: " + mask_texture_path)
|
|
||||||
return
|
|
||||||
|
|
||||||
mask_texture = ImageTexture.create_from_image(img)
|
|
||||||
|
|
||||||
## Apply or remove mask shader
|
|
||||||
func updateMaskShader():
|
|
||||||
if use_mask and mask_texture != null:
|
|
||||||
# Create mask material with shader
|
|
||||||
var mask_shader = load("res://ui_scenes/selectedSprite/mask.gdshader")
|
|
||||||
var mask_material = ShaderMaterial.new()
|
|
||||||
mask_material.shader = mask_shader
|
|
||||||
mask_material.set_shader_parameter("mask_texture", mask_texture)
|
|
||||||
mask_material.set_shader_parameter("use_mask", true)
|
|
||||||
mask_material.set_shader_parameter("mask_opacity", mask_opacity)
|
|
||||||
|
|
||||||
sprite.material = mask_material
|
|
||||||
else:
|
|
||||||
# Remove mask material
|
|
||||||
sprite.material = null
|
|
||||||
|
|
||||||
## Set mask texture from external source
|
|
||||||
func setMaskTexture(texture_path: String, opacity: float = 1.0):
|
|
||||||
mask_texture_path = texture_path
|
|
||||||
mask_opacity = opacity
|
|
||||||
use_mask = true
|
|
||||||
loadMaskTexture()
|
|
||||||
updateMaskShader()
|
|
||||||
|
|
||||||
## Remove masking
|
|
||||||
func removeMask():
|
|
||||||
use_mask = false
|
|
||||||
mask_texture = null
|
|
||||||
mask_texture_path = ""
|
|
||||||
updateMaskShader()
|
|
||||||
|
|||||||
@@ -138,13 +138,10 @@ func _on_bounce_gravity_value_changed(value):
|
|||||||
|
|
||||||
func costumeButtonsPressed(label,id):
|
func costumeButtonsPressed(label,id):
|
||||||
label.text = "AWAITING INPUT"
|
label.text = "AWAITING INPUT"
|
||||||
await Global.main.emptiedCapture
|
|
||||||
awaitingCostumeInput = id - 1
|
awaitingCostumeInput = id - 1
|
||||||
|
|
||||||
|
|
||||||
await Global.main.pressedKey
|
await Global.main.pressedKey
|
||||||
label.text = "costume " + str(id) + " key: \"" + Global.main.costumeKeys[id - 1] + "\""
|
label.text = "costume " + str(id) + " key: \"" + Global.main.costumeKeys[id - 1] + "\""
|
||||||
await Global.main.emptiedCapture
|
|
||||||
awaitingCostumeInput = -1
|
awaitingCostumeInput = -1
|
||||||
|
|
||||||
func _on_costume_button_1_pressed():
|
func _on_costume_button_1_pressed():
|
||||||
@@ -204,8 +201,9 @@ func _on_costume_check_toggled(button_pressed):
|
|||||||
## @param button_pressed: bool - Whether StreamDeck should be enabled
|
## @param button_pressed: bool - Whether StreamDeck should be enabled
|
||||||
func _on_stream_deck_check_toggled(button_pressed):
|
func _on_stream_deck_check_toggled(button_pressed):
|
||||||
Saving.settings["useStreamDeck"] = button_pressed
|
Saving.settings["useStreamDeck"] = button_pressed
|
||||||
if get_node_or_null("/root/ElgatoStreamDeck"):
|
var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck")
|
||||||
get_node("/root/ElgatoStreamDeck").refresh_connection()
|
if streamdeck_node != null:
|
||||||
|
streamdeck_node.refresh_connection()
|
||||||
Global.pushUpdate("StreamDeck integration " + ("enabled" if button_pressed else "disabled") + ".")
|
Global.pushUpdate("StreamDeck integration " + ("enabled" if button_pressed else "disabled") + ".")
|
||||||
|
|
||||||
## Handle experimental microphone loudness toggle
|
## Handle experimental microphone loudness toggle
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ extends Node2D
|
|||||||
|
|
||||||
|
|
||||||
@onready var coverCollider = $Area2D/CollisionShape2D
|
@onready var coverCollider = $Area2D/CollisionShape2D
|
||||||
|
@onready var wobbleSyncControl = null # Will be assigned in _ready()
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
Global.spriteEdit = self
|
Global.spriteEdit = self
|
||||||
|
|
||||||
|
# Find the wobble sync control node
|
||||||
|
wobbleSyncControl = find_child("WobbleSyncControl")
|
||||||
|
if wobbleSyncControl == null:
|
||||||
|
print("Warning: WobbleSyncControl node not found in sprite viewer")
|
||||||
|
|
||||||
func setImage():
|
func setImage():
|
||||||
if Global.heldSprite == null:
|
if Global.heldSprite == null:
|
||||||
return
|
return
|
||||||
@@ -73,21 +78,26 @@ func setImage():
|
|||||||
$Animation/animFramesLabel.text = "sprite frames: " + str(Global.heldSprite.frames)
|
$Animation/animFramesLabel.text = "sprite frames: " + str(Global.heldSprite.frames)
|
||||||
$Animation/animFrames.value = Global.heldSprite.frames
|
$Animation/animFrames.value = Global.heldSprite.frames
|
||||||
|
|
||||||
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
$Opacity/opacityLabel.text = "opacity: " + str(int(Global.heldSprite.spriteOpacity * 100)) + "%"
|
||||||
|
$Opacity/opacitySlider.value = Global.heldSprite.spriteOpacity
|
||||||
|
$Opacity/affectChildrenCheck.button_pressed = Global.heldSprite.affectChildrenOpacity
|
||||||
|
|
||||||
# Update masking controls
|
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||||
$Masking/useMaskCheckbox.button_pressed = Global.heldSprite.use_mask
|
|
||||||
$Masking/maskOpacityLabel.text = "mask opacity: " + str(Global.heldSprite.mask_opacity)
|
|
||||||
$Masking/maskOpacity.value = Global.heldSprite.mask_opacity
|
|
||||||
if Global.heldSprite.mask_texture_path.is_empty():
|
|
||||||
$Masking/maskPathLabel.text = "mask: (none)"
|
|
||||||
else:
|
|
||||||
$Masking/maskPathLabel.text = "mask: " + Global.heldSprite.mask_texture_path.get_file()
|
|
||||||
|
|
||||||
changeRotLimit()
|
changeRotLimit()
|
||||||
|
|
||||||
setLayerButtons()
|
setLayerButtons()
|
||||||
|
|
||||||
|
# Update wobble sync control
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.setSprite(Global.heldSprite)
|
||||||
|
# Force a refresh to ensure dropdown is populated
|
||||||
|
await get_tree().process_frame
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
|
# Update wobble control states based on sync status
|
||||||
|
updateWobbleControlStates()
|
||||||
|
|
||||||
if Global.heldSprite.parentId == null:
|
if Global.heldSprite.parentId == null:
|
||||||
$Buttons/Unlink.visible = false
|
$Buttons/Unlink.visible = false
|
||||||
parentSpin.visible = false
|
parentSpin.visible = false
|
||||||
@@ -103,6 +113,30 @@ func setImage():
|
|||||||
parentSpin.pixel_size = 1.5 / nodes[0].imageData.get_size().y
|
parentSpin.pixel_size = 1.5 / nodes[0].imageData.get_size().y
|
||||||
parentSpin.hframes = nodes[0].frames
|
parentSpin.hframes = nodes[0].frames
|
||||||
parentSpin.visible = true
|
parentSpin.visible = true
|
||||||
|
|
||||||
|
## Update wobble control states based on sync status
|
||||||
|
func updateWobbleControlStates():
|
||||||
|
if Global.heldSprite == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var isSynced = Global.heldSprite.isSynced()
|
||||||
|
|
||||||
|
# Keep wobble controls enabled even when synced (they edit group settings)
|
||||||
|
$WobbleControl/xFrq.editable = true
|
||||||
|
$WobbleControl/xAmp.editable = true
|
||||||
|
$WobbleControl/yFrq.editable = true
|
||||||
|
$WobbleControl/yAmp.editable = true
|
||||||
|
$WobbleControl/rFrq.editable = true
|
||||||
|
$WobbleControl/rAmp.editable = true
|
||||||
|
|
||||||
|
# Visual feedback for sync state - slight tint to indicate group editing
|
||||||
|
var tint = Color(1.0, 1.0, 0.9) if isSynced else Color.WHITE
|
||||||
|
$WobbleControl/xFrq.modulate = tint
|
||||||
|
$WobbleControl/xAmp.modulate = tint
|
||||||
|
$WobbleControl/yFrq.modulate = tint
|
||||||
|
$WobbleControl/yAmp.modulate = tint
|
||||||
|
$WobbleControl/rFrq.modulate = tint
|
||||||
|
$WobbleControl/rAmp.modulate = tint
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
|
|
||||||
@@ -136,31 +170,55 @@ func _on_drag_slider_value_changed(value):
|
|||||||
|
|
||||||
|
|
||||||
func _on_x_frq_value_changed(value):
|
func _on_x_frq_value_changed(value):
|
||||||
|
print("DEBUG: _on_x_frq_value_changed called with value: ", value)
|
||||||
$WobbleControl/xFrqLabel.text = "x frequency: " + str(value)
|
$WobbleControl/xFrqLabel.text = "x frequency: " + str(value)
|
||||||
Global.heldSprite.xFrq = value
|
Global.heldSprite.updateWobbleParameter("xFrq", value)
|
||||||
|
# Refresh wobble sync control after parameter change
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
|
|
||||||
func _on_x_amp_value_changed(value):
|
func _on_x_amp_value_changed(value):
|
||||||
|
print("DEBUG: _on_x_amp_value_changed called with value: ", value)
|
||||||
$WobbleControl/xAmpLabel.text = "x amplitude: " + str(value)
|
$WobbleControl/xAmpLabel.text = "x amplitude: " + str(value)
|
||||||
Global.heldSprite.xAmp = value
|
Global.heldSprite.updateWobbleParameter("xAmp", value)
|
||||||
|
# Refresh wobble sync control after parameter change
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
|
|
||||||
func _on_y_frq_value_changed(value):
|
func _on_y_frq_value_changed(value):
|
||||||
|
print("DEBUG: _on_y_frq_value_changed called with value: ", value)
|
||||||
$WobbleControl/yFrqLabel.text = "y frequency: " + str(value)
|
$WobbleControl/yFrqLabel.text = "y frequency: " + str(value)
|
||||||
Global.heldSprite.yFrq = value
|
Global.heldSprite.updateWobbleParameter("yFrq", value)
|
||||||
|
# Refresh wobble sync control after parameter change
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
func _on_y_amp_value_changed(value):
|
func _on_y_amp_value_changed(value):
|
||||||
|
print("DEBUG: _on_y_amp_value_changed called with value: ", value)
|
||||||
$WobbleControl/yAmpLabel.text = "y amplitude: " + str(value)
|
$WobbleControl/yAmpLabel.text = "y amplitude: " + str(value)
|
||||||
Global.heldSprite.yAmp = value
|
Global.heldSprite.updateWobbleParameter("yAmp", value)
|
||||||
|
# Refresh wobble sync control after parameter change
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
|
|
||||||
func _on_r_frq_value_changed(value):
|
func _on_r_frq_value_changed(value):
|
||||||
|
print("DEBUG: _on_r_frq_value_changed called with value: ", value)
|
||||||
$WobbleControl/rFrqLabel.text = "rotation frequency: " + str(value)
|
$WobbleControl/rFrqLabel.text = "rotation frequency: " + str(value)
|
||||||
Global.heldSprite.rFrq = value
|
Global.heldSprite.updateWobbleParameter("rFrq", value)
|
||||||
|
# Refresh wobble sync control after parameter change
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
func _on_r_amp_value_changed(value):
|
func _on_r_amp_value_changed(value):
|
||||||
|
print("DEBUG: _on_r_amp_value_changed called with value: ", value)
|
||||||
$WobbleControl/rAmpLabel.text = "rotation amplitude: " + str(value) + "%"
|
$WobbleControl/rAmpLabel.text = "rotation amplitude: " + str(value) + "%"
|
||||||
Global.heldSprite.rAmp = value
|
Global.heldSprite.updateWobbleParameter("rAmp", value)
|
||||||
|
# Refresh wobble sync control after parameter change
|
||||||
|
if wobbleSyncControl:
|
||||||
|
wobbleSyncControl.updateUI()
|
||||||
|
|
||||||
|
|
||||||
func _on_r_drag_value_changed(value):
|
func _on_r_drag_value_changed(value):
|
||||||
@@ -185,6 +243,10 @@ func _on_blinking_pressed():
|
|||||||
|
|
||||||
|
|
||||||
func _on_trash_pressed():
|
func _on_trash_pressed():
|
||||||
|
# Clean up wobble sync group membership before deleting
|
||||||
|
if Global.heldSprite.wobbleSyncGroup != "" and WobbleSyncManager:
|
||||||
|
WobbleSyncManager.onSpriteDeleted(Global.heldSprite.id)
|
||||||
|
|
||||||
Global.heldSprite.queue_free()
|
Global.heldSprite.queue_free()
|
||||||
Global.heldSprite = null
|
Global.heldSprite = null
|
||||||
|
|
||||||
@@ -373,25 +435,17 @@ func _on_delete_pressed():
|
|||||||
func _on_set_toggle_pressed():
|
func _on_set_toggle_pressed():
|
||||||
$VisToggle/setToggle/Label.text = "toggle: AWAITING INPUT"
|
$VisToggle/setToggle/Label.text = "toggle: AWAITING INPUT"
|
||||||
await Global.main.fatfuckingballs
|
await Global.main.fatfuckingballs
|
||||||
|
|
||||||
|
var keys = await Global.main.spriteVisToggles
|
||||||
|
var key = keys[0]
|
||||||
|
Global.heldSprite.toggle = key
|
||||||
|
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||||
|
|
||||||
## Masking signal handlers
|
func _on_opacity_slider_value_changed(value):
|
||||||
func _on_use_mask_checkbox_toggled(button_pressed):
|
$Opacity/opacityLabel.text = "opacity: " + str(int(value * 100)) + "%"
|
||||||
Global.heldSprite.use_mask = button_pressed
|
Global.heldSprite.spriteOpacity = value
|
||||||
Global.heldSprite.updateMaskShader()
|
Global.heldSprite.updateOpacity()
|
||||||
|
|
||||||
func _on_mask_opacity_value_changed(value):
|
func _on_affect_children_check_toggled(button_pressed):
|
||||||
Global.heldSprite.mask_opacity = value
|
Global.heldSprite.affectChildrenOpacity = button_pressed
|
||||||
$Masking/maskOpacityLabel.text = "mask opacity: " + str(value)
|
Global.heldSprite.updateOpacity()
|
||||||
Global.heldSprite.updateMaskShader()
|
|
||||||
|
|
||||||
func _on_select_mask_pressed():
|
|
||||||
Global.main.openMaskSelection()
|
|
||||||
|
|
||||||
func updateMaskDisplay(path: String):
|
|
||||||
$Masking/maskPathLabel.text = "mask: " + path.get_file()
|
|
||||||
$Masking/useMaskCheckbox.button_pressed = true
|
|
||||||
|
|
||||||
func _on_clear_mask_pressed():
|
|
||||||
Global.heldSprite.removeMask()
|
|
||||||
$Masking/maskPathLabel.text = "mask: (none)"
|
|
||||||
$Masking/useMaskCheckbox.button_pressed = false
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
[gd_scene load_steps=75 format=4 uid="uid://d3anahesvdgfh"]
|
[gd_scene load_steps=76 format=4 uid="uid://d3anahesvdgfh"]
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://4tbpehro4x5p" path="res://ui_scenes/spriteEditMenu/border.png" id="1_b54o8"]
|
[ext_resource type="Texture2D" uid="uid://4tbpehro4x5p" path="res://ui_scenes/spriteEditMenu/border.png" id="1_b54o8"]
|
||||||
[ext_resource type="Script" uid="uid://bgoaqt86bc73p" path="res://ui_scenes/spriteEditMenu/sprite_viewer.gd" id="1_hp0e3"]
|
[ext_resource type="Script" uid="uid://bgoaqt86bc73p" path="res://ui_scenes/spriteEditMenu/sprite_viewer.gd" id="1_hp0e3"]
|
||||||
[ext_resource type="Shader" uid="uid://bfxkb7p0y73yn" path="res://ui_scenes/spriteEditMenu/sprite_viewer.gdshader" id="3_ky0lu"]
|
[ext_resource type="Shader" uid="uid://bfxkb7p0y73yn" path="res://ui_scenes/spriteEditMenu/sprite_viewer.gdshader" id="3_ky0lu"]
|
||||||
[ext_resource type="Texture2D" uid="uid://xxhqegm7rq6n" path="res://ui_scenes/button sprites/add.png" id="3_tjnto"]
|
[ext_resource type="Texture2D" uid="uid://xxhqegm7rq6n" path="res://ui_scenes/button sprites/add.png" id="3_tjnto"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bkwf3j2pdspxw" path="res://ui_scenes/spriteEditMenu/wobble_sync_control.tscn" id="3_wobble_sync"]
|
||||||
[ext_resource type="Texture2D" uid="uid://bd68uiemrcy5v" path="res://ui_scenes/spriteEditMenu/speaking.png" id="4_shsbk"]
|
[ext_resource type="Texture2D" uid="uid://bd68uiemrcy5v" path="res://ui_scenes/spriteEditMenu/speaking.png" id="4_shsbk"]
|
||||||
[ext_resource type="Texture2D" uid="uid://doxw3dy86i2e4" path="res://ui_scenes/spriteEditMenu/blink.png" id="5_ed14x"]
|
[ext_resource type="Texture2D" uid="uid://doxw3dy86i2e4" path="res://ui_scenes/spriteEditMenu/blink.png" id="5_ed14x"]
|
||||||
[ext_resource type="Texture2D" uid="uid://dn5hs0pfutv1l" path="res://ui_scenes/spriteEditMenu/trash.png" id="6_304pj"]
|
[ext_resource type="Texture2D" uid="uid://dn5hs0pfutv1l" path="res://ui_scenes/spriteEditMenu/trash.png" id="6_304pj"]
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
[ext_resource type="Texture2D" uid="uid://gflmeocxfnrq" path="res://ui_scenes/settings/deleteHotkey.png" id="27_fgu6g"]
|
[ext_resource type="Texture2D" uid="uid://gflmeocxfnrq" path="res://ui_scenes/settings/deleteHotkey.png" id="27_fgu6g"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_21kva"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_21kva"]
|
||||||
size = Vector2(245, 1596.5)
|
size = Vector2(245, 1550)
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hxmah"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hxmah"]
|
||||||
shader = ExtResource("3_ky0lu")
|
shader = ExtResource("3_ky0lu")
|
||||||
@@ -14285,11 +14286,12 @@ script = ExtResource("1_hp0e3")
|
|||||||
z_index = -2
|
z_index = -2
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
|
||||||
position = Vector2(122.5, 725)
|
position = Vector2(122.5, 775)
|
||||||
shape = SubResource("RectangleShape2D_21kva")
|
shape = SubResource("RectangleShape2D_21kva")
|
||||||
|
|
||||||
[node name="Border" type="Sprite2D" parent="."]
|
[node name="Border" type="Sprite2D" parent="."]
|
||||||
scale = Vector2(1, 1.16)
|
position = Vector2(0, 2.22901)
|
||||||
|
scale = Vector2(1, 1.37547)
|
||||||
texture = ExtResource("1_b54o8")
|
texture = ExtResource("1_b54o8")
|
||||||
centered = false
|
centered = false
|
||||||
offset = Vector2(-9, -10.3448)
|
offset = Vector2(-9, -10.3448)
|
||||||
@@ -14349,13 +14351,13 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.362)
|
|||||||
fov = 36.5
|
fov = 36.5
|
||||||
|
|
||||||
[node name="Position" type="Node2D" parent="."]
|
[node name="Position" type="Node2D" parent="."]
|
||||||
position = Vector2(0, 10)
|
position = Vector2(0, 20)
|
||||||
|
|
||||||
[node name="fileTitle" type="Label" parent="Position"]
|
[node name="fileTitle" type="Label" parent="Position"]
|
||||||
offset_left = 10.0
|
offset_left = 10.0
|
||||||
offset_top = 134.0
|
offset_top = 127.0
|
||||||
offset_right = 236.0
|
offset_right = 236.0
|
||||||
offset_bottom = 154.0
|
offset_bottom = 147.0
|
||||||
text = "user://greenThing/head1.png"
|
text = "user://greenThing/head1.png"
|
||||||
label_settings = SubResource("LabelSettings_6favr")
|
label_settings = SubResource("LabelSettings_6favr")
|
||||||
|
|
||||||
@@ -14381,7 +14383,7 @@ offset_bottom = 227.0
|
|||||||
text = "layer : 0"
|
text = "layer : 0"
|
||||||
|
|
||||||
[node name="Buttons" type="Node2D" parent="."]
|
[node name="Buttons" type="Node2D" parent="."]
|
||||||
position = Vector2(0, 27)
|
position = Vector2(0, 141)
|
||||||
|
|
||||||
[node name="Speaking" type="Sprite2D" parent="Buttons"]
|
[node name="Speaking" type="Sprite2D" parent="Buttons"]
|
||||||
position = Vector2(42, 523)
|
position = Vector2(42, 523)
|
||||||
@@ -14468,11 +14470,11 @@ scrollable = false
|
|||||||
|
|
||||||
[node name="WobbleControl" type="Node2D" parent="."]
|
[node name="WobbleControl" type="Node2D" parent="."]
|
||||||
z_index = 1
|
z_index = 1
|
||||||
position = Vector2(-3, 378)
|
position = Vector2(-3, 491)
|
||||||
|
|
||||||
[node name="animationBox" type="Sprite2D" parent="WobbleControl"]
|
[node name="animationBox" type="Sprite2D" parent="WobbleControl"]
|
||||||
position = Vector2(125, 384.5)
|
position = Vector2(125, 431)
|
||||||
scale = Vector2(1, 1.25769)
|
scale = Vector2(1, 1.65769)
|
||||||
texture = ExtResource("15_wqi8b")
|
texture = ExtResource("15_wqi8b")
|
||||||
|
|
||||||
[node name="xFrqLabel" type="Label" parent="WobbleControl"]
|
[node name="xFrqLabel" type="Label" parent="WobbleControl"]
|
||||||
@@ -14488,7 +14490,7 @@ offset_top = 263.0
|
|||||||
offset_right = 235.0
|
offset_right = 235.0
|
||||||
offset_bottom = 283.0
|
offset_bottom = 283.0
|
||||||
theme = SubResource("Theme_mrbyn")
|
theme = SubResource("Theme_mrbyn")
|
||||||
max_value = 0.1
|
max_value = 1.0
|
||||||
step = 0.001
|
step = 0.001
|
||||||
scrollable = false
|
scrollable = false
|
||||||
|
|
||||||
@@ -14521,7 +14523,7 @@ offset_top = 364.0
|
|||||||
offset_right = 235.0
|
offset_right = 235.0
|
||||||
offset_bottom = 384.0
|
offset_bottom = 384.0
|
||||||
theme = SubResource("Theme_mrbyn")
|
theme = SubResource("Theme_mrbyn")
|
||||||
max_value = 0.1
|
max_value = 1.0
|
||||||
step = 0.001
|
step = 0.001
|
||||||
scrollable = false
|
scrollable = false
|
||||||
|
|
||||||
@@ -14554,7 +14556,7 @@ offset_top = 465.0
|
|||||||
offset_right = 235.0
|
offset_right = 235.0
|
||||||
offset_bottom = 485.0
|
offset_bottom = 485.0
|
||||||
theme = SubResource("Theme_mrbyn")
|
theme = SubResource("Theme_mrbyn")
|
||||||
max_value = 0.1
|
max_value = 1.0
|
||||||
step = 0.001
|
step = 0.001
|
||||||
scrollable = false
|
scrollable = false
|
||||||
|
|
||||||
@@ -14573,6 +14575,12 @@ offset_bottom = 528.0
|
|||||||
theme = SubResource("Theme_mrbyn")
|
theme = SubResource("Theme_mrbyn")
|
||||||
scrollable = false
|
scrollable = false
|
||||||
|
|
||||||
|
[node name="WobbleSyncControl" parent="WobbleControl" instance=ExtResource("3_wobble_sync")]
|
||||||
|
offset_left = 12.0
|
||||||
|
offset_top = 559.0
|
||||||
|
offset_right = 235.0
|
||||||
|
offset_bottom = 624.0
|
||||||
|
|
||||||
[node name="Rotation" type="Node2D" parent="."]
|
[node name="Rotation" type="Node2D" parent="."]
|
||||||
position = Vector2(-1, 224)
|
position = Vector2(-1, 224)
|
||||||
|
|
||||||
@@ -14612,7 +14620,7 @@ scrollable = false
|
|||||||
|
|
||||||
[node name="RotationalLimits" type="Node2D" parent="."]
|
[node name="RotationalLimits" type="Node2D" parent="."]
|
||||||
z_index = -1
|
z_index = -1
|
||||||
position = Vector2(0, 325)
|
position = Vector2(0, 532)
|
||||||
|
|
||||||
[node name="RotBack" type="Sprite2D" parent="RotationalLimits"]
|
[node name="RotBack" type="Sprite2D" parent="RotationalLimits"]
|
||||||
clip_children = 2
|
clip_children = 2
|
||||||
@@ -14725,8 +14733,43 @@ offset_bottom = 1068.0
|
|||||||
theme = SubResource("Theme_mrbyn")
|
theme = SubResource("Theme_mrbyn")
|
||||||
scrollable = false
|
scrollable = false
|
||||||
|
|
||||||
|
[node name="Opacity" type="Node2D" parent="."]
|
||||||
|
position = Vector2(-2, -611)
|
||||||
|
|
||||||
|
[node name="opacityLabel" type="Label" parent="Opacity"]
|
||||||
|
offset_left = 11.0
|
||||||
|
offset_top = 1090.0
|
||||||
|
offset_right = 159.0
|
||||||
|
offset_bottom = 1116.0
|
||||||
|
text = "opacity: 100%"
|
||||||
|
|
||||||
|
[node name="opacitySlider" type="HSlider" parent="Opacity"]
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = 1111.0
|
||||||
|
offset_right = 233.0
|
||||||
|
offset_bottom = 1131.0
|
||||||
|
theme = SubResource("Theme_mrbyn")
|
||||||
|
max_value = 1.0
|
||||||
|
step = 0.01
|
||||||
|
value = 1.0
|
||||||
|
scrollable = false
|
||||||
|
|
||||||
|
[node name="affectChildrenLabel" type="Label" parent="Opacity"]
|
||||||
|
offset_left = 11.0
|
||||||
|
offset_top = 1140.0
|
||||||
|
offset_right = 159.0
|
||||||
|
offset_bottom = 1166.0
|
||||||
|
text = "affect children:"
|
||||||
|
|
||||||
|
[node name="affectChildrenCheck" type="CheckBox" parent="Opacity"]
|
||||||
|
offset_left = 140.0
|
||||||
|
offset_top = 1140.0
|
||||||
|
offset_right = 200.0
|
||||||
|
offset_bottom = 1166.0
|
||||||
|
theme = SubResource("Theme_mrbyn")
|
||||||
|
|
||||||
[node name="Layers" type="Node2D" parent="."]
|
[node name="Layers" type="Node2D" parent="."]
|
||||||
position = Vector2(0, 327)
|
position = Vector2(0, 538)
|
||||||
|
|
||||||
[node name="Layer1" type="Sprite2D" parent="Layers"]
|
[node name="Layer1" type="Sprite2D" parent="Layers"]
|
||||||
position = Vector2(23, 878)
|
position = Vector2(23, 878)
|
||||||
@@ -14863,7 +14906,7 @@ position = Vector2(23, 878)
|
|||||||
texture = ExtResource("20_px0dt")
|
texture = ExtResource("20_px0dt")
|
||||||
|
|
||||||
[node name="VisToggle" type="Node2D" parent="."]
|
[node name="VisToggle" type="Node2D" parent="."]
|
||||||
position = Vector2(11, 1289)
|
position = Vector2(11, 1496)
|
||||||
|
|
||||||
[node name="setToggle" type="Button" parent="VisToggle"]
|
[node name="setToggle" type="Button" parent="VisToggle"]
|
||||||
custom_minimum_size = Vector2(0, 37.125)
|
custom_minimum_size = Vector2(0, 37.125)
|
||||||
@@ -14910,66 +14953,6 @@ position = Vector2(-2, -2)
|
|||||||
texture = ExtResource("27_fgu6g")
|
texture = ExtResource("27_fgu6g")
|
||||||
centered = false
|
centered = false
|
||||||
|
|
||||||
[node name="Masking" type="Node2D" parent="."]
|
|
||||||
position = Vector2(0, 1360.83)
|
|
||||||
|
|
||||||
[node name="animationBox" type="Sprite2D" parent="Masking"]
|
|
||||||
position = Vector2(125, 92.4052)
|
|
||||||
scale = Vector2(1, 0.905768)
|
|
||||||
texture = ExtResource("15_wqi8b")
|
|
||||||
|
|
||||||
[node name="maskingLabel" type="Label" parent="Masking"]
|
|
||||||
offset_left = 20.0
|
|
||||||
offset_right = 120.0
|
|
||||||
offset_bottom = 26.0
|
|
||||||
text = "MASKING"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="useMaskCheckbox" type="CheckBox" parent="Masking"]
|
|
||||||
offset_left = 20.0
|
|
||||||
offset_top = 30.0
|
|
||||||
offset_right = 200.0
|
|
||||||
offset_bottom = 54.0
|
|
||||||
text = "Use Mask"
|
|
||||||
|
|
||||||
[node name="maskOpacityLabel" type="Label" parent="Masking"]
|
|
||||||
offset_left = 20.0
|
|
||||||
offset_top = 60.0
|
|
||||||
offset_right = 200.0
|
|
||||||
offset_bottom = 86.0
|
|
||||||
text = "mask opacity: 1.0"
|
|
||||||
|
|
||||||
[node name="maskOpacity" type="HSlider" parent="Masking"]
|
|
||||||
offset_left = 20.0
|
|
||||||
offset_top = 90.0
|
|
||||||
offset_right = 200.0
|
|
||||||
offset_bottom = 106.0
|
|
||||||
max_value = 1.0
|
|
||||||
step = 0.01
|
|
||||||
value = 1.0
|
|
||||||
|
|
||||||
[node name="maskPathLabel" type="Label" parent="Masking"]
|
|
||||||
offset_left = 20.0
|
|
||||||
offset_top = 120.0
|
|
||||||
offset_right = 400.0
|
|
||||||
offset_bottom = 146.0
|
|
||||||
text = "mask: (none)"
|
|
||||||
autowrap_mode = 2
|
|
||||||
|
|
||||||
[node name="selectMask" type="Button" parent="Masking"]
|
|
||||||
offset_left = 20.0
|
|
||||||
offset_top = 150.0
|
|
||||||
offset_right = 120.0
|
|
||||||
offset_bottom = 181.0
|
|
||||||
text = "Select Mask"
|
|
||||||
|
|
||||||
[node name="clearMask" type="Button" parent="Masking"]
|
|
||||||
offset_left = 130.0
|
|
||||||
offset_top = 150.0
|
|
||||||
offset_right = 220.0
|
|
||||||
offset_bottom = 181.0
|
|
||||||
text = "Clear Mask"
|
|
||||||
|
|
||||||
[connection signal="pressed" from="Buttons/Speaking/speaking" to="." method="_on_speaking_pressed"]
|
[connection signal="pressed" from="Buttons/Speaking/speaking" to="." method="_on_speaking_pressed"]
|
||||||
[connection signal="pressed" from="Buttons/Blinking/blinking" to="." method="_on_blinking_pressed"]
|
[connection signal="pressed" from="Buttons/Blinking/blinking" to="." method="_on_blinking_pressed"]
|
||||||
[connection signal="pressed" from="Buttons/Trash/trash" to="." method="_on_trash_pressed"]
|
[connection signal="pressed" from="Buttons/Trash/trash" to="." method="_on_trash_pressed"]
|
||||||
@@ -14989,6 +14972,8 @@ text = "Clear Mask"
|
|||||||
[connection signal="value_changed" from="RotationalLimits/rotLimitMax" to="." method="_on_rot_limit_max_value_changed"]
|
[connection signal="value_changed" from="RotationalLimits/rotLimitMax" to="." method="_on_rot_limit_max_value_changed"]
|
||||||
[connection signal="value_changed" from="Animation/animFrames" to="." method="_on_anim_frames_value_changed"]
|
[connection signal="value_changed" from="Animation/animFrames" to="." method="_on_anim_frames_value_changed"]
|
||||||
[connection signal="value_changed" from="Animation/animSpeed" to="." method="_on_anim_speed_value_changed"]
|
[connection signal="value_changed" from="Animation/animSpeed" to="." method="_on_anim_speed_value_changed"]
|
||||||
|
[connection signal="value_changed" from="Opacity/opacitySlider" to="." method="_on_opacity_slider_value_changed"]
|
||||||
|
[connection signal="toggled" from="Opacity/affectChildrenCheck" to="." method="_on_affect_children_check_toggled"]
|
||||||
[connection signal="pressed" from="Layers/Layer1/layerButton1" to="." method="_on_layer_button_1_pressed"]
|
[connection signal="pressed" from="Layers/Layer1/layerButton1" to="." method="_on_layer_button_1_pressed"]
|
||||||
[connection signal="pressed" from="Layers/Layer2/layerButton2" to="." method="_on_layer_button_2_pressed"]
|
[connection signal="pressed" from="Layers/Layer2/layerButton2" to="." method="_on_layer_button_2_pressed"]
|
||||||
[connection signal="pressed" from="Layers/Layer3/layerButton3" to="." method="_on_layer_button_3_pressed"]
|
[connection signal="pressed" from="Layers/Layer3/layerButton3" to="." method="_on_layer_button_3_pressed"]
|
||||||
@@ -15001,7 +14986,3 @@ text = "Clear Mask"
|
|||||||
[connection signal="pressed" from="Layers/Layer10/layerButton10" to="." method="_on_layer_button_10_pressed"]
|
[connection signal="pressed" from="Layers/Layer10/layerButton10" to="." method="_on_layer_button_10_pressed"]
|
||||||
[connection signal="pressed" from="VisToggle/setToggle" to="." method="_on_set_toggle_pressed"]
|
[connection signal="pressed" from="VisToggle/setToggle" to="." method="_on_set_toggle_pressed"]
|
||||||
[connection signal="pressed" from="VisToggle/setToggle/delete" to="." method="_on_delete_pressed"]
|
[connection signal="pressed" from="VisToggle/setToggle/delete" to="." method="_on_delete_pressed"]
|
||||||
[connection signal="toggled" from="Masking/useMaskCheckbox" to="." method="_on_use_mask_checkbox_toggled"]
|
|
||||||
[connection signal="value_changed" from="Masking/maskOpacity" to="." method="_on_mask_opacity_value_changed"]
|
|
||||||
[connection signal="pressed" from="Masking/selectMask" to="." method="_on_select_mask_pressed"]
|
|
||||||
[connection signal="pressed" from="Masking/clearMask" to="." method="_on_clear_mask_pressed"]
|
|
||||||
|
|||||||
207
ui_scenes/spriteEditMenu/wobble_sync_control.gd
Normal file
207
ui_scenes/spriteEditMenu/wobble_sync_control.gd
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
## WobbleSyncControl - UI component for managing wobble sync groups
|
||||||
|
## Designed to integrate into the sprite editor panel
|
||||||
|
|
||||||
|
@onready var groupDropdown = $VBoxContainer/GroupRow/GroupDropdown
|
||||||
|
@onready var syncIndicator = $VBoxContainer/SyncIndicator
|
||||||
|
@onready var createGroupDialog = $CreateGroupDialog
|
||||||
|
@onready var groupNameInput = $CreateGroupDialog/VBoxContainer/LineEdit
|
||||||
|
|
||||||
|
var currentSprite = null
|
||||||
|
var updating_dropdown = false # Flag to prevent recursive calls
|
||||||
|
|
||||||
|
signal group_changed(sprite_id: int, group_name: String)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Wait for autoload to be ready
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
# Connect UI signals - no add button to connect
|
||||||
|
groupDropdown.item_selected.connect(_on_group_dropdown_selected)
|
||||||
|
createGroupDialog.confirmed.connect(_on_create_group_confirmed)
|
||||||
|
createGroupDialog.canceled.connect(_on_create_group_cancelled)
|
||||||
|
|
||||||
|
# Connect to wobble sync manager signals
|
||||||
|
if WobbleSyncManager:
|
||||||
|
WobbleSyncManager.group_created.connect(_on_group_created)
|
||||||
|
WobbleSyncManager.group_deleted.connect(_on_group_deleted)
|
||||||
|
|
||||||
|
# Setup create group dialog
|
||||||
|
createGroupDialog.title = "Create Wobble Sync Group"
|
||||||
|
createGroupDialog.size = Vector2(300, 150)
|
||||||
|
createGroupDialog.unresizable = false
|
||||||
|
createGroupDialog.transient = true # Prevent click-through
|
||||||
|
createGroupDialog.exclusive = true # Make modal
|
||||||
|
createGroupDialog.popup_window = false # Keep as embedded dialog
|
||||||
|
createGroupDialog.always_on_top = true
|
||||||
|
|
||||||
|
updateUI()
|
||||||
|
|
||||||
|
## Update the UI based on current sprite selection
|
||||||
|
func updateUI():
|
||||||
|
updateGroupDropdown()
|
||||||
|
updateSyncIndicator()
|
||||||
|
|
||||||
|
## Update the group dropdown with available options
|
||||||
|
func updateGroupDropdown():
|
||||||
|
updating_dropdown = true # Prevent recursive calls
|
||||||
|
|
||||||
|
groupDropdown.clear()
|
||||||
|
|
||||||
|
# Add "None" option
|
||||||
|
groupDropdown.add_item("None")
|
||||||
|
|
||||||
|
# Add existing groups
|
||||||
|
if WobbleSyncManager:
|
||||||
|
var groups = WobbleSyncManager.getGroupNames()
|
||||||
|
print("DEBUG: updateGroupDropdown - available groups: ", groups)
|
||||||
|
for group_name in groups:
|
||||||
|
groupDropdown.add_item(group_name)
|
||||||
|
else:
|
||||||
|
print("DEBUG: updateGroupDropdown - WobbleSyncManager not available")
|
||||||
|
|
||||||
|
# Add "Create New..." option
|
||||||
|
groupDropdown.add_separator()
|
||||||
|
groupDropdown.add_item("Create New...")
|
||||||
|
|
||||||
|
# Select current group if sprite is synced
|
||||||
|
if currentSprite != null and currentSprite.wobbleSyncGroup != "":
|
||||||
|
print("DEBUG: updateGroupDropdown - sprite has group '", currentSprite.wobbleSyncGroup, "'")
|
||||||
|
var group_index = -1
|
||||||
|
for i in range(groupDropdown.get_item_count()):
|
||||||
|
if groupDropdown.get_item_text(i) == currentSprite.wobbleSyncGroup:
|
||||||
|
group_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if group_index != -1:
|
||||||
|
print("DEBUG: updateGroupDropdown - selecting group at index ", group_index)
|
||||||
|
groupDropdown.selected = group_index
|
||||||
|
else:
|
||||||
|
print("DEBUG: updateGroupDropdown - group '", currentSprite.wobbleSyncGroup, "' not found in dropdown")
|
||||||
|
groupDropdown.selected = 0 # Select "None"
|
||||||
|
else:
|
||||||
|
print("DEBUG: updateGroupDropdown - sprite has no group, selecting 'None'")
|
||||||
|
groupDropdown.selected = 0 # Select "None"
|
||||||
|
|
||||||
|
updating_dropdown = false
|
||||||
|
|
||||||
|
## Update the sync indicator
|
||||||
|
func updateSyncIndicator():
|
||||||
|
if currentSprite == null:
|
||||||
|
syncIndicator.visible = false
|
||||||
|
return
|
||||||
|
|
||||||
|
if currentSprite.isSynced():
|
||||||
|
syncIndicator.visible = true
|
||||||
|
syncIndicator.text = "⚡ Synced to \"" + currentSprite.wobbleSyncGroup + "\""
|
||||||
|
else:
|
||||||
|
syncIndicator.visible = false
|
||||||
|
|
||||||
|
## Set the current sprite to manage
|
||||||
|
func setSprite(sprite):
|
||||||
|
print("DEBUG: WobbleSyncControl.setSprite called with sprite ID: ", sprite.id if sprite else "null")
|
||||||
|
currentSprite = sprite
|
||||||
|
if currentSprite:
|
||||||
|
print("DEBUG: Current sprite wobbleSyncGroup: '", currentSprite.wobbleSyncGroup, "'")
|
||||||
|
print("DEBUG: Current sprite isSynced(): ", currentSprite.isSynced())
|
||||||
|
updateUI()
|
||||||
|
|
||||||
|
## Get available groups for the dropdown (excluding "None" and "Create New...")
|
||||||
|
func getAvailableGroups() -> Array:
|
||||||
|
var groups = []
|
||||||
|
for i in range(1, groupDropdown.get_item_count() - 2): # Skip "None" and "Create New..."
|
||||||
|
groups.append(groupDropdown.get_item_text(i))
|
||||||
|
return groups
|
||||||
|
|
||||||
|
## Handle group dropdown selection
|
||||||
|
func _on_group_dropdown_selected(index: int):
|
||||||
|
if currentSprite == null or updating_dropdown:
|
||||||
|
return
|
||||||
|
|
||||||
|
var selected_text = groupDropdown.get_item_text(index)
|
||||||
|
print("DEBUG: Dropdown selected: '", selected_text, "' at index ", index)
|
||||||
|
|
||||||
|
match selected_text:
|
||||||
|
"None":
|
||||||
|
# Remove sprite from any group
|
||||||
|
if currentSprite.wobbleSyncGroup != "" and WobbleSyncManager:
|
||||||
|
print("DEBUG: Removing sprite ID ", currentSprite.id, " from group '", currentSprite.wobbleSyncGroup, "'")
|
||||||
|
WobbleSyncManager.removeSpriteFromAllGroups(currentSprite.id)
|
||||||
|
Global.pushUpdate("Removed sprite from wobble sync group")
|
||||||
|
|
||||||
|
"Create New...":
|
||||||
|
print("DEBUG: User selected 'Create New...' from dropdown")
|
||||||
|
# Show create group dialog
|
||||||
|
groupNameInput.text = ""
|
||||||
|
groupNameInput.placeholder_text = "Enter group name..."
|
||||||
|
createGroupDialog.popup_centered_clamped(Vector2i(300, 150))
|
||||||
|
groupNameInput.grab_focus()
|
||||||
|
# Reset dropdown to current selection
|
||||||
|
updateGroupDropdown()
|
||||||
|
|
||||||
|
_:
|
||||||
|
# Join selected group
|
||||||
|
if WobbleSyncManager and WobbleSyncManager.hasGroup(selected_text):
|
||||||
|
print("DEBUG: Adding sprite ID ", currentSprite.id, " to group '", selected_text, "'")
|
||||||
|
WobbleSyncManager.addSpriteToGroup(currentSprite.id, selected_text)
|
||||||
|
# Refresh sprite editor to show updated wobble values
|
||||||
|
if Global.spriteEdit:
|
||||||
|
Global.spriteEdit.setImage()
|
||||||
|
else:
|
||||||
|
print("DEBUG: Group '", selected_text, "' not found or WobbleSyncManager not available")
|
||||||
|
|
||||||
|
updateSyncIndicator()
|
||||||
|
|
||||||
|
## Handle create group dialog confirmation
|
||||||
|
func _on_create_group_confirmed():
|
||||||
|
var group_name = groupNameInput.text.strip_edges()
|
||||||
|
print("DEBUG: Create group confirmed with name: '", group_name, "'")
|
||||||
|
|
||||||
|
if group_name == "":
|
||||||
|
print("DEBUG: Group name is empty")
|
||||||
|
Global.pushUpdate("Group name cannot be empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
if WobbleSyncManager and WobbleSyncManager.hasGroup(group_name):
|
||||||
|
print("DEBUG: Group '", group_name, "' already exists")
|
||||||
|
Global.pushUpdate("Group \"" + group_name + "\" already exists")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create group with current sprite
|
||||||
|
if currentSprite != null and WobbleSyncManager:
|
||||||
|
print("DEBUG: Creating group '", group_name, "' with sprite ID ", currentSprite.id)
|
||||||
|
if WobbleSyncManager.createGroup(group_name, currentSprite.id):
|
||||||
|
print("DEBUG: Group created successfully")
|
||||||
|
createGroupDialog.hide()
|
||||||
|
updateUI()
|
||||||
|
# Refresh sprite editor to show sync status
|
||||||
|
if Global.spriteEdit:
|
||||||
|
Global.spriteEdit.setImage()
|
||||||
|
else:
|
||||||
|
print("DEBUG: Failed to create group")
|
||||||
|
else:
|
||||||
|
print("DEBUG: No current sprite to add to group")
|
||||||
|
|
||||||
|
## Handle create group dialog cancellation
|
||||||
|
func _on_create_group_cancelled():
|
||||||
|
createGroupDialog.hide()
|
||||||
|
updateGroupDropdown() # Reset dropdown to current selection
|
||||||
|
|
||||||
|
## Handle group creation signal from manager
|
||||||
|
func _on_group_created(group_name: String):
|
||||||
|
updateGroupDropdown()
|
||||||
|
|
||||||
|
## Handle group deletion signal from manager
|
||||||
|
func _on_group_deleted(group_name: String):
|
||||||
|
updateGroupDropdown()
|
||||||
|
updateSyncIndicator()
|
||||||
|
|
||||||
|
## Check if current sprite can have wobble syncing applied
|
||||||
|
func canSync() -> bool:
|
||||||
|
return currentSprite != null
|
||||||
|
|
||||||
|
## Force refresh the UI - useful after loading saves
|
||||||
|
func forceRefresh():
|
||||||
|
print("DEBUG: WobbleSyncControl.forceRefresh called")
|
||||||
|
updateUI()
|
||||||
1
ui_scenes/spriteEditMenu/wobble_sync_control.gd.uid
Normal file
1
ui_scenes/spriteEditMenu/wobble_sync_control.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ykov43vxml0k
|
||||||
59
ui_scenes/spriteEditMenu/wobble_sync_control.tscn
Normal file
59
ui_scenes/spriteEditMenu/wobble_sync_control.tscn
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bkwf3j2pdspxw"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://ykov43vxml0k" path="res://ui_scenes/spriteEditMenu/wobble_sync_control.gd" id="1_k8h4v"]
|
||||||
|
|
||||||
|
[node name="WobbleSyncControl" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_k8h4v")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="GroupRow" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GroupLabel" type="Label" parent="VBoxContainer/GroupRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 0
|
||||||
|
text = "Group:"
|
||||||
|
|
||||||
|
[node name="GroupDropdown" type="OptionButton" parent="VBoxContainer/GroupRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="SyncIndicator" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "⚡ Synced to \"Group Name\""
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="CreateGroupDialog" type="ConfirmationDialog" parent="."]
|
||||||
|
process_mode = 3
|
||||||
|
title = "Create Wobble Sync Group"
|
||||||
|
size = Vector2i(300, 150)
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="CreateGroupDialog"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 8.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -8.0
|
||||||
|
offset_bottom = -49.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="CreateGroupDialog/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Enter name for new wobble sync group:"
|
||||||
|
|
||||||
|
[node name="LineEdit" type="LineEdit" parent="CreateGroupDialog/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
placeholder_text = "Group name..."
|
||||||
Reference in New Issue
Block a user