From bc30cfd6ca8d79bee7ea363dc1b8fc00a484352e Mon Sep 17 00:00:00 2001 From: Twirpytherobot <32495213+Twirpytherobot@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:18:36 +0000 Subject: [PATCH] Health and attacks! --- .godot/editor/editor_layout.cfg | 12 +- .godot/editor/editor_script_doc_cache.res | Bin 3103 -> 0 bytes .godot/editor/filesystem_cache10 | 28 +-- .godot/editor/filesystem_update4 | 3 +- .godot/editor/project_metadata.cfg | 2 +- .godot/editor/script_editor_cache.cfg | 30 ++- .godot/global_script_class_cache.cfg | 10 +- .godot/uid_cache.bin | Bin 997 -> 1041 bytes CLAUDE.md | 111 ++++++++++++ level/scripts/base_unit.gd | 117 ++++++++++++ level/scripts/base_unit.gd.uid | 1 + level/scripts/player.gd | 211 ++++++++++++++++++++-- level/scripts/spring_arm_offset.gd | 3 + project.godot | 5 + 14 files changed, 493 insertions(+), 40 deletions(-) delete mode 100644 .godot/editor/editor_script_doc_cache.res create mode 100644 CLAUDE.md create mode 100644 level/scripts/base_unit.gd create mode 100644 level/scripts/base_unit.gd.uid diff --git a/.godot/editor/editor_layout.cfg b/.godot/editor/editor_layout.cfg index ec1a4ab..987aaf2 100644 --- a/.godot/editor/editor_layout.cfg +++ b/.godot/editor/editor_layout.cfg @@ -32,8 +32,8 @@ open_scenes=PackedStringArray("res://level/scenes/level.tscn", "res://level/scen current_scene="res://level/scenes/player.tscn" center_split_offset=0 selected_default_debugger_tab_idx=1 -selected_main_editor_idx=3 -selected_bottom_panel_item=0 +selected_main_editor_idx=2 +selected_bottom_panel_item=1 [EditorWindow] @@ -44,8 +44,8 @@ size=Vector2i(1152, 648) [ScriptEditor] -open_scripts=["res://level/scripts/3d_godot_robot.gd", "res://level/scripts/level.gd", "res://level/scripts/player.gd", "res://README.md"] -selected_script="res://level/scripts/player.gd" +open_scripts=["res://level/scripts/3d_godot_robot.gd", "res://level/scripts/level.gd", "res://level/scripts/player.gd", "res://README.md", "res://level/scripts/spring_arm_offset.gd"] +selected_script="res://level/scripts/spring_arm_offset.gd" open_help=[] script_split_offset=200 list_split_offset=0 @@ -53,8 +53,8 @@ zoom_factor=1.0 [GameView] -floating_window_rect=Rect2i(634, 315, 1292, 767) -floating_window_screen=1 +floating_window_rect=Rect2i(2677, 131, 1292, 767) +floating_window_screen=0 [ShaderEditor] diff --git a/.godot/editor/editor_script_doc_cache.res b/.godot/editor/editor_script_doc_cache.res deleted file mode 100644 index 78533a8afca8224714202970336ba3816c4a6fba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3103 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 diff --git a/.godot/editor/filesystem_cache10 b/.godot/editor/filesystem_cache10 index 5ed7cb9..b783b8e 100644 --- a/.godot/editor/filesystem_cache10 +++ b/.godot/editor/filesystem_cache10 @@ -1,30 +1,32 @@ 63f7b34db8d8cdea90c76aacccf841ec -::res://::1762971203 +::res://::1762974779 +CLAUDE.md::TextFile::-1::1762974557::0::1::::<><><>0<>0<><>:: CONTRIBUTING.md::TextFile::-1::1762703890::0::1::::<><><>0<>0<><>:: icon.png::CompressedTexture2D::7745181525272711891::1762703890::1762703997::1::::<><><>0<>0<>896be222f4526152412fc7150ec81337<>res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex:: -README.md::TextFile::-1::1762703890::0::1::::<><><>0<>0<><>:: -::res://assets/::1762703936 -::res://assets/characters/::1762972117 -::res://assets/characters/Lobster/::1762972124 +README.md::TextFile::-1::1762972834::0::1::::<><><>0<>0<><>:: +::res://assets/::1762972860 +::res://assets/characters/::1762972860 +::res://assets/characters/Lobster/::1762972860 10029_Lobster_v1_Diffuse.jpg::CompressedTexture2D::1636629097341229697::1762972122::1762972126::1::::<><><>0<>0<>6340ceb996fa69b121c26459d93e8863<>res://.godot/imported/10029_Lobster_v1_Diffuse.jpg-042296e567cd8e99b847d125c0bf3e99.s3tc.ctex:: 10029_Lobster_v1_iterations-2.obj::Mesh::8031617903903645591::1762972122::1762972124::1::::<><><>0<>0<>6f735946f3e6cee1022fc19030e5e510<>res://.godot/imported/10029_Lobster_v1_iterations-2.obj-007e872ca27f7329d9bece1ae9a73118.mesh<*>res://.godot/imported/10029_Lobster_v1_iterations-2.obj-007e872ca27f7329d9bece1ae9a73118.mesh::uid://xk2boxo0jwrp::::res://assets/characters/Lobster/10029_Lobster_v1_Diffuse.jpg -::res://assets/characters/player/::1762704000 +::res://assets/characters/player/::1762972860 3DGodotRobot.glb::PackedScene::5656896519777153630::1762703890::1762704000::1::::<><><>0<>0<>bb220f20c8949e4b43553be149732094<>res://.godot/imported/3DGodotRobot.glb-c7060093cec96d07cab7f0bfb35f5061.scn::uid://bdg48m6l86q8i::::res://assets/characters/player/3DGodotRobot_GodotPalette.png 3DGodotRobot_GodotPalette.png::CompressedTexture2D::2611193883834911314::1762703890::1762703998::1::::<><><>0<>0<>a2b865fe920c4004f33acb6ec9f0a050<>res://.godot/imported/3DGodotRobot_GodotPalette.png-db2cb37c015bbbe580a404413e47cf86.s3tc.ctex:: -::res://assets/characters/player/GodotRobotPaletteSwap/::1762703998 +::res://assets/characters/player/GodotRobotPaletteSwap/::1762972860 GodotGreenPalette.png::CompressedTexture2D::2473362712891021041::1762703890::1762703998::1::::<><><>0<>0<>b098d3d8ca31773160ee1f4035b79a4e<>res://.godot/imported/GodotGreenPalette.png-8c760c09d8ace0593bb63002a2ed8eae.s3tc.ctex:: GodotPalette.png::CompressedTexture2D::382661063308374971::1762703890::1762703998::1::::<><><>0<>0<>2bdb257df9dcc0460d586ea92204ceb3<>res://.godot/imported/GodotPalette.png-a2d735eec81ad2647941b719336144cd.s3tc.ctex:: GodotRedPalette.png::CompressedTexture2D::3612187353401932242::1762703890::1762703996::1::::<><><>0<>0<>12aa761020ebe4c596572a3c09f2ee0b<>res://.godot/imported/GodotRedPalette.png-08bd2ae0a0cfe45c50f42f0c3a248d1b.ctex:: GodotYellowPalette.png::CompressedTexture2D::6381944650169859036::1762703890::1762703996::1::::<><><>0<>0<>96b97d4593ec92e6955954d301295c0a<>res://.godot/imported/GodotYellowPalette.png-827e66defded795634af82b4a85f17de.ctex:: -::res://assets/fonts/::1762703995 +::res://assets/fonts/::1762972860 Kurland.ttf::FontFile::7721683626151354491::1762703891::1762703995::1::::<><><>0<>0<>dacc2e7578d27658799cf58f30bac804<>res://.godot/imported/Kurland.ttf-14f9cbbd8657b37f475042f6a32feeab.fontdata:: -::res://level/::1762703936 -::res://level/scenes/::1762703936 +::res://level/::1762972860 +::res://level/scenes/::1762972860 level.tscn::PackedScene::8575440581694956823::1762703891::0::1::::<><><>0<>0<><>::uid://d0dgljwwl463n::::res://level/scripts/level.gd<>uid://cffjduipbb3s5::::res://level/scenes/player.tscn<>uid://diapabmalpcrj::::res://assets/fonts/Kurland.ttf -player.tscn::PackedScene::5134660348171653994::1762703891::0::1::::<><><>0<>0<><>::uid://c2si8gkbnde0c::::res://level/scripts/player.gd<>uid://bdg48m6l86q8i::::res://assets/characters/player/3DGodotRobot_GodotPalette.png<>uid://v8j54rbc0ik::::res://level/scripts/3d_godot_robot.gd<>uid://cw6pxwst2gh85::::res://assets/characters/player/GodotRobotPaletteSwap/GodotYellowPalette.png<>uid://bbid6mowxhd5b::::res://assets/characters/player/GodotRobotPaletteSwap/GodotGreenPalette.png<>uid://fpmmv2oxjcdv::::res://assets/characters/player/GodotRobotPaletteSwap/GodotPalette.png<>uid://bj7yrijm7bppq::::res://level/scripts/spring_arm_offset.gd<>uid://brp1gy30s4rks::::res://assets/characters/player/GodotRobotPaletteSwap/GodotRedPalette.png -::res://level/scripts/::1762703936 +player.tscn::PackedScene::5134660348171653994::1762972234::0::1::::<><><>0<>0<><>::uid://c2si8gkbnde0c::::res://level/scripts/player.gd<>uid://bdg48m6l86q8i::::res://assets/characters/player/3DGodotRobot_GodotPalette.png<>uid://v8j54rbc0ik::::res://level/scripts/3d_godot_robot.gd<>uid://cw6pxwst2gh85::::res://assets/characters/player/GodotRobotPaletteSwap/GodotYellowPalette.png<>uid://bbid6mowxhd5b::::res://assets/characters/player/GodotRobotPaletteSwap/GodotGreenPalette.png<>uid://fpmmv2oxjcdv::::res://assets/characters/player/GodotRobotPaletteSwap/GodotPalette.png<>uid://bj7yrijm7bppq::::res://level/scripts/spring_arm_offset.gd<>uid://brp1gy30s4rks::::res://assets/characters/player/GodotRobotPaletteSwap/GodotRedPalette.png<>uid://dmottq5u3my52::::res://assets/characters/Lobster/10029_Lobster_v1_iterations-2.obj +::res://level/scripts/::1762974651 3d_godot_robot.gd::GDScript::45373287015371390::1762703891::0::1::::Body<>Node3D<><>0<>0<><>:: +base_unit.gd::GDScript::928298549479668245::1762974583::0::1::::BaseUnit<>CharacterBody3D<><>0<>0<><>:: level.gd::GDScript::8920560728693148825::1762703891::0::1::::<>Node3D<><>0<>0<><>:: network.gd::GDScript::5061006158354274844::1762703891::0::1::::<>Node<><>0<>0<><>:: -player.gd::GDScript::6705643942978221932::1762703891::0::1::::Character<>CharacterBody3D<><>0<>0<><>:: +player.gd::GDScript::6705643942978221932::1762974651::0::1::::Character<>BaseUnit<><>0<>0<><>:: spring_arm_offset.gd::GDScript::3085668365566169618::1762703891::0::1::::SpringArmCharacter<>Node3D<><>0<>0<><>:: diff --git a/.godot/editor/filesystem_update4 b/.godot/editor/filesystem_update4 index 34ead9e..cd834bc 100644 --- a/.godot/editor/filesystem_update4 +++ b/.godot/editor/filesystem_update4 @@ -1 +1,2 @@ -res://level/scenes/player.tscn +res://level/scripts/player.gd +res://level/scripts/spring_arm_offset.gd diff --git a/.godot/editor/project_metadata.cfg b/.godot/editor/project_metadata.cfg index d2886f3..3db8370 100644 --- a/.godot/editor/project_metadata.cfg +++ b/.godot/editor/project_metadata.cfg @@ -10,7 +10,7 @@ executable_path="C:/Users/James/Desktop/Godot_v4.5-stable_win64.exe" [recent_files] scenes=["res://level/scenes/player.tscn", "res://level/scenes/level.tscn"] -scripts=["res://README.md", "res://level/scripts/level.gd", "res://level/scripts/3d_godot_robot.gd", "res://level/scripts/player.gd"] +scripts=["res://level/scripts/spring_arm_offset.gd", "res://README.md", "res://level/scripts/level.gd", "res://level/scripts/3d_godot_robot.gd", "res://level/scripts/player.gd"] [debug_options] diff --git a/.godot/editor/script_editor_cache.cfg b/.godot/editor/script_editor_cache.cfg index db4929c..6eb38c5 100644 --- a/.godot/editor/script_editor_cache.cfg +++ b/.godot/editor/script_editor_cache.cfg @@ -3,12 +3,16 @@ state={ "bookmarks": PackedInt32Array(), "breakpoints": PackedInt32Array(), -"column": 73, +"column": 18, "folded_lines": Array[int]([]), "h_scroll_position": 0, -"row": 112, -"scroll_position": 90.99999999999999, -"selection": false, +"row": 169, +"scroll_position": 169.0, +"selection": true, +"selection_from_column": 18, +"selection_from_line": 169, +"selection_to_column": 48, +"selection_to_line": 174, "syntax_highlighter": "GDScript" } @@ -45,11 +49,25 @@ state={ state={ "bookmarks": PackedInt32Array(), "breakpoints": PackedInt32Array(), -"column": 98, +"column": 18, "folded_lines": Array[int]([]), "h_scroll_position": 0, -"row": 22, +"row": 2, "scroll_position": 0.0, "selection": false, "syntax_highlighter": "Markdown" } + +[res://level/scripts/spring_arm_offset.gd] + +state={ +"bookmarks": PackedInt32Array(), +"breakpoints": PackedInt32Array(), +"column": 0, +"folded_lines": Array[int]([]), +"h_scroll_position": 0, +"row": 10, +"scroll_position": 0.0, +"selection": false, +"syntax_highlighter": "GDScript" +} diff --git a/.godot/global_script_class_cache.cfg b/.godot/global_script_class_cache.cfg index db39352..f90308e 100644 --- a/.godot/global_script_class_cache.cfg +++ b/.godot/global_script_class_cache.cfg @@ -1,4 +1,12 @@ list=[{ +"base": &"CharacterBody3D", +"class": &"BaseUnit", +"icon": "", +"is_abstract": false, +"is_tool": false, +"language": &"GDScript", +"path": "res://level/scripts/base_unit.gd" +}, { "base": &"Node3D", "class": &"Body", "icon": "", @@ -7,7 +15,7 @@ list=[{ "language": &"GDScript", "path": "res://level/scripts/3d_godot_robot.gd" }, { -"base": &"CharacterBody3D", +"base": &"BaseUnit", "class": &"Character", "icon": "", "is_abstract": false, diff --git a/.godot/uid_cache.bin b/.godot/uid_cache.bin index 19a05472353eed17bd9a1a21be100995cbf270a4..0e8512f55ba57872692a9561c29331eb01fe6e6e 100644 GIT binary patch delta 61 zcmV-D0K)&}2ayN_5&!@IlTZVd3GvpDZb&9mB1)5?0~?XgBC$$60tprRKF6H;;S3;? TkOCJ7VqtS-Uv+M2lY;`ygOnCe delta 36 scmbQp@syoKkb!|= float: + return current_health / max_health if max_health > 0 else 0.0 diff --git a/level/scripts/base_unit.gd.uid b/level/scripts/base_unit.gd.uid new file mode 100644 index 0000000..16e56c7 --- /dev/null +++ b/level/scripts/base_unit.gd.uid @@ -0,0 +1 @@ +uid://nhw7amcyksft diff --git a/level/scripts/player.gd b/level/scripts/player.gd index 0f044db..54f4ded 100644 --- a/level/scripts/player.gd +++ b/level/scripts/player.gd @@ -1,4 +1,4 @@ -extends CharacterBody3D +extends BaseUnit class_name Character const NORMAL_SPEED = 6.0 @@ -8,6 +8,7 @@ const JUMP_VELOCITY = 10 enum SkinColor { BLUE, YELLOW, GREEN, RED } @onready var nickname: Label3D = $PlayerNick/Nickname +@onready var health_label: Label3D = null # Optional 3D health label @export_category("Objects") @export var _body: Node3D = null @@ -25,18 +26,45 @@ enum SkinColor { BLUE, YELLOW, GREEN, RED } @onready var _limbs_head_mesh: MeshInstance3D = get_node("3DGodotRobot/RobotArmature/Skeleton3D/Llimbs and head") var _current_speed: float -var _respawn_point = Vector3(0, 5, 0) var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") +# Attack system +@export var attack_damage: float = 10.0 +@export var attack_range: float = 3.0 +@export var attack_cooldown: float = 0.5 +var _attack_timer: float = 0.0 + func _enter_tree(): - set_multiplayer_authority(str(name).to_int()) + super._enter_tree() $SpringArmOffset/SpringArm3D/Camera3D.current = is_multiplayer_authority() func _ready(): - return + super._ready() + set_respawn_point(Vector3(0, 5, 0)) + + # Try to get optional health label + if has_node("PlayerNick/HealthLabel"): + health_label = get_node("PlayerNick/HealthLabel") + + # Connect health signals + health_changed.connect(_on_health_changed) + died.connect(_on_died) + respawned.connect(_on_respawned) + + # Update health display + _update_health_display() + + # Create 2D UI health bar for local player + if is_multiplayer_authority(): + _create_health_ui() func _physics_process(delta): - if not is_multiplayer_authority(): return + # Check if multiplayer is ready + if multiplayer.multiplayer_peer == null: + return + + if not is_multiplayer_authority(): + return var current_scene = get_tree().get_current_scene() if current_scene and current_scene.has_method("is_chat_visible") and current_scene.is_chat_visible() and is_on_floor(): @@ -57,10 +85,24 @@ func _physics_process(delta): move_and_slide() _body.animate(velocity) -func _process(_delta): - if not is_multiplayer_authority(): return +func _process(delta): + # Check if multiplayer is ready + if multiplayer.multiplayer_peer == null: + return + + if not is_multiplayer_authority(): + return + _check_fall_and_respawn() + # Update attack cooldown + if _attack_timer > 0: + _attack_timer -= delta + + # Handle attack input + if Input.is_action_just_pressed("attack") and _attack_timer <= 0 and not is_dead: + _perform_attack() + func freeze(): velocity.x = 0 velocity.z = 0 @@ -98,13 +140,10 @@ func is_running() -> bool: return false func _check_fall_and_respawn(): - if global_transform.origin.y < -15.0: + if global_transform.origin.y < -15.0 and multiplayer.is_server(): + # Use BaseUnit's respawn system _respawn() -func _respawn(): - global_transform.origin = _respawn_point - velocity = Vector3.ZERO - @rpc("any_peer", "reliable") func change_nick(new_nick: String): if nickname: @@ -134,3 +173,151 @@ func set_mesh_texture(mesh_instance: MeshInstance3D, texture: CompressedTexture2 var new_material := material new_material.albedo_texture = texture mesh_instance.set_surface_override_material(0, new_material) + +## Attack system +func _perform_attack(): + if not is_multiplayer_authority() or is_dead: + return + + _attack_timer = attack_cooldown + + # Find nearest enemy in range + var space_state = get_world_3d().direct_space_state + var query = PhysicsShapeQueryParameters3D.new() + var sphere = SphereShape3D.new() + sphere.radius = attack_range + query.shape = sphere + query.transform = global_transform + query.collision_mask = 1 # Player layer + + var results = space_state.intersect_shape(query) + + for result in results: + var hit_body = result["collider"] + if hit_body != self and hit_body is BaseUnit: + var attacker_id = multiplayer.get_unique_id() + + # If we're the server, apply damage directly + if multiplayer.is_server(): + _server_apply_damage(hit_body.name, attack_damage, attacker_id) + else: + # Otherwise, request server to apply damage + rpc_id(1, "_server_apply_damage", hit_body.name, attack_damage, attacker_id) + break # Only hit one target per attack + +## Server-side damage application +@rpc("any_peer", "reliable") +func _server_apply_damage(target_name: String, damage: float, attacker_id: int): + if not multiplayer.is_server(): + return + + # Get the target from the players container + var level = get_tree().get_current_scene() + if not level or not level.has_node("PlayersContainer"): + return + + var players_container = level.get_node("PlayersContainer") + if not players_container.has_node(target_name): + return + + var target = players_container.get_node(target_name) + if target and target is BaseUnit: + target.take_damage(damage, attacker_id) + +## Health display and callbacks +func _create_health_ui(): + # Create a 2D UI for the local player's health + var canvas = CanvasLayer.new() + canvas.name = "HealthUI" + add_child(canvas) + + # Health bar background + var health_bg = ColorRect.new() + health_bg.name = "HealthBG" + health_bg.color = Color(0.2, 0.2, 0.2, 0.8) + health_bg.position = Vector2(20, 20) + health_bg.size = Vector2(200, 30) + canvas.add_child(health_bg) + + # Health bar (current health) + var health_bar = ColorRect.new() + health_bar.name = "HealthBar" + health_bar.color = Color(0.0, 0.8, 0.0, 1.0) # Green + health_bar.position = Vector2(22, 22) + health_bar.size = Vector2(196, 26) + canvas.add_child(health_bar) + + # Health text + var health_text = Label.new() + health_text.name = "HealthText" + health_text.position = Vector2(20, 20) + health_text.size = Vector2(200, 30) + health_text.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + health_text.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + health_text.add_theme_color_override("font_color", Color.WHITE) + health_text.add_theme_color_override("font_outline_color", Color.BLACK) + health_text.add_theme_constant_override("outline_size", 2) + health_text.text = "HP: %d/%d" % [int(current_health), int(max_health)] + canvas.add_child(health_text) + +func _update_health_display(): + # Update 3D label if it exists + if health_label: + health_label.text = "HP: %d/%d" % [int(current_health), int(max_health)] + + # Update 2D UI for local player + if is_multiplayer_authority() and has_node("HealthUI"): + var health_bar = get_node_or_null("HealthUI/HealthBar") + var health_text = get_node_or_null("HealthUI/HealthText") + + if health_bar: + var health_percent = get_health_percent() + health_bar.size.x = 196 * health_percent + + # Change color based on health + if health_percent > 0.6: + health_bar.color = Color(0.0, 0.8, 0.0) # Green + elif health_percent > 0.3: + health_bar.color = Color(1.0, 0.8, 0.0) # Yellow + else: + health_bar.color = Color(0.8, 0.0, 0.0) # Red + + if health_text: + health_text.text = "HP: %d/%d" % [int(current_health), int(max_health)] + +func _on_health_changed(_old_health: float, _new_health: float): + _update_health_display() + +func _on_died(killer_id: int): + # Disable player when dead + set_physics_process(false) + set_process(false) + + # Visual feedback - could add death animation here + if _body: + _body.visible = false + + # Print death message + var killer_name = "Unknown" + if Network.players.has(killer_id): + killer_name = Network.players[killer_id]["nick"] + + if is_multiplayer_authority(): + print("You were killed by ", killer_name) + + # Show death message on UI + if has_node("HealthUI/HealthText"): + get_node("HealthUI/HealthText").text = "DEAD - Respawning..." + +func _on_respawned(): + # Re-enable player + set_physics_process(true) + set_process(true) + + if _body: + _body.visible = true + + _update_health_display() + + if is_multiplayer_authority(): + print("You respawned!") diff --git a/level/scripts/spring_arm_offset.gd b/level/scripts/spring_arm_offset.gd index 5b6cff5..9f6e061 100644 --- a/level/scripts/spring_arm_offset.gd +++ b/level/scripts/spring_arm_offset.gd @@ -7,6 +7,9 @@ const MOUSE_SENSIBILITY: float = 0.005 @export var _spring_arm: SpringArm3D = null func _unhandled_input(_event) -> void: + # Check if multiplayer is ready + if multiplayer.multiplayer_peer == null: + return if (_event is InputEventMouseMotion) and is_multiplayer_authority(): rotate_y(-_event.relative.x * MOUSE_SENSIBILITY) diff --git a/project.godot b/project.godot index 7cf2670..64464c6 100644 --- a/project.godot +++ b/project.godot @@ -68,6 +68,11 @@ toggle_chat={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +attack={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} [layer_names]