Compare commits

...

27 Commits

Author SHA1 Message Date
085d6a21a0 Update README.md !build 2025-07-20 06:33:37 +10:00
99d2d3f508 Update UI positions during zoom changes 2025-07-20 06:26:23 +10:00
dad22ece22 !build fixed opacity shader on open 2025-07-20 06:19:21 +10:00
52496df10e added panning to edit mode 2025-07-20 06:13:09 +10:00
29b903fadd Improve GitHub Actions build automation workflow
- Update build automation wording and descriptions
- Refine workflow trigger conditions
- Enhance build process testing and validation
- Consolidate multiple build automation improvements
2025-07-20 05:48:34 +10:00
e8191e2de8 Merge branch 'master' of https://github.com/litruv/PNGTuber-Plus 2025-07-20 05:34:26 +10:00
b7b7d7cdec test some build description stuff out !build 2025-07-20 05:34:24 +10:00
fc6e4a1028 Update README.md 2025-07-20 05:28:11 +10:00
1dfe21363b update wording 2025-07-20 04:42:36 +10:00
fde3f23ed5 build trigger 2025-07-20 04:17:21 +10:00
79e54c28da Merge branch 'master' of https://github.com/litruv/PNGTuber-Plus 2025-07-20 04:15:13 +10:00
a8f327ae5a Add opacity slider with shader-based rendering that preserves clip mask behavior
- Added opacity slider (0-100%) in sprite options with 'affect children' checkbox
- Implemented hierarchical opacity that affects entire sprite hierarchy when enabled
- Created shader-based opacity system that doesn't interfere with clip mask functionality
- Clip masks now work with original image pixels regardless of opacity settings
- Removed shadow effects from selected sprites to prevent visual conflicts
- Updated save/load system to persist opacity properties with backward compatibility

Fixes clipping behavior issues where opacity changes previously affected clip mask detection
2025-07-20 04:14:58 +10:00
1295bcbcba Add opacity slider with shader-based rendering that preserves clip mask behavior
- Added opacity slider (0-100%) in sprite options with 'affect children' checkbox
- Implemented hierarchical opacity that affects entire sprite hierarchy when enabled
- Created shader-based opacity system that doesn't interfere with clip mask functionality
- Clip masks now work with original image pixels regardless of opacity settings
- Removed shadow effects from selected sprites to prevent visual conflicts
- Updated save/load system to persist opacity properties with backward compatibility

Fixes clipping behavior issues where opacity changes previously affected clip mask detection
2025-07-20 04:07:41 +10:00
758f2ca885 fix key listening again 2025-07-20 02:48:16 +10:00
524920d140 Merge branch 'master' of https://github.com/litruv/PNGTuber-Plus 2025-07-20 02:38:29 +10:00
f9c72448d9 Merge pull request #2 from litruv/copilot/fix-1
Fix costume key assignment regression - resolves "awaiting input" hang
2025-07-20 02:31:13 +10:00
copilot-swe-agent[bot]
135e63ef52 Fix costume key assignment regression by handling mouse click cancellation
Co-authored-by: litruv <3798007+litruv@users.noreply.github.com>
2025-07-19 16:25:12 +00:00
copilot-swe-agent[bot]
c86e68d383 Initial plan 2025-07-19 16:17:53 +00:00
f226c14c00 Revert "adding mask options"
This reverts commit c74dccf1cf.
2025-07-20 02:12:22 +10:00
d345e700fc Merge branch 'master' of https://github.com/litruv/PNGTuber-Plus 2025-07-20 00:03:50 +10:00
bc187cff09 Improve GitHub Actions workflow versioning and release formatting
- Add automated release info generation with date-based versioning
- Generate semantic version tags (v2025.MM.DD-buildNUMBER format)
- Implement dynamic build numbering based on commit count
- Streamline release name format to 'PNGTuber+ vDATE Build NUMBER'
- Update release body with proper credits and installation instructions
- Remove hardcoded release descriptions in favor of standardized format
2025-07-20 00:03:23 +10:00
fc4cacdc9f Update release name format in build workflow 2025-07-19 23:58:28 +10:00
848361d19b update versioning to be not awful 2025-07-19 23:52:46 +10:00
711865c6a4 update kaiakairos social to bluesky 2025-07-19 23:42:03 +10:00
d231b730f7 Add experimental mic loudness UI toggle to settings 2025-07-19 22:16:40 +10:00
8ba361f3c2 Integrate microphone improvements from k0ffinz repository
- Add experimental microphone loudness detection feature
- Remove microphone restart interval for better stability
- Improve microphone initialization and error handling
- Fix audio delay issues by setting channel_disable_time=0.0
- Update settings UI to include experimental mic loudness toggle
- Add credits to k0ffinz/Ralsi for microphone improvements

These changes improve audio responsiveness and fix various
microphone-related issues including WASAPI errors and growing
audio delay problems.
2025-07-19 22:00:53 +10:00
c74dccf1cf adding mask options 2025-07-19 21:52:49 +10:00
20 changed files with 473 additions and 121 deletions

View File

@@ -16,6 +16,7 @@ jobs:
export-windows:
name: Windows Export
runs-on: self-hosted
if: contains(github.event.head_commit.message, '!build')
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -99,6 +100,74 @@ jobs:
runs-on: self-hosted
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && success()
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate Release Info
id: release_info
run: |
# Get current date and time
$date = Get-Date -Format "yyyy.MM.dd"
$time = Get-Date -Format "HHmm"
# Get short commit hash (first 7 characters)
$shortSha = "${{ github.sha }}".Substring(0, 7)
# Get commit count since last tag (for build number)
$buildNumber = & git rev-list --count HEAD
# Create version-like tag and release name
$tag = "v$date-build$buildNumber"
$releaseName = "PNGTuber+ v$date Build $buildNumber"
Write-Host "Generated tag: $tag"
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
echo "tag=$tag" >> $env:GITHUB_OUTPUT
echo "release_name=$releaseName" >> $env:GITHUB_OUTPUT
echo "short_sha=$shortSha" >> $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
uses: actions/download-artifact@v4
with:
@@ -111,21 +180,18 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: build-${{ github.sha }}
release_name: PNGTuber Plus Build ${{ github.sha }}
tag_name: ${{ steps.release_info.outputs.tag }}
release_name: ${{ steps.release_info.outputs.release_name }}
body: |
Automated build of PNGTuber Plus
## Recent Updates by Litruv
- **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
${{ steps.release_info.outputs.commit_history }}
## Installation
1. Download PNGTuber-Plus.exe from the assets below
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)
draft: false
prerelease: false

11
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build PNGTuber-Plus",
"type": "shell",
"command": "cd c:\\DEV\\Sync\\PNGTuber-Plus; .\\build-windows.bat",
"group": "build"
}
]
}

View File

