diff --git a/CHANGELOG.md b/CHANGELOG.md index df3a77dc..dd49ef07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## v0.3.1 Combat Demo ⚔️ - Battlers, Stats, and Animations + +### New + +Combat instances have been fleshed out to include several new combat-specific nodes: + - Battlers form two 'teams' and face off against each other. One team wins when the other's battlers have all been defeated (health points have been depleted). + - BattlerStats track a Battlers given numerical characteristics, including health points. + - A BattlerAnim(ation) node brings Battlers to life, animating in response to various stimuli acting on the battler. + - an 'active turn queue' allows battlers to act in sequence as time passes. + - A Battler has a repertoire of BattlerActions, selecting one (alongside any necessary targets) to perform on its turn. + +### Changes +- Combat resolves (victory or loss on the player's part) automatically when one 'team' is defeated. +- A series of cyber-themed elements dictate how Battlers and actions play out statistically. +- Actions and combat resolution wait for animations and timers to play out, allowing for a smooth combat experience. +- Miscellaneous fixes to the demo. + ## v0.3.0 Combat Demo ⚔️ ### New diff --git a/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import b/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import index 694a2aeb..16d9a069 100644 --- a/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import +++ b/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Roboto-Bold.ttf-a0c3395776dbc11ee676c5f1ea9c0 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import b/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import index d7c809a8..9034952e 100644 --- a/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import +++ b/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Roboto-Italic.ttf-844485a0171d6031f98f4829003 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import b/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import index 16d8db10..3f33bc5c 100644 --- a/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import +++ b/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Roboto-Regular.ttf-d9ce0640effe9e93230b445b37 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/assets/battlers/bear_anim.tscn b/assets/battlers/bear_anim.tscn new file mode 100644 index 00000000..214210b6 --- /dev/null +++ b/assets/battlers/bear_anim.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=4 format=3 uid="uid://bl8bmbt7v3lrj"] + +[ext_resource type="PackedScene" uid="uid://badexg85lctrq" path="res://src/combat/battlers/battler_anim.tscn" id="1_cpjl2"] +[ext_resource type="Texture2D" uid="uid://pkp6t20skjpe" path="res://assets/battlers/bear.png" id="2_3eyxi"] +[ext_resource type="AnimationLibrary" uid="uid://o2ktahx2nkki" path="res://assets/battlers/default_battler_animations.res" id="2_c0d1t"] + +[node name="BearAnim" instance=ExtResource("1_cpjl2")] + +[node name="AnimationPlayer" parent="Pivot" index="0"] +libraries = { +"": ExtResource("2_c0d1t") +} + +[node name="Sprite2D" type="Sprite2D" parent="Pivot" index="1"] +texture = ExtResource("2_3eyxi") +offset = Vector2(0, -185) diff --git a/assets/battlers/bugcat_anim.tscn b/assets/battlers/bugcat_anim.tscn new file mode 100644 index 00000000..bcc3a9e5 --- /dev/null +++ b/assets/battlers/bugcat_anim.tscn @@ -0,0 +1,106 @@ +[gd_scene load_steps=7 format=3 uid="uid://ugsq7u4cue4w"] + +[ext_resource type="PackedScene" uid="uid://badexg85lctrq" path="res://src/combat/battlers/battler_anim.tscn" id="1_ws2uh"] +[ext_resource type="Texture2D" uid="uid://e4b6flk7roy3" path="res://assets/battlers/bugcat.png" id="2_jiam4"] + +[sub_resource type="Animation" id="Animation_r4wpi"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(1, 1, 1, 1)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 0)] +} + +[sub_resource type="Animation" id="Animation_2u36l"] +resource_name = "die" +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 1), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 0.87451), Color(1, 1, 1, 0.74902), Color(1, 0, 0, 0.623529), Color(1, 1, 1, 0.498039), Color(1, 1, 1, 0)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 2 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Vector2(0, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0)] +} + +[sub_resource type="Animation" id="Animation_yc1ca"] +resource_name = "hurt" +length = 0.6 +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 2 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Vector2(0, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(8, 0), Vector2(-8, 0), Vector2(8, 0), Vector2(-8, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(0, 0)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_va8v1"] +_data = { +"RESET": SubResource("Animation_r4wpi"), +"die": SubResource("Animation_2u36l"), +"hurt": SubResource("Animation_yc1ca") +} + +[node name="BugcatAnim" instance=ExtResource("1_ws2uh")] + +[node name="AnimationPlayer" parent="Pivot" index="0"] +libraries = { +"": SubResource("AnimationLibrary_va8v1") +} + +[node name="Sprite2D" type="Sprite2D" parent="Pivot" index="1"] +texture = ExtResource("2_jiam4") +offset = Vector2(0, -163) diff --git a/assets/battlers/default_battler_animations.res b/assets/battlers/default_battler_animations.res new file mode 100644 index 00000000..296cc15e Binary files /dev/null and b/assets/battlers/default_battler_animations.res differ diff --git a/assets/battlers/squirrel_anim.tscn b/assets/battlers/squirrel_anim.tscn new file mode 100644 index 00000000..8986b73b --- /dev/null +++ b/assets/battlers/squirrel_anim.tscn @@ -0,0 +1,106 @@ +[gd_scene load_steps=7 format=3 uid="uid://cch8nxgex1edr"] + +[ext_resource type="PackedScene" uid="uid://badexg85lctrq" path="res://src/combat/battlers/battler_anim.tscn" id="1_d3f8c"] +[ext_resource type="Texture2D" uid="uid://cbgfjvlm8kx4k" path="res://assets/battlers/squirrel.png" id="2_0aqb7"] + +[sub_resource type="Animation" id="Animation_r4wpi"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(1, 1, 1, 1)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 0)] +} + +[sub_resource type="Animation" id="Animation_2u36l"] +resource_name = "die" +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 1), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 0.87451), Color(1, 1, 1, 0.74902), Color(1, 0, 0, 0.623529), Color(1, 1, 1, 0.498039), Color(1, 1, 1, 0)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 2 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Vector2(0, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0)] +} + +[sub_resource type="Animation" id="Animation_yc1ca"] +resource_name = "hurt" +length = 0.6 +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 2 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [Vector2(0, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(8, 0), Vector2(-8, 0), Vector2(8, 0), Vector2(-8, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(16, 0), Vector2(-16, 0), Vector2(0, 0)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_xl0qy"] +_data = { +"RESET": SubResource("Animation_r4wpi"), +"die": SubResource("Animation_2u36l"), +"hurt": SubResource("Animation_yc1ca") +} + +[node name="SquirrelAnim" instance=ExtResource("1_d3f8c")] + +[node name="AnimationPlayer" parent="Pivot" index="0"] +libraries = { +"": SubResource("AnimationLibrary_xl0qy") +} + +[node name="Sprite2D" type="Sprite2D" parent="Pivot" index="1"] +texture = ExtResource("2_0aqb7") +offset = Vector2(0, -145) diff --git a/assets/battlers/wolf_anim.tscn b/assets/battlers/wolf_anim.tscn new file mode 100644 index 00000000..6d7cb4d7 --- /dev/null +++ b/assets/battlers/wolf_anim.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=4 format=3 uid="uid://8f7e4yxs3poj"] + +[ext_resource type="PackedScene" uid="uid://badexg85lctrq" path="res://src/combat/battlers/battler_anim.tscn" id="1_ffl4t"] +[ext_resource type="Texture2D" uid="uid://dr8cs6liv45hd" path="res://assets/battlers/wolf.png" id="2_nnyj7"] +[ext_resource type="AnimationLibrary" uid="uid://o2ktahx2nkki" path="res://assets/battlers/default_battler_animations.res" id="2_q1kws"] + +[node name="WolfAnim" instance=ExtResource("1_ffl4t")] + +[node name="AnimationPlayer" parent="Pivot" index="0"] +libraries = { +"": ExtResource("2_q1kws") +} + +[node name="Sprite2D" type="Sprite2D" parent="Pivot" index="1"] +texture = ExtResource("2_nnyj7") +offset = Vector2(0, -235) diff --git a/assets/characters/ ghost.atlastex b/assets/characters/ ghost.atlastex new file mode 100644 index 00000000..e5b4fc78 Binary files /dev/null and b/assets/characters/ ghost.atlastex differ diff --git a/assets/characters/ghost.atlastex b/assets/characters/ghost.atlastex deleted file mode 100644 index be17760e..00000000 Binary files a/assets/characters/ghost.atlastex and /dev/null differ diff --git a/assets/characters/ghost_gfx.tscn b/assets/characters/ghost_gfx.tscn index dd5d5707..19c4399b 100644 --- a/assets/characters/ghost_gfx.tscn +++ b/assets/characters/ghost_gfx.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=7 format=3 uid="uid://cgwailb13nkcd"] [ext_resource type="PackedScene" uid="uid://caxhff6by5nqu" path="res://src/field/gamepieces/animation/gamepiece_animation.tscn" id="1_iqw7y"] -[ext_resource type="Texture2D" uid="uid://ber3qsogrcx52" path="res://assets/characters/ghost.atlastex" id="2_reoms"] +[ext_resource type="Texture2D" uid="uid://dopua6orsf0bs" path="res://assets/characters/ ghost.atlastex" id="2_xwhv8"] [sub_resource type="Animation" id="Animation_sr8xj"] length = 0.001 @@ -55,5 +55,5 @@ autoplay = "idle" shape = SubResource("RectangleShape2D_b20ad") [node name="Sprite" parent="GFX" index="0"] -texture = ExtResource("2_reoms") +texture = ExtResource("2_xwhv8") offset = Vector2(0, -6) diff --git a/assets/gui/font/Kenney Pixel.ttf.import b/assets/gui/font/Kenney Pixel.ttf.import index 9d4c7ff4..8e84774a 100644 --- a/assets/gui/font/Kenney Pixel.ttf.import +++ b/assets/gui/font/Kenney Pixel.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Kenney Pixel.ttf-bc32a7a7b8d1d9a0762362cb30a2 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/assets/gui/font/SourceCodePro-Bold.ttf.import b/assets/gui/font/SourceCodePro-Bold.ttf.import index ac0f0d7a..ec51e16b 100644 --- a/assets/gui/font/SourceCodePro-Bold.ttf.import +++ b/assets/gui/font/SourceCodePro-Bold.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/SourceCodePro-Bold.ttf-13dab6b19191054d33554b Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/assets/terrain/tilesets/kenney_obstacles.tres b/assets/terrain/tilesets/kenney_obstacles.tres index bb638c07..e573272a 100644 --- a/assets/terrain/tilesets/kenney_obstacles.tres +++ b/assets/terrain/tilesets/kenney_obstacles.tres @@ -7,110 +7,74 @@ texture = ExtResource("1_g3gb6") separation = Vector2i(1, 1) 3:1/0 = 0 -3:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:1/0/physics_layer_0/angular_velocity = 0.0 3:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:0/0 = 0 -3:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:0/0/physics_layer_0/angular_velocity = 0.0 3:2/0 = 0 3:2/0/terrain_set = 0 3:2/0/terrain = 1 -3:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:2/0/physics_layer_0/angular_velocity = 0.0 3:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:2/0 = 0 4:2/0/terrain_set = 0 4:2/0/terrain = 0 -4:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:2/0/physics_layer_0/angular_velocity = 0.0 4:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:1/0 = 0 -4:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:1/0/physics_layer_0/angular_velocity = 0.0 4:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:0/0 = 0 -4:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:0/0/physics_layer_0/angular_velocity = 0.0 5:0/0 = 0 5:0/0/terrain_set = 0 5:0/0/terrain = 0 -5:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:0/0/physics_layer_0/angular_velocity = 0.0 5:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:1/0 = 0 -5:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:1/0/physics_layer_0/angular_velocity = 0.0 5:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:2/0 = 0 -5:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:2/0/physics_layer_0/angular_velocity = 0.0 5:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:2/0 = 0 6:2/0/terrain_set = 0 6:2/0/terrain = 0 -6:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:2/0/physics_layer_0/angular_velocity = 0.0 6:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:2/0/terrains_peering_bit/right_side = 0 6:2/0/terrains_peering_bit/top_side = 0 7:2/0 = 0 7:2/0/terrain_set = 0 7:2/0/terrain = 0 -7:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:2/0/physics_layer_0/angular_velocity = 0.0 7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:2/0/terrains_peering_bit/top_side = 0 8:2/0 = 0 8:2/0/terrain_set = 0 8:2/0/terrain = 0 -8:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:2/0/physics_layer_0/angular_velocity = 0.0 8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:2/0/terrains_peering_bit/left_side = 0 8:2/0/terrains_peering_bit/top_side = 0 8:1/0 = 0 8:1/0/terrain_set = 0 8:1/0/terrain = 0 -8:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:1/0/physics_layer_0/angular_velocity = 0.0 8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:1/0/terrains_peering_bit/left_side = 0 8:0/0 = 0 8:0/0/terrain_set = 0 8:0/0/terrain = 0 -8:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:0/0/physics_layer_0/angular_velocity = 0.0 8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:0/0/terrains_peering_bit/bottom_side = 0 8:0/0/terrains_peering_bit/left_side = 0 7:0/0 = 0 7:0/0/terrain_set = 0 7:0/0/terrain = 0 -7:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:0/0/physics_layer_0/angular_velocity = 0.0 7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:0/0/terrains_peering_bit/bottom_side = 0 6:0/0 = 0 6:0/0/terrain_set = 0 6:0/0/terrain = 0 -6:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:0/0/physics_layer_0/angular_velocity = 0.0 6:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:0/0/terrains_peering_bit/right_side = 0 6:0/0/terrains_peering_bit/bottom_side = 0 6:1/0 = 0 6:1/0/terrain_set = 0 6:1/0/terrain = 0 -6:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:1/0/physics_layer_0/angular_velocity = 0.0 6:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:1/0/terrains_peering_bit/right_side = 0 7:1/0 = 0 7:1/0/terrain_set = 0 7:1/0/terrain = 0 -7:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:1/0/physics_layer_0/angular_velocity = 0.0 7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:1/0/terrains_peering_bit/right_side = 0 7:1/0/terrains_peering_bit/bottom_side = 0 @@ -119,405 +83,231 @@ separation = Vector2i(1, 1) 9:1/0 = 0 9:1/0/terrain_set = 0 9:1/0/terrain = 1 -9:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:1/0/physics_layer_0/angular_velocity = 0.0 9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:1/0/terrains_peering_bit/right_side = 1 9:2/0 = 0 9:2/0/terrain_set = 0 9:2/0/terrain = 1 -9:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:2/0/physics_layer_0/angular_velocity = 0.0 9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:2/0/terrains_peering_bit/right_side = 1 9:2/0/terrains_peering_bit/top_side = 1 10:2/0 = 0 10:2/0/terrain_set = 0 10:2/0/terrain = 1 -10:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:2/0/physics_layer_0/angular_velocity = 0.0 10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:2/0/terrains_peering_bit/top_side = 1 11:2/0 = 0 11:2/0/terrain_set = 0 11:2/0/terrain = 1 -11:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:2/0/physics_layer_0/angular_velocity = 0.0 11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:2/0/terrains_peering_bit/left_side = 1 11:2/0/terrains_peering_bit/top_side = 1 11:1/0 = 0 11:1/0/terrain_set = 0 11:1/0/terrain = 1 -11:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:1/0/physics_layer_0/angular_velocity = 0.0 11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:1/0/terrains_peering_bit/left_side = 1 11:0/0 = 0 11:0/0/terrain_set = 0 11:0/0/terrain = 1 -11:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:0/0/physics_layer_0/angular_velocity = 0.0 11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:0/0/terrains_peering_bit/bottom_side = 1 11:0/0/terrains_peering_bit/left_side = 1 10:0/0 = 0 10:0/0/terrain_set = 0 10:0/0/terrain = 1 -10:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:0/0/physics_layer_0/angular_velocity = 0.0 10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:0/0/terrains_peering_bit/bottom_side = 1 9:0/0 = 0 9:0/0/terrain_set = 0 9:0/0/terrain = 1 -9:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:0/0/physics_layer_0/angular_velocity = 0.0 9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:0/0/terrains_peering_bit/right_side = 1 9:0/0/terrains_peering_bit/bottom_side = 1 10:1/0 = 0 10:1/0/terrain_set = 0 10:1/0/terrain = 1 -10:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:1/0/physics_layer_0/angular_velocity = 0.0 10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:1/0/terrains_peering_bit/right_side = 1 10:1/0/terrains_peering_bit/bottom_side = 1 10:1/0/terrains_peering_bit/left_side = 1 10:1/0/terrains_peering_bit/top_side = 1 10:6/0 = 0 -10:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:6/0/physics_layer_0/angular_velocity = 0.0 10:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:6/0 = 0 -9:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:6/0/physics_layer_0/angular_velocity = 0.0 9:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:6/0 = 0 -8:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:6/0/physics_layer_0/angular_velocity = 0.0 8:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:5/0 = 0 -8:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:5/0/physics_layer_0/angular_velocity = 0.0 8:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:4/0 = 0 -8:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:4/0/physics_layer_0/angular_velocity = 0.0 8:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:3/0 = 0 -8:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:3/0/physics_layer_0/angular_velocity = 0.0 8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:3/0 = 0 -9:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:3/0/physics_layer_0/angular_velocity = 0.0 9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:3/0 = 0 -10:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:3/0/physics_layer_0/angular_velocity = 0.0 10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:4/0 = 0 -10:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:4/0/physics_layer_0/angular_velocity = 0.0 10:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:4/0 = 0 -11:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:4/0/physics_layer_0/angular_velocity = 0.0 11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:5/0 = 0 -11:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:5/0/physics_layer_0/angular_velocity = 0.0 11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:5/0 = 0 -10:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:5/0/physics_layer_0/angular_velocity = 0.0 10:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:5/0 = 0 -9:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:5/0/physics_layer_0/angular_velocity = 0.0 9:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:3/0 = 0 -11:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:3/0/physics_layer_0/angular_velocity = 0.0 11:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:8/0 = 0 -0:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:8/0/physics_layer_0/angular_velocity = 0.0 0:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:8/0 = 0 -1:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:8/0/physics_layer_0/angular_velocity = 0.0 1:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:8/0 = 0 -2:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:8/0/physics_layer_0/angular_velocity = 0.0 2:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:9/0 = 0 -2:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:9/0/physics_layer_0/angular_velocity = 0.0 2:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:10/0 = 0 -2:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:10/0/physics_layer_0/angular_velocity = 0.0 2:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:10/0 = 0 -1:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:10/0/physics_layer_0/angular_velocity = 0.0 1:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:10/0 = 0 -0:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:10/0/physics_layer_0/angular_velocity = 0.0 0:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:9/0 = 0 -0:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:9/0/physics_layer_0/angular_velocity = 0.0 0:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:9/0 = 0 -1:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:9/0/physics_layer_0/angular_velocity = 0.0 1:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:8/0 = 0 -3:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:8/0/physics_layer_0/angular_velocity = 0.0 3:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:8/0 = 0 -4:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:8/0/physics_layer_0/angular_velocity = 0.0 4:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:8/0 = 0 -5:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:8/0/physics_layer_0/angular_velocity = 0.0 5:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:8/0 = 0 -6:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:8/0/physics_layer_0/angular_velocity = 0.0 6:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:10/0 = 0 -6:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:10/0/physics_layer_0/angular_velocity = 0.0 6:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:10/0 = 0 -5:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:10/0/physics_layer_0/angular_velocity = 0.0 5:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:10/0 = 0 -4:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:10/0/physics_layer_0/angular_velocity = 0.0 4:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:10/0 = 0 -3:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:10/0/physics_layer_0/angular_velocity = 0.0 3:10/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:9/0 = 0 -3:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:9/0/physics_layer_0/angular_velocity = 0.0 4:9/0 = 0 -4:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:9/0/physics_layer_0/angular_velocity = 0.0 5:9/0 = 0 -5:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:9/0/physics_layer_0/angular_velocity = 0.0 6:9/0 = 0 -6:9/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:9/0/physics_layer_0/angular_velocity = 0.0 7:6/0 = 0 -7:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:6/0/physics_layer_0/angular_velocity = 0.0 7:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:5/0 = 0 -7:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:5/0/physics_layer_0/angular_velocity = 0.0 7:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:4/0 = 0 -7:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:4/0/physics_layer_0/angular_velocity = 0.0 7:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:4/0 = 0 -6:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:4/0/physics_layer_0/angular_velocity = 0.0 6:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:4/0 = 0 -5:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:4/0/physics_layer_0/angular_velocity = 0.0 5:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:4/0 = 0 -4:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:4/0/physics_layer_0/angular_velocity = 0.0 4:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:4/0 = 0 -3:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:4/0/physics_layer_0/angular_velocity = 0.0 3:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:4/0 = 0 -2:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:4/0/physics_layer_0/angular_velocity = 0.0 2:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:4/0 = 0 -1:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:4/0/physics_layer_0/angular_velocity = 0.0 1:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:4/0 = 0 -0:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:4/0/physics_layer_0/angular_velocity = 0.0 0:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:5/0 = 0 -0:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:5/0/physics_layer_0/angular_velocity = 0.0 0:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:6/0 = 0 -0:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:6/0/physics_layer_0/angular_velocity = 0.0 0:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:6/0 = 0 -1:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:6/0/physics_layer_0/angular_velocity = 0.0 1:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:6/0 = 0 -2:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:6/0/physics_layer_0/angular_velocity = 0.0 3:6/0 = 0 -3:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:6/0/physics_layer_0/angular_velocity = 0.0 3:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:6/0 = 0 -4:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:6/0/physics_layer_0/angular_velocity = 0.0 4:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:6/0 = 0 -5:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:6/0/physics_layer_0/angular_velocity = 0.0 5:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:6/0 = 0 -6:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:6/0/physics_layer_0/angular_velocity = 0.0 6:5/0 = 0 -6:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:5/0/physics_layer_0/angular_velocity = 0.0 6:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:5/0 = 0 -5:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:5/0/physics_layer_0/angular_velocity = 0.0 5:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:5/0 = 0 -4:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:5/0/physics_layer_0/angular_velocity = 0.0 4:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:5/0 = 0 -3:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:5/0/physics_layer_0/angular_velocity = 0.0 3:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:5/0 = 0 -2:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:5/0/physics_layer_0/angular_velocity = 0.0 2:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:5/0 = 0 -1:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:5/0/physics_layer_0/angular_velocity = 0.0 1:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:7/0 = 0 -4:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:7/0/physics_layer_0/angular_velocity = 0.0 4:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:7/0 = 0 -0:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:7/0/physics_layer_0/angular_velocity = 0.0 0:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:7/0 = 0 -1:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:7/0/physics_layer_0/angular_velocity = 0.0 1:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:7/0 = 0 -5:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:7/0/physics_layer_0/angular_velocity = 0.0 5:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:6/0 = 0 -11:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:6/0/physics_layer_0/angular_velocity = 0.0 11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:7/0 = 0 -10:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:7/0/physics_layer_0/angular_velocity = 0.0 10:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:8/0 = 0 -8:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:8/0/physics_layer_0/angular_velocity = 0.0 8:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:7/0 = 0 -8:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:7/0/physics_layer_0/angular_velocity = 0.0 7:8/0 = 0 -7:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:8/0/physics_layer_0/angular_velocity = 0.0 7:3/0 = 0 7:3/0/terrain_set = 1 7:3/0/terrain = 2 -7:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:3/0/physics_layer_0/angular_velocity = 0.0 6:3/0 = 0 6:3/0/terrain_set = 1 6:3/0/terrain = 0 -6:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:3/0/physics_layer_0/angular_velocity = 0.0 6:3/0/terrains_peering_bit/bottom_right_corner = 0 6:3/0/terrains_peering_bit/top_left_corner = 0 6:3/0/terrains_peering_bit/top_right_corner = 0 5:3/0 = 0 5:3/0/terrain_set = 1 5:3/0/terrain = 0 -5:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:3/0/physics_layer_0/angular_velocity = 0.0 5:3/0/terrains_peering_bit/bottom_left_corner = 0 5:3/0/terrains_peering_bit/top_left_corner = 0 5:3/0/terrains_peering_bit/top_right_corner = 0 4:3/0 = 0 4:3/0/terrain_set = 1 4:3/0/terrain = 0 -4:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:3/0/physics_layer_0/angular_velocity = 0.0 4:3/0/terrains_peering_bit/bottom_right_corner = 0 4:3/0/terrains_peering_bit/bottom_left_corner = 0 4:3/0/terrains_peering_bit/top_left_corner = 0 3:3/0 = 0 3:3/0/terrain_set = 1 3:3/0/terrain = 0 -3:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:3/0/physics_layer_0/angular_velocity = 0.0 3:3/0/terrains_peering_bit/bottom_right_corner = 0 3:3/0/terrains_peering_bit/bottom_left_corner = 0 3:3/0/terrains_peering_bit/top_right_corner = 0 2:3/0 = 0 2:3/0/terrain_set = 1 2:3/0/terrain = 0 -2:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:3/0/physics_layer_0/angular_velocity = 0.0 2:3/0/terrains_peering_bit/top_left_corner = 0 1:3/0 = 0 1:3/0/terrain_set = 1 1:3/0/terrain = 0 -1:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:3/0/physics_layer_0/angular_velocity = 0.0 1:3/0/terrains_peering_bit/top_left_corner = 0 1:3/0/terrains_peering_bit/top_right_corner = 0 0:3/0 = 0 0:3/0/terrain_set = 1 0:3/0/terrain = 0 -0:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:3/0/physics_layer_0/angular_velocity = 0.0 0:3/0/terrains_peering_bit/top_right_corner = 0 0:2/0 = 0 0:2/0/terrain_set = 1 0:2/0/terrain = 0 -0:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:2/0/physics_layer_0/angular_velocity = 0.0 0:2/0/terrains_peering_bit/bottom_right_corner = 0 0:2/0/terrains_peering_bit/top_right_corner = 0 1:2/0 = 0 1:2/0/terrain_set = 1 1:2/0/terrain = 0 -1:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:2/0/physics_layer_0/angular_velocity = 0.0 1:2/0/terrains_peering_bit/bottom_right_corner = 0 1:2/0/terrains_peering_bit/bottom_left_corner = 0 1:2/0/terrains_peering_bit/top_left_corner = 0 @@ -525,21 +315,15 @@ separation = Vector2i(1, 1) 2:2/0 = 0 2:2/0/terrain_set = 1 2:2/0/terrain = 0 -2:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:2/0/physics_layer_0/angular_velocity = 0.0 2:2/0/terrains_peering_bit/bottom_left_corner = 0 2:2/0/terrains_peering_bit/top_left_corner = 0 2:1/0 = 0 2:1/0/terrain_set = 1 2:1/0/terrain = 0 -2:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:1/0/physics_layer_0/angular_velocity = 0.0 2:1/0/terrains_peering_bit/bottom_left_corner = 0 2:0/0 = 0 2:0/0/terrain_set = 1 2:0/0/terrain = 1 -2:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:0/0/physics_layer_0/angular_velocity = 0.0 2:0/0/terrains_peering_bit/bottom_right_corner = 1 2:0/0/terrains_peering_bit/bottom_left_corner = 1 2:0/0/terrains_peering_bit/top_left_corner = 1 @@ -547,8 +331,6 @@ separation = Vector2i(1, 1) 1:0/0 = 0 1:0/0/terrain_set = 1 1:0/0/terrain = 1 -1:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:0/0/physics_layer_0/angular_velocity = 0.0 1:0/0/terrains_peering_bit/bottom_right_corner = 1 1:0/0/terrains_peering_bit/bottom_left_corner = 1 1:0/0/terrains_peering_bit/top_left_corner = 1 @@ -557,8 +339,6 @@ separation = Vector2i(1, 1) 0:0/0/terrain_set = 1 0:0/0/terrain = 1 0:0/0/probability = 30.0 -0:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:0/0/physics_layer_0/angular_velocity = 0.0 0:0/0/terrains_peering_bit/bottom_right_corner = 1 0:0/0/terrains_peering_bit/bottom_left_corner = 1 0:0/0/terrains_peering_bit/top_left_corner = 1 @@ -566,210 +346,100 @@ separation = Vector2i(1, 1) 0:1/0 = 0 0:1/0/terrain_set = 1 0:1/0/terrain = 0 -0:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:1/0/physics_layer_0/angular_velocity = 0.0 0:1/0/terrains_peering_bit/bottom_right_corner = 0 1:1/0 = 0 1:1/0/terrain_set = 1 1:1/0/terrain = 0 -1:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:1/0/physics_layer_0/angular_velocity = 0.0 1:1/0/terrains_peering_bit/bottom_right_corner = 0 1:1/0/terrains_peering_bit/bottom_left_corner = 0 9:4/0 = 0 -9:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:4/0/physics_layer_0/angular_velocity = 0.0 9:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:8/0 = 0 -11:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:8/0/physics_layer_0/angular_velocity = 0.0 11:8/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:8/0 = 0 -9:8/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:8/0/physics_layer_0/angular_velocity = 0.0 11:10/0 = 0 -11:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:10/0/physics_layer_0/angular_velocity = 0.0 10:10/0 = 0 -10:10/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:10/0/physics_layer_0/angular_velocity = 0.0 [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_xmrbd"] texture = ExtResource("2_ou4ia") separation = Vector2i(1, 1) 5:7/0 = 0 -5:7/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:7/0/physics_layer_0/angular_velocity = 0.0 5:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:5/0 = 0 -3:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:5/0/physics_layer_0/angular_velocity = 0.0 3:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:6/0 = 0 -3:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:6/0/physics_layer_0/angular_velocity = 0.0 3:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:6/0 = 0 -10:6/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:6/0/physics_layer_0/angular_velocity = 0.0 10:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 10:4/0 = 0 -10:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -10:4/0/physics_layer_0/angular_velocity = 0.0 10:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 9:4/0 = 0 -9:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:4/0/physics_layer_0/angular_velocity = 0.0 9:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 11:4/0 = 0 -11:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -11:4/0/physics_layer_0/angular_velocity = 0.0 11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:4/0 = 0 -5:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:4/0/physics_layer_0/angular_velocity = 0.0 4:4/0 = 0 -4:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:4/0/physics_layer_0/angular_velocity = 0.0 3:4/0 = 0 -3:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:4/0/physics_layer_0/angular_velocity = 0.0 2:4/0 = 0 -2:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:4/0/physics_layer_0/angular_velocity = 0.0 1:4/0 = 0 -1:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:4/0/physics_layer_0/angular_velocity = 0.0 0:4/0 = 0 -0:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:4/0/physics_layer_0/angular_velocity = 0.0 5:3/0 = 0 -5:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:3/0/physics_layer_0/angular_velocity = 0.0 5:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:3/0 = 0 -6:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:3/0/physics_layer_0/angular_velocity = 0.0 7:3/0 = 0 -7:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:3/0/physics_layer_0/angular_velocity = 0.0 8:3/0 = 0 -8:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:3/0/physics_layer_0/angular_velocity = 0.0 4:3/0 = 0 -4:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:3/0/physics_layer_0/angular_velocity = 0.0 4:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:2/0 = 0 -4:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:2/0/physics_layer_0/angular_velocity = 0.0 4:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:2/0 = 0 -5:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:2/0/physics_layer_0/angular_velocity = 0.0 5:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:3/0 = 0 -3:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:3/0/physics_layer_0/angular_velocity = 0.0 2:3/0 = 0 -2:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:3/0/physics_layer_0/angular_velocity = 0.0 1:3/0 = 0 -1:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:3/0/physics_layer_0/angular_velocity = 0.0 0:3/0 = 0 -0:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:3/0/physics_layer_0/angular_velocity = 0.0 0:2/0 = 0 -0:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:2/0/physics_layer_0/angular_velocity = 0.0 0:1/0 = 0 -0:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:1/0/physics_layer_0/angular_velocity = 0.0 0:0/0 = 0 -0:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -0:0/0/physics_layer_0/angular_velocity = 0.0 9:0/0 = 0 -9:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -9:0/0/physics_layer_0/angular_velocity = 0.0 8:0/0 = 0 -8:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:0/0/physics_layer_0/angular_velocity = 0.0 8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 8:2/0 = 0 -8:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -8:2/0/physics_layer_0/angular_velocity = 0.0 8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:0/0 = 0 -7:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:0/0/physics_layer_0/angular_velocity = 0.0 7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 7:2/0 = 0 -7:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -7:2/0/physics_layer_0/angular_velocity = 0.0 7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:2/0 = 0 -6:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:2/0/physics_layer_0/angular_velocity = 0.0 6:1/0 = 0 -6:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:1/0/physics_layer_0/angular_velocity = 0.0 6:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 6:0/0 = 0 -6:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -6:0/0/physics_layer_0/angular_velocity = 0.0 6:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:2/0 = 0 -1:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:2/0/physics_layer_0/angular_velocity = 0.0 1:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:2/0 = 0 -2:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:2/0/physics_layer_0/angular_velocity = 0.0 2:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:2/0 = 0 -3:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:2/0/physics_layer_0/angular_velocity = 0.0 3:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:1/0 = 0 -3:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:1/0/physics_layer_0/angular_velocity = 0.0 3:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:0/0 = 0 -3:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -3:0/0/physics_layer_0/angular_velocity = 0.0 3:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:0/0 = 0 -2:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:0/0/physics_layer_0/angular_velocity = 0.0 2:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:0/0 = 0 -1:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:0/0/physics_layer_0/angular_velocity = 0.0 1:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:1/0 = 0 -1:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -1:1/0/physics_layer_0/angular_velocity = 0.0 1:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:1/0 = 0 -2:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -2:1/0/physics_layer_0/angular_velocity = 0.0 2:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:1/0 = 0 -4:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:1/0/physics_layer_0/angular_velocity = 0.0 4:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:1/0 = 0 -5:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:1/0/physics_layer_0/angular_velocity = 0.0 5:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 5:0/0 = 0 -5:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -5:0/0/physics_layer_0/angular_velocity = 0.0 5:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:0/0 = 0 -4:0/0/physics_layer_0/linear_velocity = Vector2(0, 0) -4:0/0/physics_layer_0/angular_velocity = 0.0 4:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) [resource] diff --git a/battlers/actions/melee_action.tres b/battlers/actions/melee_action.tres new file mode 100644 index 00000000..7076ca38 --- /dev/null +++ b/battlers/actions/melee_action.tres @@ -0,0 +1,15 @@ +[gd_resource type="Resource" script_class="AttackBattlerAction" load_steps=2 format=3 uid="uid://752xsau3xyx0"] + +[ext_resource type="Script" path="res://src/combat/actions/battler_action_attack.gd" id="1_k2okg"] + +[resource] +script = ExtResource("1_k2okg") +hit_chance = 100.0 +base_damage = 50 +label = "Base combat action" +description = "A combat action." +energy_cost = 0 +element = 0 +targets_self = false +targets_all = false +readiness_saved = 0.0 diff --git a/battlers/actions/ranged_action.tres b/battlers/actions/ranged_action.tres new file mode 100644 index 00000000..1684f66a --- /dev/null +++ b/battlers/actions/ranged_action.tres @@ -0,0 +1,18 @@ +[gd_resource type="Resource" script_class="RangedBattlerAction" load_steps=2 format=3 uid="uid://bs4g81qa8u57q"] + +[ext_resource type="Script" path="res://src/combat/actions/battler_action_projectile.gd" id="1_hylg4"] + +[resource] +script = ExtResource("1_hylg4") +attack_distance = 350.0 +attack_time = 0.25 +return_time = 0.25 +hit_chance = 100.0 +base_damage = 2 +label = "Base combat action" +description = "A combat action." +energy_cost = 0 +element = 0 +targets_self = false +targets_all = false +readiness_saved = 0.0 diff --git a/battlers/bear/bear_stats.tres b/battlers/bear/bear_stats.tres new file mode 100644 index 00000000..b64be741 --- /dev/null +++ b/battlers/bear/bear_stats.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="BattlerStats" load_steps=2 format=3 uid="uid://bn4nqbuhq4ih8"] + +[ext_resource type="Script" path="res://src/combat/battlers/battler_stats.gd" id="1_o2q0b"] + +[resource] +script = ExtResource("1_o2q0b") +base_max_health = 100 +base_max_energy = 6 +base_attack = 10 +base_defense = 10 +base_speed = 70 +base_hit_chance = 100 +base_evasion = 0 diff --git a/battlers/bear/player_melee_action.tres b/battlers/bear/player_melee_action.tres new file mode 100644 index 00000000..5f6aba68 --- /dev/null +++ b/battlers/bear/player_melee_action.tres @@ -0,0 +1,15 @@ +[gd_resource type="Resource" script_class="AttackBattlerAction" load_steps=2 format=3 uid="uid://dp7wo0l4es3w4"] + +[ext_resource type="Script" path="res://src/combat/actions/battler_action_attack.gd" id="1_oh0gs"] + +[resource] +script = ExtResource("1_oh0gs") +hit_chance = 100.0 +base_damage = 150 +label = "Base combat action" +description = "A combat action." +energy_cost = 0 +element = 0 +targets_self = false +targets_all = false +readiness_saved = 0.0 diff --git a/battlers/bugcat_stats.tres b/battlers/bugcat_stats.tres new file mode 100644 index 00000000..f7fc0dec --- /dev/null +++ b/battlers/bugcat_stats.tres @@ -0,0 +1,14 @@ +[gd_resource type="Resource" script_class="BattlerStats" load_steps=2 format=3 uid="uid://chuarvle06xxf"] + +[ext_resource type="Script" path="res://src/combat/battlers/battler_stats.gd" id="1_dv0tm"] + +[resource] +script = ExtResource("1_dv0tm") +affinity = 0 +base_max_health = 50 +base_max_energy = 6 +base_attack = 10 +base_defense = 10 +base_speed = 100 +base_hit_chance = 100 +base_evasion = 15 diff --git a/battlers/squirrel_stats.tres b/battlers/squirrel_stats.tres new file mode 100644 index 00000000..943df9c4 --- /dev/null +++ b/battlers/squirrel_stats.tres @@ -0,0 +1,14 @@ +[gd_resource type="Resource" script_class="BattlerStats" load_steps=2 format=3 uid="uid://bka1pe2l3gbv"] + +[ext_resource type="Script" path="res://src/combat/battlers/battler_stats.gd" id="1_5cya5"] + +[resource] +script = ExtResource("1_5cya5") +affinity = 0 +base_max_health = 100 +base_max_energy = 6 +base_attack = 10 +base_defense = 10 +base_speed = 65 +base_hit_chance = 100 +base_evasion = 0 diff --git a/battlers/wolf_stats.tres b/battlers/wolf_stats.tres new file mode 100644 index 00000000..034cf0ab --- /dev/null +++ b/battlers/wolf_stats.tres @@ -0,0 +1,14 @@ +[gd_resource type="Resource" script_class="BattlerStats" load_steps=2 format=3 uid="uid://chd4ncpr7k7lg"] + +[ext_resource type="Script" path="res://src/combat/battlers/battler_stats.gd" id="1_8t274"] + +[resource] +script = ExtResource("1_8t274") +affinity = 0 +base_max_health = 100 +base_max_energy = 6 +base_attack = 10 +base_defense = 10 +base_speed = 60 +base_hit_chance = 100 +base_evasion = 0 diff --git a/default_bus_layout.tres b/default_bus_layout.tres index 4d7bb039..b6bedd4a 100644 --- a/default_bus_layout.tres +++ b/default_bus_layout.tres @@ -5,7 +5,7 @@ bus/1/name = &"Music" bus/1/solo = false bus/1/mute = false bus/1/bypass_fx = false -bus/1/volume_db = -5.04538 +bus/1/volume_db = -80.0 bus/1/send = &"Master" bus/2/name = &"SFX" bus/2/solo = false diff --git a/maps/test_combat_arena.tscn b/maps/test_combat_arena.tscn deleted file mode 100644 index eb1777b7..00000000 --- a/maps/test_combat_arena.tscn +++ /dev/null @@ -1,20 +0,0 @@ -[gd_scene load_steps=5 format=3 uid="uid://wivtmf75ic3f"] - -[ext_resource type="PackedScene" uid="uid://b3ciqydkjnkkx" path="res://src/combat/combat_arena.tscn" id="1_etpkf"] -[ext_resource type="Texture2D" uid="uid://w55nt3s833tb" path="res://assets/arenas/steppes.png" id="2_i30nm"] -[ext_resource type="AudioStream" uid="uid://dagiejnj8dwqb" path="res://assets/music/squashin_bugs_fixed.mp3" id="2_vo8mp"] -[ext_resource type="Texture2D" uid="uid://e4b6flk7roy3" path="res://assets/battlers/bugcat.png" id="3_rfyrg"] - -[node name="TestCombatArena" instance=ExtResource("1_etpkf")] -music = ExtResource("2_vo8mp") - -[node name="Background" parent="." index="0"] -texture = ExtResource("2_i30nm") - -[node name="Sprite2D" type="Sprite2D" parent="Battlers" index="0"] -position = Vector2(538, 550) -texture = ExtResource("3_rfyrg") - -[node name="Sprite2D2" type="Sprite2D" parent="Battlers" index="1"] -position = Vector2(362, 702) -texture = ExtResource("3_rfyrg") diff --git a/maps/test_combat_arena2.tscn b/maps/test_combat_arena2.tscn deleted file mode 100644 index 08ab2271..00000000 --- a/maps/test_combat_arena2.tscn +++ /dev/null @@ -1,25 +0,0 @@ -[gd_scene load_steps=6 format=3 uid="uid://bq6b26pctmol4"] - -[ext_resource type="PackedScene" uid="uid://b3ciqydkjnkkx" path="res://src/combat/combat_arena.tscn" id="1_tqh6v"] -[ext_resource type="Texture2D" uid="uid://w55nt3s833tb" path="res://assets/arenas/steppes.png" id="2_5pnp5"] -[ext_resource type="AudioStream" uid="uid://dj7yf3u7fdxdu" path="res://assets/music/the_fun_run.mp3" id="2_gvpi1"] -[ext_resource type="Texture2D" uid="uid://dr8cs6liv45hd" path="res://assets/battlers/wolf.png" id="3_d5gu6"] -[ext_resource type="Texture2D" uid="uid://cbgfjvlm8kx4k" path="res://assets/battlers/squirrel.png" id="4_kpf44"] - -[node name="TestCombatArena" instance=ExtResource("1_tqh6v")] -music = ExtResource("2_gvpi1") - -[node name="Background" parent="." index="0"] -texture = ExtResource("2_5pnp5") - -[node name="Sprite2D" type="Sprite2D" parent="Battlers" index="0"] -position = Vector2(586, 485) -texture = ExtResource("3_d5gu6") - -[node name="Sprite2D2" type="Sprite2D" parent="Battlers" index="1"] -position = Vector2(243, 658) -texture = ExtResource("4_kpf44") - -[node name="Sprite2D3" type="Sprite2D" parent="Battlers" index="2"] -position = Vector2(602, 777) -texture = ExtResource("4_kpf44") diff --git a/maps/town/battles/test_combat_arena.tscn b/maps/town/battles/test_combat_arena.tscn new file mode 100644 index 00000000..19ee9d8e --- /dev/null +++ b/maps/town/battles/test_combat_arena.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=12 format=3 uid="uid://wivtmf75ic3f"] + +[ext_resource type="PackedScene" uid="uid://b3ciqydkjnkkx" path="res://src/combat/combat_arena.tscn" id="1_etpkf"] +[ext_resource type="Texture2D" uid="uid://w55nt3s833tb" path="res://assets/arenas/steppes.png" id="2_i30nm"] +[ext_resource type="AudioStream" uid="uid://dagiejnj8dwqb" path="res://assets/music/squashin_bugs_fixed.mp3" id="2_vo8mp"] +[ext_resource type="Script" path="res://src/combat/battlers/battler.gd" id="4_6ngh4"] +[ext_resource type="Resource" uid="uid://chuarvle06xxf" path="res://battlers/bugcat_stats.tres" id="5_1st0g"] +[ext_resource type="Resource" uid="uid://bn4nqbuhq4ih8" path="res://battlers/bear/bear_stats.tres" id="5_id2tu"] +[ext_resource type="Script" path="res://src/combat/actions/battler_action.gd" id="6_b04cs"] +[ext_resource type="PackedScene" uid="uid://ugsq7u4cue4w" path="res://assets/battlers/bugcat_anim.tscn" id="6_r8rpm"] +[ext_resource type="Resource" uid="uid://bs4g81qa8u57q" path="res://battlers/actions/ranged_action.tres" id="9_273jv"] +[ext_resource type="PackedScene" uid="uid://bl8bmbt7v3lrj" path="res://assets/battlers/bear_anim.tscn" id="10_ajkh0"] +[ext_resource type="Resource" uid="uid://dp7wo0l4es3w4" path="res://battlers/bear/player_melee_action.tres" id="10_uukpg"] + +[node name="TestCombatArena" instance=ExtResource("1_etpkf")] +music = ExtResource("2_vo8mp") + +[node name="Background" parent="." index="0"] +texture = ExtResource("2_i30nm") + +[node name="Battler" type="Node2D" parent="Battlers" index="0"] +position = Vector2(465, 722) +script = ExtResource("4_6ngh4") +stats = ExtResource("5_1st0g") +actions = Array[ExtResource("6_b04cs")]([ExtResource("9_273jv")]) +metadata/_edit_group_ = true + +[node name="BugcatAnim" parent="Battlers/Battler" index="0" instance=ExtResource("6_r8rpm")] + +[node name="Battler2" type="Node2D" parent="Battlers" index="1"] +position = Vector2(284, 896) +script = ExtResource("4_6ngh4") +stats = ExtResource("5_1st0g") +actions = Array[ExtResource("6_b04cs")]([ExtResource("9_273jv")]) +metadata/_edit_group_ = true + +[node name="BugcatAnim" parent="Battlers/Battler2" index="0" instance=ExtResource("6_r8rpm")] + +[node name="PlayerBattler" type="Node2D" parent="Battlers" index="2"] +position = Vector2(1594, 738) +script = ExtResource("4_6ngh4") +stats = ExtResource("5_id2tu") +actions = Array[ExtResource("6_b04cs")]([ExtResource("10_uukpg")]) +is_player = true +metadata/_edit_group_ = true + +[node name="BearAnim" parent="Battlers/PlayerBattler" index="0" instance=ExtResource("10_ajkh0")] +scale = Vector2(-1, 1) +direction = 0 diff --git a/maps/town/battles/test_combat_arena2.tscn b/maps/town/battles/test_combat_arena2.tscn new file mode 100644 index 00000000..60d8d689 --- /dev/null +++ b/maps/town/battles/test_combat_arena2.tscn @@ -0,0 +1,61 @@ +[gd_scene load_steps=15 format=3 uid="uid://bq6b26pctmol4"] + +[ext_resource type="PackedScene" uid="uid://b3ciqydkjnkkx" path="res://src/combat/combat_arena.tscn" id="1_tqh6v"] +[ext_resource type="Texture2D" uid="uid://w55nt3s833tb" path="res://assets/arenas/steppes.png" id="2_5pnp5"] +[ext_resource type="AudioStream" uid="uid://dj7yf3u7fdxdu" path="res://assets/music/the_fun_run.mp3" id="2_gvpi1"] +[ext_resource type="PackedScene" uid="uid://8f7e4yxs3poj" path="res://assets/battlers/wolf_anim.tscn" id="5_2jmm4"] +[ext_resource type="Resource" uid="uid://bn4nqbuhq4ih8" path="res://battlers/bear/bear_stats.tres" id="5_dbmw8"] +[ext_resource type="Resource" uid="uid://bka1pe2l3gbv" path="res://battlers/squirrel_stats.tres" id="5_ejdro"] +[ext_resource type="PackedScene" uid="uid://cch8nxgex1edr" path="res://assets/battlers/squirrel_anim.tscn" id="6_758fa"] +[ext_resource type="Script" path="res://src/combat/battlers/battler.gd" id="6_q0bkx"] +[ext_resource type="Script" path="res://src/combat/actions/battler_action.gd" id="7_7p3qq"] +[ext_resource type="Resource" uid="uid://bs4g81qa8u57q" path="res://battlers/actions/ranged_action.tres" id="7_vldb3"] +[ext_resource type="Resource" uid="uid://752xsau3xyx0" path="res://battlers/actions/melee_action.tres" id="8_71nny"] +[ext_resource type="PackedScene" uid="uid://bl8bmbt7v3lrj" path="res://assets/battlers/bear_anim.tscn" id="9_jq0mb"] +[ext_resource type="Resource" uid="uid://chd4ncpr7k7lg" path="res://battlers/wolf_stats.tres" id="9_n8llw"] +[ext_resource type="Resource" uid="uid://dp7wo0l4es3w4" path="res://battlers/bear/player_melee_action.tres" id="13_232fw"] + +[node name="TestCombatArena" instance=ExtResource("1_tqh6v")] +music = ExtResource("2_gvpi1") + +[node name="Background" parent="." index="0"] +texture = ExtResource("2_5pnp5") + +[node name="Battler2" type="Node2D" parent="Battlers" index="0"] +position = Vector2(202, 788) +script = ExtResource("6_q0bkx") +stats = ExtResource("5_ejdro") +actions = Array[ExtResource("7_7p3qq")]([ExtResource("7_vldb3")]) +metadata/_edit_group_ = true + +[node name="SquirrelAnim" parent="Battlers/Battler2" index="0" instance=ExtResource("6_758fa")] + +[node name="Battler3" type="Node2D" parent="Battlers" index="1"] +position = Vector2(586, 902) +script = ExtResource("6_q0bkx") +stats = ExtResource("5_ejdro") +actions = Array[ExtResource("7_7p3qq")]([ExtResource("7_vldb3")]) +metadata/_edit_group_ = true + +[node name="SquirrelAnim" parent="Battlers/Battler3" index="0" instance=ExtResource("6_758fa")] + +[node name="Battler" type="Node2D" parent="Battlers" index="2"] +position = Vector2(471, 669) +script = ExtResource("6_q0bkx") +stats = ExtResource("9_n8llw") +actions = Array[ExtResource("7_7p3qq")]([ExtResource("8_71nny")]) +metadata/_edit_group_ = true + +[node name="WolfAnim" parent="Battlers/Battler" index="0" instance=ExtResource("5_2jmm4")] + +[node name="PlayerBattler" type="Node2D" parent="Battlers" index="3"] +position = Vector2(1620, 766) +script = ExtResource("6_q0bkx") +stats = ExtResource("5_dbmw8") +actions = Array[ExtResource("7_7p3qq")]([ExtResource("13_232fw")]) +is_player = true +metadata/_edit_group_ = true + +[node name="BearAnim" parent="Battlers/PlayerBattler" index="0" instance=ExtResource("9_jq0mb")] +scale = Vector2(-1, 1) +direction = 0 diff --git a/project.godot b/project.godot index e9324212..6e2e770e 100644 --- a/project.godot +++ b/project.godot @@ -12,7 +12,7 @@ config_version=5 config/name="OpenRPG" run/main_scene="res://src/main.tscn" -config/features=PackedStringArray("4.2", "GL Compatibility") +config/features=PackedStringArray("4.3", "GL Compatibility") config/icon="res://icon.svg" [autoload] @@ -133,10 +133,10 @@ theme/custom="res://assets/gui/default.theme" dialogic_default_action={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":88,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":88,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null) ] } @@ -147,12 +147,12 @@ select={ } interact={ "deadzone": 0.5, -"events": [null, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [null, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } back={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } diff --git a/src/combat/actions/battler_action.gd b/src/combat/actions/battler_action.gd new file mode 100644 index 00000000..c7566e15 --- /dev/null +++ b/src/combat/actions/battler_action.gd @@ -0,0 +1,35 @@ +## Discrete actions that a [Battler] may take on its turn. +## +## The following class is an interface that specific actions should implement. [method execute] is +## called once an action has been chosen and is a coroutine, containing the logic of the action +## including any animations or effects. +class_name BattlerAction extends Resource + +## An action-specific icon. Shown primarily in menus. +@export var icon: Texture +## The 'name' of the action. Shown primarily in menus. +@export var label: = "Base combat action" +## Tells the player exactly what an action does. Shown primarily in menus. +@export var description: = "A combat action." +## Amount of energy required to perform the action. +@export_range(0, 10) var energy_cost: = 0 +## The action's [enum Elements.Types]. +@export var element: = Elements.Types.NONE +@export var targets_self: = false +@export var targets_all: = false +## The amount of [member Battler.readiness] left to the Battler after acting. This can be used to +## design weak attacks that allow the Battler to take fast turns. +@export_range(0.0, 100.0) var readiness_saved: = 0.0 + + +## Returns true if the [Battler] is able to use the action. +func can_be_used_by(battler: Battler) -> bool: + return energy_cost <= battler.stats.energy + + +## The body of the action, where different animations/modifiers/damage/etc. will be played out. +## Battler actions are (almost?) always coroutines, so it is expected that the caller will wait for +## execution to finish. +## [br][br]Note: The base action class does nothing, but must be overridden to do anything. +func execute(source: Battler, _targets: Array[Battler] = []) -> void: + await source.get_tree().process_frame diff --git a/src/combat/actions/battler_action_attack.gd b/src/combat/actions/battler_action_attack.gd new file mode 100644 index 00000000..451918d4 --- /dev/null +++ b/src/combat/actions/battler_action_attack.gd @@ -0,0 +1,40 @@ +# A sample [BattlerAction] implementation that simulates a direct melee hit. +class_name AttackBattlerAction extends BattlerAction + +const ATTACK_DISTANCE: = 350.0 + +## A to-hit modifier for this attack that will be influenced by the target Battler's +## [member BattlerStats.evasion]. +@export var hit_chance: = 100.0 +@export var base_damage: = 50 + + +func execute(source: Battler, targets: Array[Battler] = []) -> void: + assert(not targets.is_empty(), "An attack action requires a target.") + var target: = targets[0] + + await source.get_tree().create_timer(0.1).timeout + + # Calculate where the acting Battler will move from and to. + var origin: = source.position + var attack_normal: float = sign(source.position.x - target.position.x) + var destination: = target.position + Vector2(ATTACK_DISTANCE*attack_normal, 0) + + # Animate movement to attack position. + var tween: = source.create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC) + tween.tween_property(source, "position", destination, 0.25) + await tween.finished + + # No attack animations yet, so wait for a short delay and then apply damage to the target. + # Normally we would wait for an attack animation's "triggered" signal. + await source.get_tree().create_timer(0.1).timeout + var hit: = BattlerHit.new(base_damage, hit_chance) + target.take_hit(hit) + await source.get_tree().create_timer(0.1).timeout + + # Animate movement back to the attacker's original position. + tween = source.create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC) + tween.tween_property(source, "position", origin, 0.25) + await tween.finished + + await source.get_tree().create_timer(0.1).timeout diff --git a/src/combat/actions/battler_action_projectile.gd b/src/combat/actions/battler_action_projectile.gd new file mode 100644 index 00000000..d39b5a39 --- /dev/null +++ b/src/combat/actions/battler_action_projectile.gd @@ -0,0 +1,42 @@ +# A sample [BattlerAction] implementation that simulates a ranged attack, such as a fireball. +class_name RangedBattlerAction extends BattlerAction + +@export var attack_distance: = 350.0 +@export var attack_time: = 0.25 +@export var return_time: = 0.25 +## A to-hit modifier for this attack that will be influenced by the target Battler's +## [member BattlerStats.evasion]. +@export var hit_chance: = 100.0 +@export var base_damage: = 50 + + +func execute(source: Battler, targets: Array[Battler] = []) -> void: + assert(not targets.is_empty(), "A ranged attack action requires a target.") + var target: = targets[0] + + await source.get_tree().create_timer(0.1).timeout + + # Calculate where the acting Battler will move from and to. + var origin: = source.position + var attack_direction: float = sign(target.position.x - source.position.x) + var destination: = origin + Vector2(attack_distance*attack_direction, 0) + + # Quickly animate the attacker to the attack position, pretending to lob a fireball or smth. + var tween: = source.create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_BACK) + tween.tween_property(source, "position", destination, attack_time) + await tween.finished + + # No attack animations yet, so wait for a short delay and then apply damage to the target. + # Normally we would wait for an attack animation's "triggered" signal and then spawn a + # projectile, waiting for impact. + await source.get_tree().create_timer(0.1).timeout + var hit: = BattlerHit.new(base_damage, hit_chance) + target.take_hit(hit) + await source.get_tree().create_timer(0.4).timeout + + # Animate movement back to the attacker's original position. + tween = source.create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC) + tween.tween_property(source, "position", origin, return_time) + await tween.finished + + await source.get_tree().create_timer(0.1).timeout diff --git a/src/combat/actions/battler_hit.gd b/src/combat/actions/battler_hit.gd new file mode 100644 index 00000000..aad7df0b --- /dev/null +++ b/src/combat/actions/battler_hit.gd @@ -0,0 +1,15 @@ +## Represents a damage-dealing hit to be applied to a target Battler. +## Encapsulates calculations for how hits are applied based on some properties. +class_name BattlerHit extends RefCounted + +var damage: = 0 +var hit_chance: = 100.0 + + +func _init(dmg: int, to_hit := 100.0) -> void: + damage = dmg + hit_chance = to_hit + + +func is_successful() -> bool: + return randf() * 100.0 < hit_chance diff --git a/src/combat/active_turn_queue.gd b/src/combat/active_turn_queue.gd new file mode 100644 index 00000000..e566d9a7 --- /dev/null +++ b/src/combat/active_turn_queue.gd @@ -0,0 +1,176 @@ +## Responds to [Battler] and input signals to determine when and how Battlers may act. +## +## The ActiveTurnQueue sorts Battlers neatly into a queue as they are ready to act. Time is paused +## as Battlers act and is resumed once actors are finished acting. The queue ceases once the player +## or enemy Battlers have been felled, signaling that the combat has finished. +## +## Note: the turn queue defers action/target selection to either AI or player input. While +## time is slowed for player input, it is not stopped completely which may result in an AI Battler +## acting while the player is taking their turn. +class_name ActiveTurnQueue extends Node2D + +## Emitted when a combat has finished, indicating whether or not it may be considered a victory for +## the player. +signal combat_finished(is_player_victory: bool) +## Emitted when a player-controlled battler finished playing a turn. That is, when the _play_turn() +## method returns. +signal player_turn_finished + +## Allows pausing the Active Time Battle during combat intro, a cutscene, or combat end. +var is_active: = true: + set(value): + if value != is_active: + is_active = value + for battler: Battler in _battlers: + battler.is_active = is_active +## Multiplier for the global pace of battle, to slow down time while the player is making decisions. +## This is meant for accessibility and to control difficulty. +var time_scale: = 1.0: + set(value): + time_scale = value + for battler: Battler in _battlers: + battler.time_scale = time_scale + +## If true, the player is currently playing a turn (navigating menus, choosing targets, etc.). +var _is_player_playing: = false + +## Only ever set true if the player has won the combat. I.e. enemy battlers are felled. +var _has_player_won: = false + +## A stack of player-controlled battlers that have to take turns. +var _queued_player_battlers: Array[Battler] = [] + +var _battlers: Array[Battler] = [] +var _party_members: Array[Battler] = [] +var _enemies: Array[Battler] = [] + + + +func _ready() -> void: + # This is required in Godot 4.3 to strongly type the array. + _battlers.assign(get_children()) + set_process(false) + + player_turn_finished.connect(func _on_player_turn_finished() -> void: + if _queued_player_battlers.is_empty(): + _is_player_playing = false + else: + _play_turn(_queued_player_battlers.pop_front()) + ) + + for battler: Battler in _battlers: + battler.ready_to_act.connect(func on_battler_ready_to_act() -> void: + if battler.is_player and _is_player_playing: + _queued_player_battlers.append(battler) + else: + _play_turn(battler) + ) + battler.health_depleted.connect(func on_battler_health_depleted() -> void: + if not _deactivate_if_side_downed(_party_members, false): + _deactivate_if_side_downed(_enemies, true) + ) + + if battler.is_player: + _party_members.append(battler) + else: + _enemies.append(battler) + + # Don't begin combat until the state has been setup. I.e. intro animations, UI is ready, etc. + is_active = false + + +# The active turn queue waits until all battlers have finished their animations before emitting the +# finished signal. +func _process(_delta: float) -> void: + for child: BattlerAnim in find_children("*", "BattlerAnim"): + # If there are still playing BattlerAnims, don't finish the battle yet. + if child.is_playing(): + return + + # There are no animations being played. Combat can now finish. + set_process(false) + combat_finished.emit(_has_player_won) + + +func _play_turn(battler: Battler) -> void: + var action: BattlerAction + var targets: Array[Battler] = [] + + # The battler is getting a new turn, so increment its energy count. + battler.stats.energy += 1 + + # The code below makes a list of selectable targets using Battler.is_selectable + var potential_targets: Array[Battler] = [] + var opponents: = _enemies if battler.is_player else _party_members + for opponent: Battler in opponents: + if opponent.is_selectable: + potential_targets.append(opponent) + + if battler.is_player: + _is_player_playing = true + battler.is_selected = true + + time_scale = 0.05 + + # Loop until the player selects a valid set of actions and targets of said action. + var is_selection_complete: = false + while not is_selection_complete: + # First of all, the player must select an action. + action = await _player_select_action_async(battler) + + # Secondly, the player must select targets for the action. + # If the target may be selected automatically, do so. + if action.targets_self: + targets = [battler] + else: + targets = await _player_select_targets_async(action, potential_targets) + + # If the player selected a correct action and target, break out of the loop. Otherwise, + # the player may reselect an action/targets. + is_selection_complete = action != null and targets != [] + + battler.is_selected = false + + else: + # Allow the AI to take a turn. + if battler.actions.size(): + action = battler.actions[0] + targets = [potential_targets[0]] + + time_scale = 0 + await battler.act(action, targets) + time_scale = 1.0 + + if battler.is_player: + player_turn_finished.emit() + + +func _player_select_action_async(battler: Battler) -> BattlerAction: + await get_tree().process_frame + return battler.actions[0] + + +func _player_select_targets_async(_action: BattlerAction, opponents: Array[Battler]) -> Array[Battler]: + await get_tree().process_frame + return [opponents[0]] + + +# Run through a provided array of battlers. If all of them are downed (that is, their health points +# are 0), finish the combat and indicate whether or not the player was victorious. +# Return true if the combat has finished, otherwise return false. +func _deactivate_if_side_downed(checked_battlers: Array[Battler], + is_player_victory: bool) -> bool: + for battler: Battler in checked_battlers: + if battler.stats.health > 0: + return false + + # If the player battlers are dead, wait for all animations to finish playing before signaling + # a resolution to the combat. + # This is done with this classes' process function, which will check each frame to see if any + # 'clean up' animations have finished. + set_process(true) + _has_player_won = is_player_victory + + # Don't allow anyone else to act. + is_active = false + return true diff --git a/src/combat/battlers/battler.gd b/src/combat/battlers/battler.gd new file mode 100644 index 00000000..f497e461 --- /dev/null +++ b/src/combat/battlers/battler.gd @@ -0,0 +1,112 @@ +## A playable combatant that carries out [BattlerActions] as its [member readiness] charges. +## +## Battlers are the playable characters or enemies that show up in battle. They have [BattlerStats], +## a list of [BattlerAction]s to choose from, and respond to a variety of stimuli such as status +## effects and [BattlerHit]s, which typically deal damage or heal the Battler. +## +## [br][br]Battlers have [BattlerAnim]ation children which play out the Battler's actions. +class_name Battler extends Node2D + +## Emitted when the battler finished their action and arrived back at their rest position. +signal action_finished +## Forwarded from the receiving of [signal BettlerStats.health_depleted]. +signal health_depleted +## Emitted when taking damage or being healed from a [BattlerHit]. +## [br][br]Note the difference between this and [signal BattlerStats.health_changed]: +## 'hit_received' is always the direct result of an action, requiring graphical feedback. +signal hit_received(value: int) +## Emitted whenever a hit targeting this battler misses. +signal hit_missed +## Emitted when the battler's `_readiness` changes. +signal readiness_changed(new_value) +## Emitted when the battler is ready to take a turn. +signal ready_to_act +## Emitted when modifying `is_selected`. The user interface will react to this for +## player-controlled battlers. +signal selection_toggled(value: bool) + +@export var stats: BattlerStats = null +# Each action's data stored in this array represents an action the battler can perform. +# These can be anything: attacks, healing spells, etc. +@export var actions: Array[BattlerAction] +# If the battler has an `ai_scene`, we will instantiate it and let the AI make decisions. +# If not, the player controls this battler. The system should allow for ally AIs. +@export var ai_scene: PackedScene +@export var is_player: = false + +## If `false`, the battler will not be able to act. +var is_active: bool = true: + set(value): + is_active = value + set_process(is_active) + +## The turn queue will change this property when another battler is acting. +var time_scale := 1.0: + set(value): + time_scale = value + +## If `true`, the battler is selected, which makes it move forward. +var is_selected: bool = false: + set(value): + if value: + assert(is_selectable) + + is_selected = value + selection_toggled.emit(is_selected) + +## If `false`, the battler cannot be targeted by any action. +var is_selectable: bool = true: + set(value): + is_selectable = value + if not is_selectable: + is_selected = false + +## When this value reaches `100.0`, the battler is ready to take their turn. +var readiness := 0.0: + set(value): + readiness = value + readiness_changed.emit(readiness) + + if readiness >= 100.0: + ready_to_act.emit() + set_process(false) + + +func _ready() -> void: + assert(stats, "Battler %s does not have stats assigned!" % name) + + # Resources are NOT unique, so treat the currently assigned BattlerStats as a prototype. + # That is, copy what it is now and use the copy, so that the original remains unaltered. + stats = stats.duplicate() + stats.initialize() + stats.health_depleted.connect(func on_stats_health_depleted() -> void: + is_active = false + is_selectable = false + health_depleted.emit() + ) + + +func _process(delta: float) -> void: + readiness += stats.speed * delta * time_scale + + +func act(action: BattlerAction, targets: Array[Battler] = []) -> void: + stats.energy -= action.energy_cost + + # action.execute() almost certainly is a coroutine. + @warning_ignore("redundant_await") + await action.execute(self, targets) + readiness = action.readiness_saved + + if is_active: + set_process(true) + + action_finished.emit() + + +func take_hit(hit: BattlerHit) -> void: + if hit.is_successful(): + hit_received.emit(hit.damage) + stats.health -= hit.damage + else: + hit_missed.emit() diff --git a/src/combat/battlers/battler_anim.gd b/src/combat/battlers/battler_anim.gd new file mode 100644 index 00000000..f69f8ba0 --- /dev/null +++ b/src/combat/battlers/battler_anim.gd @@ -0,0 +1,154 @@ +## The visual representation of a [Battler]. +## +## Battler animations respond visually to a closed set of stimiuli, such as receiving a hit or +## moving to a position. These animations often represent a single character or a class of enemies +## and are added as children to a given Battler. +## +## [br][br]Note: BattlerAnims must be children of a Battler object to function correctly! +@tool +class_name BattlerAnim extends Marker2D + +## Dictates how far the battler moves forwards and backwards at the beginning/end of its turn. +const MOVE_OFFSET: = 40.0 + +## Determines which direction the battler faces on the screen. +enum Direction { LEFT, RIGHT } + +## Emitted whenever an action-based animation wants to apply an effect. May be triggered multiple +## times per animation. +@warning_ignore("unused_signal") +signal action_triggered + +## Forward AnimationPlayer's same signal. +signal animation_finished(name) + +## Determines which direction the [BattlerAnim] faces. This is generally set by whichever "side" +## the battler is on, player or enemy. +@export var direction: = Direction.RIGHT: + set(value): + direction = value + + scale.x = 1 + if direction == Direction.LEFT: + scale.x = -1 + +## Determines the time it takes for the [BattlerAnim] to slide forward or backward when its turn +## comes up. +@export var select_move_time: = 0.3 + +var _move_tween: Tween = null +var _rest_position: = Vector2.ZERO + +var _battler: Battler: + set(value): + # If this object had a previous battler parent and had connected to its signals, disconnect + # these before setting the new parent. + if _battler and not Engine.is_editor_hint(): + if _battler.health_depleted.is_connected(_on_battler_health_depleted): + _battler.health_depleted.disconnect(_on_battler_health_depleted) + if _battler.hit_received.is_connected(_on_battler_hit_received): + _battler.hit_received.disconnect(_on_battler_hit_received) + if _battler.selection_toggled.is_connected(_on_battler_selection_toggled): + _battler.selection_toggled.disconnect(_on_battler_selection_toggled) + + _battler = value + if _battler and not Engine.is_editor_hint(): + _battler.health_depleted.connect(_on_battler_health_depleted) + _battler.hit_received.connect(_on_battler_hit_received) + _battler.selection_toggled.connect(_on_battler_selection_toggled) + + update_configuration_warnings() + +@onready var _anim: = $Pivot/AnimationPlayer as AnimationPlayer + + +func _ready() -> void: + _anim.animation_finished.connect(_on_animation_player_finished) + + _rest_position = position + + +func _notification(msg: int) -> void: + if msg == NOTIFICATION_PARENTED: + _battler = get_parent() as Battler + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: = [] + if not _battler: + warnings.append("Requires a Battler as a parent to function correctly!") + + return warnings + + +# Functions that wraps around the animation players' `play()` function, delegating the work to the +# `AnimationPlayerDamage` node when necessary. +func play(anim_name: String) -> void: + assert(_anim.has_animation(anim_name), "Battler animation '%s' does not have animation '%s'!" + % [name, anim_name]) + + _anim.play(anim_name) + + +## Returns true if an animation is currently playing, otherwise returns false. +func is_playing() -> bool: + return _anim.is_playing() + + +## Queues the specified animation sequence and plays it if the animation player is stopped. +func queue_animation(anim_name: String) -> void: + assert(_anim.has_animation(anim_name), "Battler animation '%s' does not have animation '%s'!" + % [name, anim_name]) + + _anim.queue(anim_name) + if not _anim.is_playing(): + _anim.play() + + +## Tween the object [constant MOVE_OFFSET] pixels from its rest position towards enemy [Battler]s. +func move_forward(duration: float) -> void: + if _move_tween: + _move_tween.kill() + + _move_tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_QUART) + _move_tween.tween_property( + self, + "position", + _rest_position + Vector2.LEFT*scale.x*MOVE_OFFSET, + duration + ) + + +## Tween the object back to its rest position. +func move_to_rest(duration: float) -> void: + if _move_tween: + _move_tween.kill() + + _move_tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_QUART) + _move_tween.tween_property( + self, + "position", + _rest_position, + duration + ) + + +func _on_animation_player_finished(anim_name: String) -> void: + animation_finished.emit(anim_name) + + +func _on_battler_selection_toggled(value: bool) -> void: + if value: + move_forward(select_move_time) + + else: + move_to_rest(select_move_time) + + +func _on_battler_hit_received(value: int) -> void: + if value > 0: + _anim.play("hurt") + + +func _on_battler_health_depleted() -> void: + _anim.play("die") diff --git a/src/combat/battlers/battler_anim.tscn b/src/combat/battlers/battler_anim.tscn new file mode 100644 index 00000000..c333174e --- /dev/null +++ b/src/combat/battlers/battler_anim.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://badexg85lctrq"] + +[ext_resource type="Script" path="res://src/combat/battlers/battler_anim.gd" id="1_j0v3v"] + +[node name="BattlerAnim" type="Marker2D"] +script = ExtResource("1_j0v3v") + +[node name="Pivot" type="Marker2D" parent="."] + +[node name="AnimationPlayer" type="AnimationPlayer" parent="Pivot"] diff --git a/src/combat/battlers/battler_stats.gd b/src/combat/battlers/battler_stats.gd new file mode 100644 index 00000000..4b02d532 --- /dev/null +++ b/src/combat/battlers/battler_stats.gd @@ -0,0 +1,182 @@ +## Numerically represents the characteristics of a specific [Battler]. +class_name BattlerStats extends Resource + +## A list of all properties that can receive bonuses. +const MODIFIABLE_STATS = [ + "max_health", "max_energy", "attack", "defense", "speed", "hit_chance", "evasion" +] + +## Emitted when [member health] has reached 0. +signal health_depleted +## Emitted whenever [member health] changes. +signal health_changed(old_value, new_value) +## Emitted whenver [member energy] changes. +signal energy_changed(old_value, new_value) + +@export_category("Elements") +## The battler's elemental affinity. Determines which attacks are more or less effective against +## this battler. +@export var affinity := Elements.Types.NONE + +@export_category("Stats") +@export var base_max_health := 100 +@export var base_max_energy := 6 +@export var base_attack := 10: + set(value): + base_attack = value + _recalculate_and_update("attack") +@export var base_defense := 10: + set(value): + base_defense = value + _recalculate_and_update("defense") +@export var base_speed := 70: + set(value): + base_speed = value + _recalculate_and_update("speed") +@export var base_hit_chance := 100: + set(value): + base_hit_chance = value + _recalculate_and_update("hit_chance") +@export var base_evasion := 0: + set(value): + base_evasion = value + _recalculate_and_update("evasion") + +var max_health := base_max_health +var max_energy := base_max_energy +var attack := base_attack +var defense := base_defense +var speed := base_speed +var hit_chance := base_hit_chance +var evasion := base_evasion + +var health := max_health: + set(value): + if value != health: + var previous_health := health + health = clampi(value, 0, max_health) + + health_changed.emit(previous_health, health) + if health == 0: + health_depleted.emit() +var energy := 0: + set(value): + if value != energy: + var previous_energy = energy + energy = clampi(value, 0, max_energy) + + energy_changed.emit(previous_energy, energy) + +# The properties below stores a list of modifiers for each property listed in MODIFIABLE_STATS. +# Dictionary keys are the name of the property (String). +# Dictionary values are another dictionary, with uid/modifier pairs. +var _modifiers := {} +var _multipliers := {} + + +func _init() -> void: + for prop_name in MODIFIABLE_STATS: + _modifiers[prop_name] = {} + _multipliers[prop_name] = {} + + +func initialize() -> void: + health = max_health + + +## Adds a modifier that affects the stat with the given `stat_name` and returns its unique id. +func add_modifier(stat_name: String, value: int) -> int: + assert(stat_name in MODIFIABLE_STATS, "Trying to add a modifier to a nonexistent stat.") + + var id := _generate_unique_id(stat_name, true) + _modifiers[stat_name][id] = value + _recalculate_and_update(stat_name) + + # Returning the id allows the caller to bind it to a signal. For instance + # with equpment, to call `remove_modifier()` upon removing the equipment. + return id + + +## Adds a multiplier that affects the stat with the given `stat_name` and returns its unique id. +func add_multiplier(stat_name: String, value: float) -> int: + assert(stat_name in MODIFIABLE_STATS, "Trying to add a modifier to a nonexistent stat.") + + var id := _generate_unique_id(stat_name, false) + _multipliers[stat_name][id] = value + _recalculate_and_update(stat_name) + + return id + + +# Removes a modifier associated with the given `stat_name`. +func remove_modifier(stat_name: String, id: int) -> void: + assert(id in _modifiers[stat_name], "Stat %s does not have a modifier with ID '%s'." % [id, + _modifiers[stat_name]]) + + _modifiers[stat_name].erase(id) + _recalculate_and_update(stat_name) + + +func remove_multiplier(stat_name: String, id: int) -> void: + assert(id in _multipliers[stat_name], "Stat %s does not have a multiplier with ID '%s'." % [id, + _multipliers[stat_name]]) + + _multipliers[stat_name].erase(id) + _recalculate_and_update(stat_name) + + +# Calculates the final value of a single stat. That is, its based value with all modifiers applied. +# We reference a stat property name using a string here and update it with the `set()` method. +func _recalculate_and_update(prop_name: String) -> void: + assert(prop_name in self, "Cannot update battler stat '%s'! Stat name is invalid!" % prop_name) + + # All our property names follow a pattern: the base stat has the same identifier as the final + # stat with the "base_" prefix. + var base_prop_id := "base_" + prop_name + assert(base_prop_id in self, "Cannot update battler stat %s! Stat does not have base value!" % prop_name) + var value := get(base_prop_id) as float + + # Multipliers apply to the stat multiplicatively. + # They are first summed, with the sole restriction that they may not go below zero. + var stat_multiplier := 1.0 + var multipliers: Array = _multipliers[prop_name].values() + for multiplier in multipliers: + stat_multiplier += multiplier + if stat_multiplier < 0.0: + stat_multiplier = 0.0 + + # Apply the cumulative multiplier to the stat. + if not is_equal_approx(stat_multiplier, 1.0): + value *= stat_multiplier + + # Add all modifiers to the stat. + var modifiers: Array = _modifiers[prop_name].values() + for modifier in modifiers: + value += modifier + + # Finally, don't allow values to drop below zero. + value = roundf(max(value, 0.0)) + + # Here's where we assign the value to the stat. For instance, if the `stat` argument is + # "attack", this is like writing 'attack = value'. + # Note that this sets an integer to a float value, so the decimal will no longer be relevent. + set(prop_name, value) + + +# Find the first unused integer in a stat's modifiers keys. +# is_modifier determines whether the id is determined from the modifier or multiplier dictionary. +func _generate_unique_id(stat_name: String, is_modifier := true) -> int: + # Generate an ID for either modifiers or multipliers. + var dictionary := _modifiers + if not is_modifier: + dictionary = _multipliers + + # If there are no keys, we return `0`, which is our first valid unique id. Without existing + # keys, calling methods like `Array.back()` will trigger an error. + var keys: Array = dictionary[stat_name].keys() + if keys.is_empty(): + return 0 + else: + # We always start from the last key, which will always be the highest number, even if we + # remove modifiers. + return keys.back() + 1 diff --git a/src/combat/combat.gd b/src/combat/combat.gd index 996512bf..44f88d04 100644 --- a/src/combat/combat.gd +++ b/src/combat/combat.gd @@ -1,3 +1,12 @@ +## Starts and ends combat, and manages the transition between the field game state and the combat game. +## +## The battle is composed mainly from a [CombatArena], which contains all necessary subelements such +## as battlers, visual effects, music, etc. +## +## This container handles the logic of switching between the field game state, the combat game +## state, and the combat results screen (e.g. experience and levelling up, loot, etc.). It is +## responsible for changing the music, playing screen transition animations, and other state-switch +## elements. extends CanvasLayer var _active_arena: CombatArena = null @@ -5,77 +14,63 @@ var _active_arena: CombatArena = null # Keep track of what music track was playing previously, and return to it once combat has finished. var _previous_music_track: AudioStream = null -@onready var _combat_containter: = $CenterContainer as CenterContainer +@onready var _combat_container: = $CenterContainer as CenterContainer +@onready var _transition_delay_timer: = $CenterContainer/TransitionDelay as Timer func _ready() -> void: FieldEvents.combat_triggered.connect(start) - - # TODO: remove with _unhandled input once Battlers have been implemented. - set_process_unhandled_input(false) - - -# TODO: This is included to allow leaving the combat state. -# In future releases, these signals will be emitted once battlers from one side have fallen. -func _unhandled_input(event: InputEvent) -> void: - if event.is_action_released("back"): - CombatEvents.did_player_win_last_combat = false - finish() - - elif event.is_action_released("interact"): - CombatEvents.did_player_win_last_combat = true - finish() +## Begin a combat. Takes a PackedScene as its only parameter, expecting it to be a CombatState object once +## instantiated. +## This is normally a response to [signal FieldEvents.combat_triggered]. func start(arena: PackedScene) -> void: - assert(not _active_arena, "Attempting to start a combat when one is ongoing!") - - # Cover the screen. + assert(_active_arena == null, "Attempting to start a combat while one is ongoing!") + await Transition.cover(0.2) - - # Try to setup the combat arena (which comes with AI battlers, etc.). + var new_arena: = arena.instantiate() - assert(new_arena is CombatArena, - "Failed to initiate combat. Provided 'arena' arugment is not a CombatArena.") - + assert( + new_arena != null, + "Failed to initiate combat. Provided 'arena' arugment is not a CombatArena." + ) + _active_arena = new_arena - _combat_containter.add_child(_active_arena) - + _combat_container.add_child(_active_arena) + + _active_arena.turn_queue.combat_finished.connect( + func on_combat_finished(is_player_victory: bool): + CombatEvents.did_player_win_last_combat = is_player_victory + + _transition_delay_timer.start() + await _transition_delay_timer.timeout + # Cover the screen again, transitioning away from the combat game state. + await Transition.cover(0.2) + + assert(_active_arena != null, "Combat finished but no active arena to clean up!") + _active_arena.queue_free() + _active_arena = null + + Music.play(_previous_music_track) + _previous_music_track = null + + # Whatever object started the combat will now be responsible for flow of the game. In + # particular, the screen is still covered, so the combat-starting object will want to decide + # what to do now that the outcome of the combat is known. + CombatEvents.combat_finished.emit() + ) + _previous_music_track = Music.get_playing_track() Music.play(_active_arena.music) - - # Let other systems know that the combat state is setup and ready to begin. + CombatEvents.combat_initiated.emit() - + # Before starting combat itself, reveal the screen again. # The Transition.clear() call is deferred since it follows on the heels of cover(), and needs a # frame to allow everything else to respond to Transition.finished. Transition.clear.call_deferred(0.2) await Transition.finished - - # TODO: remove with _unhandled input once Battlers have been implemented. - set_process_unhandled_input(true) - - -func finish() -> void: - # TODO: remove with _unhandled input once Battlers have been implemented. - set_process_unhandled_input(false) - - if not _active_arena: - return - - # Cover the screen again, transitioning away from the combat game state. - await Transition.cover(0.2) - - _active_arena.queue_free() - _active_arena = null - - Music.play(_previous_music_track) - _previous_music_track = null - - # Signal that the combat has been finished and all combat objects have been dealt with - # accordingly. The field game 'state' will now be in focus. - # Note that whatever object started the combat will now be responsible for flow of the game. In - # particular, the screen is still covered, so the combat-starting object will want to decide - # what to do now that the outcome of the combat is known. - CombatEvents.combat_finished.emit() + + # Begin the combat. The turn queue takes over from here. + _active_arena.turn_queue.is_active = true diff --git a/src/combat/combat_arena.gd b/src/combat/combat_arena.gd index 04f74055..31340cff 100644 --- a/src/combat/combat_arena.gd +++ b/src/combat/combat_arena.gd @@ -1,3 +1,7 @@ +## An arena is the background for a battle. It is a Control node that contains the battlers and the turn queue. +## It also contains the music that plays during the battle. class_name CombatArena extends Control @export var music: AudioStream + +@onready var turn_queue: ActiveTurnQueue = $Battlers diff --git a/src/combat/combat_arena.tscn b/src/combat/combat_arena.tscn index 658e2228..58585511 100644 --- a/src/combat/combat_arena.tscn +++ b/src/combat/combat_arena.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3 uid="uid://b3ciqydkjnkkx"] +[gd_scene load_steps=3 format=3 uid="uid://b3ciqydkjnkkx"] [ext_resource type="Script" path="res://src/combat/combat_arena.gd" id="1_iqdn5"] +[ext_resource type="Script" path="res://src/combat/active_turn_queue.gd" id="2_ll0ok"] [node name="CombatArena" type="Control"] custom_minimum_size = Vector2(1920, 1080) @@ -19,17 +20,9 @@ offset_bottom = 40.0 [node name="Battlers" type="Node2D" parent="."] y_sort_enabled = true +script = ExtResource("2_ll0ok") [node name="Foreground" type="Control" parent="."] anchors_preset = 0 offset_right = 40.0 offset_bottom = 40.0 - -[node name="InstructionsLabel" type="Label" parent="Foreground"] -layout_mode = 0 -offset_left = 890.0 -offset_top = 823.0 -offset_right = 1808.0 -offset_bottom = 970.0 -text = "Press 'ESCAPE' to lose combat. -Press 'SPACE' to win combat." diff --git a/src/combat/elements.gd b/src/combat/elements.gd new file mode 100644 index 00000000..f1fc6151 --- /dev/null +++ b/src/combat/elements.gd @@ -0,0 +1,15 @@ +class_name Elements extends RefCounted + +#enum Types { NONE, BUG, BREAK, SEEK, VOID, STATIC, CONTROL, GUARD, EXCEPTION } +enum Types { NONE, BUG, BREAK, SEEK } + +## Elements may have an advantage against others (attack power, chance-to-hit, etc.). These +## relationships are stored in the following dictionary. +## Dictionary values are the elements against which the dictionary key is strong. The weak elements +## are stored as a list. An empty list indicates that the element has no advantage against others. +const ADVANTAGES: = { + Types.NONE: [], + Types.BUG: [Types.BREAK], + Types.BREAK: [Types.NONE, Types.SEEK], + Types.SEEK: [Types.BUG], +} diff --git a/src/common/combat_events.gd b/src/common/combat_events.gd index da38a3dd..5e9171b5 100644 --- a/src/common/combat_events.gd +++ b/src/common/combat_events.gd @@ -3,11 +3,13 @@ extends Node ## Emitted whenever a combat has been setup and is ready to become the active 'game state'. At this ## point, the screen is fully covered by the [ScreenTransition] autoload. +@warning_ignore("unused_signal") signal combat_initiated(arena: PackedScene) ## Emitted whenever the player has finished with the combat state regardless of whether or not the ## combat was won by the player. At this point the screen has faded to black and any events that ## immediately follow the combat may occur. +@warning_ignore("unused_signal") signal combat_finished(is_player_victory: bool) diff --git a/src/common/field_events.gd b/src/common/field_events.gd index c72e85de..4d221365 100644 --- a/src/common/field_events.gd +++ b/src/common/field_events.gd @@ -6,20 +6,25 @@ extends Node const PROCESS_PRIORITY: = 99999999 ## Emitted when the cursor moves to a new position on the field gameboard. +@warning_ignore("unused_signal") signal cell_highlighted(cell: Vector2i) ## Emitted when the player selects a cell on the field gameboard via the [FieldCursor]. +@warning_ignore("unused_signal") signal cell_selected(cell: Vector2i) ## Emitted whenever a combat is triggered. This will lead to a transition from the field 'state' to ## a combat 'state'. +@warning_ignore("unused_signal") signal combat_triggered(arena: PackedScene) ## Emitted when a [Cutscene] begins, signalling that the player should yield control of their ## character to the cutscene code. +@warning_ignore("unused_signal") signal cutscene_began ## Emitted when a [Cutscene] ends, restoring normal mode of play. +@warning_ignore("unused_signal") signal cutscene_ended ## Gamepiece related signals, usually emitted by the the gamepieces themselves. @@ -27,13 +32,16 @@ signal gamepiece_cell_changed(gamepiece: Gamepiece, old_cell: Vector2i) ## Emitted when the player sets a movement path for their focused gamepiece. ## The destination is the last cell in the path. +@warning_ignore("unused_signal") signal player_path_set(gamepiece: Gamepiece, destination_cell: Vector2i) ## Emitted whenever terrain passability changes. Pathfinders will need to be rebuilt. +@warning_ignore("unused_signal") signal terrain_changed ## Emitted whenever ALL input within the field state is to be paused or resumed. ## Typically emitted by combat, dialogues, etc. +@warning_ignore("unused_signal") signal input_paused(is_paused: bool) # The physics engine updates a frame after physics object move, which plays havoc with our diff --git a/src/main.tscn b/src/main.tscn index 5a50e67e..0ed73c5c 100644 --- a/src/main.tscn +++ b/src/main.tscn @@ -23,7 +23,7 @@ [ext_resource type="Script" path="res://src/field/gameboard/debug_map_boundaries.gd" id="18_cqtg7"] [ext_resource type="PackedScene" uid="uid://ccm8tsjysf8b5" path="res://assets/characters/wizard_gfx.tscn" id="19_ius2d"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_name_label.gd" id="19_lpxfg"] -[ext_resource type="PackedScene" uid="uid://oot0x5n44b2r" path="res://src/field/cutscenes/interaction.tscn" id="19_sa6jd"] +[ext_resource type="PackedScene" uid="uid://oot0x5n44b2r" path="res://src/field/cutscenes/Interaction.tscn" id="19_sa6jd"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="20_g8d34"] [ext_resource type="Script" path="res://src/field/ui/dialogue_window.gd" id="20_lk3dv"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Character/node_portrait_container.gd" id="21_f53lt"] @@ -42,14 +42,14 @@ [ext_resource type="PackedScene" uid="uid://c8jtuge5yaqxa" path="res://src/field/cutscenes/templates/treasure_chests/chest.tscn" id="33_7djdg"] [ext_resource type="PackedScene" uid="uid://p0e4b0txynkd" path="res://src/field/cutscenes/templates/doors/door.tscn" id="33_lbtsh"] [ext_resource type="Texture2D" uid="uid://d2qfi5sncf72b" path="res://assets/terrain/town_tilemap.png" id="33_ofo2l"] -[ext_resource type="PackedScene" uid="uid://wivtmf75ic3f" path="res://maps/test_combat_arena.tscn" id="34_moieg"] +[ext_resource type="PackedScene" uid="uid://wivtmf75ic3f" path="res://maps/town/battles/test_combat_arena.tscn" id="34_moieg"] [ext_resource type="PackedScene" uid="uid://cubp81mykng3h" path="res://src/field/cutscenes/popups/interaction_popup.tscn" id="34_qs25x"] [ext_resource type="Script" path="res://src/field/ui/inventory/ui_inventory.gd" id="34_tk01t"] [ext_resource type="AudioStream" uid="uid://duwauer3vwtne" path="res://assets/music/Insect Factory.mp3" id="34_xbc6u"] [ext_resource type="Script" path="res://maps/town/door_unlock_interaction.gd" id="34_xtrml"] [ext_resource type="Resource" path="res://maps/town/sign.dtl" id="35_gv62s"] [ext_resource type="Script" path="res://maps/town/conversation_encounter.gd" id="35_i66wm"] -[ext_resource type="PackedScene" uid="uid://bq6b26pctmol4" path="res://maps/test_combat_arena2.tscn" id="35_x5y1m"] +[ext_resource type="PackedScene" uid="uid://bq6b26pctmol4" path="res://maps/town/battles/test_combat_arena2.tscn" id="35_x5y1m"] [ext_resource type="PackedScene" uid="uid://cgwailb13nkcd" path="res://assets/characters/ghost_gfx.tscn" id="36_7n3c5"] [ext_resource type="Resource" path="res://maps/town/encounter_before_combat.dtl" id="36_fu3a1"] [ext_resource type="PackedScene" uid="uid://bd624fi8r2avm" path="res://src/field/cutscenes/templates/area_transitions/area_transition.tscn" id="37_6xxks"] @@ -58,7 +58,7 @@ [ext_resource type="Resource" path="res://maps/town/encounter_on_loss.dtl" id="38_f1335"] [ext_resource type="PackedScene" uid="uid://ctuol1ff8u5e2" path="res://maps/house/wand_pedestal_interaction.tscn" id="38_n1c7a"] [ext_resource type="AudioStream" uid="uid://ohgp0pb24my4" path="res://assets/sfx/chop.ogg" id="39_02ti3"] -[ext_resource type="PackedScene" uid="uid://dpopnsfpfdasl" path="res://src/field/cutscenes/trigger.tscn" id="39_p2lth"] +[ext_resource type="PackedScene" uid="uid://dpopnsfpfdasl" path="res://src/field/cutscenes/Trigger.tscn" id="39_p2lth"] [ext_resource type="Script" path="res://maps/grove/game_end_trigger.gd" id="40_lduow"] [ext_resource type="AudioStream" uid="uid://ctnn0hqkx67k7" path="res://assets/music/Apple Cider.mp3" id="41_uxhyb"] [ext_resource type="AudioStream" uid="uid://c5w0ripyqfl68" path="res://assets/sfx/drop_002.ogg" id="42_bc1t5"] @@ -363,10 +363,9 @@ size = Vector2(15, 15) [node name="Main" type="Node2D"] -[node name="Field" type="Node2D" parent="." node_paths=PackedStringArray("opening_cutscene", "focused_game_piece")] +[node name="Field" type="Node2D" parent="." node_paths=PackedStringArray("focused_game_piece")] scale = Vector2(5, 5) script = ExtResource("2_bkxev") -opening_cutscene = NodePath("OpeningCutscene") focused_game_piece = NodePath("Terrain/Gamepieces/Player") gameboard = ExtResource("6_kd8tv") @@ -957,4 +956,8 @@ grow_vertical = 2 size_flags_horizontal = 4 metadata/_edit_lock_ = true +[node name="TransitionDelay" type="Timer" parent="Combat/CenterContainer"] +wait_time = 0.5 +one_shot = true + [connection signal="area_entered" from="Field/Terrain/Gamepieces/Grove/GameEndTrigger/Area2D2" to="Field/Terrain/Gamepieces/Grove/GameEndTrigger" method="_on_area_entered"]