Mostly done?
This commit is contained in:
parent
22d7326565
commit
a08c13b1a3
118 changed files with 2558 additions and 2519 deletions
|
|
@ -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