Mostly done?
This commit is contained in:
parent
22d7326565
commit
a08c13b1a3
118 changed files with 2558 additions and 2519 deletions
79
scenes/scripts/animal.gd
Normal file
79
scenes/scripts/animal.gd
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
@tool
|
||||
extends Node2D
|
||||
|
||||
@export var animal_type : String:
|
||||
set(value):
|
||||
animal_type = value
|
||||
if is_node_ready():
|
||||
_update_sprite()
|
||||
|
||||
@export var flip_horizontal : bool = false:
|
||||
set(value):
|
||||
flip_horizontal = value
|
||||
if is_node_ready():
|
||||
_update_sprite()
|
||||
|
||||
@export var show_shavings : bool = false:
|
||||
set(value):
|
||||
show_shavings = value
|
||||
if is_node_ready():
|
||||
_update_shavings()
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
_update_sprite()
|
||||
_update_shavings()
|
||||
|
||||
|
||||
func _update_sprite() -> void:
|
||||
|
||||
# Hide all animal sprites first
|
||||
%Fox.visible = false
|
||||
%Porcupine.visible = false
|
||||
%Wolf.visible = false
|
||||
%Cat.visible = false
|
||||
%Goose.visible = false
|
||||
%Frog.visible = false
|
||||
%Chick.visible = false
|
||||
%Dog.visible = false
|
||||
|
||||
# Show the appropriate animal based on animal_type
|
||||
var active_sprite: AnimatedSprite2D
|
||||
match animal_type:
|
||||
"Fox":
|
||||
%Fox.visible = true
|
||||
active_sprite = %Fox
|
||||
"Porcupine":
|
||||
%Porcupine.visible = true
|
||||
active_sprite = %Porcupine
|
||||
"Wolf":
|
||||
%Wolf.visible = true
|
||||
active_sprite = %Wolf
|
||||
"Cat":
|
||||
%Cat.visible = true
|
||||
active_sprite = %Cat
|
||||
"Goose":
|
||||
%Goose.visible = true
|
||||
active_sprite = %Goose
|
||||
"Frog":
|
||||
%Frog.visible = true
|
||||
active_sprite = %Frog
|
||||
"Chick":
|
||||
%Chick.visible = true
|
||||
active_sprite = %Chick
|
||||
"Dog":
|
||||
%Dog.visible = true
|
||||
active_sprite = %Dog
|
||||
|
||||
# Apply horizontal flip if enabled
|
||||
if active_sprite:
|
||||
active_sprite.flip_h = flip_horizontal
|
||||
|
||||
|
||||
func _update_shavings() -> void:
|
||||
%Shavings.visible = show_shavings
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
1
scenes/scripts/animal.gd.uid
Normal file
1
scenes/scripts/animal.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://uhlsvqaaemre
|
||||
|
|
@ -1,58 +1,136 @@
|
|||
extends TextureButton
|
||||
@onready var label: Label = $CenterContainer/Label # Adjust path to your Label node
|
||||
@onready var name_label: Label = $CenterContainer/VBoxContainer/NameLabel
|
||||
@onready var price_label: Label = $CenterContainer/VBoxContainer/PriceLabel
|
||||
|
||||
var unlock_id = "" # Store the unlock ID
|
||||
var unlock_id = -1 # Store the unlock ID
|
||||
var unlock_description_text = "" # Store the description for custom tooltip
|
||||
|
||||
func _ready():
|
||||
label.visible = false # Hide label initially
|
||||
name_label.visible = false # Hide label initially
|
||||
price_label.visible = false
|
||||
adjust_label_font_size()
|
||||
# Connect the pressed signal
|
||||
pressed.connect(_on_button_pressed)
|
||||
# Connect to currency changes to update button state
|
||||
Inventory.currency_changed.connect(_on_currency_changed)
|
||||
# Connect to unlock events to update button state when items are unlocked
|
||||
Unlocks.item_unlocked.connect(_on_item_unlocked)
|
||||
|
||||
func setup(unlock_data):
|
||||
# Log.pr("Setting up button for unlock:", unlock_data.unlock_name)
|
||||
unlock_id = unlock_data.unlock_id # Store the ID
|
||||
if label:
|
||||
label.visible = false
|
||||
label.text = unlock_data.unlock_name + " " + str(unlock_data.get_next_rank())
|
||||
label.text = label.text + " - " + Global.currency_symbol + str(unlock_data.get_next_cost())
|
||||
label.text = label.text + "\n" + unlock_data.get_next_modifiers_string()
|
||||
#self.disabled = unlock_data.is_unlocked
|
||||
if name_label and price_label:
|
||||
name_label.visible = false
|
||||
price_label.visible = false
|
||||
update_button_state(unlock_data)
|
||||
adjust_label_font_size()
|
||||
else:
|
||||
Log.pr("Warning: Label node not found in button.")
|
||||
Log.pr("Warning: Label nodes not found in button.")
|
||||
|
||||
func update_button_state(unlock_data):
|
||||
# Store unlock description for custom tooltip
|
||||
if unlock_data.unlock_description:
|
||||
unlock_description_text = unlock_data.unlock_description
|
||||
tooltip_text = unlock_data.unlock_description
|
||||
|
||||
# Check if at max rank
|
||||
if not unlock_data.can_rank_up():
|
||||
self.disabled = true
|
||||
name_label.text = unlock_data.unlock_name + " (MAX)"
|
||||
price_label.text = ""
|
||||
return
|
||||
|
||||
# Build name text - only show rank if it's a scaling unlock
|
||||
if unlock_data.is_scaling:
|
||||
name_label.text = unlock_data.unlock_name + " " + str(unlock_data.get_next_rank())
|
||||
else:
|
||||
name_label.text = unlock_data.unlock_name
|
||||
|
||||
# Build price text
|
||||
price_label.text = Global.currency_symbol + Global.format_number(unlock_data.get_next_cost())
|
||||
|
||||
# Check if player has enough currency
|
||||
var cost = unlock_data.get_next_cost()
|
||||
var current_currency = Inventory.get_currency()
|
||||
self.disabled = current_currency < cost
|
||||
|
||||
func _on_currency_changed(new_amount: float):
|
||||
# Update button state when currency changes
|
||||
if unlock_id >= 0:
|
||||
var unlock_data = Unlocks.get_unlock_by_id(unlock_id)
|
||||
if unlock_data:
|
||||
update_button_state(unlock_data)
|
||||
|
||||
func _on_item_unlocked():
|
||||
# Update button state when any item is unlocked (in case this button reached max rank)
|
||||
if unlock_id >= 0:
|
||||
var unlock_data = Unlocks.get_unlock_by_id(unlock_id)
|
||||
if unlock_data:
|
||||
update_button_state(unlock_data)
|
||||
adjust_label_font_size()
|
||||
|
||||
func _on_button_pressed():
|
||||
# Log.pr("Button pressed, unlocking item:", unlock_id)
|
||||
Unlocks.unlock_item(unlock_id)
|
||||
|
||||
func adjust_label_font_size():
|
||||
if not label:
|
||||
if not name_label or not price_label:
|
||||
return
|
||||
var available_width = size.x - 10
|
||||
var available_height = size.y - 10
|
||||
# Start with a reasonable font size
|
||||
var font_size = 32
|
||||
|
||||
# Calculate font sizes for both labels
|
||||
# Name label gets 60% of the height, price label gets 40%
|
||||
var name_height = available_height * 0.6
|
||||
var price_height = available_height * 0.4
|
||||
|
||||
# Start with reasonable font sizes
|
||||
var name_font_size = 32
|
||||
var price_font_size = 24
|
||||
var min_font_size = 8
|
||||
# Get or create a font
|
||||
var font = label.get_theme_font("font")
|
||||
# Binary search for the optimal font size
|
||||
while font_size > min_font_size:
|
||||
label.add_theme_font_size_override("font_size", font_size)
|
||||
# Force update and get the actual text size
|
||||
await get_tree().process_frame
|
||||
var text_size = font.get_string_size(label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size)
|
||||
# Check if it fits
|
||||
if text_size.x <= available_width and text_size.y <= available_height:
|
||||
|
||||
# Get fonts
|
||||
var name_font = name_label.get_theme_font("font")
|
||||
var price_font = price_label.get_theme_font("font")
|
||||
|
||||
# Calculate name label font size without applying it
|
||||
while name_font_size > min_font_size:
|
||||
var text_size = name_font.get_string_size(name_label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, name_font_size)
|
||||
if text_size.x <= available_width and text_size.y <= name_height:
|
||||
break
|
||||
# Reduce font size and try again
|
||||
font_size -= 1
|
||||
label.add_theme_font_size_override("font_size", font_size)
|
||||
label.visible = true # Show label after resizing is complete
|
||||
name_font_size -= 1
|
||||
|
||||
# Calculate price label font size without applying it
|
||||
while price_font_size > min_font_size:
|
||||
var text_size = price_font.get_string_size(price_label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, price_font_size)
|
||||
if text_size.x <= available_width and text_size.y <= price_height:
|
||||
break
|
||||
price_font_size -= 1
|
||||
|
||||
# Apply both font sizes at once
|
||||
name_label.add_theme_font_size_override("font_size", name_font_size)
|
||||
price_label.add_theme_font_size_override("font_size", price_font_size)
|
||||
|
||||
name_label.visible = true
|
||||
price_label.visible = true
|
||||
|
||||
# Call this function whenever you change the label text
|
||||
func set_label_text(new_text: String):
|
||||
if label:
|
||||
label.visible = false # Hide while resizing
|
||||
label.text = new_text
|
||||
if name_label:
|
||||
name_label.visible = false # Hide while resizing
|
||||
price_label.visible = false
|
||||
name_label.text = new_text
|
||||
price_label.text = ""
|
||||
adjust_label_font_size()
|
||||
|
||||
# Override to create custom tooltip with larger font
|
||||
func _make_custom_tooltip(for_text: String) -> Object:
|
||||
var tooltip_label = Label.new()
|
||||
tooltip_label.text = for_text
|
||||
tooltip_label.add_theme_font_size_override("font_size", 26)
|
||||
|
||||
# Create a panel container for background
|
||||
var panel = PanelContainer.new()
|
||||
panel.add_child(tooltip_label)
|
||||
|
||||
return panel
|
||||
|
|
|
|||
41
scenes/scripts/config_panel.gd
Normal file
41
scenes/scripts/config_panel.gd
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
extends Panel
|
||||
|
||||
@onready var music_toggle: CheckButton = %MusicToggle
|
||||
@onready var chop_toggle: CheckButton = %ChopToggle
|
||||
@onready var money_toggle: CheckButton = %MoneyToggle
|
||||
@onready var close_button: Button = %CloseButton
|
||||
|
||||
func _ready():
|
||||
# Initialize toggle states from Global settings
|
||||
music_toggle.button_pressed = Global.play_background_music
|
||||
chop_toggle.button_pressed = Global.play_chop_sound
|
||||
money_toggle.button_pressed = Global.play_money_sound
|
||||
|
||||
# Connect signals
|
||||
music_toggle.toggled.connect(_on_music_toggled)
|
||||
chop_toggle.toggled.connect(_on_chop_toggled)
|
||||
money_toggle.toggled.connect(_on_money_toggled)
|
||||
close_button.pressed.connect(_on_close_pressed)
|
||||
|
||||
# Start hidden
|
||||
visible = false
|
||||
|
||||
func _on_music_toggled(enabled: bool):
|
||||
Global.play_background_music = enabled
|
||||
var audio_manager = get_node("/root/Audio")
|
||||
if enabled:
|
||||
audio_manager.play_background_music()
|
||||
else:
|
||||
audio_manager.stop_background_music()
|
||||
|
||||
func _on_chop_toggled(enabled: bool):
|
||||
Global.play_chop_sound = enabled
|
||||
|
||||
func _on_money_toggled(enabled: bool):
|
||||
Global.play_money_sound = enabled
|
||||
|
||||
func _on_close_pressed():
|
||||
visible = false
|
||||
|
||||
func toggle_visibility():
|
||||
visible = !visible
|
||||
1
scenes/scripts/config_panel.gd.uid
Normal file
1
scenes/scripts/config_panel.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://2jm25u1ehlac
|
||||
|
|
@ -8,10 +8,19 @@ extends Control
|
|||
@onready var timer_label : Label = %Timer
|
||||
|
||||
@onready var game_complete_screen : Panel = %GameCompleted
|
||||
@onready var completion_time_label : Label = %CompletionTimeLabel
|
||||
@onready var continue_button : TextureButton = %ContinueButton
|
||||
@onready var config_button : Button = %ConfigButton
|
||||
@onready var config_panel : Panel = %ConfigPanel
|
||||
@onready var player_name_input : TextEdit = %TextEdit
|
||||
@onready var submit_score_button : TextureButton = %SubmitScoreButton
|
||||
@onready var submission_status_label : Label = %SubmissionStatusLabel
|
||||
|
||||
var game_timer : Timer
|
||||
var elapsed_time := 0.0
|
||||
var current_nonce : String = ""
|
||||
var bridge = null
|
||||
var score_submitted := false
|
||||
|
||||
func _ready():
|
||||
populate_modifiers_display()
|
||||
|
|
@ -34,6 +43,7 @@ func _ready():
|
|||
|
||||
Inventory.currency_changed.connect(_on_currency_changed)
|
||||
Inventory.currency_added.connect(spawn_currency_increase)
|
||||
Inventory.currency_added.connect(_on_currency_added)
|
||||
Inventory.wood_changed.connect(_on_currency_changed)
|
||||
Inventory.wood_added.connect(spawn_wood_increase)
|
||||
Inventory.stock_added.connect(spawn_stock_increase)
|
||||
|
|
@ -41,17 +51,27 @@ func _ready():
|
|||
Unlocks.item_unlocked.connect(populate_unlock_buttons)
|
||||
GameManager.currency_goal_met.connect(_on_currency_goal_met)
|
||||
|
||||
if config_button:
|
||||
config_button.pressed.connect(_on_config_button_pressed)
|
||||
|
||||
if submit_score_button:
|
||||
submit_score_button.pressed.connect(_on_submit_score_button_pressed)
|
||||
|
||||
# Initialize JavaScript bridge for web builds
|
||||
if OS.has_feature("web"):
|
||||
bridge = JavaScriptBridge.get_interface("godotBridge")
|
||||
|
||||
# get_tree().paused = true
|
||||
|
||||
|
||||
func update_currency_label():
|
||||
currency_label.text = Global.currency_symbol + " " + str(int(Inventory.get_currency()))
|
||||
currency_label.text = Global.currency_symbol + Global.format_number(Inventory.get_currency())
|
||||
|
||||
func update_wood_label():
|
||||
wood_label.text = "W: " + str(int(Inventory.get_wood()))
|
||||
wood_label.text = Global.format_number(Inventory.get_wood())
|
||||
|
||||
func update_stock_label():
|
||||
stock_label.text = "S: " + str(int(Inventory.get_stock()))
|
||||
stock_label.text = Global.format_number(Inventory.get_stock())
|
||||
|
||||
func spawn_currency_increase(value, _total):
|
||||
spawn_inventory_change_value(value, _total, "+", Global.currency_symbol, Global.money_color)
|
||||
|
|
@ -64,7 +84,7 @@ func spawn_stock_increase(value, _total):
|
|||
|
||||
func spawn_inventory_change_value(value, _total, display_sign: String = "+", symbol: String = "", label_color: Color = Color.WHITE):
|
||||
var float_label = Label.new()
|
||||
float_label.text = display_sign + symbol + str(int(abs(value)))
|
||||
float_label.text = display_sign + symbol + Global.format_number(abs(value))
|
||||
float_label.add_theme_font_size_override("font_size", 16)
|
||||
float_label.modulate = label_color
|
||||
|
||||
|
|
@ -99,10 +119,10 @@ func populate_unlock_buttons():
|
|||
func populate_modifiers_display():
|
||||
var modifiers_text = ""
|
||||
|
||||
modifiers_text = modifiers_text + "Sale Price: " + Global.currency_symbol + str(Unlocks.get_sale_price_per_item()) + "\n"
|
||||
modifiers_text = modifiers_text + "Items Produced Per Tick: " + str(Unlocks.get_items_produced_per_tick()) + "\n"
|
||||
modifiers_text = modifiers_text + "Wood per Click: " + str(Unlocks.get_wood_per_click()) + "\n\n"
|
||||
modifiers_text = modifiers_text + "Demand: " + str(int(Unlocks.get_sale_demand())) + "\n\n"
|
||||
modifiers_text = modifiers_text + "Sale Price: " + Global.currency_symbol + Global.format_number(Unlocks.get_sale_price_per_item()) + "\n"
|
||||
modifiers_text = modifiers_text + "Items Produced Per Tick: " + Global.format_number(Unlocks.get_items_produced_per_tick()) + "\n"
|
||||
modifiers_text = modifiers_text + "Wood per Click: " + Global.format_number(Unlocks.get_wood_per_click()) + "\n\n"
|
||||
modifiers_text = modifiers_text + "Demand: " + Global.format_number(Unlocks.get_sale_demand()) + "\n\n"
|
||||
|
||||
modifiers_text = modifiers_text + "Current Modifiers:\n"
|
||||
for key in Unlocks.current_modifiers.keys():
|
||||
|
|
@ -129,7 +149,20 @@ func _on_timer_tick():
|
|||
func _on_currency_goal_met():
|
||||
Log.pr("Currency goal met!")
|
||||
get_tree().paused = true
|
||||
completion_time_label.text = format_time(elapsed_time)
|
||||
game_complete_screen.visible = true
|
||||
score_submitted = false
|
||||
|
||||
# Request nonce from API when game is completed
|
||||
if bridge != null:
|
||||
# Disable submit button until nonce is received
|
||||
submit_score_button.disabled = true
|
||||
submission_status_label.visible = false
|
||||
_request_nonce()
|
||||
else:
|
||||
# No web bridge available, hide the submit button entirely
|
||||
submit_score_button.visible = false
|
||||
submission_status_label.visible = false
|
||||
|
||||
func format_time(seconds: float) -> String:
|
||||
var total_seconds := int(seconds)
|
||||
|
|
@ -145,3 +178,228 @@ func _on_continue_button_pressed() -> void:
|
|||
Global.game_continue_pressed = true
|
||||
game_complete_screen.visible = false
|
||||
get_tree().paused = false
|
||||
|
||||
func _on_config_button_pressed() -> void:
|
||||
if config_panel:
|
||||
config_panel.toggle_visibility()
|
||||
|
||||
func _on_currency_added(_value, _total):
|
||||
var audio_manager = get_node("/root/Audio")
|
||||
if audio_manager:
|
||||
audio_manager.play_money_sound()
|
||||
|
||||
# HIGH SCORE SUBMISSION FUNCTIONS
|
||||
|
||||
func _request_nonce():
|
||||
if bridge == null:
|
||||
return
|
||||
|
||||
# Call JavaScript bridge to request nonce
|
||||
var result = bridge.requestNonce()
|
||||
|
||||
# Set up polling to check if nonce was received
|
||||
# (since JS callbacks are async)
|
||||
var nonce_check_timer = Timer.new()
|
||||
nonce_check_timer.wait_time = 0.5
|
||||
nonce_check_timer.one_shot = false
|
||||
nonce_check_timer.process_mode = Node.PROCESS_MODE_ALWAYS # Run even when paused
|
||||
add_child(nonce_check_timer)
|
||||
|
||||
var attempts = 0
|
||||
var max_attempts = 20 # 10 seconds total
|
||||
|
||||
nonce_check_timer.timeout.connect(func():
|
||||
attempts += 1
|
||||
var nonce = _get_nonce_from_js()
|
||||
|
||||
if nonce != null and nonce != "" and nonce != "null":
|
||||
current_nonce = nonce
|
||||
# Enable submit button when nonce is ready
|
||||
submit_score_button.disabled = false
|
||||
nonce_check_timer.stop()
|
||||
nonce_check_timer.queue_free()
|
||||
elif attempts >= max_attempts:
|
||||
# Failed to get nonce - show error and keep button disabled
|
||||
submission_status_label.text = "Failed to connect to server"
|
||||
submission_status_label.modulate = Color(1.0, 0.3, 0.3) # Red
|
||||
submission_status_label.visible = true
|
||||
nonce_check_timer.stop()
|
||||
nonce_check_timer.queue_free()
|
||||
)
|
||||
|
||||
nonce_check_timer.start()
|
||||
|
||||
func _get_nonce_from_js() -> String:
|
||||
if bridge == null:
|
||||
return ""
|
||||
|
||||
# Try to get the nonce that JavaScript stored
|
||||
var result = JavaScriptBridge.eval("""
|
||||
(function() {
|
||||
if (window.godotNonce) {
|
||||
return window.godotNonce;
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
""", true)
|
||||
|
||||
return str(result) if result != null else ""
|
||||
|
||||
func _on_submit_score_button_pressed():
|
||||
if score_submitted:
|
||||
submission_status_label.text = "Score already submitted!"
|
||||
submission_status_label.modulate = Color(1.0, 0.8, 0.3) # Yellow
|
||||
submission_status_label.visible = true
|
||||
return
|
||||
|
||||
if bridge == null or current_nonce == "":
|
||||
# This shouldn't happen as button should be disabled
|
||||
return
|
||||
|
||||
# Disable button during submission and show status
|
||||
submit_score_button.disabled = true
|
||||
submission_status_label.text = "Submitting..."
|
||||
submission_status_label.modulate = Color(1.0, 1.0, 1.0) # White
|
||||
submission_status_label.visible = true
|
||||
|
||||
# Get player name from input and sanitize
|
||||
var player_name = _sanitize_player_name(player_name_input.text)
|
||||
if player_name == "":
|
||||
player_name = "Anonymous"
|
||||
|
||||
# Get completion time in seconds
|
||||
var completion_time_seconds = int(elapsed_time)
|
||||
|
||||
# Create and encode payload on GDScript side
|
||||
var encoded_payload = _create_encoded_payload(current_nonce, player_name, completion_time_seconds)
|
||||
|
||||
# Call JavaScript to submit score with pre-encoded payload and nonce
|
||||
bridge.submitScore(encoded_payload, current_nonce)
|
||||
|
||||
# Poll for submission result
|
||||
var submit_check_timer = Timer.new()
|
||||
submit_check_timer.wait_time = 0.5
|
||||
submit_check_timer.one_shot = false
|
||||
submit_check_timer.process_mode = Node.PROCESS_MODE_ALWAYS # Run even when paused
|
||||
add_child(submit_check_timer)
|
||||
|
||||
var attempts = 0
|
||||
var max_attempts = 30 # 15 seconds total
|
||||
|
||||
submit_check_timer.timeout.connect(func():
|
||||
attempts += 1
|
||||
var result = _get_submission_result_from_js()
|
||||
|
||||
if result.has("completed") and result["completed"]:
|
||||
submission_status_label.visible = true
|
||||
if result["success"]:
|
||||
submission_status_label.text = result.get("message", "Score submitted!")
|
||||
submission_status_label.modulate = Color(0.5, 1.0, 0.5) # Green
|
||||
score_submitted = true
|
||||
|
||||
# Show rank if available
|
||||
if result.has("rank") and result["rank"] > 0:
|
||||
submission_status_label.text += " (Rank #%d)" % result["rank"]
|
||||
else:
|
||||
submission_status_label.text = result.get("message", "Failed to submit")
|
||||
submission_status_label.modulate = Color(1.0, 0.3, 0.3) # Red
|
||||
submit_score_button.disabled = false
|
||||
|
||||
submit_check_timer.stop()
|
||||
submit_check_timer.queue_free()
|
||||
elif attempts >= max_attempts:
|
||||
submission_status_label.text = "Submission timeout"
|
||||
submission_status_label.modulate = Color(1.0, 0.3, 0.3) # Red
|
||||
submission_status_label.visible = true
|
||||
submit_score_button.disabled = false
|
||||
submit_check_timer.stop()
|
||||
submit_check_timer.queue_free()
|
||||
)
|
||||
|
||||
submit_check_timer.start()
|
||||
|
||||
func _get_submission_result_from_js() -> Dictionary:
|
||||
if bridge == null:
|
||||
return {}
|
||||
|
||||
var result = JavaScriptBridge.eval("""
|
||||
(function() {
|
||||
if (window.godotSubmissionResult) {
|
||||
var result = window.godotSubmissionResult;
|
||||
window.godotSubmissionResult = null; // Clear after reading
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
return '{}';
|
||||
})();
|
||||
""", true)
|
||||
|
||||
if result != null and result != "":
|
||||
var json = JSON.new()
|
||||
var error = json.parse(str(result))
|
||||
if error == OK:
|
||||
return json.data
|
||||
|
||||
return {}
|
||||
|
||||
func _create_encoded_payload(nonce: String, player_name: String, completion_time: int) -> String:
|
||||
# Create the payload as JSON
|
||||
var payload = {
|
||||
"nonce": nonce,
|
||||
"gameId": "whittling-clicker", # Unique identifier for this game
|
||||
"playerName": player_name,
|
||||
"completionTime": completion_time,
|
||||
"timestamp": Time.get_unix_time_from_system() * 1000 # Convert to milliseconds
|
||||
}
|
||||
|
||||
var json_string = JSON.stringify(payload)
|
||||
|
||||
# XOR encode with key derived from nonce
|
||||
var key = "WHITTLING_KEY_" + nonce.substr(0, 8)
|
||||
var encoded_bytes = _xor_encode(json_string, key)
|
||||
|
||||
# Base64 encode (using raw bytes)
|
||||
var base64_encoded = Marshalls.raw_to_base64(encoded_bytes)
|
||||
|
||||
return base64_encoded
|
||||
|
||||
func _sanitize_player_name(name: String) -> String:
|
||||
# Strip leading/trailing whitespace
|
||||
name = name.strip_edges()
|
||||
|
||||
# Remove control characters and most special characters, keep letters, numbers, spaces, and safe punctuation
|
||||
var safe_name = ""
|
||||
for i in range(name.length()):
|
||||
var c = name[i]
|
||||
var code = name.unicode_at(i)
|
||||
|
||||
# Allow: letters, numbers, spaces, hyphens, underscores, periods
|
||||
# Block: control chars, path separators, quotes, angle brackets, etc
|
||||
if (code >= 48 and code <= 57) or \
|
||||
(code >= 65 and code <= 90) or \
|
||||
(code >= 97 and code <= 122) or \
|
||||
c == " " or c == "-" or c == "_" or c == ".":
|
||||
safe_name += c
|
||||
|
||||
# Limit length to 50 characters
|
||||
safe_name = safe_name.substr(0, 50)
|
||||
|
||||
# Remove multiple consecutive spaces
|
||||
while safe_name.find(" ") != -1:
|
||||
safe_name = safe_name.replace(" ", " ")
|
||||
|
||||
# Strip again after processing
|
||||
safe_name = safe_name.strip_edges()
|
||||
|
||||
return safe_name
|
||||
|
||||
func _xor_encode(text: String, key: String) -> PackedByteArray:
|
||||
var text_bytes = text.to_utf8_buffer()
|
||||
var key_bytes = key.to_utf8_buffer()
|
||||
var key_length = key_bytes.size()
|
||||
var result = PackedByteArray()
|
||||
|
||||
for i in range(text_bytes.size()):
|
||||
var xor_byte = text_bytes[i] ^ key_bytes[i % key_length]
|
||||
result.append(xor_byte)
|
||||
|
||||
return result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue