diff --git a/3D/MapScenes/TestLevel.tscn b/3D/MapScenes/TestLevel.tscn
index 457cebde..730ed398 100644
--- a/3D/MapScenes/TestLevel.tscn
+++ b/3D/MapScenes/TestLevel.tscn
@@ -3264,7 +3264,7 @@ Target = NodePath("../Door")
[node name="Enemies" type="Node3D" parent="."]
[node name="FloorEmitter" parent="Enemies" instance=ExtResource("63_r8ono")]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.2387, 1.15056, 24.7368)
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.2387, 1.24491, 24.7368)
Script = ExtResource("64_fi82p")
EmitOnStart = false
diff --git a/Cirno.sln.DotSettings.user b/Cirno.sln.DotSettings.user
index bf49faed..4d72f5f2 100644
--- a/Cirno.sln.DotSettings.user
+++ b/Cirno.sln.DotSettings.user
@@ -20,6 +20,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
diff --git a/Scenes/Actors/IsoPlayer_FSM.tscn b/Scenes/Actors/IsoPlayer_FSM.tscn
index 27260123..f591a149 100644
--- a/Scenes/Actors/IsoPlayer_FSM.tscn
+++ b/Scenes/Actors/IsoPlayer_FSM.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=49 format=3 uid="uid://rimplblbptcd"]
+[gd_scene load_steps=63 format=3 uid="uid://rimplblbptcd"]
[ext_resource type="Script" uid="uid://88smibkin17p" path="res://Scripts/Components/FSM/3DPlayer/IsoPlayerFSMProxy.cs" id="1_cc7e7"]
[ext_resource type="Texture2D" uid="uid://ddwhrlrgj6i00" path="res://Sprites/Actors/Cirno.png" id="1_vex34"]
@@ -28,6 +28,12 @@
[ext_resource type="Script" uid="uid://hdw15b6fi7de" path="res://Scripts/Components/Actors/3D/PlayerDamageReceiver3D.cs" id="26_ok6gd"]
[ext_resource type="Script" uid="uid://ddsqqfx1usc3j" path="res://Scripts/Resources/DamageResistance.cs" id="27_5v3dv"]
[ext_resource type="Script" uid="uid://cqwvssstkrdmw" path="res://Scripts/Components/Actors/ActorResourceProvider.cs" id="28_b3jpo"]
+[ext_resource type="Script" uid="uid://b2cqsoywem26t" path="res://Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs" id="29_tmq7e"]
+[ext_resource type="Texture2D" uid="uid://bwjrdlnysft15" path="res://Sprites/Actors/Focus_Circle.png" id="30_rikg1"]
+[ext_resource type="Texture2D" uid="uid://bc4tp44e00g0d" path="res://Sprites/Actors/Focus_Square.png" id="31_iw7um"]
+[ext_resource type="Texture2D" uid="uid://bf37ce6jskdel" path="res://Sprites/SmallHitbox.png" id="32_chmen"]
+[ext_resource type="Script" uid="uid://byiv30s1ahdyh" path="res://Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs" id="33_iw7um"]
+[ext_resource type="Texture2D" uid="uid://cf2855sd3hqty" path="res://Sprites/Actors/Aiming_Reticule_Small.png" id="34_chmen"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_fg04g"]
radius = 0.349554
@@ -132,6 +138,66 @@ metadata/_custom_type_script = "uid://ddsqqfx1usc3j"
[sub_resource type="SphereShape3D" id="SphereShape3D_p313o"]
radius = 0.0913725
+[sub_resource type="AtlasTexture" id="AtlasTexture_n5ijo"]
+atlas = ExtResource("30_rikg1")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_h1heu"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_n5ijo")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_vwjki"]
+atlas = ExtResource("31_iw7um")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_nwocn"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_vwjki")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_igu66"]
+atlas = ExtResource("32_chmen")
+region = Rect2(0, 0, 4, 4)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_nk07c"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_igu66")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_12erk"]
+atlas = ExtResource("34_chmen")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_gndug"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_12erk")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
[node name="IsoPlayerFsm" type="CharacterBody3D" node_paths=PackedStringArray("PlayerFSM")]
collision_layer = 2
collision_mask = 17
@@ -150,7 +216,7 @@ _inputProvider = NodePath("../../InputProvider")
AnimationProvider = NodePath("../../AnimationProvider")
Storage = NodePath("../../Storage")
DamageReceiver = NodePath("../../DamageReceiver")
-_moduleNodes = [NodePath("../../InputProvider"), NodePath("../../MovementModule"), NodePath("../../ShadowModule"), NodePath("../../InteractionController"), NodePath("../../ActivationProvider"), NodePath("../../WeaponModule"), NodePath("../../AcidDeathModule")]
+_moduleNodes = [NodePath("../../InputProvider"), NodePath("../../MovementModule"), NodePath("../../ShadowModule"), NodePath("../../InteractionController"), NodePath("../../ActivationProvider"), NodePath("../../WeaponModule"), NodePath("../../AcidDeathModule"), NodePath("../../CrosshairModule")]
[node name="Dead" type="Node" parent="StateMachine"]
script = ExtResource("5_ok250")
@@ -167,12 +233,12 @@ pixel_size = 0.05
texture_filter = 0
sprite_frames = ExtResource("6_yq7h2")
animation = &"idle"
-frame_progress = 0.1756
+frame_progress = 0.755868
script = ExtResource("9_yarib")
[node name="Legs" type="AnimatedSprite3D" parent="AnimationProvider"]
+transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 0, 0)
pixel_size = 0.05
-billboard = 1
texture_filter = 0
sprite_frames = ExtResource("7_l4f8l")
animation = &"idle"
@@ -192,14 +258,15 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.101789, 0)
visible = false
shape = SubResource("CapsuleShape3D_fg04g")
-[node name="MovementModule" type="Node" parent="." node_paths=PackedStringArray("PlayerStorage", "_inputProvider")]
+[node name="MovementModule" type="Node" parent="." node_paths=PackedStringArray("PlayerStorage", "_inputProvider", "HitboxSpriteProvider")]
script = ExtResource("5_fg04g")
PlayerStorage = NodePath("../Storage")
_inputProvider = NodePath("../InputProvider")
-Speed = 6
-StrafeSpeed = 4
+HitboxSpriteProvider = NodePath("../StrafeSpriteProvider")
+Speed = 5
+StrafeSpeed = 2
Acceleration = 150.0
-Deceleration = 25.0
+Deceleration = 20.0
Gravity = -50.0
FallSpeed = 100.0
@@ -309,7 +376,52 @@ ResourceName = "Shield"
_maxResource = 32.0
[node name="CollisionShape2D" type="CollisionShape3D" parent="DamageReceiver"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00122255, -0.00129807, 0.000553966)
shape = SubResource("SphereShape3D_p313o")
+[node name="StrafeSpriteProvider" type="Node3D" parent="." node_paths=PackedStringArray("Hitbox", "Circle", "Square")]
+script = ExtResource("29_tmq7e")
+Hitbox = NodePath("Hitbox")
+Circle = NodePath("MagicCircle")
+Square = NodePath("MagicSquare")
+RotationSpeed = 1.0
+
+[node name="MagicCircle" type="AnimatedSprite3D" parent="StrafeSpriteProvider"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, -0.3, 0)
+modulate = Color(1, 1, 1, 0.501961)
+pixel_size = 0.05
+texture_filter = 0
+render_priority = -1
+sprite_frames = SubResource("SpriteFrames_h1heu")
+
+[node name="MagicSquare" type="AnimatedSprite3D" parent="StrafeSpriteProvider"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, -0.3, 0)
+modulate = Color(1, 1, 1, 0.501961)
+pixel_size = 0.05
+texture_filter = 0
+render_priority = -1
+sprite_frames = SubResource("SpriteFrames_nwocn")
+
+[node name="Hitbox" type="AnimatedSprite3D" parent="StrafeSpriteProvider"]
+pixel_size = 0.05
+billboard = 1
+texture_filter = 0
+render_priority = 1
+sprite_frames = SubResource("SpriteFrames_nk07c")
+
+[node name="CrosshairModule" type="Node3D" parent="." node_paths=PackedStringArray("Storage", "AnimatedSprite")]
+script = ExtResource("33_iw7um")
+Storage = NodePath("../Storage")
+AnimatedSprite = NodePath("Crosshair")
+CrosshairDistance = 2.0
+
+[node name="Crosshair" type="AnimatedSprite3D" parent="CrosshairModule"]
+transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 0, 0)
+pixel_size = 0.05
+no_depth_test = true
+texture_filter = 0
+render_priority = 1
+sprite_frames = SubResource("SpriteFrames_gndug")
+
[connection signal="area_entered" from="AcidDetector" to="AcidDeathModule" method="OnAcidCollision"]
[connection signal="area_entered" from="DamageReceiver" to="DamageReceiver" method="_on_damage_hitbox_area_entered"]
diff --git a/Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs b/Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs
new file mode 100644
index 00000000..a1f9ddaf
--- /dev/null
+++ b/Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs
@@ -0,0 +1,70 @@
+using Cirno.Scripts.Components.FSM;
+using Cirno.Scripts.Components.FSM._3DPlayer;
+using Cirno.Scripts.Components.FSM.Player;
+using Cirno.Scripts.Utils;
+using Godot;
+
+namespace Cirno.Scripts.Components.Actors._3D;
+
+public partial class PlayerCrosshairModule3D : ModuleBase
+{
+ [Export] public IsoPlayerStorageModule Storage { get; set; }
+ [Export]
+ public AnimatedSprite3D AnimatedSprite { get; private set; }
+
+ [Export]
+ public float CrosshairDistance { get; private set; }
+
+ public bool Enabled { get; set; } = false;
+
+ private IStateMachine _machine;
+
+ public void UpdatePosition(Vector2 facingDirection)
+ {
+ AnimatedSprite.Position = CalculateCrosshairPosition(facingDirection);
+ }
+
+ private Vector3 CalculateCrosshairPosition(Vector2 facingDirection)
+ {
+ var pos2d = facingDirection * CrosshairDistance;
+
+ var pos3d = pos2d.ToVector3(0);
+
+ return pos3d;
+ }
+
+ public override void EnterState(PlayerState state)
+ {
+ Enabled = true;
+ AnimatedSprite.Show();
+ }
+
+ public override void ExitState(PlayerState state)
+ {
+ Enabled = false;
+ AnimatedSprite.Hide();
+ }
+
+ public override void _Ready()
+ {
+ AnimatedSprite.Hide();
+ }
+
+ public override void Init(IStateMachine machine)
+ {
+ _machine = machine;
+ AnimatedSprite.Hide();
+ }
+
+ public override void Process(double delta)
+ {
+ if (!Enabled) return;
+
+ }
+
+ public override void PhysicsProcess(double delta)
+ {
+ if (!Enabled) return;
+ AnimatedSprite.Position = CalculateCrosshairPosition(Storage.FacingDirection);
+ }
+}
\ No newline at end of file
diff --git a/Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs.uid b/Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs.uid
new file mode 100644
index 00000000..3b64c6d6
--- /dev/null
+++ b/Scripts/Components/Actors/3D/PlayerCrosshairModule3D.cs.uid
@@ -0,0 +1 @@
+uid://byiv30s1ahdyh
diff --git a/Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs b/Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs
new file mode 100644
index 00000000..3b12fe90
--- /dev/null
+++ b/Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs
@@ -0,0 +1,35 @@
+using Godot;
+
+namespace Cirno.Scripts.Components.Actors._3D;
+
+public partial class PlayerHitboxSpriteProvider3D : Node3D
+{
+ [Export]
+ public AnimatedSprite3D Hitbox { get; private set; }
+ [Export]
+ public AnimatedSprite3D Circle { get; private set; }
+ [Export]
+ public AnimatedSprite3D Square { get; private set; }
+
+ [Export] public float RotationSpeed { get; private set; } = 10f;
+
+ public override void _Process(double delta)
+ {
+ if (!Visible) return;
+ Circle.Rotate(Vector3.Up, (float)(RotationSpeed * delta));
+ Square.Rotate(Vector3.Up, (float)(-RotationSpeed * delta));
+ }
+
+ public void SetVisibility(bool isVisible)
+ {
+ if (isVisible == Visible) return;
+ if (isVisible)
+ {
+ Show();
+ }
+ else
+ {
+ Hide();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs.uid b/Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs.uid
new file mode 100644
index 00000000..d3a7081d
--- /dev/null
+++ b/Scripts/Components/Actors/3D/PlayerHitboxSpriteProvider3D.cs.uid
@@ -0,0 +1 @@
+uid://b2cqsoywem26t
diff --git a/Scripts/Components/FSM/3DPlayer/Active.cs b/Scripts/Components/FSM/3DPlayer/Active.cs
index 9c82ab9e..9ff47936 100644
--- a/Scripts/Components/FSM/3DPlayer/Active.cs
+++ b/Scripts/Components/FSM/3DPlayer/Active.cs
@@ -17,6 +17,8 @@ public partial class Active : BaseState
[Export] public PlayerDamageReceiver3D DamageReceiver { get; private set; }
+ //[Export] public PlayerHitboxSpriteProvider3D HitboxSpriteProvider { get; private set; }
+
public override void Init(IStateMachine machine)
{
base.Init(machine);
diff --git a/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs b/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs
index 4aed5611..110bc832 100644
--- a/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs
+++ b/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs
@@ -1,4 +1,5 @@
using Cirno.Scripts.Components.Actors;
+using Cirno.Scripts.Components.Actors._3D;
using Godot;
namespace Cirno.Scripts.Components.FSM._3DPlayer;
@@ -8,6 +9,8 @@ public partial class IsoMovementModule : ModuleBase