@@ -2,15 +2,61 @@
![Build Status](https://github.com/litruv/PNGTuber-Plus/workflows/Build%20Windows/badge.svg)
### Recent Updates by Litruv
- **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
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.
## 🙏 Credits
---
- **Original Project**: PNGTuber Plus base application
- **StreamDeck Plugin**: Based on [BoyneGames StreamDeck Godot Plugin](https://github.com/BoyneGames/streamdeck-godot-plugin)
- **Enhanced Features**: Max Litruv Boonzaayer
## 🔧 Fork Changes (Litruv Edition)
### 🎛️ Opacity Controls
Added a proper opacity slider for sprites, with an "affect children" toggle. Clip masks still behave like they should.
![Opacity Demo](https://github.com/user-attachments/assets/d5a05508-1cb1-46e0-8c90-d0aac73398db)
### 🌀 Rotation Wobble
Sprites can now do a subtle (or chaotic) rotational wobble. Frequency + amplitude sliders included.
![Rotation Wobble](https://github.com/user-attachments/assets/f45c3cc8-98d8-48cf-8821-b015de0a2f78)
### ⌨️ Enhanced Modifier Keys
Supports combos like Ctrl+Shift+Alt+Cmd. For people with more keybinds than fingers.
![Modifier Keys](https://github.com/user-attachments/assets/6d492134-f929-4e3e-a6c6-a89cf16cd62f)
### ↗️ Middle Mouse Button Panning
Hold MMB in the editor to pan around the scene, it'll reset when you go back to live.
![Panning](https://github.com/user-attachments/assets/b8687b69-fc84-43c9-950b-70395ae54820)
### 🎤 Microphone Fixes
Merged in [k0ffinzs](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)

View File

@@ -29,13 +29,13 @@ var blink = false
var blinkTick = 0
#Audio Listener
var currentMicrophone = null
var playa:AudioStreamPlayer
var speaking = false
var spectrum
var volume = 0
var volumeSensitivity = 0.0
var experimentalMicLoudness = false
var volumeLimit = 0.0
var senseLimit = 0.0
@@ -44,8 +44,6 @@ var senseLimit = 0.0
signal startSpeaking
signal stopSpeaking
var micResetTime = 180
var updatePusherNode = null
var rand = RandomNumberGenerator.new()
@@ -56,28 +54,23 @@ func _ready():
if !Saving.settings.has("useStreamDeck"):
Saving.settings["useStreamDeck"] = false
if Saving.settings.has("secondsToMicReset"):
Global.micResetTime = Saving.settings["secondsToMicReset"]
if Saving.settings.has("experimentalMicLoudness"):
Global.experimentalMicLoudness = Saving.settings["experimentalMicLoudness"]
else:
Saving.settings["secondsToMicReset"] = 180
Saving.settings["experimentalMicLoudness"] = false
createMicrophone()
func createMicrophone():
var playa = AudioStreamPlayer.new()
var mic = AudioStreamMicrophone.new()
playa.stream = mic
playa.autoplay = true
if playa != null:
remove_child(playa)
playa.free()
playa = AudioStreamPlayer.new()
playa.bus = "MIC"
playa.stream = AudioStreamMicrophone.new()
add_child(playa)
currentMicrophone = playa
await get_tree().create_timer(micResetTime).timeout
if currentMicrophone != playa:
return
deleteAllMics()
currentMicrophone = null
await get_tree().create_timer(0.25).timeout
createMicrophone()
await get_tree().create_timer(0.25).timeout # apparently it works to fix WASAPI "Initialize failed with error 0xffffffff88890002" and "init_input_device" errors
playa.play()
func deleteAllMics():
for child in get_children():
@@ -87,8 +80,9 @@ func deleteAllMics():
func _process(delta):
animationTick += 1
volume = spectrum.get_magnitude_for_frequency_range(20, 20000).length()
if currentMicrophone != null:
volume = (AudioServer.get_bus_peak_volume_left_db(1, 0) + AudioServer.get_bus_peak_volume_right_db(1, 0)) / 1600.0 + 0.25 if experimentalMicLoudness else spectrum.get_magnitude_for_frequency_range(20, 20000).length()
if playa != null:
volumeSensitivity = lerp(volumeSensitivity,0.0,delta*2)
if volume>volumeLimit:
@@ -112,7 +106,7 @@ func _process(delta):
heldSprite.z += 1
heldSprite.setZIndex()
pushUpdate("Moved sprite layer.")
if main.editMode:
if main and main.editMode:
if Input.is_action_just_pressed("reparent"):
reparentMode = !reparentMode
Global.chain.enable(reparentMode)
@@ -121,7 +115,7 @@ func _process(delta):
reparentMode = false
Global.chain.enable(reparentMode)
if main.editMode:
if main and main.editMode:
if reparentMode:
RenderingServer.set_default_clear_color(Color.POWDER_BLUE)
else:
@@ -132,7 +126,7 @@ func _process(delta):
scrollSprites()
if !main.fileSystemOpen:
if !main or main.fileSystemOpen:
if Input.is_action_just_pressed("refresh"):
refresh()
@@ -228,7 +222,7 @@ func scrollSprites():
if Input.is_action_pressed("control"):
return
if !main.editMode:
if !main or !main.editMode:
return
if main.fileSystemOpen:

View File

@@ -54,13 +54,13 @@ var settings = {
"bounce":250,
"gravity":1000,
"maxFPS":60,
"secondsToMicReset":180,
"backgroundColor":var_to_str(Color(0.0,0.0,0.0,0.0)),
"filtering":false,
"costumeKeys":["1","2","3","4","5","6","7","8","9","0"],
"blinkSpeed":1.0,
"blinkChance":200,
"bounceOnCostumeChange":false,
"experimentalMicLoudness":false,
}
var settingsPath = "user://settings.pngtp"

View File

@@ -43,6 +43,12 @@ var bounceOnCostumeChange = false
#Zooming
var scaleOverall = 100
#Camera Panning
var isPanning = false
var initialPanPosition = Vector2.ZERO
var initialCameraOffset = Vector2.ZERO
var cameraOffset = Vector2.ZERO
var bounceChange = 0.0
#IMPORTANT
@@ -130,7 +136,7 @@ func _ready():
var s = get_viewport().get_visible_rect().size
origin.position = s*0.5
camera.position = origin.position
updateCameraPosition()
func _process(delta):
var hold = origin.get_parent().position.y
@@ -152,18 +158,46 @@ func _process(delta):
followShadow()
func followShadow():
shadow.visible = is_instance_valid(Global.heldSprite)
if !shadow.visible:
return
## Handle input events, especially for camera panning
func _input(event):
# 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:
# Reset camera in view mode
resetCameraPosition()
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
# Handle mouse movement during panning
elif editMode and event is InputEventMouseMotion and isPanning:
var totalDelta = event.global_position - initialPanPosition
shadow.texture = Global.heldSprite.sprite.texture
shadow.hframes = Global.heldSprite.sprite.hframes
shadow.frame = Global.heldSprite.sprite.frame
# Calculate new camera offset from initial position plus total movement
cameraOffset = initialCameraOffset - totalDelta
# 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
func followShadow():
# Shadow disabled for selected sprites
shadow.visible = false
func isFileSystemOpen():
@@ -198,14 +232,8 @@ func onWindowSizeChange():
lines.position = s*0.5
lines.drawLine()
camera.position = origin.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
updateCameraPosition()
spriteList.position.x = s.x - 233
pushUpdates.position.y = controlPanel.position.y
pushUpdates.position.x = editControls.position.x
func zoomScene():
#Handles Zooming
@@ -233,11 +261,43 @@ func changeZoom():
pushUpdates.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.text = "Zoom : " + 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
updateCameraPosition()
Global.pushUpdate("Camera reset to center.")
#When the user speaks!
func onSpeak():
@@ -252,6 +312,10 @@ func swapMode():
editMode = !editMode
Global.pushUpdate("Toggled editing mode.")
# Reset camera when entering view mode
if !editMode:
resetCameraPosition()
get_viewport().transparent_bg = !editMode
if Global.backgroundColor.a != 0.0:
get_viewport().transparent_bg = false
@@ -362,6 +426,10 @@ func _on_load_dialog_file_selected(path):
sprite.clipped = data[item]["clipped"]
if data[item].has("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"]
origin.add_child(sprite)
sprite.position = str_to_var(data[item]["pos"])
@@ -370,6 +438,13 @@ func _on_load_dialog_file_selected(path):
Saving.settings["lastAvatar"] = path
Global.spriteList.updateData()
# Update opacity shaders for all loaded sprites after they're all created
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)
onWindowSizeChange()
@@ -424,6 +499,9 @@ func _on_save_dialog_file_selected(path):
data[id]["toggle"] = child.toggle
data[id]["spriteOpacity"] = child.spriteOpacity
data[id]["affectChildrenOpacity"] = child.affectChildrenOpacity
id += 1
Saving.settings["lastAvatar"] = path
@@ -445,9 +523,9 @@ func _on_kofi_pressed():
Global.pushUpdate("Support me on ko-fi!")
func _on_twitter_pressed():
OS.shell_open("https://twitter.com/kaiakairos")
Global.pushUpdate("Follow me on twitter!")
func _on_bluesky_pressed():
OS.shell_open("https://bsky.app/profile/kaiakairos.net")
Global.pushUpdate("Follow me on bluesky!")
func _on_replace_button_pressed():
@@ -500,9 +578,16 @@ func _on_duplicate_button_pressed():
sprite.costumeLayers = Global.heldSprite.costumeLayers
sprite.spriteOpacity = Global.heldSprite.spriteOpacity
sprite.affectChildrenOpacity = Global.heldSprite.affectChildrenOpacity
origin.add_child(sprite)
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.spriteList.updateData()
@@ -547,7 +632,7 @@ func moveSpriteMenu(delta):
var size = get_viewport().get_visible_rect().size
var windowLength = 1400
var windowLength = 1500
$ViewerArrows/Arrows.position.y = size.y - 25
@@ -627,47 +712,49 @@ 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:
continue
var key_name = OS.get_keycode_string(i) if !OS.get_keycode_string(i).strip_edges().is_empty() else "Keycode" + str(i)
keyStrings.append(modifier_prefix + key_name)
# Skip mouse button keycodes (common source of phantom inputs)
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:
return
if keyStrings.size() <= 0:
emit_signal("emptiedCapture")
emit_signal("fatfuckingballs")
return
if settingsMenu.awaitingCostumeInput >= 0:
if keyStrings[0] == "Keycode1":
if !settingsMenu.hasMouse:
emit_signal("pressedKey")
return
# Cancel key assignment if user clicks outside the settings menu
if keyStrings[0] == "Keycode1" and !settingsMenu.hasMouse:
# Don't assign the key, just complete the flow
emit_signal("pressedKey")
return
var currentButton = costumeKeys[settingsMenu.awaitingCostumeInput]
costumeKeys[settingsMenu.awaitingCostumeInput] = keyStrings[0]
Saving.settings["costumeKeys"] = costumeKeys
Global.pushUpdate("Changed costume " + str(settingsMenu.awaitingCostumeInput+1) + " hotkey from \"" + currentButton + "\" to \"" + keyStrings[0] + "\"")
emit_signal("pressedKey")
return
# Handle sprite visibility toggles if not waiting for costume input
spriteVisToggles.emit(keyStrings)
for key in keyStrings:
var i = costumeKeys.find(key)
if i >= 0:
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)

View File

@@ -14341,7 +14341,7 @@ offset_bottom = 27.0
theme = SubResource("Theme_rv56a")
flat = true
[node name="twitter" type="Button" parent="ControlPanel/Links"]
[node name="bluesky" type="Button" parent="ControlPanel/Links"]
offset_left = -5.0
offset_top = -29.0
offset_right = 61.0
@@ -14730,7 +14730,7 @@ z_index = -4096
[connection signal="pressed" from="ControlPanel/SettingsButton/settingsButtons" to="." method="_on_settings_buttons_pressed"]
[connection signal="pressed" from="ControlPanel/Edit/Button" to="." method="swapMode"]
[connection signal="pressed" from="ControlPanel/Links/kofi" to="." method="_on_kofi_pressed"]
[connection signal="pressed" from="ControlPanel/Links/twitter" to="." method="_on_twitter_pressed"]
[connection signal="pressed" from="ControlPanel/Links/bluesky" to="." method="_on_bluesky_pressed"]
[connection signal="pressed" from="EditControls/Add/addButton" to="." method="_on_add_button_pressed"]
[connection signal="pressed" from="EditControls/Link/linkButton" to="." method="_on_link_button_pressed"]
[connection signal="pressed" from="EditControls/Exit/Button2" to="." method="swapMode"]
@@ -14744,5 +14744,4 @@ z_index = -4096
[connection signal="visibility_changed" from="ReplaceDialog" to="." method="_on_replace_dialog_visibility_changed"]
[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="bg_key_pressed" from="BackgroundInputCapture" to="." method="bgInputSprite"]
[connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="_on_background_input_capture_bg_key_pressed"]

View File

@@ -23,6 +23,7 @@ config/icon="res://icon.png"
[audio]
driver/enable_input=true
buses/channel_disable_time=0.0
[autoload]
@@ -135,6 +136,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)
]
}
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={
"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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -7,17 +7,11 @@ func _ready():
func _on_button_pressed():
if !get_parent().get_parent().get_parent().visible:
return
AudioServer.input_device = micName
Global.deleteAllMics()
Global.currentMicrophone = null
Global.createMicrophone()
get_parent().get_parent().get_parent().visible = false
await get_tree().create_timer(1.0).timeout
Global.createMicrophone()

View File

@@ -9,7 +9,7 @@ func _ready():
Global.mouse = self
func _process(delta):
if Global.main.editMode:
if Global.main and Global.main.editMode:
if text != "":
label.text = text
visible = true

View 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;
}

View File

@@ -0,0 +1 @@
uid://byxs44vljsg2f

View File

@@ -28,6 +28,9 @@ var imageSize = Vector2.ZERO
@onready var wob = $WobbleOrigin
@onready var outlineScene = preload("res://ui_scenes/selectedSprite/outline.tscn")
@onready var opacityShader = preload("res://ui_scenes/selectedSprite/opacity.gdshader")
var opacityMaterial = null
#Visuals
var mouseOffset = Vector2.ZERO
@@ -83,6 +86,10 @@ var clipped = false
var tick = 0
#Opacity
var spriteOpacity = 1.0
var affectChildrenOpacity = false
#Vis toggle
var toggle = "null"
@@ -262,10 +269,27 @@ func setZIndex():
sprite.z_index = z
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 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
sprite.modulate.a = baseAlpha # Only apply talk/blink/edit visibility
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
func delete():
queue_free()
@@ -403,6 +427,39 @@ func getAllLinkedSprites():
linkedSprites.append(node)
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()
if totalOpacity >= 1.0:
# No shader needed for 100% opacity
sprite.material = null
else:
# Create or update shader material for opacity less than 100%
if opacityMaterial == null:
opacityMaterial = ShaderMaterial.new()
opacityMaterial.shader = opacityShader
opacityMaterial.set_shader_parameter("opacity", totalOpacity)
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):
if keys.has(toggle):
$WobbleOrigin/DragOrigin.visible = !$WobbleOrigin/DragOrigin.visible

