diff --git a/.gitmodules b/.gitmodules
index 481cc20..b7d210e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
[submodule "godot-cpp"]
path = godot-cpp
url = https://github.com/godotengine/godot-cpp
- branch = 3.4
+ branch = 3.5
diff --git a/godot-cpp b/godot-cpp
index ced274f..867374d 160000
--- a/godot-cpp
+++ b/godot-cpp
@@ -1 +1 @@
-Subproject commit ced274fbe62c07dd9bb6791a77392f4bdc625152
+Subproject commit 867374da404887337909e8b7b9de5a8acbc47569
diff --git a/godot/Main.tscn b/godot/Main.tscn
index a9c8579..06271a1 100644
--- a/godot/Main.tscn
+++ b/godot/Main.tscn
@@ -4,5 +4,9 @@
[ext_resource path="res://levels/Level2.tscn" type="PackedScene" id=2]
[node name="Main" type="Node"]
+pause_mode = 2
script = ExtResource( 1 )
level = ExtResource( 2 )
+
+[node name="Level" type="Node" parent="."]
+pause_mode = 1
diff --git a/godot/assets/fonts/data/PixelOperator-Bold.tres b/godot/assets/fonts/data/PixelOperator-Bold.tres
new file mode 100644
index 0000000..4860407
--- /dev/null
+++ b/godot/assets/fonts/data/PixelOperator-Bold.tres
@@ -0,0 +1,4 @@
+[gd_resource type="DynamicFontData" format=2]
+
+[resource]
+font_path = "res://assets/fonts/ttf/PixelOperator8-Bold.ttf"
diff --git a/godot/assets/fonts/data/PixelOperator.tres b/godot/assets/fonts/data/PixelOperator.tres
new file mode 100644
index 0000000..8817659
--- /dev/null
+++ b/godot/assets/fonts/data/PixelOperator.tres
@@ -0,0 +1,4 @@
+[gd_resource type="DynamicFontData" format=2]
+
+[resource]
+font_path = "res://assets/fonts/ttf/PixelOperatorHB8.ttf"
diff --git a/godot/assets/fonts/ttf/PixelOperator-Bold.ttf b/godot/assets/fonts/ttf/PixelOperator-Bold.ttf
new file mode 100644
index 0000000..5f4d9f0
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperator-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c1963fa21043a357212e0a9e8ae1986bc375c1759e68bd3533c4f71b164614b0
+size 16984
diff --git a/godot/assets/fonts/ttf/PixelOperator.ttf b/godot/assets/fonts/ttf/PixelOperator.ttf
new file mode 100644
index 0000000..7eeeec2
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperator.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8d805274eaf227855147153182a96a86ff395ddbc7d2d378095af8831b764a3e
+size 17272
diff --git a/godot/assets/fonts/ttf/PixelOperator8-Bold.ttf b/godot/assets/fonts/ttf/PixelOperator8-Bold.ttf
new file mode 100644
index 0000000..3f95f88
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperator8-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b46a109cdac6c2f7acb4a57451dc3c8f7e2d391b871500270eadf139f0ac906e
+size 18624
diff --git a/godot/assets/fonts/ttf/PixelOperator8.ttf b/godot/assets/fonts/ttf/PixelOperator8.ttf
new file mode 100644
index 0000000..6dd6601
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperator8.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cccb9ef6cf18977b6e5721d49a1a6e78dd6a6f1c4f69537470f7dc1dc829ffc
+size 19944
diff --git a/godot/assets/fonts/ttf/PixelOperatorHB.ttf b/godot/assets/fonts/ttf/PixelOperatorHB.ttf
new file mode 100644
index 0000000..a141a96
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorHB.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fba7efcd1595f795e7ced3dd88bcac890a4fd00a3d7a2212ec0ba67f9d7bff49
+size 17204
diff --git a/godot/assets/fonts/ttf/PixelOperatorHB8.ttf b/godot/assets/fonts/ttf/PixelOperatorHB8.ttf
new file mode 100644
index 0000000..5ac3f34
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorHB8.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3569a5481727cc277be02ffa72348d40bf3ad9f03b9687b0da3b19fb717d00c8
+size 19076
diff --git a/godot/assets/fonts/ttf/PixelOperatorMono-Bold.ttf b/godot/assets/fonts/ttf/PixelOperatorMono-Bold.ttf
new file mode 100644
index 0000000..7c8738f
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorMono-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7b8a5f6cfd5fdb06ddac7d976935babf05afa5ef7bf6b8c1b8cf919b481d33e1
+size 16744
diff --git a/godot/assets/fonts/ttf/PixelOperatorMono.ttf b/godot/assets/fonts/ttf/PixelOperatorMono.ttf
new file mode 100644
index 0000000..ee4e92c
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorMono.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e171000f12d196e7a8a1a91c1a429ef3264a0ff007d5f62fa3dc44ef8df51226
+size 16972
diff --git a/godot/assets/fonts/ttf/PixelOperatorMono8-Bold.ttf b/godot/assets/fonts/ttf/PixelOperatorMono8-Bold.ttf
new file mode 100644
index 0000000..90af97f
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorMono8-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:11704bf2bddcde0996909166ad9d10a7ff38eb311a59cb07991f63e5f0185f76
+size 18624
diff --git a/godot/assets/fonts/ttf/PixelOperatorMono8.ttf b/godot/assets/fonts/ttf/PixelOperatorMono8.ttf
new file mode 100644
index 0000000..2fc70fa
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorMono8.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b627505e178c0c51b0ce7d432f2974fdc2064339f17fb5242b7237becad3c467
+size 19872
diff --git a/godot/assets/fonts/ttf/PixelOperatorMonoHB.ttf b/godot/assets/fonts/ttf/PixelOperatorMonoHB.ttf
new file mode 100644
index 0000000..11d13d2
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorMonoHB.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ae7eda40c2fb15e3672a79dee3860d13ab62b3e25f0cccee770b3f45b84c4955
+size 17120
diff --git a/godot/assets/fonts/ttf/PixelOperatorMonoHB8.ttf b/godot/assets/fonts/ttf/PixelOperatorMonoHB8.ttf
new file mode 100644
index 0000000..56b4a78
--- /dev/null
+++ b/godot/assets/fonts/ttf/PixelOperatorMonoHB8.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6cc07440ab17ad773fb2814c028d8fe70ecab835f2a577c081a3d226b256be92
+size 19020
diff --git a/godot/assets/music/level2.ogg b/godot/assets/music/level2.ogg
new file mode 100644
index 0000000..548241f
--- /dev/null
+++ b/godot/assets/music/level2.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7f80b1f828f132293790f730b899596631e3f5de834dca0bc66d4335ba648893
+size 1053717
diff --git a/godot/assets/music/level2.ogg.import b/godot/assets/music/level2.ogg.import
new file mode 100644
index 0000000..54354c0
--- /dev/null
+++ b/godot/assets/music/level2.ogg.import
@@ -0,0 +1,15 @@
+[remap]
+
+importer="ogg_vorbis"
+type="AudioStreamOGGVorbis"
+path="res://.import/level2.ogg-0cba17639fcb1c4e45dc4f01c5837760.oggstr"
+
+[deps]
+
+source_file="res://assets/music/level2.ogg"
+dest_files=[ "res://.import/level2.ogg-0cba17639fcb1c4e45dc4f01c5837760.oggstr" ]
+
+[params]
+
+loop=true
+loop_offset=0
diff --git a/godot/assets/sounds/coin.wav b/godot/assets/sounds/coin.wav
new file mode 100644
index 0000000..437d810
--- /dev/null
+++ b/godot/assets/sounds/coin.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3feda8ae12b154d33c41d286bfc8423acb5bd30027de8590b44179ccdaefe0f6
+size 132564
diff --git a/godot/assets/sounds/coin.wav.import b/godot/assets/sounds/coin.wav.import
new file mode 100644
index 0000000..f7e7100
--- /dev/null
+++ b/godot/assets/sounds/coin.wav.import
@@ -0,0 +1,23 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+path="res://.import/coin.wav-9081ee1c6d81d9c34d08bc916297b892.sample"
+
+[deps]
+
+source_file="res://assets/sounds/coin.wav"
+dest_files=[ "res://.import/coin.wav-9081ee1c6d81d9c34d08bc916297b892.sample" ]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/assets/sounds/died.wav b/godot/assets/sounds/died.wav
new file mode 100644
index 0000000..96f68c5
--- /dev/null
+++ b/godot/assets/sounds/died.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6ef38b521ccfa436c210c13ba14731b093fc1c6564da6e6d5a008c959c9cb517
+size 857132
diff --git a/godot/assets/sounds/died.wav.import b/godot/assets/sounds/died.wav.import
new file mode 100644
index 0000000..b92b919
--- /dev/null
+++ b/godot/assets/sounds/died.wav.import
@@ -0,0 +1,23 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+path="res://.import/died.wav-fc4528ad616bbce5115ae092cb80d346.sample"
+
+[deps]
+
+source_file="res://assets/sounds/died.wav"
+dest_files=[ "res://.import/died.wav-fc4528ad616bbce5115ae092cb80d346.sample" ]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/assets/sounds/gem.wav b/godot/assets/sounds/gem.wav
new file mode 100644
index 0000000..b4eb998
--- /dev/null
+++ b/godot/assets/sounds/gem.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ec7fa9358699bc8f9be0cb47c027efc2099803f5fc81d24aac066ae5f2b3a406
+size 145348
diff --git a/godot/assets/sounds/gem.wav.import b/godot/assets/sounds/gem.wav.import
new file mode 100644
index 0000000..60eb36b
--- /dev/null
+++ b/godot/assets/sounds/gem.wav.import
@@ -0,0 +1,23 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+path="res://.import/gem.wav-49535e5638777427fa416449f386aaf6.sample"
+
+[deps]
+
+source_file="res://assets/sounds/gem.wav"
+dest_files=[ "res://.import/gem.wav-49535e5638777427fa416449f386aaf6.sample" ]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/assets/sounds/jump.wav b/godot/assets/sounds/jump.wav
new file mode 100644
index 0000000..03982ea
--- /dev/null
+++ b/godot/assets/sounds/jump.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:147904084c9eaea71eae200292905b3adb41d862e0e09b5aab866cfb28ba4576
+size 176602
diff --git a/godot/assets/sounds/jump.wav.import b/godot/assets/sounds/jump.wav.import
new file mode 100644
index 0000000..f8e1faf
--- /dev/null
+++ b/godot/assets/sounds/jump.wav.import
@@ -0,0 +1,23 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+path="res://.import/jump.wav-395b727cde98999423d5c020c9c3492f.sample"
+
+[deps]
+
+source_file="res://assets/sounds/jump.wav"
+dest_files=[ "res://.import/jump.wav-395b727cde98999423d5c020c9c3492f.sample" ]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/assets/sounds/ui_popup.wav b/godot/assets/sounds/ui_popup.wav
new file mode 100644
index 0000000..f00a22a
--- /dev/null
+++ b/godot/assets/sounds/ui_popup.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7af5be9759b98365633e251a6a0c72f4840975f141a9d57e40ea70ab4beebbc2
+size 51772
diff --git a/godot/assets/sounds/ui_popup.wav.import b/godot/assets/sounds/ui_popup.wav.import
new file mode 100644
index 0000000..679a364
--- /dev/null
+++ b/godot/assets/sounds/ui_popup.wav.import
@@ -0,0 +1,23 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+path="res://.import/ui_popup.wav-b70bc280bf50aa4e775c08111118c3ae.sample"
+
+[deps]
+
+source_file="res://assets/sounds/ui_popup.wav"
+dest_files=[ "res://.import/ui_popup.wav-b70bc280bf50aa4e775c08111118c3ae.sample" ]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/assets/sounds/ui_select.wav b/godot/assets/sounds/ui_select.wav
new file mode 100644
index 0000000..ec3580d
--- /dev/null
+++ b/godot/assets/sounds/ui_select.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e43e035f6953c9854eee64f2f45b8d7d7107a412b82bbbe625253e1103c1bbb1
+size 62564
diff --git a/godot/assets/sounds/ui_select.wav.import b/godot/assets/sounds/ui_select.wav.import
new file mode 100644
index 0000000..3e3d8c0
--- /dev/null
+++ b/godot/assets/sounds/ui_select.wav.import
@@ -0,0 +1,23 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+path="res://.import/ui_select.wav-a1e6656879b8474ab79f3a15e3073001.sample"
+
+[deps]
+
+source_file="res://assets/sounds/ui_select.wav"
+dest_files=[ "res://.import/ui_select.wav-a1e6656879b8474ab79f3a15e3073001.sample" ]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/characters/enemies/WalkingEnemy.gd b/godot/characters/enemies/WalkingEnemy.gd
new file mode 100644
index 0000000..c1c54b5
--- /dev/null
+++ b/godot/characters/enemies/WalkingEnemy.gd
@@ -0,0 +1,38 @@
+extends KinematicBody2D
+
+
+signal player_touched
+
+
+var velocity = Vector2()
+export var direction = -1
+export var detect_edges = true
+export var speed = 25
+export var gravity = 9.8
+
+
+func _ready() -> void:
+ if direction == 1:
+ $AnimatedSprite.flip_h = true
+ $FloorChecker.position.x = $CollisionShape2D.shape.get_extents().x * direction
+ $FloorChecker.enabled = detect_edges
+
+
+func _physics_process(_delta: float) -> void:
+ if is_on_wall() or not $FloorChecker.is_colliding() and is_on_floor() and $FloorChecker.enabled:
+ direction *= -1
+ $AnimatedSprite.flip_h = not $AnimatedSprite.flip_h
+ $FloorChecker.position.x = $CollisionShape2D.shape.get_extents().x * direction
+
+ velocity.y += gravity
+ velocity.x = speed * direction
+ velocity = move_and_slide(velocity, Vector2.UP)
+
+ for i in get_slide_count():
+ var collision = get_slide_collision(i)
+ if collision.collider.name == "Player":
+ emit_signal("player_touched")
+
+
+func squash() -> void:
+ queue_free()
diff --git a/godot/characters/enemies/blightwing/Blightwing.gd b/godot/characters/enemies/blightwing/Blightwing.gd
new file mode 100644
index 0000000..6186bea
--- /dev/null
+++ b/godot/characters/enemies/blightwing/Blightwing.gd
@@ -0,0 +1,82 @@
+extends KinematicBody2D
+
+
+signal player_touched
+
+
+export var direction = -1
+export var speed = 50
+export var follow_path = false
+var target: Vector2 = Vector2(0, 0)
+var target_id = 0
+var start_position = Vector2()
+
+
+func _ready() -> void:
+ start_position = position
+ if follow_path:
+ if $Path.get_child_count() == 0:
+ follow_path = false
+ else:
+ target = $Path.get_child(0).position
+ if direction == 1:
+ $AnimatedSprite.flip_h = true
+
+
+func _physics_process(delta: float) -> void:
+ if $LeftWallChecker.is_colliding():
+ wall_checker_collided($LeftWallChecker)
+ elif $RightWallChecker.is_colliding():
+ wall_checker_collided($RightWallChecker)
+
+ if not follow_path:
+ var target_position = position
+ target_position.x *= 2 * direction
+ position = position.move_toward(target_position, round(speed * delta))
+
+ var velocity = get_velocity_towards_target(delta)
+ var collision = move_and_collide(velocity, true, true, true)
+ if collision and collision.collider.name != "Player":
+ var _collision = move_and_collide(velocity)
+ else:
+ var velocity = get_velocity_towards_target(delta)
+
+ var collision = move_and_collide(velocity, true, true, true)
+ if collision and collision.collider.name != "Player":
+ var _collision = move_and_collide(velocity)
+ else:
+ position = position.move_toward(start_position + target, round(speed * delta))
+
+ if position == start_position + target:
+ if $Path.get_child_count() - 1 == target_id:
+ target_id = -1
+ target = $Path.position
+ else:
+ target_id += 1
+ target = $Path.get_child(target_id).position
+
+ if start_position.x + target.x > position.x:
+ $AnimatedSprite.flip_h = true
+ elif start_position.x + target.x < position.x:
+ $AnimatedSprite.flip_h = false
+
+
+func get_velocity_towards_target(delta: float) -> Vector2:
+ var velocity = Vector2(0, 0)
+ if start_position.x + target.x > position.x:
+ velocity.x = speed * delta
+ elif start_position.x + target.x < position.x:
+ velocity.x = -speed * delta
+
+ if start_position.y + target.y > position.y:
+ velocity.y = speed * delta
+ elif start_position.y + target.y < position.y:
+ velocity.y = -speed * delta
+ return velocity
+
+
+func wall_checker_collided(wall_checker: RayCast2D) -> void:
+ if wall_checker.get_collider().name == "Player":
+ emit_signal("player_touched")
+ direction *= -1
+ $AnimatedSprite.flip_h = not $AnimatedSprite.flip_h
diff --git a/godot/characters/enemies/blightwing/Blightwing.tscn b/godot/characters/enemies/blightwing/Blightwing.tscn
new file mode 100644
index 0000000..4173207
--- /dev/null
+++ b/godot/characters/enemies/blightwing/Blightwing.tscn
@@ -0,0 +1,55 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+[ext_resource path="res://characters/enemies/blightwing/Blightwing.gd" type="Script" id=2]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 144, 48, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=2]
+atlas = ExtResource( 1 )
+region = Rect2( 168, 48, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=3]
+atlas = ExtResource( 1 )
+region = Rect2( 192, 48, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=4]
+animations = [ {
+"frames": [ SubResource( 1 ), SubResource( 2 ), SubResource( 3 ), SubResource( 2 ) ],
+"loop": true,
+"name": "fly",
+"speed": 6.0
+} ]
+
+[sub_resource type="RectangleShape2D" id=5]
+extents = Vector2( 4.5, 5 )
+
+[node name="Blightwing" type="KinematicBody2D" groups=["enemy", "rideable"]]
+collision_layer = 8
+collision_mask = 11
+script = ExtResource( 2 )
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 4 )
+animation = "fly"
+frame = 3
+playing = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2( 1.66894e-06, 1.5 )
+rotation = 1.5708
+shape = SubResource( 5 )
+
+[node name="LeftWallChecker" type="RayCast2D" parent="."]
+rotation = 1.5708
+enabled = true
+cast_to = Vector2( 0, 10 )
+collision_mask = 11
+
+[node name="RightWallChecker" type="RayCast2D" parent="."]
+rotation = -1.5708
+enabled = true
+cast_to = Vector2( 0, 10 )
+collision_mask = 11
diff --git a/godot/characters/enemies/blockface/Blockface.gd b/godot/characters/enemies/blockface/Blockface.gd
new file mode 100644
index 0000000..b307882
--- /dev/null
+++ b/godot/characters/enemies/blockface/Blockface.gd
@@ -0,0 +1,27 @@
+extends KinematicBody2D
+
+
+signal player_touched
+
+
+onready var start_position = position
+var velocity = Vector2()
+export var speed = 50.0
+export var fall_speed = 75.0
+var return_to_start: bool = false
+
+
+func _physics_process(delta: float) -> void:
+ if return_to_start:
+ position = position.move_toward(start_position, speed * delta)
+ else:
+ var collision = move_and_collide(Vector2(0, (position.y + fall_speed) * delta))
+ if collision:
+ return_to_start = true
+ $AnimatedSprite.play("normal")
+ if collision.collider.name == "Player":
+ emit_signal("player_touched")
+
+ if position.y <= start_position.y:
+ return_to_start = false
+ $AnimatedSprite.play("angry")
diff --git a/godot/characters/enemies/blockface/Blockface.tscn b/godot/characters/enemies/blockface/Blockface.tscn
new file mode 100644
index 0000000..afd9d0d
--- /dev/null
+++ b/godot/characters/enemies/blockface/Blockface.tscn
@@ -0,0 +1,40 @@
+[gd_scene load_steps=7 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+[ext_resource path="res://characters/enemies/blockface/Blockface.gd" type="Script" id=2]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 96, 24, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=2]
+atlas = ExtResource( 1 )
+region = Rect2( 120, 24, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=3]
+animations = [ {
+"frames": [ SubResource( 1 ) ],
+"loop": true,
+"name": "normal",
+"speed": 5.0
+}, {
+"frames": [ SubResource( 2 ) ],
+"loop": true,
+"name": "angry",
+"speed": 5.0
+} ]
+
+[sub_resource type="RectangleShape2D" id=4]
+extents = Vector2( 9, 9 )
+
+[node name="Blockface" type="KinematicBody2D" groups=["enemy", "rideable"]]
+collision_layer = 8
+collision_mask = 11
+script = ExtResource( 2 )
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 3 )
+animation = "normal"
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource( 4 )
diff --git a/godot/characters/enemies/dreadtooth/Dreadtooth.gd b/godot/characters/enemies/dreadtooth/Dreadtooth.gd
new file mode 100644
index 0000000..32e2fdf
--- /dev/null
+++ b/godot/characters/enemies/dreadtooth/Dreadtooth.gd
@@ -0,0 +1 @@
+extends "res://characters/enemies/WalkingEnemy.gd"
diff --git a/godot/characters/enemies/dreadtooth/Dreadtooth.tscn b/godot/characters/enemies/dreadtooth/Dreadtooth.tscn
new file mode 100644
index 0000000..0315c52
--- /dev/null
+++ b/godot/characters/enemies/dreadtooth/Dreadtooth.tscn
@@ -0,0 +1,55 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+[ext_resource path="res://characters/enemies/dreadtooth/Dreadtooth.gd" type="Script" id=2]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 192, 24, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=2]
+atlas = ExtResource( 1 )
+region = Rect2( 144, 24, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=3]
+atlas = ExtResource( 1 )
+region = Rect2( 168, 24, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=4]
+animations = [ {
+"frames": [ SubResource( 1 ) ],
+"loop": true,
+"name": "hide",
+"speed": 5.0
+}, {
+"frames": [ SubResource( 2 ), SubResource( 3 ) ],
+"loop": true,
+"name": "walk",
+"speed": 5.0
+} ]
+
+[sub_resource type="RectangleShape2D" id=5]
+extents = Vector2( 7.5, 6.5 )
+
+[node name="Dreadtooth" type="KinematicBody2D" groups=["enemy"]]
+collision_layer = 8
+collision_mask = 11
+script = ExtResource( 2 )
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 4 )
+animation = "walk"
+frame = 1
+playing = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2( -0.5, 5.5 )
+shape = SubResource( 5 )
+
+[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."]
+polygon = PoolVector2Array( 0, -7, 3, -4, 3, -1, -4, -1, -4, -4, -1, -7 )
+
+[node name="FloorChecker" type="RayCast2D" parent="."]
+enabled = true
+cast_to = Vector2( 0, 20 )
+collision_mask = 10
diff --git a/godot/characters/enemies/gravevine/Gravevine.tscn b/godot/characters/enemies/gravevine/Gravevine.tscn
new file mode 100644
index 0000000..386a83f
--- /dev/null
+++ b/godot/characters/enemies/gravevine/Gravevine.tscn
@@ -0,0 +1,35 @@
+[gd_scene load_steps=6 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 48, 24, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=2]
+atlas = ExtResource( 1 )
+region = Rect2( 72, 24, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=3]
+animations = [ {
+"frames": [ SubResource( 1 ), SubResource( 2 ) ],
+"loop": true,
+"name": "default",
+"speed": 5.0
+} ]
+
+[sub_resource type="RectangleShape2D" id=4]
+extents = Vector2( 5.5, 7.5 )
+
+[node name="Gravevine" type="KinematicBody2D" groups=["enemy"]]
+collision_layer = 8
+collision_mask = 11
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 3 )
+frame = 1
+playing = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2( -0.5, 0.5 )
+shape = SubResource( 4 )
diff --git a/godot/characters/enemies/shelly/Shelly.gd b/godot/characters/enemies/shelly/Shelly.gd
new file mode 100644
index 0000000..32e2fdf
--- /dev/null
+++ b/godot/characters/enemies/shelly/Shelly.gd
@@ -0,0 +1 @@
+extends "res://characters/enemies/WalkingEnemy.gd"
diff --git a/godot/characters/enemies/shelly/Shelly.tscn b/godot/characters/enemies/shelly/Shelly.tscn
new file mode 100644
index 0000000..0b23ae6
--- /dev/null
+++ b/godot/characters/enemies/shelly/Shelly.tscn
@@ -0,0 +1,51 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+[ext_resource path="res://characters/enemies/shelly/Shelly.gd" type="Script" id=2]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 48, 48, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=2]
+atlas = ExtResource( 1 )
+region = Rect2( 0, 48, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=3]
+atlas = ExtResource( 1 )
+region = Rect2( 24, 48, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=4]
+animations = [ {
+"frames": [ SubResource( 1 ) ],
+"loop": true,
+"name": "hide",
+"speed": 5.0
+}, {
+"frames": [ SubResource( 2 ), SubResource( 3 ) ],
+"loop": true,
+"name": "walk",
+"speed": 3.0
+} ]
+
+[sub_resource type="RectangleShape2D" id=5]
+extents = Vector2( 7.5, 6.5 )
+
+[node name="Shelly" type="KinematicBody2D" groups=["enemy", "squashable"]]
+collision_layer = 8
+collision_mask = 11
+script = ExtResource( 2 )
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 4 )
+animation = "walk"
+playing = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2( 1, 5.5 )
+shape = SubResource( 5 )
+
+[node name="FloorChecker" type="RayCast2D" parent="."]
+enabled = true
+cast_to = Vector2( 0, 20 )
+collision_mask = 10
diff --git a/godot/characters/enemies/spikeball/Spikeball.tscn b/godot/characters/enemies/spikeball/Spikeball.tscn
new file mode 100644
index 0000000..ce2caad
--- /dev/null
+++ b/godot/characters/enemies/spikeball/Spikeball.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 192, 0, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=2]
+animations = [ {
+"frames": [ SubResource( 1 ) ],
+"loop": true,
+"name": "default",
+"speed": 5.0
+} ]
+
+[sub_resource type="CircleShape2D" id=3]
+radius = 9.05539
+
+[node name="Spikeball" type="KinematicBody2D" groups=["enemy"]]
+collision_layer = 8
+collision_mask = 11
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 2 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource( 3 )
diff --git a/godot/characters/enemies/super_shelly/SuperShelly.gd b/godot/characters/enemies/super_shelly/SuperShelly.gd
new file mode 100644
index 0000000..32e2fdf
--- /dev/null
+++ b/godot/characters/enemies/super_shelly/SuperShelly.gd
@@ -0,0 +1 @@
+extends "res://characters/enemies/WalkingEnemy.gd"
diff --git a/godot/characters/enemies/super_shelly/SuperShelly.tscn b/godot/characters/enemies/super_shelly/SuperShelly.tscn
new file mode 100644
index 0000000..a7259db
--- /dev/null
+++ b/godot/characters/enemies/super_shelly/SuperShelly.tscn
@@ -0,0 +1,52 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://assets/characters/characters.png" type="Texture" id=1]
+[ext_resource path="res://characters/enemies/super_shelly/SuperShelly.gd" type="Script" id=2]
+
+[sub_resource type="AtlasTexture" id=1]
+atlas = ExtResource( 1 )
+region = Rect2( 120, 48, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=2]
+atlas = ExtResource( 1 )
+region = Rect2( 72, 48, 24, 24 )
+
+[sub_resource type="AtlasTexture" id=3]
+atlas = ExtResource( 1 )
+region = Rect2( 96, 48, 24, 24 )
+
+[sub_resource type="SpriteFrames" id=4]
+animations = [ {
+"frames": [ SubResource( 1 ) ],
+"loop": true,
+"name": "hide",
+"speed": 5.0
+}, {
+"frames": [ SubResource( 2 ), SubResource( 3 ) ],
+"loop": true,
+"name": "walk",
+"speed": 3.0
+} ]
+
+[sub_resource type="RectangleShape2D" id=5]
+extents = Vector2( 12, 10.5 )
+
+[node name="SuperShelly" type="KinematicBody2D" groups=["enemy", "squashable"]]
+collision_layer = 8
+collision_mask = 11
+script = ExtResource( 2 )
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
+frames = SubResource( 4 )
+animation = "walk"
+frame = 1
+playing = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2( 0, 1.5 )
+shape = SubResource( 5 )
+
+[node name="FloorChecker" type="RayCast2D" parent="."]
+enabled = true
+cast_to = Vector2( 0, 20 )
+collision_mask = 10
diff --git a/godot/characters/player/Player.tscn b/godot/characters/player/Player.tscn
index 858efb3..89b38d9 100644
--- a/godot/characters/player/Player.tscn
+++ b/godot/characters/player/Player.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=9 format=2]
+[gd_scene load_steps=10 format=2]
[ext_resource path="res://characters/player/sprites/green.tres" type="SpriteFrames" id=1]
[ext_resource path="res://characters/player/states/Idle.gdns" type="Script" id=2]
@@ -7,12 +7,13 @@
[ext_resource path="res://characters/player/Player.gdns" type="Script" id=5]
[ext_resource path="res://characters/player/states/Jump.gdns" type="Script" id=6]
[ext_resource path="res://characters/player/states/Fall.gdns" type="Script" id=7]
+[ext_resource path="res://assets/sounds/jump.wav" type="AudioStream" id=8]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 7, 12 )
[node name="Player" type="KinematicBody2D"]
-collision_mask = 2
+collision_mask = 10
script = ExtResource( 5 )
[node name="AnimatedSprite" type="AnimatedSprite" parent="."]
@@ -40,3 +41,14 @@ script = ExtResource( 6 )
[node name="Fall" type="Node" parent="StateMachine"]
script = ExtResource( 7 )
+
+[node name="PlatformDetector" type="RayCast2D" parent="."]
+position = Vector2( 12, 0 )
+enabled = true
+cast_to = Vector2( 0, 32 )
+collision_mask = 24
+
+[node name="Sounds" type="Node" parent="."]
+
+[node name="Jump" type="AudioStreamPlayer" parent="Sounds"]
+stream = ExtResource( 8 )
diff --git a/godot/levels/Level2.tscn b/godot/levels/Level2.tscn
index 717c1fe..811360b 100644
--- a/godot/levels/Level2.tscn
+++ b/godot/levels/Level2.tscn
@@ -1,9 +1,11 @@
-[gd_scene load_steps=5 format=2]
+[gd_scene load_steps=7 format=2]
[ext_resource path="res://CameraLimit.gdns" type="Script" id=1]
[ext_resource path="res://characters/player/Player.tscn" type="PackedScene" id=2]
[ext_resource path="res://assets/backgrounds/hills.png" type="Texture" id=3]
[ext_resource path="res://levels/Level2.tmx" type="PackedScene" id=4]
+[ext_resource path="res://assets/music/level2.ogg" type="AudioStream" id=5]
+[ext_resource path="res://levels/MusicPlayer.gd" type="Script" id=6]
[node name="Level2" type="Node2D"]
@@ -39,3 +41,8 @@ centered = false
[node name="Level2" parent="Map" instance=ExtResource( 4 )]
script = ExtResource( 1 )
+
+[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
+pause_mode = 2
+stream = ExtResource( 5 )
+script = ExtResource( 6 )
diff --git a/godot/levels/MusicPlayer.gd b/godot/levels/MusicPlayer.gd
new file mode 100644
index 0000000..ab397b1
--- /dev/null
+++ b/godot/levels/MusicPlayer.gd
@@ -0,0 +1,16 @@
+extends AudioStreamPlayer
+
+
+# there is currently a bug with AudioStreamPlayer
+# it doesn't pause when the tree is paused
+# so let's manually check if paused and pause the audio
+# for this to work pause mode for the node is set to process instead of inherit
+
+
+func _physics_process(_delta: float) -> void:
+ if get_tree().paused and playing:
+ stop()
+ playing = false
+ elif not get_tree().paused and not playing:
+ play()
+ playing = true
diff --git a/godot/levels/PrototypeEnemies.tmx b/godot/levels/PrototypeEnemies.tmx
new file mode 100644
index 0000000..34a536c
--- /dev/null
+++ b/godot/levels/PrototypeEnemies.tmx
@@ -0,0 +1,40 @@
+
+
diff --git a/godot/levels/PrototypeEnemies.tmx.import b/godot/levels/PrototypeEnemies.tmx.import
new file mode 100644
index 0000000..2113587
--- /dev/null
+++ b/godot/levels/PrototypeEnemies.tmx.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="vnen.tiled_importer"
+type="PackedScene"
+path="res://.import/PrototypeEnemies.tmx-5728a1d35b5e3ba88a2df2406b1cd931.scn"
+
+[deps]
+
+source_file="res://levels/PrototypeEnemies.tmx"
+dest_files=[ "res://.import/PrototypeEnemies.tmx-5728a1d35b5e3ba88a2df2406b1cd931.scn" ]
+
+[params]
+
+custom_properties=true
+tile_metadata=false
+uv_clip=false
+y_sort=false
+image_flags=0
+collision_layer=2
+collision_mask=0
+embed_internal_images=false
+save_tiled_properties=false
+add_background=true
+post_import_script=""
diff --git a/godot/levels/PrototypeEnemies.tscn b/godot/levels/PrototypeEnemies.tscn
new file mode 100644
index 0000000..d53033f
--- /dev/null
+++ b/godot/levels/PrototypeEnemies.tscn
@@ -0,0 +1,98 @@
+[gd_scene load_steps=12 format=2]
+
+[ext_resource path="res://CameraLimit.gdns" type="Script" id=1]
+[ext_resource path="res://characters/player/Player.tscn" type="PackedScene" id=2]
+[ext_resource path="res://levels/PrototypeEnemies.tmx" type="PackedScene" id=3]
+[ext_resource path="res://assets/backgrounds/mountains.png" type="Texture" id=4]
+[ext_resource path="res://characters/enemies/gravevine/Gravevine.tscn" type="PackedScene" id=5]
+[ext_resource path="res://characters/enemies/dreadtooth/Dreadtooth.tscn" type="PackedScene" id=6]
+[ext_resource path="res://characters/enemies/super_shelly/SuperShelly.tscn" type="PackedScene" id=7]
+[ext_resource path="res://characters/enemies/spikeball/Spikeball.tscn" type="PackedScene" id=8]
+[ext_resource path="res://characters/enemies/shelly/Shelly.tscn" type="PackedScene" id=9]
+[ext_resource path="res://characters/enemies/blockface/Blockface.tscn" type="PackedScene" id=10]
+[ext_resource path="res://characters/enemies/blightwing/Blightwing.tscn" type="PackedScene" id=11]
+
+[node name="PrototypeEnemies" type="Node"]
+
+[node name="Player" parent="." instance=ExtResource( 2 )]
+position = Vector2( 156, 438 )
+collision/safe_margin = 0.12
+
+[node name="Camera2D" type="Camera2D" parent="Player"]
+current = true
+limit_left = 0
+limit_top = 0
+limit_right = 512
+limit_bottom = 288
+drag_margin_h_enabled = true
+drag_margin_v_enabled = true
+__meta__ = {
+"_edit_bone_": true
+}
+
+[node name="VisibilityNotifier2D" type="VisibilityNotifier2D" parent="Player/Camera2D"]
+rect = Rect2( 0, 0, 24, 24 )
+
+[node name="ParallaxBackground" type="ParallaxBackground" parent="."]
+
+[node name="ParallaxLayer" type="ParallaxLayer" parent="ParallaxBackground"]
+motion_scale = Vector2( 0.2, 0.1 )
+motion_offset = Vector2( 0, -288 )
+motion_mirroring = Vector2( 528, 0 )
+
+[node name="Sprite" type="Sprite" parent="ParallaxBackground/ParallaxLayer"]
+texture = ExtResource( 4 )
+centered = false
+
+[node name="Map" type="Node" parent="."]
+
+[node name="PrototypeEnemies" parent="Map" instance=ExtResource( 3 )]
+script = ExtResource( 1 )
+
+[node name="Enemies" type="Node" parent="."]
+unique_name_in_owner = true
+
+[node name="Spikeball" parent="Enemies" instance=ExtResource( 8 )]
+position = Vector2( 106, 547 )
+
+[node name="Blightwing" parent="Enemies" instance=ExtResource( 11 )]
+position = Vector2( 488, 400 )
+follow_path = true
+
+[node name="Path" type="Node2D" parent="Enemies/Blightwing"]
+
+[node name="Node2D" type="Node2D" parent="Enemies/Blightwing/Path"]
+position = Vector2( 0, 96 )
+
+[node name="Node2D2" type="Node2D" parent="Enemies/Blightwing/Path"]
+position = Vector2( -128, 96 )
+
+[node name="Node2D3" type="Node2D" parent="Enemies/Blightwing/Path"]
+position = Vector2( -128, 0 )
+
+[node name="Blockface" parent="Enemies" instance=ExtResource( 10 )]
+position = Vector2( 206, 404 )
+
+[node name="Dreadtooth" parent="Enemies" instance=ExtResource( 6 )]
+position = Vector2( 316, 379 )
+
+[node name="Dreadtooth2" parent="Enemies" instance=ExtResource( 6 )]
+position = Vector2( 350, 263 )
+direction = 1
+detect_edges = false
+
+[node name="Gravevine" parent="Enemies" instance=ExtResource( 5 )]
+position = Vector2( 250, 550 )
+
+[node name="Shelly" parent="Enemies" instance=ExtResource( 9 )]
+position = Vector2( 247, 439 )
+
+[node name="SuperShelly" parent="Enemies" instance=ExtResource( 7 )]
+position = Vector2( 62, 546 )
+
+[connection signal="player_touched" from="Enemies/Blightwing" to="Player" method="_on_player_touched"]
+[connection signal="player_touched" from="Enemies/Blockface" to="Player" method="_on_player_touched"]
+[connection signal="player_touched" from="Enemies/Dreadtooth" to="Player" method="_on_player_touched"]
+[connection signal="player_touched" from="Enemies/Dreadtooth2" to="Player" method="_on_player_touched"]
+[connection signal="player_touched" from="Enemies/Shelly" to="Player" method="_on_player_touched"]
+[connection signal="player_touched" from="Enemies/SuperShelly" to="Player" method="_on_player_touched"]
diff --git a/godot/monitor/EnterButton.gd b/godot/monitor/EnterButton.gd
new file mode 100644
index 0000000..de051dd
--- /dev/null
+++ b/godot/monitor/EnterButton.gd
@@ -0,0 +1,123 @@
+extends Button
+
+
+signal input_validated(player)
+
+
+func is_valid_name(name: String) -> bool:
+ if name.strip_edges() == "":
+ print_debug("Name is empty!")
+ return false
+ if name.split(" ").size() == 1:
+ print_debug("Doesn't contain at least a first and last name!")
+ return false
+ return true
+
+
+func is_valid_rut(rut: String) -> bool:
+ var rut_node = get_node("%Rut")
+ rut = rut_node.clean_rut(rut)
+ if rut.length() < 8 or rut.length() > 9:
+ print_debug("RUT length is invalid!")
+ return false
+
+ var rut_temp: String = rut.substr(0, rut.length() - 1)
+ var verifier: String = rut.substr(rut.length() - 1, 1)
+
+ if not rut_temp.is_valid_integer():
+ print_debug("RUT isn't a valid integer!")
+ return false
+
+ if rut_temp.to_int() > 50000000:
+ print_debug("RUT is too large, that is a company!")
+ return false
+
+ if verifier != generate_verifier(rut_temp):
+ return false
+
+ return true
+
+
+func generate_verifier(rut: String) -> String:
+ if not rut.is_valid_integer():
+ print_debug("RUT isn't a valid integer!")
+ return ""
+
+ var multiplier: int = 2
+ var sum: int = 0
+ var remainder: int = 0
+ var division: int = 0
+ var rut_length: int = rut.length()
+
+ var i: int = rut_length - 1
+ while i >= 0:
+ sum = sum + rut.substr(i, 1).to_int() * multiplier
+ multiplier = multiplier + 1
+ if multiplier == 8:
+ multiplier = 2
+ i = i - 1
+
+ var tempSum: float = sum
+ division = int(floor(tempSum / 11))
+ division = division * 11
+ remainder = sum - division
+
+ if remainder != 0:
+ remainder = 11 - remainder
+
+ if remainder == 10:
+ return "k"
+ else:
+ return String(remainder)
+
+
+func is_valid_email(email: String) -> bool:
+ var regex = RegEx.new()
+ regex.compile("\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+")
+ if regex.search(email) == null:
+ print_debug("Isn't a valid email address!")
+ return false
+ return true
+
+
+func _on_Button_pressed() -> void:
+ var name = get_node("%Name")
+ var rut = get_node("%Rut")
+ var email = get_node("%Email")
+ var player: Dictionary = {
+ "name" : name.text,
+ "rut" : get_node("%Rut").clean_rut(rut.text),
+ "email" : email.text
+ }
+
+ if not is_valid_name(player.name):
+ show_error_message("Ingresa un nombre completo valido por favor!")
+ return
+
+ if not is_valid_rut(player.rut):
+ show_error_message("Ingresa un RUT valido por favor!")
+ return
+
+ if not is_valid_email(player.email):
+ show_error_message("Ingresa un email valido por favor!")
+ return
+
+ emit_signal("input_validated", player)
+
+
+func show_error_message(message: String) -> void:
+ var popup = get_node("%PopupDialog")
+ popup.get_node("ErrorMessage").text = message
+ popup.popup()
+ popup.focus_mode = Control.FOCUS_ALL
+ popup.grab_focus()
+
+
+func _on_PopupDialog_gui_input(event: InputEvent) -> void:
+ var popup = get_node("%PopupDialog")
+ if popup.visible and event.is_pressed():
+ popup.call_deferred("hide")
+
+
+func _on_text_entered(_new_text: String) -> void:
+ call_deferred("_on_Button_pressed")
diff --git a/godot/monitor/GUI.tscn b/godot/monitor/GUI.tscn
new file mode 100644
index 0000000..c5d0203
--- /dev/null
+++ b/godot/monitor/GUI.tscn
@@ -0,0 +1,249 @@
+[gd_scene load_steps=18 format=2]
+
+[ext_resource path="res://assets/fonts/data/PixelOperator.tres" type="DynamicFontData" id=1]
+[ext_resource path="res://assets/fonts/data/PixelOperator-Bold.tres" type="DynamicFontData" id=2]
+[ext_resource path="res://monitor/Rut.gd" type="Script" id=3]
+[ext_resource path="res://monitor/UISounds.gd" type="Script" id=4]
+[ext_resource path="res://monitor/EnterButton.gd" type="Script" id=5]
+[ext_resource path="res://assets/sounds/ui_popup.wav" type="AudioStream" id=6]
+[ext_resource path="res://assets/sounds/ui_select.wav" type="AudioStream" id=7]
+[ext_resource path="res://monitor/MonitorGUI.gd" type="Script" id=8]
+
+[sub_resource type="DynamicFont" id=1]
+font_data = ExtResource( 1 )
+
+[sub_resource type="DynamicFont" id=14]
+size = 8
+extra_spacing_top = 4
+font_data = ExtResource( 1 )
+
+[sub_resource type="StyleBoxFlat" id=13]
+content_margin_left = 5.0
+content_margin_right = 5.0
+content_margin_top = 2.0
+content_margin_bottom = 2.0
+bg_color = Color( 0.313726, 0.290196, 0.290196, 0.752941 )
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="DynamicFont" id=11]
+size = 12
+extra_spacing_top = 2
+extra_spacing_bottom = 2
+extra_spacing_char = 2
+extra_spacing_space = 2
+font_data = ExtResource( 2 )
+
+[sub_resource type="StyleBoxFlat" id=12]
+content_margin_left = 5.0
+content_margin_right = 5.0
+content_margin_top = 5.0
+content_margin_bottom = 5.0
+bg_color = Color( 0.313726, 0.290196, 0.290196, 0.752941 )
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="InputEventAction" id=17]
+action = "ui_accept"
+pressed = true
+
+[sub_resource type="ShortCut" id=18]
+shortcut = SubResource( 17 )
+
+[sub_resource type="StyleBoxFlat" id=15]
+bg_color = Color( 0.239216, 0.239216, 0.239216, 1 )
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+expand_margin_left = 5.0
+expand_margin_right = 5.0
+expand_margin_top = 5.0
+expand_margin_bottom = 5.0
+
+[sub_resource type="DynamicFont" id=16]
+font_data = ExtResource( 2 )
+
+[node name="MonitorGUI" type="CanvasLayer"]
+pause_mode = 2
+script = ExtResource( 8 )
+
+[node name="ColorRect" type="ColorRect" parent="."]
+margin_right = 40.0
+margin_bottom = 40.0
+rect_min_size = Vector2( 512, 288 )
+color = Color( 0.0117647, 0.00784314, 0.00784314, 0.376471 )
+
+[node name="GUI" type="Control" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+rect_min_size = Vector2( 512, 0 )
+
+[node name="Sounds" type="Node" parent="GUI"]
+script = ExtResource( 4 )
+
+[node name="Popup" type="AudioStreamPlayer" parent="GUI/Sounds"]
+stream = ExtResource( 6 )
+
+[node name="Menu" type="AudioStreamPlayer" parent="GUI/Sounds"]
+stream = ExtResource( 7 )
+
+[node name="VBoxContainer" type="VBoxContainer" parent="GUI"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+custom_constants/separation = 0
+
+[node name="CenterContainer" type="CenterContainer" parent="GUI/VBoxContainer"]
+margin_right = 512.0
+margin_bottom = 96.0
+rect_min_size = Vector2( 0, 96 )
+size_flags_horizontal = 3
+
+[node name="Label" type="Label" parent="GUI/VBoxContainer/CenterContainer"]
+margin_top = 30.0
+margin_right = 512.0
+margin_bottom = 65.0
+rect_min_size = Vector2( 512, 0 )
+custom_fonts/font = SubResource( 1 )
+text = "Ingresa su nombre completo, RUT y email por favor!"
+align = 1
+autowrap = true
+
+[node name="CenterContainer2" type="CenterContainer" parent="GUI/VBoxContainer"]
+margin_top = 96.0
+margin_right = 512.0
+margin_bottom = 192.0
+rect_min_size = Vector2( 0, 96 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="GUI/VBoxContainer/CenterContainer2"]
+margin_left = 16.0
+margin_top = 14.0
+margin_right = 496.0
+margin_bottom = 82.0
+rect_min_size = Vector2( 480, 0 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Name" type="LineEdit" parent="GUI/VBoxContainer/CenterContainer2/VBoxContainer2"]
+unique_name_in_owner = true
+margin_right = 480.0
+margin_bottom = 20.0
+focus_neighbour_left = NodePath("../../../CenterContainer3/Button")
+focus_neighbour_top = NodePath("../../../CenterContainer3/Button")
+focus_neighbour_right = NodePath("../Rut")
+focus_neighbour_bottom = NodePath("../Rut")
+focus_next = NodePath("../Rut")
+focus_previous = NodePath("../../../CenterContainer3/Button")
+custom_fonts/font = SubResource( 14 )
+custom_styles/normal = SubResource( 13 )
+max_length = 256
+context_menu_enabled = false
+clear_button_enabled = true
+placeholder_text = "Nombre Completo"
+caret_blink = true
+
+[node name="Rut" type="LineEdit" parent="GUI/VBoxContainer/CenterContainer2/VBoxContainer2"]
+unique_name_in_owner = true
+margin_top = 24.0
+margin_right = 480.0
+margin_bottom = 44.0
+focus_neighbour_left = NodePath("../Name")
+focus_neighbour_top = NodePath("../Name")
+focus_neighbour_right = NodePath("../Email")
+focus_neighbour_bottom = NodePath("../Email")
+focus_next = NodePath("../Email")
+focus_previous = NodePath("../Name")
+custom_fonts/font = SubResource( 14 )
+custom_styles/normal = SubResource( 13 )
+max_length = 12
+context_menu_enabled = false
+clear_button_enabled = true
+placeholder_text = "RUT"
+caret_blink = true
+script = ExtResource( 3 )
+
+[node name="Email" type="LineEdit" parent="GUI/VBoxContainer/CenterContainer2/VBoxContainer2"]
+unique_name_in_owner = true
+margin_top = 48.0
+margin_right = 480.0
+margin_bottom = 68.0
+focus_neighbour_left = NodePath("../Rut")
+focus_neighbour_top = NodePath("../Rut")
+focus_neighbour_right = NodePath("../../../CenterContainer3/Button")
+focus_neighbour_bottom = NodePath("../../../CenterContainer3/Button")
+focus_next = NodePath("../../../CenterContainer3/Button")
+focus_previous = NodePath("../Rut")
+custom_fonts/font = SubResource( 14 )
+custom_styles/normal = SubResource( 13 )
+max_length = 256
+context_menu_enabled = false
+clear_button_enabled = true
+placeholder_text = "Email"
+caret_blink = true
+
+[node name="CenterContainer3" type="CenterContainer" parent="GUI/VBoxContainer"]
+margin_top = 192.0
+margin_right = 512.0
+margin_bottom = 288.0
+rect_min_size = Vector2( 0, 96 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Button" type="Button" parent="GUI/VBoxContainer/CenterContainer3"]
+margin_left = 197.0
+margin_top = 34.0
+margin_right = 314.0
+margin_bottom = 61.0
+focus_neighbour_left = NodePath("../../CenterContainer2/VBoxContainer2/Email")
+focus_neighbour_top = NodePath("../../CenterContainer2/VBoxContainer2/Email")
+focus_neighbour_right = NodePath("../../CenterContainer2/VBoxContainer2/Name")
+focus_neighbour_bottom = NodePath("../../CenterContainer2/VBoxContainer2/Name")
+focus_next = NodePath("../../CenterContainer2/VBoxContainer2/Name")
+focus_previous = NodePath("../../CenterContainer2/VBoxContainer2/Email")
+custom_fonts/font = SubResource( 11 )
+custom_styles/normal = SubResource( 12 )
+shortcut_in_tooltip = false
+shortcut = SubResource( 18 )
+text = "Ingresar"
+script = ExtResource( 5 )
+
+[node name="PopupDialog" type="PopupDialog" parent="."]
+unique_name_in_owner = true
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 96.0
+margin_top = 96.0
+margin_right = -96.0
+margin_bottom = -96.0
+custom_styles/panel = SubResource( 15 )
+
+[node name="ErrorMessage" type="Label" parent="PopupDialog"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 7
+custom_fonts/font = SubResource( 16 )
+text = "Error Message"
+align = 1
+valign = 1
+autowrap = true
+
+[connection signal="focus_entered" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Name" to="GUI/Sounds" method="_play_ui_select_sound"]
+[connection signal="text_entered" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Name" to="GUI/VBoxContainer/CenterContainer3/Button" method="_on_text_entered"]
+[connection signal="focus_entered" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Rut" to="GUI/Sounds" method="_play_ui_select_sound"]
+[connection signal="text_changed" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Rut" to="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Rut" method="_on_Rut_text_changed"]
+[connection signal="text_entered" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Rut" to="GUI/VBoxContainer/CenterContainer3/Button" method="_on_text_entered"]
+[connection signal="focus_entered" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Email" to="GUI/Sounds" method="_play_ui_select_sound"]
+[connection signal="text_entered" from="GUI/VBoxContainer/CenterContainer2/VBoxContainer2/Email" to="GUI/VBoxContainer/CenterContainer3/Button" method="_on_text_entered"]
+[connection signal="focus_entered" from="GUI/VBoxContainer/CenterContainer3/Button" to="GUI/Sounds" method="_play_ui_select_sound"]
+[connection signal="pressed" from="GUI/VBoxContainer/CenterContainer3/Button" to="GUI/VBoxContainer/CenterContainer3/Button" method="_on_Button_pressed"]
+[connection signal="focus_entered" from="PopupDialog" to="GUI/Sounds" method="_play_popup_sound"]
+[connection signal="gui_input" from="PopupDialog" to="GUI/VBoxContainer/CenterContainer3/Button" method="_on_PopupDialog_gui_input"]
diff --git a/godot/monitor/Monitor.gd b/godot/monitor/Monitor.gd
new file mode 100644
index 0000000..004c1f9
--- /dev/null
+++ b/godot/monitor/Monitor.gd
@@ -0,0 +1,227 @@
+extends Node
+
+
+signal monitor_loaded()
+
+
+export var monitor_enabled: bool = false
+export var development_url: String = "http://localhost:4050/api/v1"
+var url_real: String = "https://alai.cromer.cl/api/v1"
+export var use_development_url: bool = false
+onready var url: String = development_url if use_development_url else url_real
+
+var start_time: int = 0
+var started: bool = false
+
+var player: Dictionary = {}
+var os_id: int = 0
+var godot_version: Dictionary = Engine.get_version_info()
+var processor_count: int = OS.get_processor_count()
+var screen_count: int = OS.get_screen_count()
+var screen_dpi: int = OS.get_screen_dpi()
+var screen_size: Vector2 = OS.get_screen_size()
+var machine_id: String = OS.get_unique_id()
+var locale: String = OS.get_locale()
+var game_version: String
+var frames: Array = []
+
+var coins: int = 0
+var points: int = 0
+var objects: Array = []
+
+const empty_object: Dictionary = {
+ "name": "Object Name",
+ "state": "Object State",
+ "position_x": 0,
+ "position_y": 0,
+ "velocity_x": 0,
+ "velocity_y": 0
+}
+const empty_frame: Dictionary = {
+ "coins": 0,
+ "points": 0,
+ "fps": 0,
+ "elapsed_time": 0,
+ "objects": [],
+}
+
+# The game dictionary holds all data to be sent to the server
+var game: Dictionary = {}
+
+
+func _ready() -> void:
+ game_version = get_parent().game_version
+
+ player["rut"] = ""
+ player["name"] = ""
+ player["email"] = ""
+
+ var os_name = OS.get_name()
+ if os_name == "Android":
+ os_id = 1
+ elif os_name == "iOS":
+ os_id = 2
+ elif os_name == "HTML5":
+ os_id = 3
+ elif os_name == "OSX":
+ os_id = 4
+ elif os_name == "Server":
+ os_id = 5
+ elif os_name == "Windows":
+ os_id = 6
+ elif os_name == "UWP":
+ os_id = 7
+ elif os_name == "X11":
+ os_id = 8
+ else:
+ os_id = 0
+
+ game["player"] = player
+ game["level_id"] = 0
+ game["os_id"] = os_id
+ game["godot_version"] = godot_version
+ game["processor_count"] = processor_count
+ game["machine_id"] = machine_id
+ game["locale"] = locale
+ game["screen_count"] = screen_count
+ game["screen_dpi"] = screen_dpi
+ game["screen_size"] = screen_size
+ game["game_version"] = game_version
+ game["won"] = false
+ game["timestamp"] = int(Time.get_unix_time_from_system())
+ game["frames"] = frames
+
+ var err = $HTTPRequest.connect("request_completed", self, "_on_request_completed")
+ if err != OK:
+ print(err)
+
+ err = $MonitorGUI.find_node("Button").connect("input_validated", self, "_on_input_validated")
+ if err != OK:
+ print(err)
+
+
+func _physics_process(_delta: float) -> void:
+ if monitor_enabled:
+ if has_node("MonitorGUI") and not $MonitorGUI.visible:
+ $MonitorGUI.visible = true
+ emit_signal("monitor_loaded")
+
+ if started and not get_tree().paused:
+ var frame = empty_frame.duplicate(true)
+ frame["coins"] = coins
+ frame["points"] = points
+ frame["fps"] = Engine.get_frames_per_second()
+ frame["elapsed_time"] = Time.get_ticks_msec() - start_time
+
+ var frame_objects = objects.duplicate()
+ frame["objects"] = frame_objects
+
+ frames.append(frame)
+
+ if Input.is_action_just_pressed("Send"):
+ stop_monitor()
+ send_data()
+ else:
+ if Input.is_action_just_pressed("Send"):
+ start_monitor()
+ else:
+ get_tree().paused = false
+ emit_signal("monitor_loaded")
+ queue_free()
+
+
+func _on_input_validated(validated_player: Dictionary) -> void:
+ $MonitorGUI.queue_free()
+ get_tree().paused = false
+ player = validated_player.duplicate(true)
+ game["player"] = player
+
+
+func _object_created(name: String, state: String, position: Vector2, velocity: Vector2) -> void:
+ if monitor_enabled:
+ add_object(name, state, position, velocity)
+
+
+func _object_updated(name: String, state: String, position: Vector2, velocity: Vector2) -> void:
+ if monitor_enabled:
+ remove_object(name)
+ add_object(name, state, position, velocity)
+
+
+func _object_removed(name: String) -> void:
+ if monitor_enabled:
+ remove_object(name)
+
+
+func start_monitor() -> void:
+ frames.clear()
+ game["level_id"] = 2 # PrototypeR
+ game["won"] = false
+ game["timestamp"] = int(Time.get_unix_time_from_system())
+ start_time = Time.get_ticks_msec()
+ started = true
+
+
+func stop_monitor() -> void:
+ started = false
+
+
+func add_object(name: String, state: String, position: Vector2, velocity: Vector2) -> void:
+ var object = empty_object.duplicate(true)
+ object["name"] = name
+ object["state"] = state
+ object["position_x"] = position.x
+ object["position_y"] = position.y
+ object["velocity_x"] = velocity.x
+ object["velocity_y"] = velocity.y
+
+ objects.append(object)
+
+
+func remove_object(name: String) -> void:
+ for i in range(0, objects.size()):
+ if objects[i]["name"] == name:
+ objects.remove(i)
+
+
+func _on_coin_update(amount: int) -> void:
+ coins = coins + amount
+
+
+func _on_request_completed(result: int, response_code: int, headers: PoolStringArray, body: PoolByteArray) -> void:
+ if result != HTTPRequest.RESULT_SUCCESS:
+ print_debug("Error: Failed to connect with error code: " + str(result))
+ return
+
+ if response_code != HTTPClient.RESPONSE_OK:
+ print_debug("Error: Failed response with error code: " + str(response_code))
+
+ print_debug(headers)
+ if body.size() > 0:
+ var json = JSON.parse(body.get_string_from_utf8())
+ print_debug(JSON.print(json.result, "\t"))
+
+
+func send_data() -> void:
+ var json = JSON.print(game)
+ var headers = ["Content-Type: application/json", "Content-Encoding: gzip", "Content-Transfer-Encoding: base64"]
+
+ var body = compress_payload(json)
+
+ print("JSON B: " + String(json.length()))
+ print("JSON MB: " + String(json.length() / pow(2, 20)))
+ print("Body B: " + String(body.length()))
+ print("Body MB: " + String(body.length() / pow(2, 20)))
+
+ $HTTPRequest.request(url + "/game", headers, false, HTTPClient.METHOD_POST, body)
+
+
+func compress_payload(payload: String) -> String:
+ var bytes = payload.to_utf8()
+ var compressed = bytes.compress(File.COMPRESSION_GZIP)
+
+ var new_payload = Marshalls.raw_to_base64(compressed)
+
+ return new_payload
+
+
diff --git a/godot/monitor/Monitor.tscn b/godot/monitor/Monitor.tscn
new file mode 100644
index 0000000..58f135d
--- /dev/null
+++ b/godot/monitor/Monitor.tscn
@@ -0,0 +1,14 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://monitor/Monitor.gd" type="Script" id=1]
+[ext_resource path="res://monitor/GUI.tscn" type="PackedScene" id=3]
+
+[node name="Monitor" type="Node"]
+pause_mode = 2
+script = ExtResource( 1 )
+
+[node name="MonitorGUI" parent="." instance=ExtResource( 3 )]
+visible = false
+
+[node name="HTTPRequest" type="HTTPRequest" parent="."]
+pause_mode = 2
diff --git a/godot/monitor/MonitorGUI.gd b/godot/monitor/MonitorGUI.gd
new file mode 100644
index 0000000..7502599
--- /dev/null
+++ b/godot/monitor/MonitorGUI.gd
@@ -0,0 +1,12 @@
+extends CanvasLayer
+
+
+export var default_name = ""
+export var default_rut = ""
+export var default_email = ""
+
+
+func _ready() -> void:
+ get_node("%Name").text = default_name
+ get_node("%Rut").text = default_rut
+ get_node("%Email").text = default_email
diff --git a/godot/monitor/Rut.gd b/godot/monitor/Rut.gd
new file mode 100644
index 0000000..85b66d0
--- /dev/null
+++ b/godot/monitor/Rut.gd
@@ -0,0 +1,71 @@
+extends LineEdit
+
+
+var previous_text: String = ""
+
+
+func _on_Rut_text_changed(_new_text: String) -> void:
+ var old_pos = caret_position
+ if text.length() > 12:
+ text = previous_text
+ old_pos = old_pos - 2
+ text = pretty_rut(text)
+ caret_position = old_pos + 1
+ previous_text = text
+
+
+func clean_rut(rut: String) -> String:
+ rut = rut.strip_escapes()
+ rut = rut.strip_edges(true, true)
+ rut = rut.to_lower()
+ rut = rut.replace(".", "")
+ rut = rut.replace("-", "")
+
+ var rut_temp: String = rut.substr(0, rut.length() - 1)
+ var verifier: String = rut.substr(rut.length() - 1, 1)
+
+ var regex = RegEx.new()
+ regex.compile("\\D")
+ rut_temp = regex.sub(rut_temp, "", true)
+
+ regex.compile("[^kK\\d]")
+ verifier = regex.sub(verifier, "", true)
+
+ rut = rut_temp + verifier
+
+ return rut
+
+
+func pretty_rut(rut: String) -> String:
+ rut = clean_rut(rut)
+
+ var rut_temp: String = rut.substr(0, rut.length() - 1)
+ var verifier: String = rut.substr(rut.length() - 1, 1)
+
+ var regex = RegEx.new()
+ regex.compile("[^kK\\d]")
+ verifier = regex.sub(verifier, "", true)
+
+ var byte_array = rut_temp.to_utf8()
+ byte_array.invert()
+
+ var new_byte_array: PoolByteArray = PoolByteArray()
+ var i = 1
+ for symbol in byte_array:
+ new_byte_array.append(symbol)
+ if i == 3:
+ new_byte_array.append(".".to_utf8()[0])
+ i = 0
+ i = i + 1
+ if new_byte_array.size() > 0 and new_byte_array[new_byte_array.size() - 1] == ".".to_utf8()[0]:
+ new_byte_array.resize(new_byte_array.size() - 1)
+
+ new_byte_array.invert()
+ rut_temp = new_byte_array.get_string_from_utf8()
+
+ if rut_temp.length() == 0 and verifier.length() > 0:
+ rut_temp = verifier
+ elif rut_temp.length() > 0 and verifier.length() > 0:
+ rut_temp = rut_temp + "-" + verifier
+
+ return rut_temp
diff --git a/godot/monitor/UISounds.gd b/godot/monitor/UISounds.gd
new file mode 100644
index 0000000..b0821c6
--- /dev/null
+++ b/godot/monitor/UISounds.gd
@@ -0,0 +1,9 @@
+extends Node
+
+
+func _play_popup_sound() -> void:
+ $Popup.play()
+
+
+func _play_ui_select_sound() -> void:
+ $Menu.play()
diff --git a/godot/project.godot b/godot/project.godot
index b52d977..b01c12e 100644
--- a/godot/project.godot
+++ b/godot/project.godot
@@ -19,7 +19,7 @@ config/icon="res://icon.png"
window/size/width=512
window/size/height=288
-window/stretch/mode="viewport"
+window/stretch/mode="2d"
window/stretch/aspect="keep"
[editor]
@@ -100,6 +100,8 @@ Send={
2d_physics/layer_1="Player"
2d_physics/layer_2="Tiles"
+2d_physics/layer_4="Enemies"
+2d_physics/layer_5="Platforms"
[physics]
diff --git a/godot/shaders/crt/crt.shader b/godot/shaders/crt/crt.shader
new file mode 100644
index 0000000..aa146a4
--- /dev/null
+++ b/godot/shaders/crt/crt.shader
@@ -0,0 +1,73 @@
+shader_type canvas_item;
+
+const float PI = 3.14159;
+
+uniform vec2 resolution = vec2(1024.0, 576.0);
+
+uniform bool show_curve = false;
+uniform bool show_vignette = false;
+uniform bool show_horizontal_scan_lines = true;
+uniform bool show_vertical_scan_lines = false;
+uniform bool gray_scale = false;
+
+uniform float curvature_x_amount : hint_range(3.0, 15.0, 0.01) = 6.0;
+uniform float curvature_y_amount : hint_range(3.0, 15.0, 0.01) = 6.0;
+uniform vec4 corner_color : hint_color = vec4(0.0, 0.0, 0.0, 1.0);
+
+uniform float vignette_size : hint_range(1, 300, 0.1) = 4.0;
+uniform float vignette_opacity : hint_range(0.0, 1.0, 0.01) = 1.0;
+
+const float brightness = 3.0;
+
+vec2 uv_curve(vec2 uv) {
+ if (show_curve) {
+ uv = uv * 2.0 - 1.0;
+ vec2 offset = abs(uv.yx) / vec2(curvature_x_amount, curvature_y_amount);
+ uv = uv + uv * offset * offset;
+ uv = uv * 0.5 + 0.5;
+ }
+ return uv;
+}
+
+void fragment() {
+ vec2 screen_uv = uv_curve(SCREEN_UV);
+ vec3 color = texture(SCREEN_TEXTURE, screen_uv).rgb;
+
+ if (show_horizontal_scan_lines || show_vertical_scan_lines) {
+ color.r = texture(SCREEN_TEXTURE, uv_curve(SCREEN_UV) + vec2(SCREEN_PIXEL_SIZE.x * 0.0), 0.0).r;
+ color.g = texture(SCREEN_TEXTURE, uv_curve(SCREEN_UV) + vec2(SCREEN_PIXEL_SIZE.x * 0.0), 0.0).g;
+ color.b = texture(SCREEN_TEXTURE, uv_curve(SCREEN_UV) + vec2(SCREEN_PIXEL_SIZE.x * 0.0), 0.0).b;
+ }
+
+ if (show_vignette) {
+ float vignette = UV.x * UV.y * (1.0 - UV.x) * (1.0 - UV.y);
+ vignette = clamp(pow((resolution.x / vignette_size) * vignette, vignette_opacity), 0.0, 1.0);
+ color *= vignette;
+ }
+
+ if (show_horizontal_scan_lines) {
+ float s = sin(uv_curve(SCREEN_UV).y * resolution.y / 2.0 * PI);
+ s = (s * 0.5 + 0.5) * 0.9 + 0.1;
+ vec4 scan_line = vec4(vec3(pow(s, 0.25)), 1.0);
+ color *= scan_line.rgb;
+ }
+
+ if (show_vertical_scan_lines) {
+ float s = sin(uv_curve(SCREEN_UV).x * resolution.x / 2.0 * PI);
+ s = (s * 0.5 + 0.5) * 0.9 + 0.1;
+ vec4 scan_line = vec4(vec3(pow(s, 0.25)), 1.0);
+ color *= scan_line.rgb;
+ }
+
+ if (show_curve) {
+ if (screen_uv.x < 0.0 || screen_uv.x > 1.0 || screen_uv.y < 0.0 || screen_uv.y > 1.0) {
+ color = corner_color.rgb;
+ }
+ }
+
+ if (gray_scale) {
+ float avg = (color.r + color.g + color.b) / brightness;
+ color = vec3(avg);
+ }
+ COLOR = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/godot/shaders/crt/crt.tres b/godot/shaders/crt/crt.tres
new file mode 100644
index 0000000..7215bcc
--- /dev/null
+++ b/godot/shaders/crt/crt.tres
@@ -0,0 +1,17 @@
+[gd_resource type="ShaderMaterial" load_steps=2 format=2]
+
+[ext_resource path="res://shaders/crt/crt.shader" type="Shader" id=1]
+
+[resource]
+shader = ExtResource( 1 )
+shader_param/resolution = Vector2( 1280, 720 )
+shader_param/show_curve = false
+shader_param/show_vignette = false
+shader_param/show_horizontal_scan_lines = true
+shader_param/show_vertical_scan_lines = false
+shader_param/gray_scale = false
+shader_param/curvature_x_amount = 6.0
+shader_param/curvature_y_amount = 6.0
+shader_param/corner_color = Color( 0, 0, 0, 1 )
+shader_param/vignette_size = 4.0
+shader_param/vignette_opacity = 1.0
diff --git a/godot/shaders/crt/crt.tscn b/godot/shaders/crt/crt.tscn
new file mode 100644
index 0000000..bfdef34
--- /dev/null
+++ b/godot/shaders/crt/crt.tscn
@@ -0,0 +1,11 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://shaders/crt/crt.tres" type="Material" id=1]
+
+[node name="CRT" type="CanvasLayer"]
+
+[node name="ColorRect" type="ColorRect" parent="."]
+material = ExtResource( 1 )
+anchor_right = 1.0
+anchor_bottom = 1.0
+mouse_filter = 2
diff --git a/src/Main.cpp b/src/Main.cpp
index 4f91c0c..6bc13fb 100644
--- a/src/Main.cpp
+++ b/src/Main.cpp
@@ -9,10 +9,13 @@ void Main::_register_methods()
{
register_method("_ready", &Main::_ready);
register_method("_physics_process", &Main::_physics_process);
+ register_method("_on_monitor_loaded", &Main::_on_monitor_loaded);
+ register_property("game_version", &Main::set_game_version, &Main::get_game_version, String(main::game_version.c_str()));
register_property>("level", &Main::set_level, &Main::get_level, NULL, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_RESOURCE_TYPE, String("PackedScene"));
register_property("full_screen", &Main::set_full_screen, &Main::get_full_screen, main::full_screen);
register_property("window_size", &Main::set_window_size, &Main::get_window_size, main::window_size);
register_property("launch_screen", &Main::set_launch_screen, &Main::get_launch_screen, main::launch_screen);
+ register_signal("monitor_loaded");
}
Main::Main()
@@ -27,7 +30,10 @@ void Main::_init()
{
_os = OS::get_singleton();
_input = Input::get_singleton();
+ _project_settings = ProjectSettings::get_singleton();
+ _resource_loader = ResourceLoader::get_singleton();
+ game_version = String(main::game_version.c_str());
full_screen = main::full_screen;
window_size = main::window_size;
launch_screen = main::launch_screen;
@@ -35,6 +41,24 @@ void Main::_init()
void Main::_ready()
{
+ auto success = _project_settings->load_resource_pack("monitor.pck");
+ if (success)
+ {
+ // Load monitor from pck
+ Godot::print("Monitor pck found, loading...");
+ load_monitor();
+ }
+ else if (_resource_loader->exists("res://monitor/Monitor.tscn"))
+ {
+ // Load monitor from alai's pck
+ load_monitor();
+ }
+ else
+ {
+ // There is no monitor included
+ _on_monitor_loaded();
+ }
+
if (get_full_screen())
{
_os->set_window_fullscreen(true);
@@ -47,10 +71,52 @@ void Main::_ready()
);
}
- if (level != NULL)
+ success = _project_settings->load_resource_pack("crt.pck");
+ if (success)
{
- add_child(level->instance());
+ // Load crt from pck
+ Godot::print("CRT pck found, loading...");
+ Ref crt_scene = _resource_loader->load("res://shaders/crt/crt.tscn");
+ add_child(crt_scene->instance());
}
+ else if (_resource_loader->exists("res://shaders/crt/crt.tscn"))
+ {
+ // Load crt from alai's pck
+ Ref crt_scene = _resource_loader->load("res://shaders/crt/crt.tscn");
+ add_child(crt_scene->instance());
+ }
+}
+
+void Main::_on_monitor_loaded()
+{
+ if (level != nullptr)
+ {
+ auto level_node = load_level();
+ connect("monitor_loaded", level_node->get_child(0)->find_node("Player", true, false), "_on_monitor_loaded");
+ emit_signal("monitor_loaded");
+ }
+}
+
+void Main::load_monitor()
+{
+ Ref monitor_scene = _resource_loader->load("res://monitor/Monitor.tscn");
+ add_child(monitor_scene->instance());
+ auto monitor = get_node("Monitor");
+ monitor->connect("monitor_loaded", this, "_on_monitor_loaded");
+ get_tree()->set_pause(true);
+}
+
+Node *Main::load_level()
+{
+ if (level != nullptr)
+ {
+ auto path = level->get_path();
+ auto loaded_level = level->instance();
+ auto level_node = get_node("Level");
+ level_node->add_child(loaded_level);
+ return level_node;
+ }
+ return nullptr;
}
void Main::_physics_process(float delta)
@@ -71,6 +137,16 @@ Ref Main::get_level()
return this->level;
}
+void Main::set_game_version(String game_version)
+{
+ this->game_version = game_version;
+}
+
+String Main::get_game_version()
+{
+ return this->game_version;
+}
+
void Main::set_full_screen(bool full_screen)
{
this->full_screen = full_screen;
diff --git a/src/Main.h b/src/Main.h
index d89c5c9..4c04cc9 100644
--- a/src/Main.h
+++ b/src/Main.h
@@ -1,12 +1,15 @@
#ifndef ALAI_MAIN_H
#define ALAI_MAIN_H
+#include
#include
#include
#include
#include
#include
#include
+#include
+#include
/**
* @brief This is the godot namespace for all the code included in the library.
@@ -21,6 +24,11 @@ namespace godot
*/
namespace main
{
+ /**
+ * @brief The default value for the game version.
+ *
+ */
+ const std::string game_version = "0.1.0";
/**
* @brief The default value for if the game should start in full screen.
*
@@ -58,12 +66,27 @@ namespace godot
*
*/
Input *_input;
+ /**
+ * @brief ProjectSettings singleton.
+ *
+ */
+ ProjectSettings *_project_settings;
+ /**
+ * @brief ResourceLoader singleton.
+ *
+ */
+ ResourceLoader *_resource_loader;
/**
* @brief The first level to load
*
*/
Ref level;
+ /**
+ * @brief The current version of the game.
+ *
+ */
+ String game_version;
/**
* @brief If the window is full screen or not.
*
@@ -137,6 +160,20 @@ namespace godot
*/
Ref get_level();
+ /**
+ * @brief Set the game version object.
+ *
+ * @param[in] game_version The new version fo the game.
+ */
+ void set_game_version(String game_version);
+
+ /**
+ * @brief Get the game version object.
+ *
+ * @return String The current version of the game.
+ */
+ String get_game_version();
+
/**
* @brief Set the full screen object.
*
@@ -179,6 +216,11 @@ namespace godot
* @return int8_t The launch screen.
*/
int8_t get_launch_screen();
+
+ void _on_monitor_loaded();
+
+ void load_monitor();
+ Node *load_level();
};
}
}
diff --git a/src/player/Player.cpp b/src/player/Player.cpp
index 843043b..868156b 100644
--- a/src/player/Player.cpp
+++ b/src/player/Player.cpp
@@ -5,6 +5,9 @@
#include
#include
#include
+#include
+#include
+#include
using namespace godot;
using namespace player;
@@ -15,12 +18,18 @@ void Player::_register_methods()
register_method("_physics_process", &Player::_physics_process);
register_method("set_velocity", &Player::set_velocity);
register_method("get_velocity", &Player::get_velocity);
+ register_method("_on_player_touched", &Player::_on_player_touched);
+ register_method("_on_monitor_loaded", &Player::_on_monitor_loaded);
//register_property>("sprite_frames", &Player::set_sprite_frames, &Player::get_sprite_frames, Ref(), GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_RESOURCE_TYPE, String("SpriteFrames"));
register_property("speed", &Player::set_speed, &Player::get_speed, player::speed);
register_property("jump_force", &Player::set_jump_force, &Player::get_jump_force, player::jump_force);
+ register_property("bounce_force", &Player::set_bounce_force, &Player::get_bounce_force, player::bounce_force);
register_property("gravity", &Player::set_gravity, &Player::get_gravity, player::gravity);
register_property("run_speed", &Player::set_run_speed, &Player::get_run_speed, player::run_speed);
register_property("double_jump", &Player::set_double_jump, &Player::get_double_jump, player::double_jump);
+ register_signal("object_created", "name", GODOT_VARIANT_TYPE_STRING, "state", GODOT_VARIANT_TYPE_STRING, "position", GODOT_VARIANT_TYPE_VECTOR2, "velocity", GODOT_VARIANT_TYPE_VECTOR2);
+ register_signal("object_updated", "name", GODOT_VARIANT_TYPE_STRING, "state", GODOT_VARIANT_TYPE_STRING, "position", GODOT_VARIANT_TYPE_VECTOR2, "velocity", GODOT_VARIANT_TYPE_VECTOR2);
+ register_signal("object_removed", "name", GODOT_VARIANT_TYPE_STRING);
}
Player::Player()
@@ -38,6 +47,7 @@ void Player::_init()
//sprite_frames = _resource_loader->load(player::sprite_frames);
set_speed(player::speed);
set_jump_force(player::jump_force);
+ set_bounce_force(player::bounce_force);
set_gravity(player::gravity);
set_run_speed(player::run_speed);
set_double_jump(player::double_jump);
@@ -58,7 +68,6 @@ void Player::_ready()
//animated_sprite->set_sprite_frames(sprite_frames);
auto node = get_parent()->find_node("Middleground");
-
if (node != nullptr)
{
auto tile_map = Object::cast_to(node);
@@ -71,13 +80,83 @@ void Player::_ready()
}
}
+void Player::_on_monitor_loaded() {
+ auto object_node = get_tree()->get_root()->find_node("Monitor", true, false);
+ if (object_node != nullptr)
+ {
+ auto state = get_node("StateMachine")->get_child(0);
+ if (state != nullptr)
+ {
+ connect("object_created", object_node, "_object_created");
+ connect("object_updated", object_node, "_object_updated");
+ connect("object_removed", object_node, "_object_removed");
+ emit_signal("object_created", this->get_name(), state->get_name(), get_global_position(), velocity);
+ }
+ else
+ {
+ WARN_PRINT("State not found!");
+ }
+ }
+#ifndef NDEBUG
+ else
+ {
+ WARN_PRINT("Monitor not found!");
+ }
+#endif
+}
+
void Player::_physics_process(float delta)
{
velocity.y += get_gravity();
- velocity = move_and_slide(velocity, Vector2::UP, true);
+ auto snap_vector = Vector2::ZERO;
+ if (!is_on_floor())
+ {
+ snap_vector = Vector2::DOWN * 20.0;
+ }
+
+ auto platform_detector = get_node("PlatformDetector");
+ auto is_on_platform = platform_detector->is_colliding();
+
+ velocity = move_and_slide_with_snap(velocity, snap_vector, Vector2::UP, !is_on_platform, 4, 0.9, false);
+ //velocity = move_and_slide(velocity, Vector2::UP, !is_on_platform);
velocity.x = Math::lerp((float) velocity.x, (float) 0, (float) 0.2);
+ auto count = get_slide_count();
+ for (int64_t i = 0; i < count; i++)
+ {
+ auto collision = get_slide_collision(i);
+ auto collision_object = collision->get_collider();
+ auto collider = Object::cast_to(collision_object);
+ if (collider->is_in_group("squashable") && Vector2::UP.dot(collision->get_normal()) > 0.1)
+ {
+ collider->call_deferred("squash");
+ /*auto dup_node = collider->duplicate();
+ auto dup = Object::cast_to(dup_node);
+ auto dup_pos = dup->get_position();
+ dup_pos.x += 24;
+ dup->set_position(dup_pos);
+ auto enemies = get_tree()->get_root()->get_node("Main")->find_node("Enemies", true, false);
+ if (enemies != nullptr) {
+ enemies->add_child(dup);
+ }
+ else
+ {
+ WARN_PRINT("Enemies not found!");
+ dup->queue_free();
+ }*/
+ velocity.y = -get_bounce_force();
+ }
+ else if (collider->is_in_group("enemy") && (collider->is_in_group("rideable") && Vector2::DOWN.dot(collision->get_normal()) > 0))
+ {
+ _on_player_touched();
+ }
+ else if (collider->is_in_group("enemy") && !collider->is_in_group("rideable"))
+ {
+ _on_player_touched();
+ }
+ }
+
// Clamp the player's position inside the camera's limits
auto camera = get_node("Camera2D");
auto position = get_global_position();
@@ -110,6 +189,16 @@ void Player::_physics_process(float delta)
}
}
}
+
+ auto state = get_node("StateMachine")->get_child(0);
+ if (state != nullptr)
+ {
+ emit_signal("object_updated", this->get_name(), state->get_name(), get_global_position(), velocity);
+ }
+ else
+ {
+ WARN_PRINT("State not found!");
+ }
}
void Player::set_sprite_frames(Ref sprite_frames)
@@ -142,6 +231,16 @@ float Player::get_jump_force()
return this->jump_force;
}
+void Player::set_bounce_force(float bounce_force)
+{
+ this->bounce_force = bounce_force;
+}
+
+float Player::get_bounce_force()
+{
+ return this->bounce_force;
+}
+
void Player::set_gravity(float gravity)
{
this->gravity = gravity;
@@ -181,3 +280,12 @@ Vector2 Player::get_velocity()
{
return this->velocity;
}
+
+void Player::_on_player_touched()
+{
+ auto error = get_tree()->change_scene("res://Main.tscn");
+ if (error != Error::OK)
+ {
+ ERR_PRINT(String().num((int) error) + " Could not load scene!");
+ }
+}
diff --git a/src/player/Player.h b/src/player/Player.h
index ee710ae..ee505a0 100644
--- a/src/player/Player.h
+++ b/src/player/Player.h
@@ -31,6 +31,11 @@ namespace godot
*
*/
const float jump_force = 300.0;
+ /**
+ * @brief The default bounce force applied when bouncing on something.
+ *
+ */
+ const float bounce_force = 200.0;
/**
* @brief The default gravity applied to the player.
*
@@ -93,6 +98,11 @@ namespace godot
*
*/
float jump_force;
+ /**
+ * @brief The force applied to the player when bouncing off something.
+ *
+ */
+ float bounce_force;
/**
* @brief The gravity applied to the player.
*
@@ -194,6 +204,20 @@ namespace godot
*/
float get_jump_force();
+ /**
+ * @brief Set the bounce force object.
+ *
+ * @param[in] bounce_force The new force applied to the player to make him bounce.
+ */
+ void set_bounce_force(float bounce_force);
+
+ /**
+ * @brief Get the bounce force object.
+ *
+ * @return float The current force being applied to the player.
+ */
+ float get_bounce_force();
+
/**
* @brief Set the gravity object.
*
@@ -250,6 +274,14 @@ namespace godot
* @return Vector2 Returns the velocity of the player.
*/
Vector2 get_velocity();
+
+ /**
+ * @brief This function is called when an enemy touches the player.
+ *
+ */
+ void _on_player_touched();
+
+ void _on_monitor_loaded();
};
}
}
diff --git a/src/player/states/PlayerJump.cpp b/src/player/states/PlayerJump.cpp
index 8255e1b..e9f9082 100644
--- a/src/player/states/PlayerJump.cpp
+++ b/src/player/states/PlayerJump.cpp
@@ -1,6 +1,8 @@
#include "player/states/PlayerJump.h"
#include "player/Player.h"
+#include
+
using namespace godot;
using namespace player;
@@ -26,6 +28,9 @@ void PlayerJump::_init()
void PlayerJump::_state_enter(const String state)
{
+ auto jump_sound = get_parent()->get_node("Sounds/Jump");
+ jump_sound->play();
+
animated_sprite = get_parent()->get_node("AnimatedSprite");
animated_sprite->stop();
animated_sprite->set_animation("air");