From cc9c4e5aa1bd29e9c528d66c83f510f30d4ed6d6 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 17 Jun 2025 11:57:59 +0200 Subject: [PATCH] 2D Character and weapons --- IsoTest/IsoMapTest2.tscn | 6 +- Scenes/Actors/IsoPlayer_FSM.tscn | 20 +- Scenes/Actors/fsm_player.tscn | 10 +- Scenes/Barrel.cs | 2 +- Scenes/test.tscn | 2 +- Scripts/Bullet.cs | 22 +- Scripts/BulletOwner.cs | 6 + Scripts/BulletOwner.cs.uid | 1 + .../Components/Actors/IMouseAimProvider.cs | 9 + .../Actors/IMouseAimProvider.cs.uid | 1 + .../Actors/KeyboardInputProvider.cs | 91 ++--- .../Components/Actors/MouseAimProvider2D.cs | 13 + .../Actors/MouseAimProvider2D.cs.uid | 1 + .../Components/Actors/MouseAimProvider3D.cs | 16 + .../Actors/MouseAimProvider3D.cs.uid | 1 + Scripts/Components/BulletSpawner.cs | 2 +- .../FSM/3DPlayer/IsoMovementModule.cs | 1 - .../FSM/3DPlayer/PlayerWeaponModule3D.cs | 70 ++++ .../FSM/3DPlayer/PlayerWeaponModule3D.cs.uid | 1 + .../FSM/3DPlayer/PlayerWeaponProvider3D.cs | 286 ++++++++++++++++ .../3DPlayer/PlayerWeaponProvider3D.cs.uid | 1 + Scripts/Components/FSM/Player/FreezeModule.cs | 1 + Scripts/Controllers/PoolingManager.cs | 32 +- Scripts/DamageType.cs | 9 + Scripts/DamageType.cs.uid | 1 + Scripts/InventoryManager.cs | 4 +- Scripts/PlayerMovement.cs | 2 +- .../ItemEffects/SpiderbombEffectResource.cs | 2 +- Scripts/Utils/VectorExtensions.cs | 11 + Scripts/Utils/VectorExtensions.cs.uid | 1 + Scripts/Weapon.cs | 2 +- Scripts/Weapons/Bullet3D.cs | 310 ++++++++++++++++++ Scripts/Weapons/Bullet3D.cs.uid | 1 + Scripts/Weapons/IBullet.cs | 34 ++ Scripts/Weapons/IBullet.cs.uid | 1 + Scripts/Weapons/Weapon3D.cs | 228 +++++++++++++ Scripts/Weapons/Weapon3D.cs.uid | 1 + 37 files changed, 1113 insertions(+), 89 deletions(-) create mode 100644 Scripts/BulletOwner.cs create mode 100644 Scripts/BulletOwner.cs.uid create mode 100644 Scripts/Components/Actors/IMouseAimProvider.cs create mode 100644 Scripts/Components/Actors/IMouseAimProvider.cs.uid create mode 100644 Scripts/Components/Actors/MouseAimProvider2D.cs create mode 100644 Scripts/Components/Actors/MouseAimProvider2D.cs.uid create mode 100644 Scripts/Components/Actors/MouseAimProvider3D.cs create mode 100644 Scripts/Components/Actors/MouseAimProvider3D.cs.uid create mode 100644 Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs create mode 100644 Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs.uid create mode 100644 Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs create mode 100644 Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs.uid create mode 100644 Scripts/DamageType.cs create mode 100644 Scripts/DamageType.cs.uid create mode 100644 Scripts/Utils/VectorExtensions.cs create mode 100644 Scripts/Utils/VectorExtensions.cs.uid create mode 100644 Scripts/Weapons/Bullet3D.cs create mode 100644 Scripts/Weapons/Bullet3D.cs.uid create mode 100644 Scripts/Weapons/IBullet.cs create mode 100644 Scripts/Weapons/IBullet.cs.uid create mode 100644 Scripts/Weapons/Weapon3D.cs create mode 100644 Scripts/Weapons/Weapon3D.cs.uid diff --git a/IsoTest/IsoMapTest2.tscn b/IsoTest/IsoMapTest2.tscn index b2efa074..2519c933 100644 --- a/IsoTest/IsoMapTest2.tscn +++ b/IsoTest/IsoMapTest2.tscn @@ -1,10 +1,11 @@ -[gd_scene load_steps=56 format=3 uid="uid://ec4m3geediis"] +[gd_scene load_steps=57 format=3 uid="uid://ec4m3geediis"] [ext_resource type="Script" uid="uid://cvisn0b641od4" path="res://addons/cyclops_level_builder/nodes/cyclops_block.gd" id="1_18fbr"] [ext_resource type="Script" uid="uid://ba0tf7ihw4hpp" path="res://Scripts/Misc/CameraController3D.cs" id="1_g4gcm"] [ext_resource type="Script" uid="uid://b8g8mflgsr5dc" path="res://Scripts/GameController.cs" id="1_joeuf"] [ext_resource type="PackedScene" uid="uid://dkwi1hu1bixoe" path="res://Scenes/HUD/HUD.tscn" id="2_itd0i"] [ext_resource type="Script" uid="uid://djeq3sxhsep3c" path="res://addons/cyclops_level_builder/resources/data_vector_byte.gd" id="2_kler0"] +[ext_resource type="Script" uid="uid://c5nxsq3tyxcx6" path="res://Scripts/InventoryManager.cs" id="3_itd0i"] [ext_resource type="Script" uid="uid://civ3w78ahacnu" path="res://addons/cyclops_level_builder/resources/data_vector_int.gd" id="3_k6bah"] [ext_resource type="Script" uid="uid://db41w3h28c2la" path="res://addons/cyclops_level_builder/resources/data_vector_float.gd" id="4_01bfr"] [ext_resource type="Script" uid="uid://c43o57os2lmc3" path="res://addons/cyclops_level_builder/resources/mesh_vector_data.gd" id="5_hmj6t"] @@ -417,6 +418,9 @@ script = ExtResource("1_joeuf") [node name="HUD" parent="GameController" instance=ExtResource("2_itd0i")] +[node name="InventoryManager" type="Node" parent="GameController"] +script = ExtResource("3_itd0i") + [node name="block0" type="Node3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 0) script = ExtResource("1_18fbr") diff --git a/Scenes/Actors/IsoPlayer_FSM.tscn b/Scenes/Actors/IsoPlayer_FSM.tscn index 22e5a5b2..45fd06ad 100644 --- a/Scenes/Actors/IsoPlayer_FSM.tscn +++ b/Scenes/Actors/IsoPlayer_FSM.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=19 format=3 uid="uid://rimplblbptcd"] +[gd_scene load_steps=22 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"] @@ -8,11 +8,14 @@ [ext_resource type="Script" uid="uid://c5brx3ail1tlh" path="res://Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs" id="5_fg04g"] [ext_resource type="Script" uid="uid://eop2ue3otxcs" path="res://Scripts/Components/FSM/3DPlayer/IsoPlayerStorageModule.cs" id="6_habpy"] [ext_resource type="Script" uid="uid://dq338w2lw5phl" path="res://Scripts/Components/Actors/KeyboardInputProvider.cs" id="7_4cdxq"] +[ext_resource type="Script" uid="uid://1fryvj4omkin" path="res://Scripts/Components/Actors/MouseAimProvider3D.cs" id="9_2ffwi"] [ext_resource type="Script" uid="uid://c8ar11sg0su2h" path="res://Scripts/Components/FSM/3DPlayer/ShadowModule.cs" id="9_fg04g"] [ext_resource type="Script" uid="uid://bm73kgly8gv2i" path="res://Scripts/Components/FSM/3DPlayer/IsoInteractionController.cs" id="10_habpy"] [ext_resource type="Script" uid="uid://d1ixvdcii6uy7" path="res://Scripts/Components/FSM/3DPlayer/SelectorController.cs" id="11_4cdxq"] [ext_resource type="Script" uid="uid://vne180ohyucn" path="res://Scripts/Components/FSM/3DPlayer/IsoActivationProvider.cs" id="11_4exx2"] [ext_resource type="AudioStream" uid="uid://myr6n2c1u503" path="res://SFX/581602__samsterbirdies__beep-error.mp3" id="13_2ffwi"] +[ext_resource type="Script" uid="uid://d2psafx4f3f58" path="res://Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs" id="15_el8as"] +[ext_resource type="Script" uid="uid://by0x0qmbmkoak" path="res://Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs" id="16_olwak"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_fg04g"] radius = 0.349554 @@ -47,7 +50,7 @@ script = ExtResource("2_3oyrx") [node name="Active" type="Node" parent="StateMachine" node_paths=PackedStringArray("_moduleNodes")] script = ExtResource("3_cc7e7") -_moduleNodes = [NodePath("../../InputProvider"), NodePath("../../MovementModule"), NodePath("../../ShadowModule"), NodePath("../../InteractionController"), NodePath("../../ActivationProvider")] +_moduleNodes = [NodePath("../../InputProvider"), NodePath("../../MovementModule"), NodePath("../../ShadowModule"), NodePath("../../InteractionController"), NodePath("../../ActivationProvider"), NodePath("../../WeaponModule")] [node name="Sprite" type="Sprite3D" parent="."] transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 0, 0) @@ -80,6 +83,9 @@ Root = NodePath("..") [node name="InputProvider" type="Node" parent="."] script = ExtResource("7_4cdxq") +[node name="MouseAimProvider" type="Node3D" parent="InputProvider"] +script = ExtResource("9_2ffwi") + [node name="Shadow" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, -0.29, 0) mesh = SubResource("QuadMesh_fg04g") @@ -120,3 +126,13 @@ bus = &"Effects" [node name="AudioListener3D" type="AudioListener3D" parent="."] current = true + +[node name="WeaponModule" type="Node" parent="." node_paths=PackedStringArray("WeaponProvider", "InputProvider", "Storage")] +script = ExtResource("15_el8as") +WeaponProvider = NodePath("WeaponProvider") +InputProvider = NodePath("../InputProvider") +Storage = NodePath("../Storage") + +[node name="WeaponProvider" type="Node" parent="WeaponModule" node_paths=PackedStringArray("StorageModule")] +script = ExtResource("16_olwak") +StorageModule = NodePath("../../Storage") diff --git a/Scenes/Actors/fsm_player.tscn b/Scenes/Actors/fsm_player.tscn index a96c67cb..3775a48e 100644 --- a/Scenes/Actors/fsm_player.tscn +++ b/Scenes/Actors/fsm_player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=91 format=3 uid="uid://c4pr2707hbeph"] +[gd_scene load_steps=92 format=3 uid="uid://c4pr2707hbeph"] [ext_resource type="Script" uid="uid://d2ubk5gucny6s" path="res://Scripts/Components/FSM/PlayerFSMProxy.cs" id="1_g3wua"] [ext_resource type="Script" uid="uid://bw2hakslndaxm" path="res://Scripts/Components/FSM/PlayerStateMachine.cs" id="1_mpmil"] @@ -27,6 +27,7 @@ [ext_resource type="Script" uid="uid://cqwvssstkrdmw" path="res://Scripts/Components/Actors/ActorResourceProvider.cs" id="15_5qlss"] [ext_resource type="Texture2D" uid="uid://cf2855sd3hqty" path="res://Sprites/Actors/Aiming_Reticule_Small.png" id="19_fnw0c"] [ext_resource type="Texture2D" uid="uid://bc4tp44e00g0d" path="res://Sprites/Actors/Focus_Square.png" id="20_3ho10"] +[ext_resource type="Script" uid="uid://bfmnmk0rfwa1i" path="res://Scripts/Components/Actors/MouseAimProvider2D.cs" id="20_3rkrn"] [ext_resource type="Script" uid="uid://dv205x8msohpv" path="res://Scripts/Components/Actors/ActivationProvider.cs" id="22_12cwd"] [ext_resource type="PackedScene" uid="uid://chkpk7erlqajg" path="res://Scenes/Selector.tscn" id="23_5tmtw"] [ext_resource type="AudioStream" uid="uid://myr6n2c1u503" path="res://SFX/581602__samsterbirdies__beep-error.mp3" id="24_5tmtw"] @@ -383,7 +384,7 @@ _animationProvider = NodePath("../../AnimationProvider") [node name="Dead" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("_animationProvider", "_inputProvider", "_healthProvider", "_motivationProvider")] script = ExtResource("4_0pqs8") _animationProvider = NodePath("../../AnimationProvider") -_inputProvider = NodePath("../../InputProvider") +_inputProvider = NodePath("") _healthProvider = NodePath("../../DamageReceiver/HealthProvider") _motivationProvider = NodePath("../../DamageReceiver/MotivationProvider") @@ -444,9 +445,12 @@ script = ExtResource("7_pmkfo") _animatedSprite = NodePath("../Legs") BlinkMaterial = ExtResource("4_5qlss") -[node name="InputProvider" type="Node2D" parent="."] +[node name="InputProvider" type="Node" parent="."] script = ExtResource("8_i6wc8") +[node name="MouseAimProvider" type="Node2D" parent="InputProvider"] +script = ExtResource("20_3rkrn") + [node name="CrosshairProvider" type="Node2D" parent="." node_paths=PackedStringArray("StorageModule", "AnimatedSprite")] script = ExtResource("9_s0ir4") StorageModule = NodePath("../Storage") diff --git a/Scenes/Barrel.cs b/Scenes/Barrel.cs index 23ca349d..e4d010f4 100644 --- a/Scenes/Barrel.cs +++ b/Scenes/Barrel.cs @@ -80,7 +80,7 @@ public partial class Barrel : Area2D, IDestructible if (ExplosionData == null) return; - var explosion = PoolingManager.Instance.SpawnBullet(ExplosionData); + var explosion = PoolingManager.Instance.SpawnBullet(ExplosionData); explosion.GlobalPosition = this.GlobalPosition; //var explosion = this.CreateSibling(ExplosionData.BulletScene); diff --git a/Scenes/test.tscn b/Scenes/test.tscn index 24ca0c06..7e725f7d 100644 --- a/Scenes/test.tscn +++ b/Scenes/test.tscn @@ -1313,7 +1313,7 @@ position = Vector2(-2000, -736) [node name="ControlPad8" parent="Parallax2D/Factory Tilemaps/LevelProps" node_paths=PackedStringArray("Targets") instance=ExtResource("12_hfkf1")] position = Vector2(-2027, -735) Targets = [NodePath("../HorizontalForceField")] -Requirements = Array[ExtResource("36_pt47r")]([ExtResource("84_ma1ta")]) +Requirements = [ExtResource("84_ma1ta")] [node name="Ammo6" parent="Parallax2D/Factory Tilemaps/LevelProps" instance=ExtResource("34_17pjh")] position = Vector2(-872, -220) diff --git a/Scripts/Bullet.cs b/Scripts/Bullet.cs index a99f60aa..0ede77e9 100644 --- a/Scripts/Bullet.cs +++ b/Scripts/Bullet.cs @@ -7,10 +7,11 @@ using Cirno.Scripts; using Cirno.Scripts.Components; using Cirno.Scripts.Controllers; using Cirno.Scripts.Resources; +using Cirno.Scripts.Weapons; -public partial class Bullet : Area2D +public partial class Bullet : Area2D, IBullet { - [Export] public float Speed = 1900f; + [Export] public float Speed { get; set; } = 1900f; public BulletOwner BulletOwner => _bulletInfo?.Owner ?? BulletOwner.None; @@ -336,21 +337,4 @@ public partial class Bullet : Area2D //QueueFree(); PoolingManager.Instance.DisableBullet(this); } -} - -public enum BulletOwner -{ - None, - Player, - Enemy -} - -public enum DamageType -{ - Neutral, - Ballistic, - Fire, - Ice, - Explosive, - Acid } \ No newline at end of file diff --git a/Scripts/BulletOwner.cs b/Scripts/BulletOwner.cs new file mode 100644 index 00000000..31d8b4b6 --- /dev/null +++ b/Scripts/BulletOwner.cs @@ -0,0 +1,6 @@ +public enum BulletOwner +{ + None, + Player, + Enemy +} \ No newline at end of file diff --git a/Scripts/BulletOwner.cs.uid b/Scripts/BulletOwner.cs.uid new file mode 100644 index 00000000..73e51699 --- /dev/null +++ b/Scripts/BulletOwner.cs.uid @@ -0,0 +1 @@ +uid://c6d6jblnwc5uy diff --git a/Scripts/Components/Actors/IMouseAimProvider.cs b/Scripts/Components/Actors/IMouseAimProvider.cs new file mode 100644 index 00000000..94b2b589 --- /dev/null +++ b/Scripts/Components/Actors/IMouseAimProvider.cs @@ -0,0 +1,9 @@ + +using Godot; + +namespace Cirno.Scripts.Components.Actors; + +public interface IMouseAimProvider +{ + public Vector2 GetMouseAimInput(); +} \ No newline at end of file diff --git a/Scripts/Components/Actors/IMouseAimProvider.cs.uid b/Scripts/Components/Actors/IMouseAimProvider.cs.uid new file mode 100644 index 00000000..33b56a9f --- /dev/null +++ b/Scripts/Components/Actors/IMouseAimProvider.cs.uid @@ -0,0 +1 @@ +uid://mr42tbagcs8t diff --git a/Scripts/Components/Actors/KeyboardInputProvider.cs b/Scripts/Components/Actors/KeyboardInputProvider.cs index 171bfda8..7ad5b787 100644 --- a/Scripts/Components/Actors/KeyboardInputProvider.cs +++ b/Scripts/Components/Actors/KeyboardInputProvider.cs @@ -6,28 +6,19 @@ namespace Cirno.Scripts.Components.Actors; public partial class KeyboardInputProvider : InputProvider { - [ExportCategory("Movement")] - [Export] - public StringName LeftAxisName { get; private set; } = "left"; - [Export] - public StringName RightAxisName { get; private set; } = "right"; - [Export] - public StringName UpAxisName { get; private set; } = "up"; - [Export] - public StringName DownAxisName { get; private set; } = "down"; + [ExportCategory("Movement")] [Export] public StringName LeftAxisName { get; private set; } = "left"; + [Export] public StringName RightAxisName { get; private set; } = "right"; + [Export] public StringName UpAxisName { get; private set; } = "up"; + [Export] public StringName DownAxisName { get; private set; } = "down"; - [ExportCategory("Aiming")] - [Export] - public StringName LeftAimName { get; private set; } = "aim_left"; - [Export] - public StringName RightAimName { get; private set; } = "aim_right"; - [Export] - public StringName UpAimName { get; private set; } = "aim_up"; - [Export] - public StringName DownAimName { get; private set; } = "aim_down"; + [ExportCategory("Aiming")] [Export] public StringName LeftAimName { get; private set; } = "aim_left"; + [Export] public StringName RightAimName { get; private set; } = "aim_right"; + [Export] public StringName UpAimName { get; private set; } = "aim_up"; + [Export] public StringName DownAimName { get; private set; } = "aim_down"; + + [ExportCategory("Action Names")] [Export] + private StringName _shootActionName = "shoot"; - [ExportCategory("Action Names")] - [Export] private StringName _shootActionName = "shoot"; [Export] private StringName _useActionName = "Use"; [Export] private StringName _scanActionName = "scan"; [Export] private StringName _strafeActionName = "strafe"; @@ -38,7 +29,14 @@ public partial class KeyboardInputProvider : InputProvider [Export] private StringName _freezeActionName = "Freeze"; [Export] private StringName _reloadActionName = "Reload"; - private enum AimInputMethod { RightStick, Mouse } + private IMouseAimProvider _mouseAImProvider; + + private enum AimInputMethod + { + RightStick, + Mouse + } + private AimInputMethod _lastUsedInput = AimInputMethod.RightStick; public override void _Ready() @@ -49,11 +47,19 @@ public partial class KeyboardInputProvider : InputProvider private void DelayedRegisterGameManager() { + _mouseAImProvider = GetNodeOrNull("MouseAimProvider"); + + if (_mouseAImProvider is null) + { + GD.Print("Mouse aim provider is null"); + } + if (GameManager.Instance is null) { GD.Print("No GameManager found for keyboard inputprovider"); return; } + GameManager.Instance.GameStateChange += InstanceOnGameStateChange; _enabled = true; } @@ -63,7 +69,7 @@ public partial class KeyboardInputProvider : InputProvider private void InstanceOnGameStateChange(GameState state) { if (state is not GameState.Playing) return; - + _enabled = false; _ = DelayResume(); @@ -75,7 +81,7 @@ public partial class KeyboardInputProvider : InputProvider await Task.Delay(200); _enabled = true; } - + public override Vector2 GetMovementInput() { return Input.GetVector(LeftAxisName, RightAxisName, UpAxisName, DownAxisName); @@ -98,26 +104,27 @@ public partial class KeyboardInputProvider : InputProvider return _lastUsedInput == AimInputMethod.RightStick ? rightStickInput : mouseInput; } - + private Vector2 GetRightStickInput() { return new Vector2( - Input.GetAxis(LeftAimName,RightAimName), + Input.GetAxis(LeftAimName, RightAimName), Input.GetAxis(UpAimName, DownAimName) ); } - + private Vector2 GetMouseAimInput() { - //Camera2D camera = GetViewport().GetCamera2D(); - //if (camera == null) return Vector2.Zero; // Ensure there's a valid camera - - //Vector2 mouseScreenPos = GetViewport().get_local_mouse_position(); - if (GameManager.Instance is null) return Vector2.Zero; - - Vector2 mouseWorldPos = DisplayServer.MouseGetPosition();// GameManager.Instance.GetGlobalMousePosition(); - - return mouseWorldPos - GameManager.Instance.PlayerPosition.Value; // Get direction vector + return _mouseAImProvider?.GetMouseAimInput() ?? Vector2.Zero; + // //Camera2D camera = GetViewport().GetCamera2D(); + // //if (camera == null) return Vector2.Zero; // Ensure there's a valid camera + // + // //Vector2 mouseScreenPos = GetViewport().get_local_mouse_position(); + // if (GameManager.Instance is null) return Vector2.Zero; + // + // Vector2 mouseWorldPos = DisplayServer.MouseGetPosition();// GameManager.Instance.GetGlobalMousePosition(); + // + // return mouseWorldPos - GameManager.Instance.PlayerPosition.Value; // Get direction vector } public override bool GetActionJustPressed(string action) @@ -134,12 +141,12 @@ public partial class KeyboardInputProvider : InputProvider { return GetActionJustPressed(_inventoryActionName); } - + public override bool GetShootPressed() { return _enabled && GetActionPressed(_shootActionName); } - + public override bool GetShootJustPressed() { return GetActionJustPressed(_shootActionName); @@ -149,6 +156,7 @@ public partial class KeyboardInputProvider : InputProvider { return GetActionJustPressed(_useActionName); } + public override bool GetScanJustPressed() { return GetActionJustPressed(_scanActionName); @@ -173,25 +181,24 @@ public partial class KeyboardInputProvider : InputProvider { return GetActionJustPressed(_pauseActionName); } - + public override bool GetFreezeJustPressed() { return GetActionJustPressed(_freezeActionName); } - + public override bool GetFreezePressed() { return GetActionPressed(_freezeActionName); } - + public override bool GetReloadJustPressed() { return GetActionJustPressed(_reloadActionName); } - + public override bool GetReloadPressed() { return GetActionPressed(_reloadActionName); } - } \ No newline at end of file diff --git a/Scripts/Components/Actors/MouseAimProvider2D.cs b/Scripts/Components/Actors/MouseAimProvider2D.cs new file mode 100644 index 00000000..fd192e4b --- /dev/null +++ b/Scripts/Components/Actors/MouseAimProvider2D.cs @@ -0,0 +1,13 @@ +using Godot; + +namespace Cirno.Scripts.Components.Actors; + +public partial class MouseAimProvider2D : Node2D, IMouseAimProvider +{ + public Vector2 GetMouseAimInput() + { + var mouseWorldPos = this.GetGlobalMousePosition(); + + return mouseWorldPos - this.GlobalPosition; + } +} \ No newline at end of file diff --git a/Scripts/Components/Actors/MouseAimProvider2D.cs.uid b/Scripts/Components/Actors/MouseAimProvider2D.cs.uid new file mode 100644 index 00000000..10343905 --- /dev/null +++ b/Scripts/Components/Actors/MouseAimProvider2D.cs.uid @@ -0,0 +1 @@ +uid://bfmnmk0rfwa1i diff --git a/Scripts/Components/Actors/MouseAimProvider3D.cs b/Scripts/Components/Actors/MouseAimProvider3D.cs new file mode 100644 index 00000000..d0edd5af --- /dev/null +++ b/Scripts/Components/Actors/MouseAimProvider3D.cs @@ -0,0 +1,16 @@ +using Cirno.Scripts.Misc; +using Godot; + +namespace Cirno.Scripts.Components.Actors; + +public partial class MouseAimProvider3D : Node3D, IMouseAimProvider +{ + public Vector2 GetMouseAimInput() + { + Vector2 mouseWorldPos = DisplayServer.MouseGetPosition(); + + var screenPosition = CameraController3D.Instance.UnprojectPosition(this.GlobalPosition); + + return mouseWorldPos - screenPosition; + } +} \ No newline at end of file diff --git a/Scripts/Components/Actors/MouseAimProvider3D.cs.uid b/Scripts/Components/Actors/MouseAimProvider3D.cs.uid new file mode 100644 index 00000000..057e1173 --- /dev/null +++ b/Scripts/Components/Actors/MouseAimProvider3D.cs.uid @@ -0,0 +1 @@ +uid://1fryvj4omkin diff --git a/Scripts/Components/BulletSpawner.cs b/Scripts/Components/BulletSpawner.cs index 8c9b13a1..c507c788 100644 --- a/Scripts/Components/BulletSpawner.cs +++ b/Scripts/Components/BulletSpawner.cs @@ -24,7 +24,7 @@ public partial class BulletSpawner : Node2D for (int i = 0; i < bulletInfo.BulletCount; i++) { // bullet = this.CreateChildOf(_gameManager.BulletsContainer, bulletScene, bulletInfo.Position); - bullet = PoolingManager.Instance.SpawnBullet(bulletInfo.OriginalBulletResource); + bullet = PoolingManager.Instance.SpawnBullet(bulletInfo.OriginalBulletResource); bullet.GlobalPosition = bulletInfo.Position; // var bullet = this.CreateChildOf(_gameManager.BulletsContainer, bulletInfo.BulletScene ?? BulletScene, bulletInfo.Position); diff --git a/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs b/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs index b2cefba9..3a2b454a 100644 --- a/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs +++ b/Scripts/Components/FSM/3DPlayer/IsoMovementModule.cs @@ -22,7 +22,6 @@ public partial class IsoMovementModule : ModuleBase _isStrafing ? StrafeSpeed : Speed; private IStateMachine _stateMachine; - private CharacterBody3D MainObject => _stateMachine.MainObject; public override void EnterState(PlayerState state) diff --git a/Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs b/Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs new file mode 100644 index 00000000..7df826e7 --- /dev/null +++ b/Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs @@ -0,0 +1,70 @@ +using Cirno.Scripts.Components.Actors; +using Godot; + +namespace Cirno.Scripts.Components.FSM._3DPlayer; + +public partial class PlayerWeaponModule3D : ModuleBase +{ + [Export] public PlayerWeaponProvider3D WeaponProvider { get; private set; } + + [Export] public InputProvider InputProvider { get; private set; } + [Export] public IsoPlayerStorageModule Storage { get; private set; } + + private IStateMachine _stateMachine; + private CharacterBody3D MainObject => _stateMachine.MainObject; + + public override void EnterState(PlayerState state) + { + + } + + public override void ExitState(PlayerState state) + { + + } + + public override void Init(IStateMachine machine) + { + _stateMachine = machine; + + WeaponProvider.Init(MainObject); + } + + public override void Process(double delta) + { + WeaponProvider.Update(delta); + + HandleShoot(); + + HandleWeaponSwitch(); + } + + public override void PhysicsProcess(double delta) + { + + } + + private void HandleShoot() + { + if (InputProvider.GetReloadJustPressed()) + { + WeaponProvider.Reload(); + return; + } + + if (!InputProvider.GetShootPressed()) return; + WeaponProvider.Shoot(Storage.AimingDirection); + } + + private void HandleWeaponSwitch() + { + if (InputProvider.GetWeaponNextJustPressed()) + { + WeaponProvider.NextWeapon(); + } + else if (InputProvider.GetWeaponPreviousJustPressed()) + { + WeaponProvider.PreviousWeapon(); + } + } +} \ No newline at end of file diff --git a/Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs.uid b/Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs.uid new file mode 100644 index 00000000..01a244e8 --- /dev/null +++ b/Scripts/Components/FSM/3DPlayer/PlayerWeaponModule3D.cs.uid @@ -0,0 +1 @@ +uid://d2psafx4f3f58 diff --git a/Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs b/Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs new file mode 100644 index 00000000..4f656dc5 --- /dev/null +++ b/Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs @@ -0,0 +1,286 @@ +using System; +using System.Linq; +using Cirno.Scripts.Resources; +using Cirno.Scripts.Weapons; +using Godot; +using Godot.Collections; + +namespace Cirno.Scripts.Components.FSM._3DPlayer; + +public partial class PlayerWeaponProvider3D : Node +{ + [Export] public IsoPlayerStorageModule StorageModule { get; set; } + [Export] public PackedScene WeaponTemplate { get; private set; } + + [Export] public double WeaponSwitchCooldown { get; private set; } = 0.5d; + + [Export] public Marker3D WeaponRightOffset { get; private set; } // local offset when facing right + [Export] public Marker3D WeaponLeftOffset { get; private set; } // local offset when facing left + + public Array EquippedWeapons { get; set; } = []; + + private int _currentWeaponIndex = 0; + + private double _switchCooldown = 0d; + private bool _switching = false; + + private int CurrentWeaponIndex + { + get => Math.Clamp(_currentWeaponIndex, 0, EquippedWeapons.Count -1); + + set + { + if (value > EquippedWeapons.Count - 1) + { + _currentWeaponIndex = 0; + return; + } + + if (value < 0) + { + _currentWeaponIndex = EquippedWeapons.Count - 1; + return; + } + + _currentWeaponIndex = value; + } + } + + + + public Weapon3D EquippedWeapon { get; set; } + + private CharacterBody3D _parent; + + public void Init(CharacterBody3D parent) + { + _parent = parent; + + InventoryManager.Instance.WeaponEquip += this.OnInventoryWeaponEquipped; + + InventoryManager.Instance.ItemAdded += OnInventoryWeaponAdded; + + EquipStartupWeapon(); + } + + public void Update(double delta) + { + RotateWeapon(); + + if (!_switching) return; + _switchCooldown += delta; + if (_switchCooldown >= WeaponSwitchCooldown) + { + _switching = false; + _switchCooldown = 0d; + } + } + + private void RotateWeapon() + { + if (EquippedWeapon is null) return; + + //EquippedWeapon.RotateWeapon(StorageModule.FacingDirection, WeaponLeftOffset.Position, WeaponRightOffset.Position); + + + + // EquippedWeapon.SetRotation(angle + Mathf.Pi / 2.0f); + // + // + // + // EquippedWeapon.FlipH = facingLeft; + // + // // 3. Position on correct side (assuming EquippedWeapon is a child of the Player node) + // EquippedWeapon.Position = facingLeft ? WeaponLeftOffset : WeaponRightOffset; + } + + private void OnInventoryWeaponEquipped(string itemKey) + { + Equip(itemKey, true); + } + + private void OnInventoryWeaponAdded(LootItem item, int amount) + { + if (item.Item is not ItemTypes.Weapon) return; + Equip(item, false); + } + + private void EquipStartupWeapon() + { + if (EquippedWeapon is not null) return; + if (!string.IsNullOrWhiteSpace(GlobalState.Session.EquippedWeaponId)) + { + // equip it + Equip(GlobalState.Session.EquippedWeaponId, false); + } + else + { + // Try to equip whatever is first + var weaponData = InventoryManager.Instance.Items.FirstOrDefault(x => x.Item.Item is ItemTypes.Weapon); + if (weaponData is null) return; + + Equip(weaponData.Item.ItemKey, false); + + } + } + + // This is a soft equip + public void AddWeapon(Weapon3D weapon) + { + EquippedWeapons.Add(weapon); + } + + // Triggered by event in inventorymanager + private void EquipWeapon(Weapon3D weapon) + { + if (EquippedWeapon == weapon) + { + return; + } + + // Need to start cooldown + EquippedWeapon?.Hide(); + + EquippedWeapon = weapon; + CurrentWeaponIndex = EquippedWeapons.IndexOf(weapon); + GlobalState.Session.EquippedWeaponId = weapon.WeaponData.ItemKey; + + EquippedWeapon.Show(); + + _switching = true; + _switchCooldown = 0d; + + InventoryManager.Instance.UpdateEquippedWeapon(weapon.WeaponData.ItemKey); + } + + public void NextWeapon() + { + CurrentWeaponIndex += 1; + + Equip(EquippedWeapons[CurrentWeaponIndex], true); + } + + public void PreviousWeapon() + { + CurrentWeaponIndex -= 1; + + Equip(EquippedWeapons[CurrentWeaponIndex], true); + } + + public void Shoot(Vector2 direction) + { + if (EquippedWeapon == null) return; + if (_switching) return; + + EquippedWeapon.ShootDirection = direction; + EquippedWeapon.Shoot(); + } + + public void Reload() + { + if (EquippedWeapon == null) return; + if (_switching) return; + + EquippedWeapon.Reload(); + } + + // Remastered method + private LootItem GetItemFromInventory(string itemKey) + { + return InventoryManager.Instance.Items.FirstOrDefault(x => x.Item.ItemKey == itemKey)?.Item; + } + + private Weapon3D GetWeaponFromLocal(string itemKey) + { + return EquippedWeapons.FirstOrDefault(x => x.WeaponData.ItemKey == itemKey); + } + + // Remastered method + private Weapon3D SpawnWeapon(string itemKey) + { + return SpawnWeapon(GetItemFromInventory(itemKey)); + } + + // Remastered method + private Weapon3D SpawnWeapon(LootItem startingItem) + { + if (startingItem is null) + { + GD.Print($"Could not spawn weapon was not in the inventory."); + return null; + } + + if (WeaponTemplate == null) + { + GD.Print("Could not spawn weapon because template is null"); + return null; + } + + // Check if it's not spawned already + var maybeExistingWeapon = GetWeaponFromLocal(startingItem.ItemKey); + if (maybeExistingWeapon is not null) return maybeExistingWeapon; + + var weapon = WeaponTemplate.Instantiate(); + this.AddChild(weapon); + //this.CreateSibling(WeaponTemplate); + weapon.WeaponData = startingItem.WeaponData; + + weapon.Sprite.Texture = startingItem.InventorySprite; + + this.AddWeapon(weapon); + return weapon; + } + + public Weapon3D Equip(LootItem item, bool force) + { + var maybeExistingWeapon = GetWeaponFromLocal(item.ItemKey); + if (maybeExistingWeapon is not null) return Equip(maybeExistingWeapon, force); + + // Spawn if not present + var spawnedWeapon = SpawnWeapon(item); + return Equip(spawnedWeapon, force); + } + + public Weapon3D Equip(string itemKey, bool force) + { + // Check in local inventory first + var maybeExistingWeapon = GetWeaponFromLocal(itemKey); + if (maybeExistingWeapon is not null) return Equip(maybeExistingWeapon, force); + + // Spawn if not present + var spawnedWeapon = SpawnWeapon(itemKey); + if (spawnedWeapon is null) + { + GD.Print($"Tried to spawn weapon {itemKey} but failed because it was null."); + return null; + }; + return Equip(spawnedWeapon, force); + } + + public Weapon3D Equip(Weapon3D weapon, bool force) + { + // When we get here we already have a spawned weapon and it's in the list + + if (weapon is null) + { + return null; + } + + // Always equip it if there's nothing equipped + if (this.EquippedWeapon is null) + { + this.EquipWeapon(weapon); + return weapon; + } + + // If it's a soft equip check for priority + if (!force && this.EquippedWeapon.WeaponData.Priority < weapon.WeaponData.Priority) + { + this.EquipWeapon(weapon); + return weapon; + } + + EquipWeapon(weapon); + return weapon; + } +} \ No newline at end of file diff --git a/Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs.uid b/Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs.uid new file mode 100644 index 00000000..a91dd66b --- /dev/null +++ b/Scripts/Components/FSM/3DPlayer/PlayerWeaponProvider3D.cs.uid @@ -0,0 +1 @@ +uid://by0x0qmbmkoak diff --git a/Scripts/Components/FSM/Player/FreezeModule.cs b/Scripts/Components/FSM/Player/FreezeModule.cs index f0a02ea8..1ea0942e 100644 --- a/Scripts/Components/FSM/Player/FreezeModule.cs +++ b/Scripts/Components/FSM/Player/FreezeModule.cs @@ -75,6 +75,7 @@ public partial class FreezeModule : ModuleBase private List GetNearbyBullets() { return (from child in PoolingManager.Instance.GetAllActiveBullets() + .Cast() where child is not null where child.Enabled // Could be redundant but better check in case of errors where child.BulletOwner is not BulletOwner.Player diff --git a/Scripts/Controllers/PoolingManager.cs b/Scripts/Controllers/PoolingManager.cs index 567a14ef..1a051824 100644 --- a/Scripts/Controllers/PoolingManager.cs +++ b/Scripts/Controllers/PoolingManager.cs @@ -2,12 +2,13 @@ using System.Collections.Immutable; using System.Linq; using Cirno.Scripts.Resources; +using Cirno.Scripts.Weapons; using Godot; using Godot.Collections; namespace Cirno.Scripts.Controllers; -public partial class PoolingManager : Node2D +public partial class PoolingManager : Node { public static PoolingManager Instance { get; private set; } @@ -15,10 +16,10 @@ public partial class PoolingManager : Node2D [Export] public bool DebugView { get; private set; } = false; - private readonly System.Collections.Generic.Dictionary> + private readonly System.Collections.Generic.Dictionary> _activeBullets = new(); - private readonly System.Collections.Generic.Dictionary> + private readonly System.Collections.Generic.Dictionary> _inactiveBullets = new(); public override void _Ready() @@ -26,7 +27,7 @@ public partial class PoolingManager : Node2D Instance = this; } - public Bullet SpawnBullet(BulletResource bulletResource, bool active = true) + public IBullet SpawnBullet(BulletResource bulletResource, bool active = true) { // Look for bullet among the inactive ones // If present move it to active, set it as active and return it @@ -53,27 +54,32 @@ public partial class PoolingManager : Node2D return bullet; } - public IEnumerable GetAllActiveBullets() + public T SpawnBullet(BulletResource bulletResource, bool active = true) where T : IBullet + { + return (T)SpawnBullet(bulletResource, active); + } + + public IEnumerable GetAllActiveBullets() { return _activeBullets.Values.SelectMany(list => list); } - public IEnumerable GetAllInActiveBullets() + public IEnumerable GetAllInActiveBullets() { return _activeBullets.Values.SelectMany(list => list); } - public List GetActiveBulletsList(BulletResource resource) + public List GetActiveBulletsList(BulletResource resource) { return GetOrCreateList(_activeBullets, resource); } - private List GetInactiveBulletsList(BulletResource resource) + private List GetInactiveBulletsList(BulletResource resource) { return GetOrCreateList(_inactiveBullets, resource); } - public void DisableBullet(Bullet bullet) + public void DisableBullet(IBullet bullet) { var activeBulletsList = GetActiveBulletsList(bullet.BulletInfo.OriginalBulletResource); @@ -106,7 +112,7 @@ public partial class PoolingManager : Node2D } - private List GetOrCreateList(System.Collections.Generic.Dictionary> dict, BulletResource resource) + private List GetOrCreateList(System.Collections.Generic.Dictionary> dict, BulletResource resource) { if (dict.TryGetValue(resource, out var list)) return list; list = []; @@ -116,9 +122,11 @@ public partial class PoolingManager : Node2D - private Bullet InstantiateBullet(BulletResource bulletData) + private IBullet InstantiateBullet(BulletResource bulletData) { - var bullet = this.CreateChild(bulletData.BulletScene); + var bullet = bulletData.BulletScene.Instantiate(); + this.AddChild(bullet); + //this.CreateChild(bulletData.BulletScene); return bullet; } diff --git a/Scripts/DamageType.cs b/Scripts/DamageType.cs new file mode 100644 index 00000000..74ec0283 --- /dev/null +++ b/Scripts/DamageType.cs @@ -0,0 +1,9 @@ +public enum DamageType +{ + Neutral, + Ballistic, + Fire, + Ice, + Explosive, + Acid +} \ No newline at end of file diff --git a/Scripts/DamageType.cs.uid b/Scripts/DamageType.cs.uid new file mode 100644 index 00000000..16801abe --- /dev/null +++ b/Scripts/DamageType.cs.uid @@ -0,0 +1 @@ +uid://bwmkakrqrb8w1 diff --git a/Scripts/InventoryManager.cs b/Scripts/InventoryManager.cs index 34e92fc5..aa2a7aea 100644 --- a/Scripts/InventoryManager.cs +++ b/Scripts/InventoryManager.cs @@ -5,13 +5,11 @@ using System.Linq; using Cirno.Scripts; using Cirno.Scripts.Resources; -public partial class InventoryManager : Node2D +public partial class InventoryManager : Node { public static InventoryManager Instance { get; private set; } public ItemsDatabase ItemsDatabase { get; set; } - - public bool RedKeycard { get; set; } private Dictionary _itemsDict = new(); diff --git a/Scripts/PlayerMovement.cs b/Scripts/PlayerMovement.cs index 74530771..cd596ffc 100644 --- a/Scripts/PlayerMovement.cs +++ b/Scripts/PlayerMovement.cs @@ -232,7 +232,7 @@ public partial class PlayerMovement : CharacterBody2D, IDestructible // emit projectile var bulletData = item.WeaponData.MakeBullet(this.GlobalPosition); - var bullet = PoolingManager.Instance.SpawnBullet(bulletData.OriginalBulletResource); + var bullet = PoolingManager.Instance.SpawnBullet(bulletData.OriginalBulletResource); bullet.GlobalPosition = this.GlobalPosition; diff --git a/Scripts/Resources/ItemEffects/SpiderbombEffectResource.cs b/Scripts/Resources/ItemEffects/SpiderbombEffectResource.cs index 0f326624..24673984 100644 --- a/Scripts/Resources/ItemEffects/SpiderbombEffectResource.cs +++ b/Scripts/Resources/ItemEffects/SpiderbombEffectResource.cs @@ -18,7 +18,7 @@ public partial class SpiderbombEffectResource : ItemEffectResource public IITemEffectMachine Execute() { - var bullet = PoolingManager.Instance.SpawnBullet(item.WeaponData.BulletData); + var bullet = PoolingManager.Instance.SpawnBullet(item.WeaponData.BulletData); bullet.GlobalPosition = parent.Machine.MainObject.GlobalPosition; //var bullet = parent.CreateChildOf(GameManager.Instance.BulletsContainer, item.WeaponData.BulletData.BulletScene, parent.GlobalPosition); diff --git a/Scripts/Utils/VectorExtensions.cs b/Scripts/Utils/VectorExtensions.cs new file mode 100644 index 00000000..9b0b7fa4 --- /dev/null +++ b/Scripts/Utils/VectorExtensions.cs @@ -0,0 +1,11 @@ +using Godot; + +namespace Cirno.Scripts.Utils; + +public static class VectorExtensions +{ + public static Vector2 ToVector2(this Vector3 original) + { + return new Vector2(original.X, original.Z); + } +} \ No newline at end of file diff --git a/Scripts/Utils/VectorExtensions.cs.uid b/Scripts/Utils/VectorExtensions.cs.uid new file mode 100644 index 00000000..cfac514e --- /dev/null +++ b/Scripts/Utils/VectorExtensions.cs.uid @@ -0,0 +1 @@ +uid://b2r4bucv0o1ui diff --git a/Scripts/Weapon.cs b/Scripts/Weapon.cs index 0e1a3678..a54941f9 100644 --- a/Scripts/Weapon.cs +++ b/Scripts/Weapon.cs @@ -165,7 +165,7 @@ public partial class Weapon : Node2D // Rotate the ShootDirection by the spread angle Vector2 spreadDirection = ShootDirection.Rotated(Mathf.DegToRad(spreadOffset)); - var bullet = PoolingManager.Instance.SpawnBullet(WeaponData.BulletData); + var bullet = PoolingManager.Instance.SpawnBullet(WeaponData.BulletData); bullet.GlobalPosition = _muzzle.GlobalPosition; //var bullet = this.CreateChildOf(_gameManager.BulletsContainer, WeaponData.BulletData.BulletScene, _muzzle.GlobalPosition); diff --git a/Scripts/Weapons/Bullet3D.cs b/Scripts/Weapons/Bullet3D.cs new file mode 100644 index 00000000..551a571d --- /dev/null +++ b/Scripts/Weapons/Bullet3D.cs @@ -0,0 +1,310 @@ +using System.Collections.Generic; +using System.Linq; +using Cirno.Scripts.Components; +using Cirno.Scripts.Controllers; +using Cirno.Scripts.Resources; +using Godot; + +namespace Cirno.Scripts.Weapons; + +public partial class Bullet3D : Area3D, IBullet +{ + [Export] public float Speed { get; set; } = 1900f; + + public BulletOwner BulletOwner => _bulletInfo?.Owner ?? BulletOwner.None; + + public float Damage => _bulletInfo?.Damage ?? 1; + + public DamageType DamageType => _bulletInfo?.DamageType ?? DamageType.Neutral; + + protected Vector2 _direction = Vector2.Right; + + private double _elapsedTime = 0f; + private BulletInfo _bulletInfo; + + public BulletInfo BulletInfo => _bulletInfo; + + private List _modifiers = new(); + + private GameManager _gameManager; + + public bool IsGrazed { get; set; } = false; + + public bool IsFrozen { get; private set; } = false; + public bool Enabled { get; private set; } = false; + + [Signal] + public delegate void OnDestroyEventHandler(); + + private AudioStreamPlayer2D _grazeSound; + private GpuParticles2D _grazeParticles; + + private CollisionShape2D _collisionShape2D; + + public override void _Ready() + { + _grazeSound = GetNodeOrNull("AudioStreamPlayer2D"); + _grazeParticles = GetNodeOrNull("GrazeParticles"); + + _collisionShape2D = GetNode("CollisionShape2D"); + } + + public void Initialize(BulletInfo bulletInfo, GameManager gameManager) + { + _bulletInfo = bulletInfo; + + _gameManager = gameManager; + + _elapsedTime = 0f; + + this.Speed = bulletInfo.Speed; + + // Need to clone them here + // _modifiers = _bulletInfo.TimeModifiers.Select(x => x.MakeClone()).ToList(); + + + // var clonedModifiers = _bulletInfo.TimeModifiers.Select(x => x.MakeClone()); + // _modifiers = clonedModifiers.ToList(); + + // Ugly hack to make instances unique + _modifiers = _bulletInfo.TimeModifiers.Select(x => x.Wrap()).ToList(); + } + + /// + /// Enables the bullet, shows the sprite and activates collisions + /// + public void Enable() + { + Enabled = true; + Show(); + if (this._collisionShape2D is null) + { + _collisionShape2D = GetNode("CollisionShape2D"); + } + + _collisionShape2D.SetDeferred(CollisionShape2D.PropertyName.Disabled, false); + } + + /// + /// Disables the bullet, hides the sprite and disables collisions + /// + public void Disable(bool hideSprite = true) + { + Enabled = false; + if (hideSprite && !BulletInfo.Attributes.HasFlag(BulletFlags.PersistSprite)) + { + Hide(); + } + + if (this._collisionShape2D is null) + { + _collisionShape2D = GetNode("CollisionShape2D"); + } + + _collisionShape2D.SetDeferred(CollisionShape2D.PropertyName.Disabled, true); + } + + public void Graze() + { + if (!Enabled) return; + _grazeSound?.Play(); + if (_grazeParticles is not null) + { + _grazeParticles.Emitting = true; + } + + IsGrazed = true; + } + + private void ApplyTimeModifiers(double delta) + { + return; + // foreach (var modifier in _modifiers) + // { + // if (_elapsedTime >= modifier.TimeModifier.TimeInSeconds) + // { + // if (!modifier.Applied) + // { + // modifier.Applied = true; + // modifier.TimeModifier.Start(this); + // modifier.Elapsed = 0; + // } + // else + // { + // modifier.Elapsed += delta; + // } + // + // modifier.TimeModifier.Update(this, delta, modifier.Elapsed); + // + // } + // } + } + + public virtual void RotateBullet(float degrees) + { + float radians = Mathf.DegToRad(degrees); + _direction = _direction.Rotated(radians).Normalized(); // Rotate direction + + if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return; + //SetRotation(Rotation + radians); + } + + public virtual void RotateSpriteDegrees(float degrees) + { + if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return; + //SetRotationDegrees(RotationDegrees + degrees); + } + + public virtual void RotateSprite(float radians) + { + if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return; + //SetRotation(Rotation + radians); + } + + public void FacePlayer() + { + if (_gameManager.Player != null) + { + //_direction = (_gameManager.PlayerPosition.Value - this.GlobalPosition).Normalized(); + RotateBullet(0); // quick hack to rotate lasers + //LookAt(player.GlobalPosition); + } + } + + + public void SetDirection(Vector2 direction) + { + var normalized = direction.Normalized(); + + _direction = normalized; + + if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return; + //SetRotation(Mathf.Atan2(normalized.Y, normalized.X) + Mathf.Pi / 2); + + + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process(double delta) + { + if (!Enabled) return; + _elapsedTime += delta; + + if (_elapsedTime >= _bulletInfo.LifeTime) + { + Destroy(); + } + } + + public override void _PhysicsProcess(double delta) + { + if (!Enabled) return; + if (_bulletInfo != null) + { + ApplyTimeModifiers(delta); + } + + if (BulletInfo.Attributes.HasFlag(BulletFlags.Controllable)) + { + ControlBullet(delta); + } + + //this.Position += ((float)(Speed * delta) * _direction); + } + + private void ControlBullet(double delta) + { + if (!Enabled) return; + var axis = Input.GetAxis("left", "right"); + + if (axis != 0) + { + float rotationSpeed = 180f; // Degrees per second + RotateBullet(axis * rotationSpeed * (float)delta); + } + } + + private void _on_visible_on_screen_notifier_2d_screen_exited() + { + if (!Enabled) return; + if (!BulletInfo.Attributes.HasFlag(BulletFlags.DieOutOfScreen)) return; + //Debug.WriteLine("Destroy bullet out of screen"); + Destroy(); + } + + private void _on_body_entered(Node2D body) + { + if (body.IsInGroup("Solid")) + { + //Debug.WriteLine("Collision"); + RequestCollisionDestruction(); + } + //// Do not Collide with body for purpose of destroying bullets + // else if (body.IsInGroup("Destroyable")) + // { + // Debug.WriteLine("Collision with destroyable object body"); + // QueueFree(); + // } + } + + private void _on_area_entered(Area2D area) + { + if (area.IsInGroup("Solid")) + { + RequestCollisionDestruction(); + return; + } + + if (area.IsInGroup("Destroyable") && area is IDestructible destructible && + CanHit(BulletOwner, destructible.BulletGroup)) + { + // hit + destructible.Hit(Damage, DamageType); + + RequestCollisionDestruction(); + } + } + + public bool CanHit(BulletOwner bulletOwner, BulletOwner targetGroup) + { + // If either is None, it always hits + if (bulletOwner == BulletOwner.None || targetGroup == BulletOwner.None) + { + return true; + } + + // Otherwise, it hits only if they are different groups + return bulletOwner != targetGroup; + } + + public void RequestCollisionDestruction() + { + if (!Enabled) return; + if (_bulletInfo.DestroyOnCollision) + { + Destroy(); + } + } + + private void Destroy() + { + if (_bulletInfo?.DestructionParticlesScene != null) + { + //this.CreateSibling(_bulletInfo.DestructionParticlesScene); + + //particle.Init(); + } + + EmitSignal(Bullet.SignalName.OnDestroy); + //QueueFree(); + PoolingManager.Instance.DisableBullet(this); + } + + public void Freeze() + { + IsFrozen = true; + EmitSignal(Bullet.SignalName.OnDestroy); + //QueueFree(); + PoolingManager.Instance.DisableBullet(this); + } +} \ No newline at end of file diff --git a/Scripts/Weapons/Bullet3D.cs.uid b/Scripts/Weapons/Bullet3D.cs.uid new file mode 100644 index 00000000..379dac5c --- /dev/null +++ b/Scripts/Weapons/Bullet3D.cs.uid @@ -0,0 +1 @@ +uid://cg6y36s7buapp diff --git a/Scripts/Weapons/IBullet.cs b/Scripts/Weapons/IBullet.cs new file mode 100644 index 00000000..3e8c560a --- /dev/null +++ b/Scripts/Weapons/IBullet.cs @@ -0,0 +1,34 @@ +using Cirno.Scripts.Components; +using Godot; + +namespace Cirno.Scripts.Weapons; + +public interface IBullet +{ + public float Speed { get; set; } + public BulletOwner BulletOwner { get; } + public float Damage { get; } + public DamageType DamageType { get; } + public BulletInfo BulletInfo { get; } + + public bool IsGrazed { get; } + + public bool IsFrozen { get; } + public bool Enabled { get; } + + public delegate void OnDestroyEventHandler(); + + public void Initialize(BulletInfo bulletInfo, GameManager gameManager); + + public void Enable(); + public void Disable(bool hideSprite = true); + public void Graze(); + public void RotateBullet(float degrees); + public void RotateSpriteDegrees(float degrees); + public void RotateSprite(float radians); + public void FacePlayer(); + public void SetDirection(Vector2 direction); + public bool CanHit(BulletOwner bulletOwner, BulletOwner targetGroup); + public void RequestCollisionDestruction(); + public void Freeze(); +} \ No newline at end of file diff --git a/Scripts/Weapons/IBullet.cs.uid b/Scripts/Weapons/IBullet.cs.uid new file mode 100644 index 00000000..36a758eb --- /dev/null +++ b/Scripts/Weapons/IBullet.cs.uid @@ -0,0 +1 @@ +uid://58w1nidcpf2j diff --git a/Scripts/Weapons/Weapon3D.cs b/Scripts/Weapons/Weapon3D.cs new file mode 100644 index 00000000..e1448310 --- /dev/null +++ b/Scripts/Weapons/Weapon3D.cs @@ -0,0 +1,228 @@ +using Cirno.Scripts.Controllers; +using Cirno.Scripts.Resources; +using Cirno.Scripts.Utils; +using Godot; + +namespace Cirno.Scripts.Weapons; + +public partial class Weapon3D : Node +{ + [Export] + public WeaponResource WeaponData { get; set; } + + [Export] + public PackedScene BulletScene { get; set; } + + [Export] + public Marker3D Muzzle { get; set; } + + [Export] + public Marker3D Pivot { get; set; } + + [Export] + public Sprite3D Sprite { get; private set; } + + [Export] public StringName PowerKey { get; set; } = "POWER"; + + [Signal] + public delegate void ShootingEventHandler(); + + [Signal] + public delegate void ReloadingEventHandler(); + + [Signal] public delegate void EmptyEventHandler(); + + public int Ammo { get; set; } = 0; + + private int _loadedAmmo; + public int LoadedAmmo + { + get => _loadedAmmo; + private set + { + _loadedAmmo = value; + _inventoryManager?.NotifyLoadedAmmoChange(WeaponData?.ItemKey, _loadedAmmo); + } + } + + public Vector2 ShootDirection { get; set; } = Vector2.Zero; + + private Timer _cooldownTimer; + + private GameManager _gameManager; + + private InventoryManager _inventoryManager; + + private readonly StringName _shieldAmmoType = "SHIELD"; + private bool UsesBattery => WeaponData.AmmoKey == _shieldAmmoType; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + _cooldownTimer = GetNode("./ShootTimer"); + + // Start full + if (WeaponData != null) + { + LoadedAmmo = WeaponData.BulletCapacity; + } + } + + public void Reload() + { + EmitSignalReloading(); + + _cooldownTimer.Start(WeaponData.ReloadTime); + + if (WeaponData.InfiniteAmmo || string.IsNullOrWhiteSpace(WeaponData.AmmoKey)) + { + LoadedAmmo = WeaponData.BulletCapacity; + } + else + { + var ammoToLoad = _inventoryManager.RemoveItem(WeaponData.AmmoKey, WeaponData.BulletCapacity - LoadedAmmo); + + if (ammoToLoad > 0) + { + LoadedAmmo = ammoToLoad; + } + else + { + EmitSignalEmpty(); + //GD.Print("Out of ammo"); + } + } + } + + public void Shoot(BulletOwner? ownerOverride = null) + { + // Waiting on reload or Rate of Fire cooldown? + if (!_cooldownTimer.IsStopped()) + { + return; + } + + // Check for battery if it's used + if (UsesBattery) + { + if (GameManager.Instance.Player.Shield.CurrentResource >= WeaponData.AmmoPerShot) + { + GameManager.Instance.Player.Shield.CurrentResource -= WeaponData.AmmoPerShot; + } + else + { + EmitSignalEmpty(); + return; + } + } + + // Out of ammo? + if (LoadedAmmo < WeaponData.AmmoPerShot) + { + if (WeaponData.AutoReload) + { + Reload(); + } + EmitSignalEmpty(); + return; + } + + EmitSignalShooting(); + + // TODO: Shoot at muzzle position, need to provide a way to turn it, on a radius? + + float halfSpread = WeaponData.SpreadAngle / 2f; + float spreadStep = WeaponData.BulletsPerShot > 1 ? WeaponData.SpreadAngle / (WeaponData.BulletsPerShot - 1) : 0; + + for (int i = 0; i < WeaponData.BulletsPerShot; i++) + { + // Calculate angle offset for this bullet + float spreadOffset = -halfSpread + (spreadStep * i); + + // Add random spread + if (WeaponData.RandomSpread > 0) + { + // Gaussian with mean = 0, stddev = WeaponData.RandomSpread + spreadOffset += RandomStuff.GaussianClamped( + mean: 0f, + stdDev: WeaponData.RandomSpread, // tuning knob + min: -halfSpread, + max: halfSpread + ); + } + + // Rotate the ShootDirection by the spread angle + Vector2 spreadDirection = ShootDirection.Rotated(Mathf.DegToRad(spreadOffset)); + + // Restore pooling + var bullet = PoolingManager.Instance.SpawnBullet(WeaponData.BulletData); + + //var bullet = WeaponData.BulletData.BulletScene.Instantiate() + + bullet.GlobalPosition = Muzzle.GlobalPosition; + + //var bullet = this.CreateChildOf(_gameManager.BulletsContainer, WeaponData.BulletData.BulletScene, _muzzle.GlobalPosition); + + if (bullet == null) + { + GD.PrintErr("Bullet is null, not shooting"); + return; + }; + + var bulletData = WeaponData.MakeBullet(Muzzle.GlobalPosition.ToVector2()); // TODO: Fix for 3D + if (ownerOverride.HasValue) + { + bulletData.Owner = ownerOverride.Value; + } + + if (bulletData.Owner is BulletOwner.Player || ownerOverride is BulletOwner.Player) + { + // Apply the P multiplier + bulletData.Damage *= + GetBulletStrengthMultiplier(bulletData.Damage, bulletData.OriginalBulletResource.MaxDamage, 20); + } + + bullet.Initialize(bulletData, _gameManager); + + //bullet.SetDirection(ShootDirection); + bullet.SetDirection(spreadDirection); + bullet.Speed = WeaponData.BulletData.BulletSpeed; + } + + if (!UsesBattery) + { + LoadedAmmo -= WeaponData.AmmoPerShot; + } + + //_inventoryManager.NotifyLoadedAmmoChange(WeaponData.ItemKey, LoadedAmmo); + // if (!string.IsNullOrWhiteSpace(WeaponData?.AmmoKey)) + // { + // // Notify hud to decrease weapon + // + // } + + _cooldownTimer.Start(WeaponData?.RateOfFire ?? 0); + } + + private float GetBulletStrengthMultiplier(float baseDamage, float maxDamage, float maxPower) + { + var p = InventoryManager.Instance.GetItemCount(PowerKey); + + float minMultiplier = 1.0f; + float maxMultiplier = maxDamage / baseDamage; + + float normalizedPower = Mathf.Clamp((float)p / maxPower, 0f, 1f); + return Mathf.Lerp(minMultiplier, maxMultiplier, normalizedPower); + } + + public void Hide() + { + + } + + public void Show() + { + + } + +} \ No newline at end of file diff --git a/Scripts/Weapons/Weapon3D.cs.uid b/Scripts/Weapons/Weapon3D.cs.uid new file mode 100644 index 00000000..71eb55ef --- /dev/null +++ b/Scripts/Weapons/Weapon3D.cs.uid @@ -0,0 +1 @@ +uid://dutroqc0grqyv