View File

@@ -46,6 +46,8 @@ func setvalues():
label.text = "costume " + str(tag) + " key: \"" + Global.main.costumeKeys[tag-1] + "\""
tag += 1
$experimentalMicLoudness/checkmark.button_pressed = Global.experimentalMicLoudness
func _on_color_picker_button_color_changed(color):
get_viewport().transparent_bg = false
RenderingServer.set_default_clear_color(color)
@@ -136,13 +138,10 @@ func _on_bounce_gravity_value_changed(value):
func costumeButtonsPressed(label,id):
label.text = "AWAITING INPUT"
await Global.main.emptiedCapture
awaitingCostumeInput = id - 1
await Global.main.pressedKey
label.text = "costume " + str(id) + " key: \"" + Global.main.costumeKeys[id - 1] + "\""
await Global.main.emptiedCapture
awaitingCostumeInput = -1
func _on_costume_button_1_pressed():
@@ -206,6 +205,13 @@ func _on_stream_deck_check_toggled(button_pressed):
ElgatoStreamDeck.refresh_connection()
Global.pushUpdate("StreamDeck integration " + ("enabled" if button_pressed else "disabled") + ".")
## Handle experimental microphone loudness toggle
## @param checked: bool - Whether experimental mic loudness should be enabled
func _on_experimental_mic_loudness_toggle(checked):
Global.experimentalMicLoudness = checked
Saving.settings["experimentalMicLoudness"] = checked
print("Experimental mic loudness: ", checked)
func _process(delta):
var g = to_local(get_global_mouse_position())

View File

@@ -14289,46 +14289,67 @@ offset_bottom = 28.0
text = "confirm"
[node name="StreamDeck" type="Node2D" parent="."]
position = Vector2(16, 373)
position = Vector2(16, 337)
[node name="Label" type="Label" parent="StreamDeck"]
offset_right = 138.0
offset_bottom = 34.0
text = "use streamdeck"
metadata/_edit_use_anchors_ = true
[node name="streamDeckCheck" type="CheckBox" parent="StreamDeck"]
offset_left = 130.0
offset_top = 1.0
offset_right = 154.0
offset_bottom = 25.0
metadata/_edit_use_anchors_ = true
[node name="experimentalMicLoudness" type="Node2D" parent="."]
position = Vector2(16, 369)
[node name="Label" type="Label" parent="experimentalMicLoudness"]
offset_right = 200.0
offset_bottom = 34.0
text = "mic loudness"
[node name="checkmark" type="CheckBox" parent="experimentalMicLoudness"]
offset_left = 130.0
offset_top = 1.0
offset_right = 154.0
offset_bottom = 25.0
metadata/_edit_use_anchors_ = true
[node name="AntiAliasing" type="Node2D" parent="."]
position = Vector2(16, 405)
position = Vector2(16, 401)
[node name="Label" type="Label" parent="AntiAliasing"]
offset_right = 138.0
offset_bottom = 34.0
text = "texture filtering"
metadata/_edit_use_anchors_ = true
[node name="CheckBox" type="CheckBox" parent="AntiAliasing"]
offset_left = 130.0
offset_top = 1.0
offset_right = 154.0
offset_bottom = 25.0
metadata/_edit_use_anchors_ = true
[node name="bounceOnCostume" type="Node2D" parent="."]
position = Vector2(17, 341)
position = Vector2(17, 305)
[node name="Label" type="Label" parent="bounceOnCostume"]
offset_right = 138.0
offset_bottom = 34.0
text = "costume bounce"
metadata/_edit_use_anchors_ = true
[node name="costumeCheck" type="CheckBox" parent="bounceOnCostume"]
offset_left = 130.0
offset_top = 1.0
offset_right = 154.0
offset_bottom = 25.0
metadata/_edit_use_anchors_ = true
[node name="BounceForce" type="Node2D" parent="."]
position = Vector2(22, 115)
@@ -14832,7 +14853,7 @@ offset_bottom = -75.0
text = "costume hotkeys"
[node name="BlinkSpeed" type="Node2D" parent="."]
position = Vector2(17, 283)
position = Vector2(17, 253)
[node name="Label" type="Label" parent="BlinkSpeed"]
offset_left = -10.0
@@ -14841,6 +14862,7 @@ offset_right = 158.0
offset_bottom = 27.0
text = "blink speed: 1"
horizontal_alignment = 1
metadata/_edit_use_anchors_ = true
[node name="blinkSpeed" type="HSlider" parent="BlinkSpeed"]
offset_left = -9.0
@@ -14851,9 +14873,10 @@ theme = SubResource("Theme_e6tc4")
max_value = 20.0
value = 1.0
scrollable = false
metadata/_edit_use_anchors_ = true
[node name="BlinkChance" type="Node2D" parent="."]
position = Vector2(17, 235)
position = Vector2(17, 205)
[node name="Label" type="Label" parent="BlinkChance"]
offset_left = -10.0
@@ -14862,6 +14885,7 @@ offset_right = 160.0
offset_bottom = 27.0
text = "blink chance: 1 in 200"
horizontal_alignment = 1
metadata/_edit_use_anchors_ = true
[node name="blinkChance" type="HSlider" parent="BlinkChance"]
offset_left = -8.0
@@ -14873,6 +14897,7 @@ min_value = 1.0
max_value = 300.0
value = 200.0
scrollable = false
metadata/_edit_use_anchors_ = true
[connection signal="pressed" from="Background/Transparent/Button" to="." method="_on_button_pressed"]
[connection signal="pressed" from="Background/Green/greenButton" to="." method="_on_green_button_pressed"]
@@ -14883,6 +14908,7 @@ scrollable = false
[connection signal="value_changed" from="MaxFPS/fpsDrag" to="." method="_on_fps_drag_value_changed"]
[connection signal="pressed" from="MaxFPS/confirm" to="." method="_on_confirm_pressed"]
[connection signal="toggled" from="StreamDeck/streamDeckCheck" to="." method="_on_stream_deck_check_toggled"]
[connection signal="toggled" from="experimentalMicLoudness/checkmark" to="." method="_on_experimental_mic_loudness_toggle"]
[connection signal="toggled" from="AntiAliasing/CheckBox" to="." method="_on_check_box_toggled"]
[connection signal="toggled" from="bounceOnCostume/costumeCheck" to="." method="_on_costume_check_toggled"]
[connection signal="value_changed" from="BounceForce/bounceForce" to="." method="_on_bounce_force_value_changed"]

