Release alpha 14 menu and developer test tools
This commit is contained in:
1 parent
026d0bad58
commit
a536b698f7
5 files changed
+464
-76
No files matched your search
@@ -3,7 +3,9 @@ extends Node2D
|
|||||||
const SCREEN := Vector2(1600.0, 900.0)
|
const SCREEN := Vector2(1600.0, 900.0)
|
||||||
const ENGLISH_UI := {
|
const ENGLISH_UI := {
|
||||||
"PROFIL": "PROFILE", "EINSATZKARTE WÄHLEN • GESPERRTE SEKTOREN IM SHOP FREISCHALTEN": "SELECT A MAP • UNLOCK SECTORS IN THE SHOP",
|
"PROFIL": "PROFILE", "EINSATZKARTE WÄHLEN • GESPERRTE SEKTOREN IM SHOP FREISCHALTEN": "SELECT A MAP • UNLOCK SECTORS IN THE SHOP",
|
||||||
"GESPERRT": "LOCKED", "Wähle einen Sektor, um Schwierigkeit und Missionsstart festzulegen.": "Select a sector to choose difficulty and start the mission.",
|
"EINSATZSEKTOREN": "DEPLOYMENT SECTORS", "KARTE WÄHLEN • GESPERRTE SEKTOREN IM SHOP FREISCHALTEN": "CHOOSE A MAP • UNLOCK LOCKED SECTORS IN THE SHOP",
|
||||||
|
"AUSGEWÄHLT": "SELECTED", "KOMMUNITY": "COMMUNITY", "HANGAR & KONTO": "HANGAR & ACCOUNT", "SYSTEM": "SYSTEM",
|
||||||
|
"GESPERRT": "LOCKED", "Wähle einen Sektor. Danach legst du Schwierigkeit und Missionsstart fest.": "Choose a sector, then set the difficulty and start your mission.",
|
||||||
"< KARTE": "< MAP", "SCHWIERIGKEIT WÄHLEN": "SELECT DIFFICULTY", "LEICHT": "EASY", "MITTEL": "MEDIUM", "SCHWER": "HARD",
|
"< KARTE": "< MAP", "SCHWIERIGKEIT WÄHLEN": "SELECT DIFFICULTY", "LEICHT": "EASY", "MITTEL": "MEDIUM", "SCHWER": "HARD",
|
||||||
"MISSION STARTEN": "START MISSION", "BESTENLISTE": "LEADERBOARD", "ARSENAL / SHOP": "ARSENAL / SHOP", "EINSTELLUNGEN": "SETTINGS",
|
"MISSION STARTEN": "START MISSION", "BESTENLISTE": "LEADERBOARD", "ARSENAL / SHOP": "ARSENAL / SHOP", "EINSTELLUNGEN": "SETTINGS",
|
||||||
"CODE EINLÖSEN": "REDEEM CODE", "SPIEL BEENDEN": "QUIT GAME", "MULTIPLAYER": "MULTIPLAYER", "SCHLIESSEN": "CLOSE",
|
"CODE EINLÖSEN": "REDEEM CODE", "SPIEL BEENDEN": "QUIT GAME", "MULTIPLAYER": "MULTIPLAYER", "SCHLIESSEN": "CLOSE",
|
||||||
@@ -33,6 +35,7 @@ const ENGLISH_UI := {
|
|||||||
"Biodom über dem Nebel": "Biodome above the nebula", "Eisige Raffinerie": "Frozen refinery", "Schmiede am Gasriesen": "Forge by the gas giant",
|
"Biodom über dem Nebel": "Biodome above the nebula", "Eisige Raffinerie": "Frozen refinery", "Schmiede am Gasriesen": "Forge by the gas giant",
|
||||||
"Verlassene Megastruktur": "Abandoned megastructure", "VERKAUFEN": "SELL", "MAX. STUFE": "MAX LEVEL", "FREIGESCHALTET": "UNLOCKED",
|
"Verlassene Megastruktur": "Abandoned megastructure", "VERKAUFEN": "SELL", "MAX. STUFE": "MAX LEVEL", "FREIGESCHALTET": "UNLOCKED",
|
||||||
"GESPERRT • %d KERNKRISTALLE": "LOCKED • %d CORE CRYSTALS", "KARTENBONUS %d • BESTE WELLE %02d • KERNKRISTALLE %04d": "MAP BONUS %d • BEST WAVE %02d • CORE CRYSTALS %04d",
|
"GESPERRT • %d KERNKRISTALLE": "LOCKED • %d CORE CRYSTALS", "KARTENBONUS %d • BESTE WELLE %02d • KERNKRISTALLE %04d": "MAP BONUS %d • BEST WAVE %02d • CORE CRYSTALS %04d",
|
||||||
|
"KARTE %02d VON %02d": "MAP %02d OF %02d", "KARTE IM SHOP FREISCHALTEN": "UNLOCK MAP IN SHOP",
|
||||||
"Bisheriges Balancing • 20 Reaktor • Kartenbonus 5": "Classic balance • 20 reactor health • map bonus 5",
|
"Bisheriges Balancing • 20 Reaktor • Kartenbonus 5": "Classic balance • 20 reactor health • map bonus 5",
|
||||||
"+32 % Gegnerhülle • +24 % Gegner • Kartenbonus 7": "+32% enemy armor • +24% enemies • map bonus 7",
|
"+32 % Gegnerhülle • +24 % Gegner • Kartenbonus 7": "+32% enemy armor • +24% enemies • map bonus 7",
|
||||||
"+72 % Grundhülle • stärkere Spätwellen • Start 340 • Kartenbonus 12": "+72% base armor • stronger late waves • start 340 • map bonus 12",
|
"+72 % Grundhülle • stärkere Spätwellen • Start 340 • Kartenbonus 12": "+72% base armor • stronger late waves • start 340 • map bonus 12",
|
||||||
@@ -195,17 +198,18 @@ const DIFFICULTY_DESCRIPTIONS := [
|
|||||||
"+72 % Grundhülle • stärkere Spätwellen • Start 340 • Kartenbonus 12"
|
"+72 % Grundhülle • stärkere Spätwellen • Start 340 • Kartenbonus 12"
|
||||||
]
|
]
|
||||||
|
|
||||||
const TOWER_DATA := [
|
var TOWER_DATA := [
|
||||||
{"name": "KINETIK", "cost": 120, "damage": 22.0, "range": 190.0, "rate": 0.55, "shot_speed": 620.0, "damage_type": "EINZELZIEL", "target_class": "ground", "color": Color("7de7ff"), "unlock_cost": 0, "description": "Zuverlässig gegen Bodenziele."},
|
{"name": "KINETIK", "cost": 120, "damage": 22.0, "range": 190.0, "rate": 0.55, "shot_speed": 620.0, "damage_type": "EINZELZIEL", "target_class": "ground", "color": Color("7de7ff"), "unlock_cost": 0, "description": "Zuverlässig gegen Bodenziele."},
|
||||||
{"name": "LASER", "cost": 180, "damage": 12.0, "range": 165.0, "rate": 0.18, "shot_speed": 1100.0, "damage_type": "EINZELZIEL", "target_class": "both", "both_damage_scale": 0.82, "color": Color("62fff1"), "unlock_cost": 0, "description": "Mehrzwecklaser; durchdringt Schilde von Luft- und Bodenzielen."},
|
{"name": "LASER", "cost": 180, "damage": 12.0, "range": 165.0, "rate": 0.18, "shot_speed": 1100.0, "damage_type": "EINZELZIEL", "target_class": "both", "both_damage_scale": 0.82, "color": Color("62fff1"), "unlock_cost": 0, "description": "Mehrzwecklaser; durchdringt Schilde von Luft- und Bodenzielen."},
|
||||||
{"name": "RAKETE", "cost": 250, "damage": 65.0, "range": 260.0, "rate": 1.40, "shot_speed": 360.0, "damage_type": "FLÄCHE 76", "target_class": "ground", "color": Color("ffb35a"), "unlock_cost": 0, "description": "Bodenraketen treffen Gegnergruppen."},
|
{"name": "RAKETE", "cost": 250, "damage": 65.0, "range": 260.0, "rate": 1.40, "shot_speed": 360.0, "damage_type": "FLÄCHE 76", "target_class": "ground", "color": Color("ffb35a"), "unlock_cost": 0, "description": "Bodenraketen treffen Gegnergruppen."},
|
||||||
{"name": "PLASMA", "cost": 230, "damage": 34.0, "range": 240.0, "rate": 1.05, "shot_speed": 480.0, "damage_type": "PLASMA-FLÄCHE 92", "target_class": "both", "both_damage_scale": 0.72, "color": Color("e27bff"), "unlock_cost": 18, "description": "Mehrzweckwaffe mit reduziertem Schaden."},
|
{"name": "PLASMA", "cost": 230, "damage": 34.0, "range": 240.0, "rate": 1.05, "shot_speed": 480.0, "damage_type": "PLASMA-FLÄCHE 92", "target_class": "both", "both_damage_scale": 0.72, "color": Color("e27bff"), "unlock_cost": 18, "description": "Mehrzweckwaffe mit reduziertem Schaden."},
|
||||||
{"name": "EMP-PULS", "cost": 210, "damage": 20.0, "range": 215.0, "rate": 0.92, "shot_speed": 540.0, "damage_type": "EMP-FLÄCHE 118 · ALLE", "target_class": "both", "both_damage_scale": 1.0, "color": Color("77e8ff"), "unlock_cost": 24, "description": "Impuls trifft alle Luft- und Bodenziele im Radius mit 20 Schaden."},
|
{"name": "EMP-PULS", "cost": 210, "damage": 20.0, "range": 215.0, "rate": 0.92, "shot_speed": 540.0, "damage_type": "EMP-FLÄCHE 118 · ALLE", "target_class": "both", "both_damage_scale": 1.0, "color": Color("77e8ff"), "unlock_cost": 24, "description": "Impuls trifft alle Luft- und Bodenziele im Radius mit 20 Schaden."},
|
||||||
{"name": "RAILGUN", "cost": 300, "damage": 77.0, "range": 330.0, "rate": 1.85, "shot_speed": 1450.0, "damage_type": "PIERCING-STRAHL", "target_class": "both", "both_damage_scale": 0.70, "color": Color("c86bff"), "unlock_cost": 30, "description": "Lila Durchschlagsstrahl trifft alle Gegner auf seiner Schusslinie."},
|
{"name": "RAILGUN", "cost": 300, "damage": 77.0, "range": 330.0, "rate": 1.85, "shot_speed": 1450.0, "damage_type": "PIERCING-STRAHL", "target_class": "both", "both_damage_scale": 0.70, "color": Color("c86bff"), "unlock_cost": 30, "description": "Lila Durchschlagsstrahl trifft alle Gegner auf seiner Schusslinie."},
|
||||||
{"name": "FLAK", "cost": 210, "damage": 22.0, "range": 230.0, "rate": 0.42, "shot_speed": 920.0, "damage_type": "LUFT-EINZELZIEL", "target_class": "air", "color": Color("ff737c"), "unlock_cost": 0, "description": "Schnelle Abfangkanone gegen gegnerische Luftschiffe."}
|
{"name": "FLAK", "cost": 210, "damage": 22.0, "range": 230.0, "rate": 0.42, "shot_speed": 920.0, "damage_type": "LUFT-EINZELZIEL", "target_class": "air", "color": Color("ff737c"), "unlock_cost": 0, "description": "Schnelle Abfangkanone gegen gegnerische Luftschiffe."},
|
||||||
|
{"name": "ADMIN-TURM", "cost": 0, "damage": 1000000.0, "range": 3000.0, "rate": 0.08, "shot_speed": 3000.0, "damage_type": "TESTMODUS", "target_class": "both", "color": Color("fff2a1"), "unlock_cost": 0, "description": "Entwicklertest: besiegt jedes Ziel mit einem Schuss."}
|
||||||
]
|
]
|
||||||
|
|
||||||
const ENEMY_DATA := [
|
var ENEMY_DATA := [
|
||||||
{"name": "JAEGER", "category": "air", "hp": 70.0, "speed": 105.0, "reward": 18, "size": 58.0, "breach_damage": 1, "shield_resistance": 0.28},
|
{"name": "JAEGER", "category": "air", "hp": 70.0, "speed": 105.0, "reward": 18, "size": 58.0, "breach_damage": 1, "shield_resistance": 0.28},
|
||||||
{"name": "FREGATTE", "category": "air", "hp": 185.0, "speed": 72.0, "reward": 34, "size": 78.0, "breach_damage": 2, "shield_resistance": 0.28},
|
{"name": "FREGATTE", "category": "air", "hp": 185.0, "speed": 72.0, "reward": 34, "size": 78.0, "breach_damage": 2, "shield_resistance": 0.28},
|
||||||
{"name": "KREUZER", "category": "air", "hp": 520.0, "speed": 43.0, "reward": 78, "size": 112.0, "breach_damage": 3, "shield_resistance": 0.28},
|
{"name": "KREUZER", "category": "air", "hp": 520.0, "speed": 43.0, "reward": 78, "size": 112.0, "breach_damage": 3, "shield_resistance": 0.28},
|
||||||
@@ -267,6 +271,17 @@ var wave_spawn_index := 0
|
|||||||
var online_wave_types_remaining: Array[int] = []
|
var online_wave_types_remaining: Array[int] = []
|
||||||
var enemy_strength_multiplier := 1.0
|
var enemy_strength_multiplier := 1.0
|
||||||
var developer_panel_open := false
|
var developer_panel_open := false
|
||||||
|
const DEVELOPER_PASSWORD := "Spring.3"
|
||||||
|
const ADMIN_TOWER_TYPE := 7
|
||||||
|
const DEVELOPER_AUTH_OP := 15
|
||||||
|
const DEVELOPER_ENERGY_OP := 16
|
||||||
|
var developer_unlocked_this_round := false
|
||||||
|
var developer_server_authorized := false
|
||||||
|
var developer_password_prompt_open := false
|
||||||
|
var developer_password_line_edit: LineEdit
|
||||||
|
var developer_password_dialog_rect := Rect2(510, 330, 580, 235)
|
||||||
|
var developer_password_input_rect := Rect2(570, 410, 460, 54)
|
||||||
|
var developer_password_cancel_button := Rect2(670, 488, 260, 48)
|
||||||
var status_text := ""
|
var status_text := ""
|
||||||
var status_timer := 0.0
|
var status_timer := 0.0
|
||||||
var elapsed := 0.0
|
var elapsed := 0.0
|
||||||
@@ -283,8 +298,8 @@ var next_spawn_route := 0
|
|||||||
|
|
||||||
var start_button := Rect2(625, 674, 350, 58)
|
var start_button := Rect2(625, 674, 350, 58)
|
||||||
var map_buttons := [
|
var map_buttons := [
|
||||||
Rect2(30, 390, 340, 130), Rect2(430, 390, 340, 130), Rect2(830, 390, 340, 130), Rect2(1230, 390, 340, 130),
|
Rect2(55, 312, 350, 150), Rect2(435, 312, 350, 150), Rect2(815, 312, 350, 150), Rect2(1195, 312, 350, 150),
|
||||||
Rect2(255, 540, 340, 130), Rect2(630, 540, 340, 130), Rect2(1005, 540, 340, 130)
|
Rect2(245, 492, 350, 150), Rect2(625, 492, 350, 150), Rect2(1005, 492, 350, 150)
|
||||||
]
|
]
|
||||||
var difficulty_buttons := [Rect2(535, 520, 165, 50), Rect2(718, 520, 165, 50), Rect2(901, 520, 165, 50)]
|
var difficulty_buttons := [Rect2(535, 520, 165, 50), Rect2(718, 520, 165, 50), Rect2(901, 520, 165, 50)]
|
||||||
var wave_button := Rect2(1216, 10, 359, 46)
|
var wave_button := Rect2(1216, 10, 359, 46)
|
||||||
@@ -293,11 +308,14 @@ var speed_button := Rect2(1070, 14, 62, 54)
|
|||||||
var auto_wave_button := Rect2(1216, 58, 359, 19)
|
var auto_wave_button := Rect2(1216, 58, 359, 19)
|
||||||
var wave_current_preview_rect := Rect2(220, 8, 258, 66)
|
var wave_current_preview_rect := Rect2(220, 8, 258, 66)
|
||||||
var wave_next_preview_rect := Rect2(486, 8, 258, 66)
|
var wave_next_preview_rect := Rect2(486, 8, 258, 66)
|
||||||
var developer_panel_rect := Rect2(1060, 104, 515, 78)
|
var developer_panel_rect := Rect2(920, 104, 660, 112)
|
||||||
var developer_strength_down_button := Rect2(1074, 140, 42, 30)
|
var developer_strength_down_button := Rect2(934, 140, 42, 28)
|
||||||
var developer_strength_up_button := Rect2(1260, 140, 42, 30)
|
var developer_strength_up_button := Rect2(1110, 140, 42, 28)
|
||||||
var developer_crystals_down_button := Rect2(1320, 140, 50, 30)
|
var developer_crystals_down_button := Rect2(1170, 140, 50, 28)
|
||||||
var developer_crystals_up_button := Rect2(1512, 140, 50, 30)
|
var developer_crystals_up_button := Rect2(1412, 140, 50, 28)
|
||||||
|
var developer_energy_down_button := Rect2(934, 176, 78, 28)
|
||||||
|
var developer_energy_up_button := Rect2(1018, 176, 78, 28)
|
||||||
|
var developer_admin_tower_button := Rect2(1370, 174, 195, 32)
|
||||||
var tower_buttons := [
|
var tower_buttons := [
|
||||||
Rect2(13, 778, 218, 112), Rect2(238, 778, 218, 112), Rect2(463, 778, 218, 112),
|
Rect2(13, 778, 218, 112), Rect2(238, 778, 218, 112), Rect2(463, 778, 218, 112),
|
||||||
Rect2(688, 778, 218, 112), Rect2(913, 778, 218, 112), Rect2(1138, 778, 218, 112),
|
Rect2(688, 778, 218, 112), Rect2(913, 778, 218, 112), Rect2(1138, 778, 218, 112),
|
||||||
@@ -314,8 +332,8 @@ var target_priority_button := Rect2(666, 698, 300, 44)
|
|||||||
var endless_button := Rect2(545, 515, 510, 70)
|
var endless_button := Rect2(545, 515, 510, 70)
|
||||||
var finish_mission_button := Rect2(625, 610, 350, 62)
|
var finish_mission_button := Rect2(625, 610, 350, 62)
|
||||||
var profile_button := Rect2(1280, 25, 270, 52)
|
var profile_button := Rect2(1280, 25, 270, 52)
|
||||||
var leaderboard_button := Rect2(40, 790, 200, 54)
|
var leaderboard_button := Rect2(40, 772, 245, 62)
|
||||||
var shop_button := Rect2(568, 790, 200, 54)
|
var shop_button := Rect2(550, 772, 245, 62)
|
||||||
var map_step_back_button := Rect2(55, 320, 170, 50)
|
var map_step_back_button := Rect2(55, 320, 170, 50)
|
||||||
var shop_back_button := Rect2(55, 35, 185, 54)
|
var shop_back_button := Rect2(55, 35, 185, 54)
|
||||||
var shop_purchase_buttons := [Rect2(190, 545, 360, 62), Rect2(620, 545, 360, 62), Rect2(1050, 545, 360, 62)]
|
var shop_purchase_buttons := [Rect2(190, 545, 360, 62), Rect2(620, 545, 360, 62), Rect2(1050, 545, 360, 62)]
|
||||||
@@ -339,7 +357,7 @@ var leaderboard_difficulty := 1
|
|||||||
var leaderboard_loading := false
|
var leaderboard_loading := false
|
||||||
var leaderboard_message := ""
|
var leaderboard_message := ""
|
||||||
var profile_back_button := Rect2(55, 35, 185, 54)
|
var profile_back_button := Rect2(55, 35, 185, 54)
|
||||||
var multiplayer_button := Rect2(304, 790, 200, 54)
|
var multiplayer_button := Rect2(295, 772, 245, 62)
|
||||||
var multiplayer_back_button := Rect2(55, 35, 185, 54)
|
var multiplayer_back_button := Rect2(55, 35, 185, 54)
|
||||||
var quick_match_button := Rect2(125, 665, 300, 64)
|
var quick_match_button := Rect2(125, 665, 300, 64)
|
||||||
var friend_match_button := Rect2(475, 665, 300, 64)
|
var friend_match_button := Rect2(475, 665, 300, 64)
|
||||||
@@ -380,9 +398,9 @@ var available_update: Dictionary = {}
|
|||||||
var update_notice_visible := false
|
var update_notice_visible := false
|
||||||
var audio_settings_open := false
|
var audio_settings_open := false
|
||||||
var master_volume := 0.65
|
var master_volume := 0.65
|
||||||
var audio_settings_button := Rect2(832, 790, 200, 54)
|
var audio_settings_button := Rect2(805, 772, 245, 62)
|
||||||
var promo_code_button := Rect2(1096, 790, 200, 54)
|
var promo_code_button := Rect2(1060, 772, 245, 62)
|
||||||
var exit_button := Rect2(1360, 790, 200, 54)
|
var exit_button := Rect2(1315, 772, 245, 62)
|
||||||
var promo_code_input_rect := Rect2(500, 335, 600, 64)
|
var promo_code_input_rect := Rect2(500, 335, 600, 64)
|
||||||
var promo_code_redeem_button := Rect2(650, 460, 300, 58)
|
var promo_code_redeem_button := Rect2(650, 460, 300, 58)
|
||||||
var promo_code_cancel_button := Rect2(55, 35, 185, 54)
|
var promo_code_cancel_button := Rect2(55, 35, 185, 54)
|
||||||
@@ -430,9 +448,32 @@ func _ready() -> void:
|
|||||||
online_manager.match_invite_received.connect(_on_match_invite_received)
|
online_manager.match_invite_received.connect(_on_match_invite_received)
|
||||||
_create_promo_code_input()
|
_create_promo_code_input()
|
||||||
_create_friend_name_input()
|
_create_friend_name_input()
|
||||||
|
_create_developer_password_input()
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func _create_developer_password_input() -> void:
|
||||||
|
developer_password_line_edit = LineEdit.new()
|
||||||
|
developer_password_line_edit.position = developer_password_input_rect.position + Vector2(12, 4)
|
||||||
|
developer_password_line_edit.size = developer_password_input_rect.size - Vector2(24, 8)
|
||||||
|
developer_password_line_edit.max_length = 40
|
||||||
|
developer_password_line_edit.secret = true
|
||||||
|
developer_password_line_edit.placeholder_text = "PASSWORT"
|
||||||
|
developer_password_line_edit.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
developer_password_line_edit.add_theme_color_override("font_color", Color("e8fbff"))
|
||||||
|
developer_password_line_edit.add_theme_color_override("font_placeholder_color", Color("7699a8"))
|
||||||
|
var input_style := StyleBoxFlat.new()
|
||||||
|
input_style.bg_color = Color("081a27")
|
||||||
|
input_style.border_color = Color("ffad62")
|
||||||
|
input_style.set_border_width_all(1)
|
||||||
|
input_style.set_content_margin_all(10)
|
||||||
|
developer_password_line_edit.add_theme_stylebox_override("normal", input_style)
|
||||||
|
developer_password_line_edit.add_theme_stylebox_override("focus", input_style)
|
||||||
|
developer_password_line_edit.text_submitted.connect(func(_value: String) -> void: _submit_developer_password())
|
||||||
|
add_child(developer_password_line_edit)
|
||||||
|
developer_password_line_edit.hide()
|
||||||
|
|
||||||
|
|
||||||
func _create_friend_name_input() -> void:
|
func _create_friend_name_input() -> void:
|
||||||
friend_name_line_edit = LineEdit.new()
|
friend_name_line_edit = LineEdit.new()
|
||||||
friend_name_line_edit.position = friend_name_rect.position + Vector2(10, 3)
|
friend_name_line_edit.position = friend_name_rect.position + Vector2(10, 3)
|
||||||
@@ -571,7 +612,7 @@ func _save_balance_cache(config: Dictionary) -> void:
|
|||||||
func _apply_balance_config(config: Dictionary) -> bool:
|
func _apply_balance_config(config: Dictionary) -> bool:
|
||||||
var towers_config = config.get("towers", [])
|
var towers_config = config.get("towers", [])
|
||||||
var enemies_config = config.get("enemies", [])
|
var enemies_config = config.get("enemies", [])
|
||||||
if not towers_config is Array or towers_config.size() != TOWER_DATA.size() or not enemies_config is Array or enemies_config.size() != ENEMY_DATA.size():
|
if not towers_config is Array or towers_config.size() != TOWER_DATA.size() - 1 or not enemies_config is Array or enemies_config.size() != ENEMY_DATA.size():
|
||||||
return false
|
return false
|
||||||
for values in towers_config:
|
for values in towers_config:
|
||||||
if not values is Dictionary or str(values.get("targetClass", "both")) not in ["air", "ground", "both"]:
|
if not values is Dictionary or str(values.get("targetClass", "both")) not in ["air", "ground", "both"]:
|
||||||
@@ -579,7 +620,7 @@ func _apply_balance_config(config: Dictionary) -> bool:
|
|||||||
for values in enemies_config:
|
for values in enemies_config:
|
||||||
if not values is Dictionary:
|
if not values is Dictionary:
|
||||||
return false
|
return false
|
||||||
for index in TOWER_DATA.size():
|
for index in TOWER_DATA.size() - 1:
|
||||||
var values: Dictionary = towers_config[index]
|
var values: Dictionary = towers_config[index]
|
||||||
var tower: Dictionary = TOWER_DATA[index]
|
var tower: Dictionary = TOWER_DATA[index]
|
||||||
tower["cost"] = clampi(int(values.get("cost", tower.cost)), 10, 50000)
|
tower["cost"] = clampi(int(values.get("cost", tower.cost)), 10, 50000)
|
||||||
@@ -603,7 +644,7 @@ func _apply_balance_config(config: Dictionary) -> bool:
|
|||||||
func _apply_promo_unlocks(entitlements: Dictionary) -> void:
|
func _apply_promo_unlocks(entitlements: Dictionary) -> void:
|
||||||
if bool(entitlements.get("all_towers", false)):
|
if bool(entitlements.get("all_towers", false)):
|
||||||
unlocked_towers = []
|
unlocked_towers = []
|
||||||
for tower_type in TOWER_DATA.size():
|
for tower_type in TOWER_DATA.size() - 1:
|
||||||
unlocked_towers.append(tower_type)
|
unlocked_towers.append(tower_type)
|
||||||
if bool(entitlements.get("all_maps", false)):
|
if bool(entitlements.get("all_maps", false)):
|
||||||
unlocked_maps = []
|
unlocked_maps = []
|
||||||
@@ -806,7 +847,7 @@ func _tower_target_class(tower_type: int) -> String:
|
|||||||
func _tower_display_order() -> Array[int]:
|
func _tower_display_order() -> Array[int]:
|
||||||
var ordered_types: Array[int] = []
|
var ordered_types: Array[int] = []
|
||||||
for target_class in ["air", "ground", "both"]:
|
for target_class in ["air", "ground", "both"]:
|
||||||
for tower_type in TOWER_DATA.size():
|
for tower_type in TOWER_DATA.size() - 1:
|
||||||
if _tower_target_class(tower_type) == target_class:
|
if _tower_target_class(tower_type) == target_class:
|
||||||
ordered_types.append(tower_type)
|
ordered_types.append(tower_type)
|
||||||
return ordered_types
|
return ordered_types
|
||||||
@@ -945,7 +986,10 @@ func _fire_tower(tower: Dictionary, target: Dictionary) -> void:
|
|||||||
var damage := _tower_damage(tower.type, tower.level)
|
var damage := _tower_damage(tower.type, tower.level)
|
||||||
if _tower_target_class(tower.type) == "both":
|
if _tower_target_class(tower.type) == "both":
|
||||||
damage *= float(data.get("both_damage_scale", 0.7))
|
damage *= float(data.get("both_damage_scale", 0.7))
|
||||||
if tower.type == 1:
|
if tower.type == ADMIN_TOWER_TYPE:
|
||||||
|
_apply_damage(target, damage, int(tower.get("owner", 0)), tower.type)
|
||||||
|
beams.append({"from": tower.pos, "to": target.pos, "ttl": 0.16, "color": data.color, "piercing": false})
|
||||||
|
elif tower.type == 1:
|
||||||
_apply_damage(target, damage, int(tower.get("owner", 0)), tower.type)
|
_apply_damage(target, damage, int(tower.get("owner", 0)), tower.type)
|
||||||
beams.append({"from": tower.pos, "to": target.pos, "ttl": 0.09, "color": data.color})
|
beams.append({"from": tower.pos, "to": target.pos, "ttl": 0.09, "color": data.color})
|
||||||
elif tower.type == 5:
|
elif tower.type == 5:
|
||||||
@@ -1055,7 +1099,21 @@ func _update_effects(delta: float) -> void:
|
|||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_F9 and game_mode in ["playing", "paused"]:
|
if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_F9 and game_mode in ["playing", "paused"]:
|
||||||
developer_panel_open = not developer_panel_open
|
if developer_password_prompt_open:
|
||||||
|
developer_password_prompt_open = false
|
||||||
|
developer_password_line_edit.hide()
|
||||||
|
elif developer_unlocked_this_round:
|
||||||
|
developer_panel_open = not developer_panel_open
|
||||||
|
else:
|
||||||
|
developer_password_prompt_open = true
|
||||||
|
developer_password_line_edit.clear()
|
||||||
|
developer_password_line_edit.show()
|
||||||
|
developer_password_line_edit.grab_focus()
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
if developer_password_prompt_open and event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||||
|
developer_password_prompt_open = false
|
||||||
|
developer_password_line_edit.hide()
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
return
|
return
|
||||||
if event is InputEventKey and event.pressed and not event.echo and event.keycode in [KEY_ESCAPE, KEY_BACK, KEY_Q]:
|
if event is InputEventKey and event.pressed and not event.echo and event.keycode in [KEY_ESCAPE, KEY_BACK, KEY_Q]:
|
||||||
@@ -1131,6 +1189,12 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
|
|
||||||
func _handle_pointer(position: Vector2, is_touch := false) -> void:
|
func _handle_pointer(position: Vector2, is_touch := false) -> void:
|
||||||
_play_ui_sound()
|
_play_ui_sound()
|
||||||
|
if developer_password_prompt_open:
|
||||||
|
if developer_password_cancel_button.has_point(position):
|
||||||
|
developer_password_prompt_open = false
|
||||||
|
developer_password_line_edit.hide()
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
if developer_panel_open and game_mode in ["playing", "paused"]:
|
if developer_panel_open and game_mode in ["playing", "paused"]:
|
||||||
if developer_strength_down_button.has_point(position):
|
if developer_strength_down_button.has_point(position):
|
||||||
_set_enemy_strength_multiplier(enemy_strength_multiplier - 0.1)
|
_set_enemy_strength_multiplier(enemy_strength_multiplier - 0.1)
|
||||||
@@ -1144,6 +1208,21 @@ func _handle_pointer(position: Vector2, is_touch := false) -> void:
|
|||||||
if developer_crystals_up_button.has_point(position):
|
if developer_crystals_up_button.has_point(position):
|
||||||
_adjust_core_crystals(25)
|
_adjust_core_crystals(25)
|
||||||
return
|
return
|
||||||
|
if developer_energy_down_button.has_point(position):
|
||||||
|
_adjust_developer_energy(-500)
|
||||||
|
return
|
||||||
|
if developer_energy_up_button.has_point(position):
|
||||||
|
_adjust_developer_energy(500)
|
||||||
|
return
|
||||||
|
if developer_admin_tower_button.has_point(position):
|
||||||
|
if coop_online_mode and not developer_server_authorized:
|
||||||
|
_show_status("ONLINE-TESTMODUS WIRD NOCH AUTORISIERT")
|
||||||
|
return
|
||||||
|
selected_tower_type = ADMIN_TOWER_TYPE
|
||||||
|
selected_tower_index = -1
|
||||||
|
_show_status("ADMIN-TURM GEWÄHLT • BAUPLATZ AUF DER KARTE ANKLICKEN")
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
if developer_panel_rect.has_point(position):
|
if developer_panel_rect.has_point(position):
|
||||||
return
|
return
|
||||||
if audio_settings_open:
|
if audio_settings_open:
|
||||||
@@ -1160,6 +1239,9 @@ func _handle_pointer(position: Vector2, is_touch := false) -> void:
|
|||||||
queue_redraw()
|
queue_redraw()
|
||||||
return
|
return
|
||||||
if game_mode == "menu":
|
if game_mode == "menu":
|
||||||
|
if _is_mobile_menu_layout():
|
||||||
|
_handle_mobile_menu_pointer(position)
|
||||||
|
return
|
||||||
if not pending_match_invite.is_empty() and Rect2(470, 226, 660, 54).has_point(position):
|
if not pending_match_invite.is_empty() and Rect2(470, 226, 660, 54).has_point(position):
|
||||||
room_code_input = String(pending_match_invite.get("room_code", ""))
|
room_code_input = String(pending_match_invite.get("room_code", ""))
|
||||||
pending_match_invite.clear()
|
pending_match_invite.clear()
|
||||||
@@ -1549,6 +1631,7 @@ func _toggle_coop_side() -> void:
|
|||||||
|
|
||||||
func _start_coop_ai_game() -> void:
|
func _start_coop_ai_game() -> void:
|
||||||
online_manager.set_presence_status("playing")
|
online_manager.set_presence_status("playing")
|
||||||
|
_reset_developer_access()
|
||||||
coop_mode = true
|
coop_mode = true
|
||||||
coop_ai_mode = true
|
coop_ai_mode = true
|
||||||
coop_online_mode = false
|
coop_online_mode = false
|
||||||
@@ -1585,6 +1668,7 @@ func _start_coop_ai_game() -> void:
|
|||||||
|
|
||||||
func _start_coop_online_game(player_slot: int) -> void:
|
func _start_coop_online_game(player_slot: int) -> void:
|
||||||
online_manager.set_presence_status("playing")
|
online_manager.set_presence_status("playing")
|
||||||
|
_reset_developer_access()
|
||||||
local_player_slot = clampi(player_slot, 0, 1)
|
local_player_slot = clampi(player_slot, 0, 1)
|
||||||
coop_mode = true
|
coop_mode = true
|
||||||
coop_ai_mode = false
|
coop_ai_mode = false
|
||||||
@@ -1751,6 +1835,7 @@ func _refresh_leaderboard() -> void:
|
|||||||
|
|
||||||
func _start_game() -> void:
|
func _start_game() -> void:
|
||||||
online_manager.set_presence_status("playing")
|
online_manager.set_presence_status("playing")
|
||||||
|
_reset_developer_access()
|
||||||
coop_online_mode = false
|
coop_online_mode = false
|
||||||
credits = int(_difficulty().start_credits)
|
credits = int(_difficulty().start_credits)
|
||||||
reactor_lives = int(_difficulty().lives)
|
reactor_lives = int(_difficulty().lives)
|
||||||
@@ -1781,6 +1866,7 @@ func _start_game() -> void:
|
|||||||
|
|
||||||
func _return_to_menu() -> void:
|
func _return_to_menu() -> void:
|
||||||
online_manager.set_presence_status("menu")
|
online_manager.set_presence_status("menu")
|
||||||
|
_reset_developer_access()
|
||||||
if coop_online_mode:
|
if coop_online_mode:
|
||||||
online_manager.leave_current_match()
|
online_manager.leave_current_match()
|
||||||
game_mode = "menu"
|
game_mode = "menu"
|
||||||
@@ -1805,6 +1891,58 @@ func _return_to_menu() -> void:
|
|||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_developer_access() -> void:
|
||||||
|
developer_panel_open = false
|
||||||
|
developer_unlocked_this_round = false
|
||||||
|
developer_server_authorized = false
|
||||||
|
developer_password_prompt_open = false
|
||||||
|
if developer_password_line_edit:
|
||||||
|
developer_password_line_edit.clear()
|
||||||
|
developer_password_line_edit.hide()
|
||||||
|
|
||||||
|
|
||||||
|
func _submit_developer_password() -> void:
|
||||||
|
if developer_password_line_edit == null:
|
||||||
|
return
|
||||||
|
if developer_password_line_edit.text == DEVELOPER_PASSWORD:
|
||||||
|
developer_unlocked_this_round = true
|
||||||
|
developer_panel_open = true
|
||||||
|
developer_password_prompt_open = false
|
||||||
|
developer_password_line_edit.hide()
|
||||||
|
if coop_online_mode:
|
||||||
|
developer_server_authorized = false
|
||||||
|
online_manager.send_match_command(DEVELOPER_AUTH_OP, {"password": developer_password_line_edit.text})
|
||||||
|
_show_status("TESTMODUS FREI • ONLINE-SERVER WIRD AUTORISIERT")
|
||||||
|
else:
|
||||||
|
developer_server_authorized = true
|
||||||
|
_show_status("TESTMODUS FÜR DIESE RUNDE FREIGESCHALTET")
|
||||||
|
else:
|
||||||
|
developer_password_line_edit.clear()
|
||||||
|
_show_status("PASSWORT FALSCH")
|
||||||
|
developer_password_line_edit.grab_focus()
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func _adjust_developer_energy(amount: int) -> void:
|
||||||
|
if amount not in [-500, 500]:
|
||||||
|
return
|
||||||
|
if coop_online_mode:
|
||||||
|
if not developer_server_authorized:
|
||||||
|
_show_status("ONLINE-TESTMODUS WIRD NOCH AUTORISIERT")
|
||||||
|
return
|
||||||
|
online_manager.send_match_command(DEVELOPER_ENERGY_OP, {"amount": amount})
|
||||||
|
_show_status("ENERGIEÄNDERUNG AN SERVER GESENDET")
|
||||||
|
return
|
||||||
|
if coop_mode:
|
||||||
|
var current_energy := int(multiplayer_manager.player_credits[local_player_slot])
|
||||||
|
multiplayer_manager.player_credits[local_player_slot] = maxi(0, current_energy + amount)
|
||||||
|
credits = int(multiplayer_manager.player_credits[0])
|
||||||
|
else:
|
||||||
|
credits = maxi(0, credits + amount)
|
||||||
|
_show_status("TESTENERGIE %s500" % ("+" if amount > 0 else "−"))
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _start_wave() -> void:
|
func _start_wave() -> void:
|
||||||
if wave_active:
|
if wave_active:
|
||||||
_show_status("WELLE LÄUFT BEREITS")
|
_show_status("WELLE LÄUFT BEREITS")
|
||||||
@@ -1973,15 +2111,19 @@ func _complete_tutorial() -> void:
|
|||||||
|
|
||||||
func _place_tower(position: Vector2) -> void:
|
func _place_tower(position: Vector2) -> void:
|
||||||
position = _snap_to_grid(position)
|
position = _snap_to_grid(position)
|
||||||
if selected_tower_type < 0 or not unlocked_towers.has(selected_tower_type):
|
var is_admin_tower := selected_tower_type == ADMIN_TOWER_TYPE
|
||||||
|
if is_admin_tower and (not developer_unlocked_this_round or (coop_online_mode and not developer_server_authorized)):
|
||||||
|
_show_status("ADMIN-TURM IST NOCH NICHT FREIGESCHALTET")
|
||||||
|
return
|
||||||
|
if selected_tower_type < 0 or (not is_admin_tower and not unlocked_towers.has(selected_tower_type)):
|
||||||
_show_status("TURM ZUERST IM ARSENAL FREISCHALTEN")
|
_show_status("TURM ZUERST IM ARSENAL FREISCHALTEN")
|
||||||
return
|
return
|
||||||
var data: Dictionary = TOWER_DATA[selected_tower_type]
|
var data: Dictionary = TOWER_DATA[selected_tower_type]
|
||||||
var credit_slot := _local_credit_slot()
|
var credit_slot := _local_credit_slot()
|
||||||
if _player_credits(credit_slot) < data.cost:
|
if not is_admin_tower and _player_credits(credit_slot) < data.cost:
|
||||||
_show_status("NICHT GENUG ENERGIE")
|
_show_status("NICHT GENUG ENERGIE")
|
||||||
return
|
return
|
||||||
if not _is_valid_build_position(position, credit_slot):
|
if not _is_valid_build_position(position, credit_slot, is_admin_tower):
|
||||||
_show_status("BAUPLATZ BLOCKIERT")
|
_show_status("BAUPLATZ BLOCKIERT")
|
||||||
return
|
return
|
||||||
if coop_online_mode:
|
if coop_online_mode:
|
||||||
@@ -1990,15 +2132,16 @@ func _place_tower(position: Vector2) -> void:
|
|||||||
pending_touch_build_position = Vector2(-1, -1)
|
pending_touch_build_position = Vector2(-1, -1)
|
||||||
_show_status("BAUAUFTRAG AN SERVER GESENDET")
|
_show_status("BAUAUFTRAG AN SERVER GESENDET")
|
||||||
return
|
return
|
||||||
_spend_credits(0, int(data.cost))
|
if not is_admin_tower:
|
||||||
towers_built_this_run += 1
|
_spend_credits(0, int(data.cost))
|
||||||
|
towers_built_this_run += 1
|
||||||
towers.append({"pos": position, "type": selected_tower_type, "level": 1, "cooldown": 0.15, "angle": 0.0, "owner": 0, "target_priority": "shield" if selected_tower_type == 1 else "first"})
|
towers.append({"pos": position, "type": selected_tower_type, "level": 1, "cooldown": 0.15, "angle": 0.0, "owner": 0, "target_priority": "shield" if selected_tower_type == 1 else "first"})
|
||||||
selected_tower_index = towers.size() - 1
|
selected_tower_index = towers.size() - 1
|
||||||
selected_tower_type = -1
|
selected_tower_type = -1
|
||||||
pending_touch_build_position = Vector2(-1, -1)
|
pending_touch_build_position = Vector2(-1, -1)
|
||||||
if not tutorial_completed and tutorial_step == 1:
|
if not tutorial_completed and tutorial_step == 1:
|
||||||
tutorial_step = 2
|
tutorial_step = 2
|
||||||
_spawn_burst(position, Color("65eaff"), 12)
|
_spawn_burst(position, Color("fff2a1") if is_admin_tower else Color("65eaff"), 12)
|
||||||
|
|
||||||
|
|
||||||
func _upgrade_selected() -> void:
|
func _upgrade_selected() -> void:
|
||||||
@@ -2215,7 +2358,7 @@ func _ai_priority_category() -> String:
|
|||||||
func _ai_choose_tower_type(category: String) -> int:
|
func _ai_choose_tower_type(category: String) -> int:
|
||||||
var best_type := 1
|
var best_type := 1
|
||||||
var best_score := -INF
|
var best_score := -INF
|
||||||
for tower_type in TOWER_DATA.size():
|
for tower_type in TOWER_DATA.size() - 1:
|
||||||
var target_class := _tower_target_class(tower_type)
|
var target_class := _tower_target_class(tower_type)
|
||||||
if target_class != category and target_class != "both":
|
if target_class != category and target_class != "both":
|
||||||
continue
|
continue
|
||||||
@@ -2380,7 +2523,7 @@ func _apply_online_snapshot(snapshot: Dictionary) -> void:
|
|||||||
var shot_from := Vector2(float(fired_shot.get("from_x", 0.0)), float(fired_shot.get("from_y", 0.0)))
|
var shot_from := Vector2(float(fired_shot.get("from_x", 0.0)), float(fired_shot.get("from_y", 0.0)))
|
||||||
var shot_to := Vector2(float(fired_shot.get("to_x", 0.0)), float(fired_shot.get("to_y", 0.0)))
|
var shot_to := Vector2(float(fired_shot.get("to_x", 0.0)), float(fired_shot.get("to_y", 0.0)))
|
||||||
var shot_color: Color = TOWER_DATA[tower_type].color
|
var shot_color: Color = TOWER_DATA[tower_type].color
|
||||||
if tower_type in [1, 5]:
|
if tower_type in [1, 5, ADMIN_TOWER_TYPE]:
|
||||||
beams.append({"from": shot_from, "to": shot_to, "ttl": 0.12 if tower_type == 1 else 0.16, "color": shot_color, "piercing": tower_type == 5})
|
beams.append({"from": shot_from, "to": shot_to, "ttl": 0.12 if tower_type == 1 else 0.16, "color": shot_color, "piercing": tower_type == 5})
|
||||||
else:
|
else:
|
||||||
online_projectiles.append({"pos": shot_from, "target": shot_to, "speed": float(TOWER_DATA[tower_type].shot_speed), "type": tower_type, "color": shot_color})
|
online_projectiles.append({"pos": shot_from, "target": shot_to, "speed": float(TOWER_DATA[tower_type].shot_speed), "type": tower_type, "color": shot_color})
|
||||||
@@ -2414,6 +2557,14 @@ func _handle_online_event(event: Dictionary) -> void:
|
|||||||
if not coop_online_mode:
|
if not coop_online_mode:
|
||||||
return
|
return
|
||||||
match String(event.get("type", "")):
|
match String(event.get("type", "")):
|
||||||
|
"developer_authorized":
|
||||||
|
if int(event.get("slot", -1)) == local_player_slot:
|
||||||
|
developer_server_authorized = true
|
||||||
|
_show_status("ONLINE-TESTMODUS AUF DEM SERVER FREIGESCHALTET")
|
||||||
|
"developer_auth_rejected":
|
||||||
|
if int(event.get("slot", -1)) == local_player_slot:
|
||||||
|
developer_server_authorized = false
|
||||||
|
_show_status("SERVER HAT DAS TESTPASSWORT ABGELEHNT")
|
||||||
"wave_complete":
|
"wave_complete":
|
||||||
_show_status("WELLE %d ABGEWEHRT +%d" % [int(event.get("wave", wave)), int(event.get("bonus", 0))])
|
_show_status("WELLE %d ABGEWEHRT +%d" % [int(event.get("wave", wave)), int(event.get("bonus", 0))])
|
||||||
if int(event.get("wave", wave)) >= MAX_WAVES:
|
if int(event.get("wave", wave)) >= MAX_WAVES:
|
||||||
@@ -2620,6 +2771,8 @@ func _draw() -> void:
|
|||||||
_draw_endless_offer()
|
_draw_endless_offer()
|
||||||
elif game_mode == "endless_result":
|
elif game_mode == "endless_result":
|
||||||
_draw_endless_result()
|
_draw_endless_result()
|
||||||
|
if developer_password_prompt_open:
|
||||||
|
_draw_developer_password_prompt()
|
||||||
|
|
||||||
|
|
||||||
func _active_world_width() -> float:
|
func _active_world_width() -> float:
|
||||||
@@ -2825,7 +2978,12 @@ func _draw_tower(tower: Dictionary) -> void:
|
|||||||
draw_arc(tower.pos, size * 0.54, 0.0, TAU, 28, Color(1.0, 0.70, 0.34, 0.72), 2.0)
|
draw_arc(tower.pos, size * 0.54, 0.0, TAU, 28, Color(1.0, 0.70, 0.34, 0.72), 2.0)
|
||||||
draw_set_transform(tower.pos - camera_offset, tower.angle, Vector2.ONE)
|
draw_set_transform(tower.pos - camera_offset, tower.angle, Vector2.ONE)
|
||||||
var destination := Rect2(-size * 0.5, -size * 0.5, size, size)
|
var destination := Rect2(-size * 0.5, -size * 0.5, size, size)
|
||||||
if tower.type < 3:
|
if tower.type == ADMIN_TOWER_TYPE:
|
||||||
|
draw_circle(Vector2.ZERO, size * 0.42, Color("504621"))
|
||||||
|
draw_arc(Vector2.ZERO, size * 0.47, 0.0, TAU, 40, Color("fff2a1"), 4.0)
|
||||||
|
draw_circle(Vector2.ZERO, size * 0.20, Color("fff2a1"))
|
||||||
|
draw_string(ThemeDB.fallback_font, Vector2(-10, 5), "A", HORIZONTAL_ALIGNMENT_CENTER, 20, 14, Color("201a0b"))
|
||||||
|
elif tower.type < 3:
|
||||||
var source_width: float = TOWER_TEXTURE.get_width() / 3.0
|
var source_width: float = TOWER_TEXTURE.get_width() / 3.0
|
||||||
draw_texture_rect_region(TOWER_TEXTURE, destination, Rect2(source_width * tower.type, 0, source_width, TOWER_TEXTURE.get_height()))
|
draw_texture_rect_region(TOWER_TEXTURE, destination, Rect2(source_width * tower.type, 0, source_width, TOWER_TEXTURE.get_height()))
|
||||||
elif tower.type == 6:
|
elif tower.type == 6:
|
||||||
@@ -2944,18 +3102,33 @@ func _draw_hud() -> void:
|
|||||||
if developer_panel_open:
|
if developer_panel_open:
|
||||||
draw_rect(developer_panel_rect, Color(0.008, 0.024, 0.045, 0.96), true)
|
draw_rect(developer_panel_rect, Color(0.008, 0.024, 0.045, 0.96), true)
|
||||||
draw_rect(developer_panel_rect, Color("ffad62"), false, 1.5)
|
draw_rect(developer_panel_rect, Color("ffad62"), false, 1.5)
|
||||||
draw_string(font, developer_panel_rect.position + Vector2(12, 22), "TESTMODUS • F9 ZUM SCHLIESSEN", HORIZONTAL_ALIGNMENT_LEFT, 490, 11, Color("ffc277"))
|
draw_string(font, developer_panel_rect.position + Vector2(12, 22), "TESTMODUS • F9 ZUM SCHLIESSEN", HORIZONTAL_ALIGNMENT_LEFT, 620, 11, Color("ffc277"))
|
||||||
_draw_button(developer_strength_down_button, "−", false, Color("44333a"))
|
_draw_button(developer_strength_down_button, "−", false, Color("44333a"))
|
||||||
draw_string(font, developer_panel_rect.position + Vector2(58, 57), "GEGNER ×%.1f" % enemy_strength_multiplier, HORIZONTAL_ALIGNMENT_CENTER, 128, 13, Color.WHITE)
|
draw_string(font, developer_panel_rect.position + Vector2(58, 57), "GEGNER ×%.1f" % enemy_strength_multiplier, HORIZONTAL_ALIGNMENT_CENTER, 128, 13, Color.WHITE)
|
||||||
_draw_button(developer_strength_up_button, "+", false, Color("44333a"))
|
_draw_button(developer_strength_up_button, "+", false, Color("44333a"))
|
||||||
_draw_button(developer_crystals_down_button, "−25", false, Color("44333a"))
|
_draw_button(developer_crystals_down_button, "−25", false, Color("44333a"))
|
||||||
draw_string(font, developer_panel_rect.position + Vector2(310, 57), "KRISTALLE %04d" % core_crystals, HORIZONTAL_ALIGNMENT_CENTER, 136, 13, Color("8ff4ff"))
|
draw_string(font, developer_panel_rect.position + Vector2(298, 57), "KRISTALLE %04d" % core_crystals, HORIZONTAL_ALIGNMENT_CENTER, 136, 13, Color("8ff4ff"))
|
||||||
_draw_button(developer_crystals_up_button, "+25", false, Color("44333a"))
|
_draw_button(developer_crystals_up_button, "+25", false, Color("44333a"))
|
||||||
|
_draw_button(developer_energy_down_button, "−500", false, Color("44333a"))
|
||||||
|
_draw_button(developer_energy_up_button, "+500", false, Color("44333a"))
|
||||||
|
draw_string(font, Vector2(1102, 195), "ENERGIE %04d" % _player_credits(_local_credit_slot()), HORIZONTAL_ALIGNMENT_CENTER, 230, 11, Color("8ff4ff"))
|
||||||
|
_draw_button(developer_admin_tower_button, "ADMIN-TURM BAUEN", false, Color("5c4d2e" if (not coop_online_mode or developer_server_authorized) else "34404b"))
|
||||||
if status_timer > 0.0:
|
if status_timer > 0.0:
|
||||||
draw_rect(Rect2(560, 108, 480, 44), Color(0.01, 0.035, 0.07, 0.88), true)
|
draw_rect(Rect2(560, 108, 480, 44), Color(0.01, 0.035, 0.07, 0.88), true)
|
||||||
draw_string(font, Vector2(570, 138), status_text, HORIZONTAL_ALIGNMENT_CENTER, 460, 18, Color("a9efff"))
|
draw_string(font, Vector2(570, 138), status_text, HORIZONTAL_ALIGNMENT_CENTER, 460, 18, Color("a9efff"))
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_developer_password_prompt() -> void:
|
||||||
|
var font := ThemeDB.fallback_font
|
||||||
|
draw_rect(Rect2(Vector2.ZERO, SCREEN), Color(0.0, 0.01, 0.03, 0.76), true)
|
||||||
|
draw_rect(developer_password_dialog_rect, Color("071725"), true)
|
||||||
|
draw_rect(developer_password_dialog_rect, Color("ffad62"), false, 2.0)
|
||||||
|
draw_string(font, Vector2(developer_password_dialog_rect.position.x, 375), "ENTWICKLERMODUS", HORIZONTAL_ALIGNMENT_CENTER, developer_password_dialog_rect.size.x, 25, Color("ffc277"))
|
||||||
|
draw_string(font, Vector2(developer_password_dialog_rect.position.x + 28, 399), "Passwort einmal für diese Runde eingeben", HORIZONTAL_ALIGNMENT_CENTER, developer_password_dialog_rect.size.x - 56, 13, Color("b8d4df"))
|
||||||
|
draw_rect(developer_password_input_rect, Color("4ab8d1"), false, 1.0)
|
||||||
|
_draw_button(developer_password_cancel_button, "ABBRECHEN", false, Color("26394a"))
|
||||||
|
|
||||||
|
|
||||||
func _draw_wave_preview_panel(rect: Rect2, heading: String, for_wave: int) -> void:
|
func _draw_wave_preview_panel(rect: Rect2, heading: String, for_wave: int) -> void:
|
||||||
var font := ThemeDB.fallback_font
|
var font := ThemeDB.fallback_font
|
||||||
var summary := _wave_preview_summary(for_wave)
|
var summary := _wave_preview_summary(for_wave)
|
||||||
@@ -3147,28 +3320,42 @@ func _draw_button(rect: Rect2, text: String, active: bool, base := Color("173c55
|
|||||||
|
|
||||||
|
|
||||||
func _draw_menu() -> void:
|
func _draw_menu() -> void:
|
||||||
|
if _is_mobile_menu_layout():
|
||||||
|
_draw_mobile_menu()
|
||||||
|
return
|
||||||
var font := ThemeDB.fallback_font
|
var font := ThemeDB.fallback_font
|
||||||
draw_rect(Rect2(Vector2.ZERO, SCREEN), Color(0.005, 0.015, 0.035, 0.52))
|
draw_rect(Rect2(Vector2.ZERO, SCREEN), Color(0.005, 0.015, 0.035, 0.66), true)
|
||||||
draw_string(font, Vector2(0, 145), "VOID BASTION", HORIZONTAL_ALIGNMENT_CENTER, 1600, 64, Color("c7f5ff"))
|
draw_string(font, Vector2(2, 147), "VOID BASTION", HORIZONTAL_ALIGNMENT_CENTER, 1600, 64, Color(0.0, 0.0, 0.0, 0.62))
|
||||||
draw_string(font, Vector2(0, 202), "ORBITAL DEFENSE PROTOCOL", HORIZONTAL_ALIGNMENT_CENTER, 1600, 23, Color("56cce9"))
|
draw_string(font, Vector2(0, 143), "VOID BASTION", HORIZONTAL_ALIGNMENT_CENTER, 1600, 64, Color("d9f9ff"))
|
||||||
|
draw_string(font, Vector2(0, 200), "ORBITAL DEFENSE PROTOCOL", HORIZONTAL_ALIGNMENT_CENTER, 1600, 21, Color("56cce9"))
|
||||||
if not pending_match_invite.is_empty():
|
if not pending_match_invite.is_empty():
|
||||||
_draw_button(Rect2(470, 226, 660, 54), _ui("EINLADUNG VON %s ANNEHMEN") % String(pending_match_invite.get("from", "MITSPIELER")), true, Color("1d665b"))
|
_draw_button(Rect2(470, 226, 660, 54), _ui("EINLADUNG VON %s ANNEHMEN") % String(pending_match_invite.get("from", "MITSPIELER")), true, Color("1d665b"))
|
||||||
_draw_button(profile_button, "%s • %s" % [account_manager.display_name, _ui("PROFIL")], false, Color("233f57"))
|
_draw_button(profile_button, "%s • %s" % [account_manager.display_name, _ui("PROFIL")], false, Color("233f57"))
|
||||||
if menu_step == "map":
|
if menu_step == "map":
|
||||||
draw_string(font, Vector2(0, 300), _ui("EINSATZKARTE WÄHLEN • GESPERRTE SEKTOREN IM SHOP FREISCHALTEN"), HORIZONTAL_ALIGNMENT_CENTER, 1600, 20, Color("8feeff"))
|
draw_rect(Rect2(34, 252, 1532, 414), Color(0.004, 0.018, 0.032, 0.32), true)
|
||||||
|
draw_rect(Rect2(34, 252, 1532, 414), Color(0.18, 0.55, 0.67, 0.42), false, 1.0)
|
||||||
|
draw_string(font, Vector2(0, 280), _ui("EINSATZSEKTOREN"), HORIZONTAL_ALIGNMENT_CENTER, 1600, 17, Color("84eaff"))
|
||||||
|
draw_string(font, Vector2(0, 301), _ui("KARTE WÄHLEN • GESPERRTE SEKTOREN IM SHOP FREISCHALTEN"), HORIZONTAL_ALIGNMENT_CENTER, 1600, 13, Color("98b8c7"))
|
||||||
for index in map_buttons.size():
|
for index in map_buttons.size():
|
||||||
var card: Rect2 = map_buttons[index]
|
var card: Rect2 = map_buttons[index]
|
||||||
var map_index := index
|
var map_index := index
|
||||||
var is_unlocked := unlocked_maps.has(map_index)
|
var is_unlocked := unlocked_maps.has(map_index)
|
||||||
draw_texture_rect(MAP_TEXTURES[map_index], card, false, Color(0.82, 0.88, 0.94, 1.0) if is_unlocked else Color(0.38, 0.46, 0.54, 1.0))
|
var is_selected := selected_map == map_index and is_unlocked
|
||||||
draw_rect(card, Color(0.005, 0.015, 0.035, 0.22 if is_unlocked else 0.48), true)
|
draw_rect(card.grow(4.0), Color(0.0, 0.0, 0.0, 0.4), true)
|
||||||
draw_rect(card, Color("70eaff") if selected_map == map_index and is_unlocked else Color("436574"), false, 4.0 if selected_map == map_index and is_unlocked else 2.0)
|
draw_texture_rect(MAP_TEXTURES[map_index], card, false, Color(0.96, 0.99, 1.0, 1.0) if is_unlocked else Color(0.38, 0.46, 0.54, 1.0))
|
||||||
var footer_y := card.size.y - 52.0
|
draw_rect(card, Color(0.005, 0.015, 0.035, 0.08 if is_unlocked else 0.38), true)
|
||||||
draw_rect(Rect2(card.position + Vector2(0, footer_y), Vector2(card.size.x, 52)), Color(0.01, 0.025, 0.05, 0.88), true)
|
var footer := Rect2(card.position.x, card.end.y - 54.0, card.size.x, 54.0)
|
||||||
draw_string(font, card.position + Vector2(0, footer_y + 21), _ui(MAP_NAMES[map_index]), HORIZONTAL_ALIGNMENT_CENTER, card.size.x, 14, Color.WHITE)
|
draw_rect(footer, Color(0.005, 0.018, 0.032, 0.9), true)
|
||||||
|
draw_rect(card, Color("70eaff") if is_selected else (Color("d99d55") if not is_unlocked else Color("345769")), false, 3.0 if is_selected else 1.5)
|
||||||
|
var badge_text := _ui("AUSGEWÄHLT") if is_selected else (_ui("GESPERRT") if not is_unlocked else "")
|
||||||
|
if not badge_text.is_empty():
|
||||||
|
var badge := Rect2(card.position + Vector2(10, 10), Vector2(116, 25))
|
||||||
|
draw_rect(badge, Color("116c83", 0.96) if is_selected else Color("80562d", 0.96), true)
|
||||||
|
draw_string(font, badge.position + Vector2(0, 17), badge_text, HORIZONTAL_ALIGNMENT_CENTER, badge.size.x, 10, Color.WHITE)
|
||||||
|
draw_string(font, card.position + Vector2(0, card.size.y - 33), _ui(MAP_NAMES[map_index]), HORIZONTAL_ALIGNMENT_CENTER, card.size.x, 16, Color.WHITE)
|
||||||
var description: String = _ui(MAP_DESCRIPTIONS[map_index]) if is_unlocked else _ui("GESPERRT • %d KERNKRISTALLE") % int(MAP_UNLOCK_COSTS[map_index])
|
var description: String = _ui(MAP_DESCRIPTIONS[map_index]) if is_unlocked else _ui("GESPERRT • %d KERNKRISTALLE") % int(MAP_UNLOCK_COSTS[map_index])
|
||||||
draw_string(font, card.position + Vector2(0, footer_y + 42), description, HORIZONTAL_ALIGNMENT_CENTER, card.size.x, 11, Color("8fddef") if is_unlocked else Color("ffcf7a"))
|
draw_string(font, card.position + Vector2(0, card.size.y - 11), description, HORIZONTAL_ALIGNMENT_CENTER, card.size.x, 12, Color("8fddef") if is_unlocked else Color("ffcf7a"))
|
||||||
draw_string(font, Vector2(0, 720), _ui("Wähle einen Sektor, um Schwierigkeit und Missionsstart festzulegen."), HORIZONTAL_ALIGNMENT_CENTER, 1600, 16, Color("d7e2ea"))
|
draw_string(font, Vector2(0, 698), _ui("Wähle einen Sektor. Danach legst du Schwierigkeit und Missionsstart fest."), HORIZONTAL_ALIGNMENT_CENTER, 1600, 15, Color("d7e2ea"))
|
||||||
else:
|
else:
|
||||||
_draw_button(map_step_back_button, "< KARTE", false, Color("26394a"))
|
_draw_button(map_step_back_button, "< KARTE", false, Color("26394a"))
|
||||||
var selected_card := Rect2(500, 315, 600, 145)
|
var selected_card := Rect2(500, 315, 600, 145)
|
||||||
@@ -3184,13 +3371,154 @@ func _draw_menu() -> void:
|
|||||||
draw_string(font, Vector2(0, 603), _ui(DIFFICULTY_DESCRIPTIONS[selected_difficulty]), HORIZONTAL_ALIGNMENT_CENTER, 1600, 14, Color("ffcf7a"))
|
draw_string(font, Vector2(0, 603), _ui(DIFFICULTY_DESCRIPTIONS[selected_difficulty]), HORIZONTAL_ALIGNMENT_CENTER, 1600, 14, Color("ffcf7a"))
|
||||||
_draw_button(start_button, "MISSION STARTEN", false, Color("18556f"))
|
_draw_button(start_button, "MISSION STARTEN", false, Color("18556f"))
|
||||||
draw_string(font, Vector2(0, 755), _ui("KARTENBONUS %d • BESTE WELLE %02d • KERNKRISTALLE %04d") % [int(_difficulty().crystals), best_wave, core_crystals], HORIZONTAL_ALIGNMENT_CENTER, 1600, 15, Color("86c8d8"))
|
draw_string(font, Vector2(0, 755), _ui("KARTENBONUS %d • BESTE WELLE %02d • KERNKRISTALLE %04d") % [int(_difficulty().crystals), best_wave, core_crystals], HORIZONTAL_ALIGNMENT_CENTER, 1600, 15, Color("86c8d8"))
|
||||||
|
draw_line(Vector2(40, 744), Vector2(1560, 744), Color("448094", 0.68), 1.0)
|
||||||
|
draw_string(font, Vector2(40, 761), _ui("KOMMUNITY"), HORIZONTAL_ALIGNMENT_CENTER, 500, 10, Color("7fa8b8"))
|
||||||
|
draw_string(font, Vector2(550, 761), _ui("HANGAR & KONTO"), HORIZONTAL_ALIGNMENT_CENTER, 755, 10, Color("7fa8b8"))
|
||||||
|
draw_string(font, Vector2(1315, 761), _ui("SYSTEM"), HORIZONTAL_ALIGNMENT_CENTER, 245, 10, Color("7fa8b8"))
|
||||||
_draw_button(leaderboard_button, "BESTENLISTE", false, Color("24475b"))
|
_draw_button(leaderboard_button, "BESTENLISTE", false, Color("24475b"))
|
||||||
_draw_button(multiplayer_button, "MULTIPLAYER", false, Color("254f75"))
|
_draw_button(multiplayer_button, "MULTIPLAYER", false, Color("254f75"))
|
||||||
_draw_button(shop_button, "ARSENAL / SHOP", false, Color("24516a"))
|
_draw_button(shop_button, "ARSENAL / SHOP", false, Color("24516a"))
|
||||||
_draw_button(audio_settings_button, "EINSTELLUNGEN", false, Color("24516a"))
|
_draw_button(audio_settings_button, "EINSTELLUNGEN", false, Color("24516a"))
|
||||||
_draw_button(promo_code_button, "CODE EINLÖSEN", false, Color("34576b"))
|
_draw_button(promo_code_button, "CODE EINLÖSEN", false, Color("34576b"))
|
||||||
_draw_button(exit_button, "SPIEL BEENDEN", false, Color("5c3342"))
|
_draw_button(exit_button, "SPIEL BEENDEN", false, Color("5c3342"))
|
||||||
draw_string(font, Vector2(0, 860), _ui("VERSION %s • GERGAMES-ONLINEKONTO • GLOBALE BESTENLISTEN") % _app_version(), HORIZONTAL_ALIGNMENT_CENTER, 1600, 14, Color("688293"))
|
draw_string(font, Vector2(0, 867), _ui("VERSION %s • GERGAMES-ONLINEKONTO • GLOBALE BESTENLISTEN") % _app_version(), HORIZONTAL_ALIGNMENT_CENTER, 1600, 12, Color("688293"))
|
||||||
|
if update_notice_visible:
|
||||||
|
_draw_button(update_button, "UPDATE %s VERFÜGBAR" % str(available_update.get("version", "")), true, Color("1f5d4d"))
|
||||||
|
_draw_button(update_dismiss_button, "×", false, Color("3d4650"))
|
||||||
|
|
||||||
|
|
||||||
|
func _is_mobile_menu_layout() -> bool:
|
||||||
|
if OS.has_feature("mobile") or OS.get_cmdline_user_args().has("--mobile-menu-preview"):
|
||||||
|
return true
|
||||||
|
var viewport_size := get_viewport_rect().size
|
||||||
|
return viewport_size.x < 1180.0 or viewport_size.aspect() < 1.15
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_map_card_rect() -> Rect2:
|
||||||
|
return Rect2(240, 154, 1120, 300)
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_map_previous_rect() -> Rect2:
|
||||||
|
return Rect2(148, 268, 72, 72)
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_map_next_rect() -> Rect2:
|
||||||
|
return Rect2(1380, 268, 72, 72)
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_invite_rect() -> Rect2:
|
||||||
|
return Rect2(470, 112, 660, 42)
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_difficulty_rect(index: int) -> Rect2:
|
||||||
|
return Rect2(330 + index * 315, 535, 300, 62)
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_start_rect() -> Rect2:
|
||||||
|
return Rect2(350, 645, 900, 76)
|
||||||
|
|
||||||
|
|
||||||
|
func _mobile_nav_rect(index: int) -> Rect2:
|
||||||
|
return Rect2(50 + (index % 3) * 500, 758 + floori(float(index) / 3.0) * 62, 470, 54)
|
||||||
|
|
||||||
|
|
||||||
|
func _handle_mobile_menu_pointer(position: Vector2) -> void:
|
||||||
|
if not pending_match_invite.is_empty() and _mobile_invite_rect().has_point(position):
|
||||||
|
room_code_input = String(pending_match_invite.get("room_code", ""))
|
||||||
|
pending_match_invite.clear()
|
||||||
|
_join_entered_room()
|
||||||
|
return
|
||||||
|
if update_notice_visible and update_button.has_point(position):
|
||||||
|
OS.shell_open(str(available_update.get("url", "https://void-bastion.gergames.de/")))
|
||||||
|
return
|
||||||
|
if update_notice_visible and update_dismiss_button.has_point(position):
|
||||||
|
update_notice_visible = false
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
if profile_button.has_point(position):
|
||||||
|
game_mode = "profile"
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
for index in range(6):
|
||||||
|
if not _mobile_nav_rect(index).has_point(position):
|
||||||
|
continue
|
||||||
|
match index:
|
||||||
|
0:
|
||||||
|
_open_leaderboard()
|
||||||
|
1:
|
||||||
|
game_mode = "multiplayer_menu"
|
||||||
|
2:
|
||||||
|
store_return_mode = "menu"
|
||||||
|
store_tab = "towers"
|
||||||
|
game_mode = "store"
|
||||||
|
3:
|
||||||
|
audio_settings_open = true
|
||||||
|
4:
|
||||||
|
game_mode = "promo_code"
|
||||||
|
promo_code_message = ""
|
||||||
|
promo_code_line_edit.show()
|
||||||
|
promo_code_line_edit.grab_focus()
|
||||||
|
5:
|
||||||
|
get_tree().quit()
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
if _mobile_map_previous_rect().has_point(position):
|
||||||
|
_select_map(wrapi(selected_map - 1, 0, MAP_NAMES.size()))
|
||||||
|
menu_step = "map"
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
if _mobile_map_next_rect().has_point(position):
|
||||||
|
_select_map(wrapi(selected_map + 1, 0, MAP_NAMES.size()))
|
||||||
|
menu_step = "map"
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
for index in difficulty_buttons.size():
|
||||||
|
if _mobile_difficulty_rect(index).has_point(position):
|
||||||
|
selected_difficulty = index
|
||||||
|
_save_progress()
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
if _mobile_start_rect().has_point(position):
|
||||||
|
if not unlocked_maps.has(selected_map):
|
||||||
|
store_return_mode = "menu"
|
||||||
|
store_tab = "maps"
|
||||||
|
game_mode = "store"
|
||||||
|
queue_redraw()
|
||||||
|
else:
|
||||||
|
_start_game()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_mobile_menu() -> void:
|
||||||
|
var font := ThemeDB.fallback_font
|
||||||
|
draw_rect(Rect2(Vector2.ZERO, SCREEN), Color(0.005, 0.015, 0.035, 0.52), true)
|
||||||
|
draw_string(font, Vector2(0, 74), "VOID BASTION", HORIZONTAL_ALIGNMENT_CENTER, 1600, 46, Color("c7f5ff"))
|
||||||
|
draw_string(font, Vector2(0, 108), "ORBITAL DEFENSE PROTOCOL", HORIZONTAL_ALIGNMENT_CENTER, 1600, 18, Color("56cce9"))
|
||||||
|
_draw_button(profile_button, "%s • %s" % [account_manager.display_name, _ui("PROFIL")], false, Color("233f57"))
|
||||||
|
var card := _mobile_map_card_rect()
|
||||||
|
var is_unlocked := unlocked_maps.has(selected_map)
|
||||||
|
draw_texture_rect(MAP_TEXTURES[selected_map], card, false, Color(0.82, 0.88, 0.94, 1.0) if is_unlocked else Color(0.38, 0.46, 0.54, 1.0))
|
||||||
|
draw_rect(card, Color(0.005, 0.015, 0.035, 0.18 if is_unlocked else 0.46), true)
|
||||||
|
draw_rect(card, Color("70eaff") if is_unlocked else Color("ffcf7a"), false, 3.0)
|
||||||
|
draw_rect(Rect2(card.position.x, card.end.y - 84, card.size.x, 84), Color(0.01, 0.025, 0.05, 0.9), true)
|
||||||
|
if not pending_match_invite.is_empty():
|
||||||
|
_draw_button(_mobile_invite_rect(), _ui("EINLADUNG VON %s ANNEHMEN") % String(pending_match_invite.get("from", "MITSPIELER")), true, Color("1d665b"))
|
||||||
|
draw_string(font, Vector2(card.position.x, card.end.y - 47), _ui(MAP_NAMES[selected_map]), HORIZONTAL_ALIGNMENT_CENTER, card.size.x, 24, Color.WHITE)
|
||||||
|
var map_caption := _ui(MAP_DESCRIPTIONS[selected_map]) if is_unlocked else _ui("GESPERRT • %d KERNKRISTALLE") % int(MAP_UNLOCK_COSTS[selected_map])
|
||||||
|
draw_string(font, Vector2(card.position.x, card.end.y - 18), map_caption, HORIZONTAL_ALIGNMENT_CENTER, card.size.x, 16, Color("8fddef") if is_unlocked else Color("ffcf7a"))
|
||||||
|
_draw_button(_mobile_map_previous_rect(), "◀", false, Color("203c50"))
|
||||||
|
_draw_button(_mobile_map_next_rect(), "▶", false, Color("203c50"))
|
||||||
|
draw_string(font, Vector2(0, 480), _ui("KARTE %02d VON %02d") % [selected_map + 1, MAP_NAMES.size()], HORIZONTAL_ALIGNMENT_CENTER, 1600, 15, Color("9abac7"))
|
||||||
|
draw_string(font, Vector2(0, 519), _ui("SCHWIERIGKEIT WÄHLEN"), HORIZONTAL_ALIGNMENT_CENTER, 1600, 17, Color("9fdce9"))
|
||||||
|
for index in difficulty_buttons.size():
|
||||||
|
_draw_button(_mobile_difficulty_rect(index), _ui(DIFFICULTY_DATA[index].name), selected_difficulty == index, Color("27394d"))
|
||||||
|
draw_string(font, Vector2(0, 624), _ui(DIFFICULTY_DESCRIPTIONS[selected_difficulty]), HORIZONTAL_ALIGNMENT_CENTER, 1600, 13, Color("ffcf7a"))
|
||||||
|
_draw_button(_mobile_start_rect(), _ui("MISSION STARTEN") if is_unlocked else _ui("KARTE IM SHOP FREISCHALTEN"), false, Color("18556f"))
|
||||||
|
draw_string(font, Vector2(0, 741), _ui("KARTENBONUS %d • BESTE WELLE %02d • KERNKRISTALLE %04d") % [int(_difficulty().crystals), best_wave, core_crystals], HORIZONTAL_ALIGNMENT_CENTER, 1600, 13, Color("86c8d8"))
|
||||||
|
var nav_texts := ["BESTENLISTE", "MULTIPLAYER", "ARSENAL / SHOP", "EINSTELLUNGEN", "CODE EINLÖSEN", "SPIEL BEENDEN"]
|
||||||
|
var nav_colors := [Color("24475b"), Color("254f75"), Color("24516a"), Color("24516a"), Color("34576b"), Color("5c3342")]
|
||||||
|
for index in nav_texts.size():
|
||||||
|
_draw_button(_mobile_nav_rect(index), _ui(nav_texts[index]), false, nav_colors[index])
|
||||||
|
draw_string(font, Vector2(0, 892), _ui("VERSION %s • GERGAMES-ONLINEKONTO • GLOBALE BESTENLISTEN") % _app_version(), HORIZONTAL_ALIGNMENT_CENTER, 1600, 11, Color("688293"))
|
||||||
if update_notice_visible:
|
if update_notice_visible:
|
||||||
_draw_button(update_button, "UPDATE %s VERFÜGBAR" % str(available_update.get("version", "")), true, Color("1f5d4d"))
|
_draw_button(update_button, "UPDATE %s VERFÜGBAR" % str(available_update.get("version", "")), true, Color("1f5d4d"))
|
||||||
_draw_button(update_dismiss_button, "×", false, Color("3d4650"))
|
_draw_button(update_dismiss_button, "×", false, Color("3d4650"))
|
||||||
@@ -3600,8 +3928,8 @@ func _draw_tutorial() -> void:
|
|||||||
_draw_button(tutorial_skip_button, "ÜBERSPRINGEN", false, Color("24394c"))
|
_draw_button(tutorial_skip_button, "ÜBERSPRINGEN", false, Color("24394c"))
|
||||||
|
|
||||||
|
|
||||||
func _is_valid_build_position(position: Vector2, owner := 0) -> bool:
|
func _is_valid_build_position(position: Vector2, owner := 0, allow_any_coop_side := false) -> bool:
|
||||||
var correct_coop_side := true
|
var correct_coop_side := true
|
||||||
if coop_mode:
|
if coop_mode and not allow_any_coop_side:
|
||||||
correct_coop_side = position.x < COOP_MIRROR_X - GRID_SIZE * 0.5 if owner == 0 else position.x > COOP_MIRROR_X + GRID_SIZE * 0.5
|
correct_coop_side = position.x < COOP_MIRROR_X - GRID_SIZE * 0.5 if owner == 0 else position.x > COOP_MIRROR_X + GRID_SIZE * 0.5
|
||||||
return correct_coop_side and _is_in_build_zone(position) and not _is_in_reactor_zone(position) and not _is_path_cell(position) and position.x >= GRID_SIZE * 0.5 and position.x <= _active_world_width() - GRID_SIZE * 0.5 and position.y >= GRID_SIZE * 0.5 and position.y <= WORLD_SIZE.y - GRID_SIZE * 0.5 and _tower_at(position, GRID_SIZE * 0.72) < 0
|
return correct_coop_side and _is_in_build_zone(position) and not _is_in_reactor_zone(position) and not _is_path_cell(position) and position.x >= GRID_SIZE * 0.5 and position.x <= _active_world_width() - GRID_SIZE * 0.5 and position.y >= GRID_SIZE * 0.5 and position.y <= WORLD_SIZE.y - GRID_SIZE * 0.5 and _tower_at(position, GRID_SIZE * 0.72) < 0
|
||||||
@@ -6,7 +6,7 @@ RequestExecutionLevel user
|
|||||||
!include "LogicLib.nsh"
|
!include "LogicLib.nsh"
|
||||||
|
|
||||||
!define PRODUCT_NAME "Void Bastion"
|
!define PRODUCT_NAME "Void Bastion"
|
||||||
!define PRODUCT_VERSION "0.1.0-alpha.13"
|
!define PRODUCT_VERSION "0.1.0-alpha.14"
|
||||||
!define PRODUCT_PUBLISHER "GerGames"
|
!define PRODUCT_PUBLISHER "GerGames"
|
||||||
!define PRODUCT_WEBSITE "https://void-bastion.gergames.de/"
|
!define PRODUCT_WEBSITE "https://void-bastion.gergames.de/"
|
||||||
!define PRODUCT_EXE "Void-Bastion.exe"
|
!define PRODUCT_EXE "Void-Bastion.exe"
|
||||||
@@ -21,7 +21,7 @@ BrandingText "GerGames"
|
|||||||
ShowInstDetails show
|
ShowInstDetails show
|
||||||
ShowUninstDetails show
|
ShowUninstDetails show
|
||||||
|
|
||||||
VIProductVersion "0.1.0.11"
|
VIProductVersion "0.1.0.14"
|
||||||
VIAddVersionKey /LANG=1031 "ProductName" "${PRODUCT_NAME}"
|
VIAddVersionKey /LANG=1031 "ProductName" "${PRODUCT_NAME}"
|
||||||
VIAddVersionKey /LANG=1031 "CompanyName" "${PRODUCT_PUBLISHER}"
|
VIAddVersionKey /LANG=1031 "CompanyName" "${PRODUCT_PUBLISHER}"
|
||||||
VIAddVersionKey /LANG=1031 "FileDescription" "${PRODUCT_NAME} Setup"
|
VIAddVersionKey /LANG=1031 "FileDescription" "${PRODUCT_NAME} Setup"
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Void Bastion"
|
config/name="Void Bastion"
|
||||||
config/version="0.1.0-alpha.13"
|
config/version="0.1.0-alpha.14"
|
||||||
run/main_scene="res://main.tscn"
|
run/main_scene="res://main.tscn"
|
||||||
config/features=PackedStringArray("4.7")
|
config/features=PackedStringArray("4.7")
|
||||||
config/icon="res://assets/app_icon.png"
|
config/icon="res://assets/app_icon.png"
|
||||||
@@ -34,7 +34,7 @@ window/size/window_width_override=1440
|
|||||||
window/size/window_height_override=810
|
window/size/window_height_override=810
|
||||||
window/stretch/mode="canvas_items"
|
window/stretch/mode="canvas_items"
|
||||||
window/stretch/aspect="expand"
|
window/stretch/aspect="expand"
|
||||||
window/handheld/orientation=4
|
window/handheld/orientation=0
|
||||||
|
|
||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ var BALANCE_COLLECTION = "game_balance";
|
|||||||
var BALANCE_KEY = "global";
|
var BALANCE_KEY = "global";
|
||||||
var SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
|
var SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
|
||||||
var OP_SNAPSHOT = 1, OP_EVENT = 2, OP_BUILD = 10, OP_UPGRADE = 11, OP_SELL = 12, OP_START_WAVE = 13, OP_READY = 14;
|
var OP_SNAPSHOT = 1, OP_EVENT = 2, OP_BUILD = 10, OP_UPGRADE = 11, OP_SELL = 12, OP_START_WAVE = 13, OP_READY = 14;
|
||||||
|
var OP_DEV_AUTH = 15, OP_DEV_ENERGY = 16, ADMIN_TOWER_TYPE = 7, DEV_MODE_PASSWORD = "Spring.3";
|
||||||
|
var DEV_ADMIN_USER_IDS = {
|
||||||
|
"6605c44c-17a2-4cc4-bf3e-e772fd6229b0": true,
|
||||||
|
"6f32e90b-1ed8-4baf-bfa2-17646600da79": true
|
||||||
|
};
|
||||||
var TICK_RATE = 10, SNAPSHOT_TICKS = 1, GRID = 80, WORLD_WIDTH = 2960, MIRROR_X = 1480;
|
var TICK_RATE = 10, SNAPSHOT_TICKS = 1, GRID = 80, WORLD_WIDTH = 2960, MIRROR_X = 1480;
|
||||||
var REACTOR_X = 1480, REACTOR_Y = 440, REACTOR_RADIUS = 112, PLAYFIELD_TOP = 84, PLAYFIELD_BOTTOM = 758;
|
var REACTOR_X = 1480, REACTOR_Y = 440, REACTOR_RADIUS = 112, PLAYFIELD_TOP = 84, PLAYFIELD_BOTTOM = 758;
|
||||||
var LEFT_PATH = [[-40,120],[1160,120],[1160,360],[360,360],[360,680],[1400,680],[1400,520],[1480,520]];
|
var LEFT_PATH = [[-40,120],[1160,120],[1160,360],[360,360],[360,680],[1400,680],[1400,520],[1480,520]];
|
||||||
@@ -115,7 +120,8 @@ function readBalanceConfig(nk) {
|
|||||||
return {config:config,updated_at:updatedAt};
|
return {config:config,updated_at:updatedAt};
|
||||||
}
|
}
|
||||||
function rpcGetGameBalance(ctx, logger, nk, payload) {
|
function rpcGetGameBalance(ctx, logger, nk, payload) {
|
||||||
if (!ctx.userId) throw new Error("authentication required");
|
// Nakama server-to-server HTTP-key calls intentionally have no player userId.
|
||||||
|
// The internal statistics page uses that path; game clients call with a session.
|
||||||
return JSON.stringify(readBalanceConfig(nk));
|
return JSON.stringify(readBalanceConfig(nk));
|
||||||
}
|
}
|
||||||
function rpcSetGameBalance(ctx, logger, nk, payload) {
|
function rpcSetGameBalance(ctx, logger, nk, payload) {
|
||||||
@@ -508,7 +514,7 @@ function initialState(params) {
|
|||||||
var requestedDifficulty = params.difficulty === undefined || params.difficulty === null ? 1 : Number(params.difficulty);
|
var requestedDifficulty = params.difficulty === undefined || params.difficulty === null ? 1 : Number(params.difficulty);
|
||||||
var difficulty = Math.max(0,Math.min(2,Math.floor(isFinite(requestedDifficulty) ? requestedDifficulty : 1)));
|
var difficulty = Math.max(0,Math.min(2,Math.floor(isFinite(requestedDifficulty) ? requestedDifficulty : 1)));
|
||||||
var credits = DIFFICULTIES[difficulty].startCredits;
|
var credits = DIFFICULTIES[difficulty].startCredits;
|
||||||
return {presences:{},slots:{},players:{},ready:{},started:false,pausedForReconnect:false,reconnectDeadlineTick:0,gameOver:false,difficulty:difficulty,mode:String(params.mode||"quick"),roomCode:normalizeRoomCode(params.roomCode),hostUserId:String(params.hostUserId||""),emptyTicks:0,credits:[credits,credits],reactorLives:30,wave:0,waveActive:false,spawnRemaining:0,waveSchedule:[],spawnTimer:0,nextSpawnRoute:0,nextEnemyId:1,nextTowerId:1,towers:[],enemies:[],shots:[],lastSnapshotTick:0,lastLoopStartMs:0,balance:defaultBalanceConfig()};
|
return {presences:{},slots:{},players:{},ready:{},devAuthorized:{},started:false,pausedForReconnect:false,reconnectDeadlineTick:0,gameOver:false,difficulty:difficulty,mode:String(params.mode||"quick"),roomCode:normalizeRoomCode(params.roomCode),hostUserId:String(params.hostUserId||""),emptyTicks:0,credits:[credits,credits],reactorLives:30,wave:0,waveActive:false,spawnRemaining:0,waveSchedule:[],spawnTimer:0,nextSpawnRoute:0,nextEnemyId:1,nextTowerId:1,towers:[],enemies:[],shots:[],lastSnapshotTick:0,lastLoopStartMs:0,balance:defaultBalanceConfig()};
|
||||||
}
|
}
|
||||||
function matchLabel(state) { return state.mode === "private" ? state.roomCode : JSON.stringify({game:"void_bastion",mode:state.mode,open:!state.started&&!state.gameOver}); }
|
function matchLabel(state) { return state.mode === "private" ? state.roomCode : JSON.stringify({game:"void_bastion",mode:state.mode,open:!state.started&&!state.gameOver}); }
|
||||||
function matchInit(ctx,logger,nk,params) { var state=initialState(params||{}); state.balance=readBalanceConfig(nk).config; return {state:state,tickRate:TICK_RATE,label:matchLabel(state)}; }
|
function matchInit(ctx,logger,nk,params) { var state=initialState(params||{}); state.balance=readBalanceConfig(nk).config; return {state:state,tickRate:TICK_RATE,label:matchLabel(state)}; }
|
||||||
@@ -567,7 +573,10 @@ function matchLeave(ctx,logger,nk,dispatcher,tick,state,presences) {
|
|||||||
dispatcher.matchLabelUpdate(matchLabel(state)); broadcastEvent(dispatcher,"partner_left",{reconnect_seconds:60}); broadcastSnapshot(dispatcher,state,tick); return {state:state};
|
dispatcher.matchLabelUpdate(matchLabel(state)); broadcastEvent(dispatcher,"partner_left",{reconnect_seconds:60}); broadcastSnapshot(dispatcher,state,tick); return {state:state};
|
||||||
}
|
}
|
||||||
function parseMessage(nk,message) { try { return JSON.parse(nk.binaryToString(message.data)||"{}"); } catch (error) { return null; } }
|
function parseMessage(nk,message) { try { return JSON.parse(nk.binaryToString(message.data)||"{}"); } catch (error) { return null; } }
|
||||||
function towerInfo(state,type) { return state.balance && state.balance.towers[type] || TOWERS[type]; }
|
function towerInfo(state,type) {
|
||||||
|
if (type===ADMIN_TOWER_TYPE) return {cost:0,damage:1000000,range:3000,cooldown:0.08,targetClass:"both",bothScale:1};
|
||||||
|
return state.balance && state.balance.towers[type] || TOWERS[type];
|
||||||
|
}
|
||||||
function enemyInfo(state,type) { return state.balance && state.balance.enemies[type] || ENEMIES[type]; }
|
function enemyInfo(state,type) { return state.balance && state.balance.enemies[type] || ENEMIES[type]; }
|
||||||
function towerDamage(state,t) { return towerInfo(state,t.type).damage*(1+(t.level-1)*0.52); }
|
function towerDamage(state,t) { return towerInfo(state,t.type).damage*(1+(t.level-1)*0.52); }
|
||||||
function towerRange(state,t) { return towerInfo(state,t.type).range*(1+(t.level-1)*0.08); }
|
function towerRange(state,t) { return towerInfo(state,t.type).range*(1+(t.level-1)*0.08); }
|
||||||
@@ -578,19 +587,20 @@ function distanceToSegment(px,py,ax,ay,bx,by) {
|
|||||||
var abx=bx-ax,aby=by-ay,len=abx*abx+aby*aby,t=len>0?((px-ax)*abx+(py-ay)*aby)/len:0;
|
var abx=bx-ax,aby=by-ay,len=abx*abx+aby*aby,t=len>0?((px-ax)*abx+(py-ay)*aby)/len:0;
|
||||||
t=Math.max(0,Math.min(1,t)); return Math.sqrt(distanceSquared(px,py,ax+abx*t,ay+aby*t));
|
t=Math.max(0,Math.min(1,t)); return Math.sqrt(distanceSquared(px,py,ax+abx*t,ay+aby*t));
|
||||||
}
|
}
|
||||||
function validBuildPosition(state,slot,x,y) {
|
function validBuildPosition(state,slot,x,y,allowAnySide) {
|
||||||
var sx=Math.floor(x/GRID)*GRID+GRID*0.5,sy=Math.floor(y/GRID)*GRID+GRID*0.5;
|
var sx=Math.floor(x/GRID)*GRID+GRID*0.5,sy=Math.floor(y/GRID)*GRID+GRID*0.5;
|
||||||
if (Math.abs(sx-x)>1||Math.abs(sy-y)>1||y<PLAYFIELD_TOP||y>PLAYFIELD_BOTTOM) return false;
|
if (Math.abs(sx-x)>1||Math.abs(sy-y)>1||x<120||x>WORLD_WIDTH-120||y<PLAYFIELD_TOP||y>PLAYFIELD_BOTTOM) return false;
|
||||||
if (slot===0&&(x<120||x>=MIRROR_X-GRID*0.5)) return false;
|
if (!allowAnySide&&slot===0&&(x<120||x>=MIRROR_X-GRID*0.5)) return false;
|
||||||
if (slot===1&&(x<=MIRROR_X+GRID*0.5||x>WORLD_WIDTH-120)) return false;
|
if (!allowAnySide&&slot===1&&(x<=MIRROR_X+GRID*0.5||x>WORLD_WIDTH-120)) return false;
|
||||||
if (distanceSquared(x,y,REACTOR_X,REACTOR_Y)<=REACTOR_RADIUS*REACTOR_RADIUS) return false;
|
if (distanceSquared(x,y,REACTOR_X,REACTOR_Y)<=REACTOR_RADIUS*REACTOR_RADIUS) return false;
|
||||||
for (var r=0;r<PATHS.length;r++) for (var i=0;i<PATHS[r].length-1;i++) if (distanceToSegment(x,y,PATHS[r][i][0],PATHS[r][i][1],PATHS[r][i+1][0],PATHS[r][i+1][1])<GRID*0.62) return false;
|
for (var r=0;r<PATHS.length;r++) for (var i=0;i<PATHS[r].length-1;i++) if (distanceToSegment(x,y,PATHS[r][i][0],PATHS[r][i][1],PATHS[r][i+1][0],PATHS[r][i+1][1])<GRID*0.62) return false;
|
||||||
for (var j=0;j<state.towers.length;j++) if (distanceSquared(x,y,state.towers[j].x,state.towers[j].y)<GRID*GRID*0.52) return false;
|
for (var j=0;j<state.towers.length;j++) if (distanceSquared(x,y,state.towers[j].x,state.towers[j].y)<GRID*GRID*0.52) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
function handleBuild(state,slot,payload) {
|
function handleBuild(state,slot,payload,userId) {
|
||||||
var type=Math.floor(Number(payload.tower_type)),x=Number(payload.x),y=Number(payload.y);
|
var type=Math.floor(Number(payload.tower_type)),x=Number(payload.x),y=Number(payload.y);
|
||||||
if (!isFinite(x)||!isFinite(y)||type<0||type>=TOWERS.length||!validBuildPosition(state,slot,x,y)||state.credits[slot]<towerInfo(state,type).cost) return false;
|
var isAdmin=type===ADMIN_TOWER_TYPE;
|
||||||
|
if (!isFinite(x)||!isFinite(y)||type<0||type>TOWERS.length||(isAdmin&&!state.devAuthorized[userId])||!validBuildPosition(state,slot,x,y,isAdmin)||state.credits[slot]<towerInfo(state,type).cost) return false;
|
||||||
state.credits[slot]-=towerInfo(state,type).cost;
|
state.credits[slot]-=towerInfo(state,type).cost;
|
||||||
state.towers.push({id:state.nextTowerId++,x:x,y:y,type:type,level:1,owner:slot,cooldown:0.15,angle:slot===0?0:Math.PI}); return true;
|
state.towers.push({id:state.nextTowerId++,x:x,y:y,type:type,level:1,owner:slot,cooldown:0.15,angle:slot===0?0:Math.PI}); return true;
|
||||||
}
|
}
|
||||||
@@ -716,7 +726,7 @@ function updateWave(state,delta,dispatcher) {
|
|||||||
function handleCommand(nk,dispatcher,state,message) {
|
function handleCommand(nk,dispatcher,state,message) {
|
||||||
var slot=state.slots[message.sender.userId]; if (slot===undefined) return;
|
var slot=state.slots[message.sender.userId]; if (slot===undefined) return;
|
||||||
var payload=parseMessage(nk,message); if (payload===null) return; var changed=false;
|
var payload=parseMessage(nk,message); if (payload===null) return; var changed=false;
|
||||||
if (message.opCode===OP_BUILD) changed=handleBuild(state,slot,payload);
|
if (message.opCode===OP_BUILD) changed=handleBuild(state,slot,payload,message.sender.userId);
|
||||||
else if (message.opCode===OP_UPGRADE) changed=handleUpgrade(state,slot,payload);
|
else if (message.opCode===OP_UPGRADE) changed=handleUpgrade(state,slot,payload);
|
||||||
else if (message.opCode===OP_SELL) changed=handleSell(state,slot,payload);
|
else if (message.opCode===OP_SELL) changed=handleSell(state,slot,payload);
|
||||||
else if (message.opCode===OP_START_WAVE) changed=startWave(state);
|
else if (message.opCode===OP_START_WAVE) changed=startWave(state);
|
||||||
@@ -730,6 +740,23 @@ function handleCommand(nk,dispatcher,state,message) {
|
|||||||
if (allReady) { state.started=true; state.pausedForReconnect=false; dispatcher.matchLabelUpdate(matchLabel(state)); broadcastEvent(dispatcher,"match_started",{}); }
|
if (allReady) { state.started=true; state.pausedForReconnect=false; dispatcher.matchLabelUpdate(matchLabel(state)); broadcastEvent(dispatcher,"match_started",{}); }
|
||||||
changed=true;
|
changed=true;
|
||||||
}
|
}
|
||||||
|
else if (message.opCode===OP_DEV_AUTH&&state.started&&!state.gameOver) {
|
||||||
|
if (DEV_ADMIN_USER_IDS[message.sender.userId]===true&&payload.password===DEV_MODE_PASSWORD) {
|
||||||
|
state.devAuthorized[message.sender.userId]=true;
|
||||||
|
broadcastEvent(dispatcher,"developer_authorized",{slot:slot});
|
||||||
|
changed=true;
|
||||||
|
} else {
|
||||||
|
broadcastEvent(dispatcher,"developer_auth_rejected",{slot:slot});
|
||||||
|
changed=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.opCode===OP_DEV_ENERGY&&state.started&&!state.gameOver&&state.devAuthorized[message.sender.userId]) {
|
||||||
|
var energyChange=Math.floor(Number(payload.amount));
|
||||||
|
if (energyChange===500||energyChange===-500) {
|
||||||
|
state.credits[slot]=Math.max(0,state.credits[slot]+energyChange);
|
||||||
|
changed=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!changed) broadcastEvent(dispatcher,"command_rejected",{op_code:message.opCode,player:slot});
|
if (!changed) broadcastEvent(dispatcher,"command_rejected",{op_code:message.opCode,player:slot});
|
||||||
}
|
}
|
||||||
function matchLoop(ctx,logger,nk,dispatcher,tick,state,messages) {
|
function matchLoop(ctx,logger,nk,dispatcher,tick,state,messages) {
|
||||||
@@ -849,8 +876,6 @@ function InitModule(ctx,logger,nk,initializer) {
|
|||||||
initializer.registerRpc("private_stats",rpcPrivateStats);
|
initializer.registerRpc("private_stats",rpcPrivateStats);
|
||||||
initializer.registerRpc("get_game_balance",rpcGetGameBalance);
|
initializer.registerRpc("get_game_balance",rpcGetGameBalance);
|
||||||
initializer.registerRpc("set_game_balance",rpcSetGameBalance);
|
initializer.registerRpc("set_game_balance",rpcSetGameBalance);
|
||||||
initializer.registerRpc("get_game_balance",rpcGetGameBalance);
|
|
||||||
initializer.registerRpc("set_game_balance",rpcSetGameBalance);
|
|
||||||
initializer.registerRpc("create_unlock_code",rpcCreateUnlockCode);
|
initializer.registerRpc("create_unlock_code",rpcCreateUnlockCode);
|
||||||
initializer.registerRpc("list_unlock_codes",rpcListUnlockCodes);
|
initializer.registerRpc("list_unlock_codes",rpcListUnlockCodes);
|
||||||
initializer.registerRpc("delete_unlock_code",rpcDeleteUnlockCode);
|
initializer.registerRpc("delete_unlock_code",rpcDeleteUnlockCode);
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ const balanceNk = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
assert.throws(() => context.rpcGetGameBalance({}, {}, balanceNk, "{}"), /authentication required/);
|
const serverBalance = JSON.parse(context.rpcGetGameBalance({}, {}, balanceNk, "{}"));
|
||||||
|
assert.strictEqual(serverBalance.config.towers[0].damage, 22, "The internal PHP page calls balance read through Nakama's HTTP-key server-to-server RPC context, which has no userId.");
|
||||||
const adminBalance = JSON.parse(context.rpcGetGameBalance({ userId: "pilot-b" }, {}, balanceNk, "{}"));
|
const adminBalance = JSON.parse(context.rpcGetGameBalance({ userId: "pilot-b" }, {}, balanceNk, "{}"));
|
||||||
assert.strictEqual(adminBalance.config.towers[0].damage, 22);
|
assert.strictEqual(adminBalance.config.towers[0].damage, 22);
|
||||||
const editedBalance = JSON.parse(JSON.stringify(adminBalance.config));
|
const editedBalance = JSON.parse(JSON.stringify(adminBalance.config));
|
||||||
@@ -372,16 +373,50 @@ assert.strictEqual(state.towers.length, 2);
|
|||||||
assert.strictEqual(state.credits[0], 310);
|
assert.strictEqual(state.credits[0], 310);
|
||||||
assert.strictEqual(state.credits[1], 250);
|
assert.strictEqual(state.credits[1], 250);
|
||||||
|
|
||||||
state = context.matchLoop({}, logger, nk, dispatcher, 6, state, [message(presenceA, 13, {})]).state;
|
state = context.matchLoop({}, logger, nk, dispatcher, 51, state,
|
||||||
|
[message(presenceA, 10, { tower_type: 7, x: 2200, y: 600 }),
|
||||||
|
message(presenceB, 15, { password: "incorrect" }),
|
||||||
|
message(presenceB, 16, { amount: 500 })]).state;
|
||||||
|
assert.strictEqual(state.towers.length, 2, "Admin tower builds must be rejected before developer authorization.");
|
||||||
|
assert.strictEqual(state.credits[1], 250, "Unauthorized player must not be able to adjust test energy.");
|
||||||
|
assert.strictEqual(state.devAuthorized["pilot-b"], undefined, "Wrong developer password must not authorize the player.");
|
||||||
|
state = context.matchLoop({}, logger, nk, dispatcher, 52, state,
|
||||||
|
[message(presenceB, 15, { password: "Spring.3" })]).state;
|
||||||
|
assert.strictEqual(state.devAuthorized["pilot-b"], undefined, "A non-admin account must not gain developer access even with the password.");
|
||||||
|
assert.strictEqual(context.DEV_ADMIN_USER_IDS["pilot-a"], undefined);
|
||||||
|
assert.strictEqual(context.DEV_ADMIN_USER_IDS["6605c44c-17a2-4cc4-bf3e-e772fd6229b0"], true);
|
||||||
|
context.DEV_ADMIN_USER_IDS["pilot-a"] = true;
|
||||||
|
state = context.matchLoop({}, logger, nk, dispatcher, 53, state,
|
||||||
|
[message(presenceA, 15, { password: "Spring.3" })]).state;
|
||||||
|
assert.strictEqual(state.devAuthorized["pilot-a"], true, "Correct developer password should authorize this player for the match.");
|
||||||
|
state = context.matchLoop({}, logger, nk, dispatcher, 54, state,
|
||||||
|
[message(presenceA, 16, { amount: 500 })]).state;
|
||||||
|
assert.strictEqual(state.credits[0], 810, "Authorized test energy addition should change server credits by 500.");
|
||||||
|
state = context.matchLoop({}, logger, nk, dispatcher, 55, state,
|
||||||
|
[message(presenceA, 16, { amount: -500 })]).state;
|
||||||
|
assert.strictEqual(state.credits[0], 310, "Authorized test energy removal should change server credits by 500.");
|
||||||
|
state = context.matchLoop({}, logger, nk, dispatcher, 56, state,
|
||||||
|
[message(presenceA, 10, { tower_type: 7, x: 2200, y: 600 })]).state;
|
||||||
|
assert.ok(state.towers.some(tower => tower.type === 7 && tower.owner === 0), "Authorized player may build the admin turret anywhere on the co-op map.");
|
||||||
|
const adminCombatState = {
|
||||||
|
balance: state.balance,
|
||||||
|
towers: [{ id: 99, type: 7, x: 2200, y: 600, owner: 0, level: 1, cooldown: 0, angle: 0 }],
|
||||||
|
enemies: [{ id: 99, type: 8, x: 2250, y: 600, hp: 100000, max_hp: 100000, shield: true, shield_hp: 1000, shield_resistance: 0.28 }],
|
||||||
|
shots: [], credits: [0, 0]
|
||||||
|
};
|
||||||
|
context.updateTowers(adminCombatState, 0.1);
|
||||||
|
assert.strictEqual(adminCombatState.enemies.length, 0, "One admin turret shot should destroy even a shielded enemy.");
|
||||||
|
|
||||||
|
state = context.matchLoop({}, logger, nk, dispatcher, 57, state, [message(presenceA, 13, {})]).state;
|
||||||
assert.strictEqual(state.wave, 1);
|
assert.strictEqual(state.wave, 1);
|
||||||
assert.strictEqual(state.waveActive, true);
|
assert.strictEqual(state.waveActive, true);
|
||||||
assert.strictEqual(context.snapshot(state, 6).wave_types_remaining.length, state.spawnRemaining);
|
assert.strictEqual(context.snapshot(state, 56).wave_types_remaining.length, state.spawnRemaining);
|
||||||
const groundSchedule = context.buildWaveSchedule(state, 3, context.wavePopulation(state, 3));
|
const groundSchedule = context.buildWaveSchedule(state, 3, context.wavePopulation(state, 3));
|
||||||
assert.ok(groundSchedule.some(entry => context.ENEMIES[entry.type].category === "ground"), "Ground units should enter the wave schedule from wave 3.");
|
assert.ok(groundSchedule.some(entry => context.ENEMIES[entry.type].category === "ground"), "Ground units should enter the wave schedule from wave 3.");
|
||||||
for (let tick = 7; tick < 30; tick++) state = context.matchLoop({}, logger, nk, dispatcher, tick, state, []).state;
|
for (let tick = 57; tick < 80; tick++) state = context.matchLoop({}, logger, nk, dispatcher, tick, state, []).state;
|
||||||
assert.ok(state.enemies.length > 0 || state.spawnRemaining > 0);
|
assert.ok(state.enemies.length > 0 || state.spawnRemaining > 0);
|
||||||
assert.ok(sent.some(entry => entry.opCode === 1 && entry.data.started === true));
|
assert.ok(sent.some(entry => entry.opCode === 1 && entry.data.started === true));
|
||||||
const compactSnapshot = context.snapshot(state, 29);
|
const compactSnapshot = context.snapshot(state, 79);
|
||||||
const untrimmedSnapshot = { ...compactSnapshot, towers: state.towers, enemies: state.enemies };
|
const untrimmedSnapshot = { ...compactSnapshot, towers: state.towers, enemies: state.enemies };
|
||||||
assert.ok(JSON.stringify(compactSnapshot).length < JSON.stringify(untrimmedSnapshot).length, "Frequent snapshots should omit simulation-only properties.");
|
assert.ok(JSON.stringify(compactSnapshot).length < JSON.stringify(untrimmedSnapshot).length, "Frequent snapshots should omit simulation-only properties.");
|
||||||
assert.ok(compactSnapshot.towers.every(tower => !Object.hasOwn(tower, "cooldown")));
|
assert.ok(compactSnapshot.towers.every(tower => !Object.hasOwn(tower, "cooldown")));
|
||||||
@@ -389,17 +424,17 @@ assert.ok(compactSnapshot.enemies.every(enemy => !Object.hasOwn(enemy, "shield_r
|
|||||||
assert.ok(state.towers.every(tower => Object.hasOwn(tower, "cooldown")), "Network serialization must not alter simulation state.");
|
assert.ok(state.towers.every(tower => Object.hasOwn(tower, "cooldown")), "Network serialization must not alter simulation state.");
|
||||||
|
|
||||||
const towerA = state.towers.find(tower => tower.owner === 0);
|
const towerA = state.towers.find(tower => tower.owner === 0);
|
||||||
state = context.matchLoop({}, logger, nk, dispatcher, 30, state, [message(presenceA, 11, { tower_id: towerA.id })]).state;
|
state = context.matchLoop({}, logger, nk, dispatcher, 80, state, [message(presenceA, 11, { tower_id: towerA.id })]).state;
|
||||||
assert.strictEqual(state.towers.find(tower => tower.id === towerA.id).level, 2);
|
assert.strictEqual(state.towers.find(tower => tower.id === towerA.id).level, 2);
|
||||||
state = context.matchLoop({}, logger, nk, dispatcher, 31, state, [message(presenceA, 12, { tower_id: towerA.id })]).state;
|
state = context.matchLoop({}, logger, nk, dispatcher, 81, state, [message(presenceA, 12, { tower_id: towerA.id })]).state;
|
||||||
assert.ok(!state.towers.some(tower => tower.id === towerA.id));
|
assert.ok(!state.towers.some(tower => tower.id === towerA.id));
|
||||||
|
|
||||||
state = context.matchLeave({}, logger, nk, dispatcher, 32, state, [presenceB]).state;
|
state = context.matchLeave({}, logger, nk, dispatcher, 82, state, [presenceB]).state;
|
||||||
assert.strictEqual(state.started, true);
|
assert.strictEqual(state.started, true);
|
||||||
assert.strictEqual(state.pausedForReconnect, true);
|
assert.strictEqual(state.pausedForReconnect, true);
|
||||||
assert.strictEqual(context.snapshot(state, 32).players[1].connected, false);
|
assert.strictEqual(context.snapshot(state, 82).players[1].connected, false);
|
||||||
assert.strictEqual(context.matchJoinAttempt({}, logger, nk, dispatcher, 33, state, presenceB, {}).accept, true);
|
assert.strictEqual(context.matchJoinAttempt({}, logger, nk, dispatcher, 83, state, presenceB, {}).accept, true);
|
||||||
state = context.matchJoin({}, logger, nk, dispatcher, 33, state, [presenceB]).state;
|
state = context.matchJoin({}, logger, nk, dispatcher, 83, state, [presenceB]).state;
|
||||||
assert.strictEqual(state.pausedForReconnect, false);
|
assert.strictEqual(state.pausedForReconnect, false);
|
||||||
assert.strictEqual(state.slots["pilot-b"], 1);
|
assert.strictEqual(state.slots["pilot-b"], 1);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user