View File

@@ -73,6 +73,10 @@ func setImage():
$Animation/animFramesLabel.text = "sprite frames: " + str(Global.heldSprite.frames)
$Animation/animFrames.value = Global.heldSprite.frames
$Opacity/opacityLabel.text = "opacity: " + str(int(Global.heldSprite.spriteOpacity * 100)) + "%"
$Opacity/opacitySlider.value = Global.heldSprite.spriteOpacity
$Opacity/affectChildrenCheck.button_pressed = Global.heldSprite.affectChildrenOpacity
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
changeRotLimit()
@@ -369,3 +373,12 @@ func _on_set_toggle_pressed():
var key = keys[0]
Global.heldSprite.toggle = key
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
func _on_opacity_slider_value_changed(value):
$Opacity/opacityLabel.text = "opacity: " + str(int(value * 100)) + "%"
Global.heldSprite.spriteOpacity = value
Global.heldSprite.updateOpacity()
func _on_affect_children_check_toggled(button_pressed):
Global.heldSprite.affectChildrenOpacity = button_pressed
Global.heldSprite.updateOpacity()

View File

@@ -29,7 +29,7 @@
[ext_resource type="Texture2D" uid="uid://gflmeocxfnrq" path="res://ui_scenes/settings/deleteHotkey.png" id="27_fgu6g"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_21kva"]
size = Vector2(245, 1341)
size = Vector2(245, 1455)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hxmah"]
shader = ExtResource("3_ky0lu")
@@ -14285,11 +14285,12 @@ script = ExtResource("1_hp0e3")
z_index = -2
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
position = Vector2(122.5, 670.5)
position = Vector2(122.5, 727.5)
shape = SubResource("RectangleShape2D_21kva")
[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")
centered = false
offset = Vector2(-9, -10.3448)
@@ -14349,13 +14350,13 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.362)
fov = 36.5
[node name="Position" type="Node2D" parent="."]
position = Vector2(0, 10)
position = Vector2(0, 20)
[node name="fileTitle" type="Label" parent="Position"]
offset_left = 10.0
offset_top = 134.0
offset_top = 127.0
offset_right = 236.0
offset_bottom = 154.0
offset_bottom = 147.0
text = "user://greenThing/head1.png"
label_settings = SubResource("LabelSettings_6favr")
@@ -14381,7 +14382,7 @@ offset_bottom = 227.0
text = "layer : 0"
[node name="Buttons" type="Node2D" parent="."]
position = Vector2(0, 27)
position = Vector2(0, 141)
[node name="Speaking" type="Sprite2D" parent="Buttons"]
position = Vector2(42, 523)
@@ -14468,7 +14469,7 @@ scrollable = false
[node name="WobbleControl" type="Node2D" parent="."]
z_index = 1
position = Vector2(-3, 378)
position = Vector2(-3, 486)
[node name="animationBox" type="Sprite2D" parent="WobbleControl"]
position = Vector2(125, 384.5)
@@ -14612,7 +14613,7 @@ scrollable = false
[node name="RotationalLimits" type="Node2D" parent="."]
z_index = -1
position = Vector2(0, 325)
position = Vector2(0, 435)
[node name="RotBack" type="Sprite2D" parent="RotationalLimits"]
clip_children = 2
@@ -14725,8 +14726,43 @@ offset_bottom = 1068.0
theme = SubResource("Theme_mrbyn")
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="."]
position = Vector2(0, 327)
position = Vector2(0, 443)
[node name="Layer1" type="Sprite2D" parent="Layers"]
position = Vector2(23, 878)
@@ -14863,7 +14899,7 @@ position = Vector2(23, 878)
texture = ExtResource("20_px0dt")
[node name="VisToggle" type="Node2D" parent="."]
position = Vector2(11, 1289)
position = Vector2(11, 1404)
[node name="setToggle" type="Button" parent="VisToggle"]
custom_minimum_size = Vector2(0, 37.125)
@@ -14929,6 +14965,8 @@ centered = false
[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/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/Layer2/layerButton2" to="." method="_on_layer_button_2_pressed"]
[connection signal="pressed" from="Layers/Layer3/layerButton3" to="." method="_on_layer_button_3_pressed"]