mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-20 14:53:47 +00:00
Enhanced loot drops system
This commit is contained in:
parent
ddde409dbf
commit
acc61f9a0e
12 changed files with 661 additions and 443 deletions
|
|
@ -1227,7 +1227,7 @@
|
||||||
// entity 7
|
// entity 7
|
||||||
{
|
{
|
||||||
"classname" "marker_spawn_enemy_fairy_guard"
|
"classname" "marker_spawn_enemy_fairy_guard"
|
||||||
"origin" "-4 140 22"
|
"origin" "12 108 22"
|
||||||
}
|
}
|
||||||
// entity 8
|
// entity 8
|
||||||
{
|
{
|
||||||
|
|
@ -1626,3 +1626,38 @@
|
||||||
"origin" "-44 -132 22"
|
"origin" "-44 -132 22"
|
||||||
"resource_path" "res://Resources/Items/IceShotgun.tres"
|
"resource_path" "res://Resources/Items/IceShotgun.tres"
|
||||||
}
|
}
|
||||||
|
// entity 73
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "-16 72 20"
|
||||||
|
}
|
||||||
|
// entity 74
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "32 64 20"
|
||||||
|
}
|
||||||
|
// entity 75
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "-8 104 20"
|
||||||
|
}
|
||||||
|
// entity 76
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "40 104 20"
|
||||||
|
}
|
||||||
|
// entity 77
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "-24 88 20"
|
||||||
|
}
|
||||||
|
// entity 78
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "-8 104 28"
|
||||||
|
}
|
||||||
|
// entity 79
|
||||||
|
{
|
||||||
|
"classname" "actor_box_red"
|
||||||
|
"origin" "40 88 20"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
[gd_scene load_steps=9 format=3 uid="uid://jffyxmft3nbw"]
|
[gd_scene format=3 uid="uid://jffyxmft3nbw"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://ninnis3a3jbn" path="res://3D/BlockbenchModels/Box/Box_Red.gltf" id="1_fi646"]
|
[ext_resource type="PackedScene" uid="uid://ninnis3a3jbn" path="res://3D/BlockbenchModels/Box/Box_Red.gltf" id="1_fi646"]
|
||||||
[ext_resource type="Script" uid="uid://ccxnvbthsvka3" path="res://Scripts/Actors/Destructible3D.cs" id="1_kvqx7"]
|
[ext_resource type="Script" uid="uid://ccxnvbthsvka3" path="res://Scripts/Actors/Destructible3D.cs" id="1_kvqx7"]
|
||||||
[ext_resource type="Resource" uid="uid://bes254wblt1lm" path="res://Resources/Bullets/3D/Explosion_Harmless_Small_3D.tres" id="2_jolck"]
|
[ext_resource type="Resource" uid="uid://bes254wblt1lm" path="res://Resources/Bullets/3D/Explosion_Harmless_Small_3D.tres" id="2_jolck"]
|
||||||
[ext_resource type="Script" uid="uid://ddsqqfx1usc3j" path="res://Scripts/Resources/DamageResistance.cs" id="3_7px86"]
|
[ext_resource type="Script" uid="uid://ddsqqfx1usc3j" path="res://Scripts/Resources/DamageResistance.cs" id="3_7px86"]
|
||||||
|
[ext_resource type="Script" uid="uid://cq65aed620ijo" path="res://Scripts/Resources/Loot/LootDrop.cs" id="4_hlryi"]
|
||||||
[ext_resource type="Script" uid="uid://hkmutmmjqh1e" path="res://Scripts/Actors/3D/PropGravityModule3D.cs" id="5_qq3cg"]
|
[ext_resource type="Script" uid="uid://hkmutmmjqh1e" path="res://Scripts/Actors/3D/PropGravityModule3D.cs" id="5_qq3cg"]
|
||||||
|
[ext_resource type="Resource" uid="uid://dy53gia1tmkah" path="res://Resources/Items/Points_Pickup.tres" id="5_r3p83"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_7px86"]
|
[sub_resource type="Resource" id="Resource_7px86"]
|
||||||
script = ExtResource("3_7px86")
|
script = ExtResource("3_7px86")
|
||||||
|
|
@ -12,33 +14,41 @@ DamageType = 4
|
||||||
Attribute = 2
|
Attribute = 2
|
||||||
metadata/_custom_type_script = "uid://ddsqqfx1usc3j"
|
metadata/_custom_type_script = "uid://ddsqqfx1usc3j"
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Resource_fkd2x"]
|
||||||
|
script = ExtResource("4_hlryi")
|
||||||
|
Item = ExtResource("5_r3p83")
|
||||||
|
Chance = 100.0
|
||||||
|
Count = 4
|
||||||
|
metadata/_custom_type_script = "uid://cq65aed620ijo"
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_hsg1w"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_hsg1w"]
|
||||||
size = Vector3(0.763428, 0.469452, 0.77832)
|
size = Vector3(0.763428, 0.469452, 0.77832)
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_hlryi"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_hlryi"]
|
||||||
size = Vector3(0.60668945, 0.09439087, 0.595726)
|
size = Vector3(0.60668945, 0.09439087, 0.595726)
|
||||||
|
|
||||||
[node name="BoxRed" type="StaticBody3D" groups=["Destroyable"]]
|
[node name="BoxRed" type="StaticBody3D" unique_id=1876150911 groups=["Destroyable"]]
|
||||||
collision_layer = 16
|
collision_layer = 16
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
script = ExtResource("1_kvqx7")
|
script = ExtResource("1_kvqx7")
|
||||||
Health = 8.0
|
Health = 8.0
|
||||||
ExplosionData = ExtResource("2_jolck")
|
ExplosionData = ExtResource("2_jolck")
|
||||||
DamageResistances = Array[ExtResource("3_7px86")]([SubResource("Resource_7px86")])
|
DamageResistances = Array[ExtResource("3_7px86")]([SubResource("Resource_7px86")])
|
||||||
|
LootDrops = Array[ExtResource("4_hlryi")]([SubResource("Resource_fkd2x")])
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=169677373]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00549316, 0.0253752, -0.000488281)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00549316, 0.0253752, -0.000488281)
|
||||||
shape = SubResource("BoxShape3D_hsg1w")
|
shape = SubResource("BoxShape3D_hsg1w")
|
||||||
|
|
||||||
[node name="blockbench_export" parent="." instance=ExtResource("1_fi646")]
|
[node name="blockbench_export" parent="." unique_id=1273956257 instance=ExtResource("1_fi646")]
|
||||||
|
|
||||||
[node name="Node" type="Area3D" parent="."]
|
[node name="Node" type="Area3D" parent="." unique_id=1769689463]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 17
|
collision_mask = 17
|
||||||
script = ExtResource("5_qq3cg")
|
script = ExtResource("5_qq3cg")
|
||||||
FallingSpeed = -2.0
|
FallingSpeed = -2.0
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Node"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Node" unique_id=1831177562]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.024291992, -0.19599915, 0.013088226)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.024291992, -0.19599915, 0.013088226)
|
||||||
shape = SubResource("BoxShape3D_hlryi")
|
shape = SubResource("BoxShape3D_hlryi")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=5 format=3 uid="uid://c2enjjxlfb5or"]
|
[gd_scene format=3 uid="uid://c2enjjxlfb5or"]
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://du8xcvbnf30o2" path="res://ExternalMaterial/Barrel/Barrels.png" id="1_2libs"]
|
[ext_resource type="Texture2D" uid="uid://du8xcvbnf30o2" path="res://ExternalMaterial/Barrel/Barrels.png" id="1_2libs"]
|
||||||
[ext_resource type="Script" uid="uid://ccxnvbthsvka3" path="res://Scripts/Actors/Destructible3D.cs" id="1_gafco"]
|
[ext_resource type="Script" uid="uid://ccxnvbthsvka3" path="res://Scripts/Actors/Destructible3D.cs" id="1_gafco"]
|
||||||
|
|
@ -8,19 +8,20 @@
|
||||||
height = 0.564575
|
height = 0.564575
|
||||||
radius = 0.321777
|
radius = 0.321777
|
||||||
|
|
||||||
[node name="StaticBody3D" type="StaticBody3D" groups=["Destroyable"]]
|
[node name="StaticBody3D" type="StaticBody3D" unique_id=1481124290 groups=["Destroyable"]]
|
||||||
collision_layer = 16
|
collision_layer = 16
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
script = ExtResource("1_gafco")
|
script = ExtResource("1_gafco")
|
||||||
|
Health = 0.6
|
||||||
ExplosionData = ExtResource("2_g54tg")
|
ExplosionData = ExtResource("2_g54tg")
|
||||||
|
|
||||||
[node name="Barrel" type="Sprite3D" parent="."]
|
[node name="Barrel" type="Sprite3D" parent="." unique_id=1883029792]
|
||||||
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 0, 0)
|
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
|
pixel_size = 0.05
|
||||||
texture_filter = 0
|
texture_filter = 0
|
||||||
texture = ExtResource("1_2libs")
|
texture = ExtResource("1_2libs")
|
||||||
hframes = 4
|
hframes = 4
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=287417792]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0376587, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0376587, 0)
|
||||||
shape = SubResource("CylinderShape3D_2libs")
|
shape = SubResource("CylinderShape3D_2libs")
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Cirno.Scripts.Controllers;
|
using Cirno.Scripts.Controllers;
|
||||||
using Cirno.Scripts.Resources;
|
using Cirno.Scripts.Resources;
|
||||||
|
using Cirno.Scripts.Resources.Loot;
|
||||||
using Cirno.Scripts.Utils;
|
using Cirno.Scripts.Utils;
|
||||||
using Cirno.Scripts.Weapons;
|
using Cirno.Scripts.Weapons;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
@ -30,12 +31,19 @@ public partial class Destructible3D : StaticBody3D, IDestructible
|
||||||
[Export] public Node Target { get; private set; }
|
[Export] public Node Target { get; private set; }
|
||||||
[Export] public string TargetGroup { get; protected set; }
|
[Export] public string TargetGroup { get; protected set; }
|
||||||
|
|
||||||
|
[ExportCategory("Loot Drops")]
|
||||||
|
[Export] public Array<LootDrop> LootDrops { get; set; } = [];
|
||||||
|
/// <summary>Radius of the circle in which dropped items are scattered uniformly.</summary>
|
||||||
|
[Export] public float LootScatterRadius { get; set; } = 1.0f;
|
||||||
|
/// <summary>Initial upward speed applied to each dropped item so it pops up before falling.</summary>
|
||||||
|
[Export] public float LootLaunchUpSpeed { get; set; } = 3.0f;
|
||||||
|
|
||||||
[Signal]
|
[Signal]
|
||||||
public delegate void ExplodedEventHandler();
|
public delegate void ExplodedEventHandler();
|
||||||
|
|
||||||
private float _currentHealth = 0f;
|
private float _currentHealth = 0f;
|
||||||
|
|
||||||
private bool _isDestroyed = false;
|
private bool _isDestroyed = false;
|
||||||
|
private readonly RandomNumberGenerator _rng = new();
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
|
@ -86,6 +94,7 @@ public partial class Destructible3D : StaticBody3D, IDestructible
|
||||||
CreateExplosion();
|
CreateExplosion();
|
||||||
CreateParticles();
|
CreateParticles();
|
||||||
CreateDebris();
|
CreateDebris();
|
||||||
|
DropLoot();
|
||||||
|
|
||||||
QueueFree();
|
QueueFree();
|
||||||
|
|
||||||
|
|
@ -137,6 +146,11 @@ public partial class Destructible3D : StaticBody3D, IDestructible
|
||||||
particle.Emitting = true;
|
particle.Emitting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DropLoot()
|
||||||
|
{
|
||||||
|
LootDropHelper.SpawnDrops(LootDrops, this, GlobalPosition, LootScatterRadius, LootLaunchUpSpeed, _rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Hit(float damage, DamageType damageType = DamageType.Neutral)
|
public void Hit(float damage, DamageType damageType = DamageType.Neutral)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,49 +2,36 @@
|
||||||
using Cirno.Scripts.Components.FSM.Enemy._3D;
|
using Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||||
using Cirno.Scripts.Enums;
|
using Cirno.Scripts.Enums;
|
||||||
using Cirno.Scripts.Resources;
|
using Cirno.Scripts.Resources;
|
||||||
|
using Cirno.Scripts.Resources.Loot;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cirno.Scripts.Components.Actors._3D;
|
namespace Cirno.Scripts.Components.Actors._3D;
|
||||||
|
|
||||||
public partial class EnemyDropModule3D : ModuleBase<EnemyState, CharacterBody3D>
|
public partial class EnemyDropModule3D : ModuleBase<EnemyState, CharacterBody3D>
|
||||||
{
|
{
|
||||||
|
|
||||||
[Export] public EnemyStorage3D StorageModule { get; private set; }
|
[Export] public EnemyStorage3D StorageModule { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Radius of the circle in which dropped items are scattered uniformly.</summary>
|
||||||
|
[Export] public float LootScatterRadius { get; set; } = 1.0f;
|
||||||
|
/// <summary>Initial upward speed applied to each dropped item so it pops up before falling.</summary>
|
||||||
|
[Export] public float LootLaunchUpSpeed { get; set; } = 3.0f;
|
||||||
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private bool _enabled = false;
|
private bool _enabled = false;
|
||||||
|
|
||||||
private RandomNumberGenerator _rng = new ();
|
private RandomNumberGenerator _rng = new();
|
||||||
|
|
||||||
public override void EnterState(EnemyState state)
|
public override void EnterState(EnemyState state)
|
||||||
{
|
{
|
||||||
_enabled = true;
|
_enabled = true;
|
||||||
|
|
||||||
foreach (var loot in StorageModule.EnemyData.LootDrops)
|
LootDropHelper.SpawnDrops(
|
||||||
{
|
StorageModule.LootDrops,
|
||||||
if (loot is not { Item: not null }) continue;
|
_machine.MainObject,
|
||||||
var roll = _rng.RandfRange(0f, 100f); // Generate a number between 0 and 100
|
_machine.MainObject.GlobalPosition,
|
||||||
if (roll <= loot.Chance) // Compare with drop chance
|
LootScatterRadius,
|
||||||
{
|
LootLaunchUpSpeed,
|
||||||
DropItem(loot.Item);
|
_rng);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DropItem(LootItem item)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.DropScenePath3D))
|
|
||||||
{
|
|
||||||
item.Spawn3D(_machine.MainObject);
|
|
||||||
|
|
||||||
GD.Print($"Dropped item: {item.ItemName}");
|
|
||||||
//var scene = GD.Load<PackedScene>(item.DropScenePath3D);
|
|
||||||
//_actor.CreateSibling<Node3D>(scene);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GD.Print($"Skipping Item with missing path: {item.ItemName}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ExitState(EnemyState state)
|
public override void ExitState(EnemyState state)
|
||||||
|
|
@ -59,17 +46,14 @@ public partial class EnemyDropModule3D : ModuleBase<EnemyState, CharacterBody3D>
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
_machine = machine;
|
_machine = machine;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Process(double delta)
|
public override void Process(double delta)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PhysicsProcess(double delta)
|
public override void PhysicsProcess(double delta)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,70 @@ namespace Cirno.Scripts.Interactables;
|
||||||
|
|
||||||
public partial class ItemPickup3D : Interactable3D
|
public partial class ItemPickup3D : Interactable3D
|
||||||
{
|
{
|
||||||
|
private const float GravityAccel = 9.8f;
|
||||||
|
// How far below the item to probe for the floor each physics step.
|
||||||
|
private const float FloorProbeDistance = 10f;
|
||||||
|
|
||||||
[Export] public Array<LootItem> LootTable = [];
|
[Export] public Array<LootItem> LootTable = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Distance from the item's origin to its bottom edge.
|
||||||
|
/// Lifts the item above the floor surface so it doesn't clip into it.
|
||||||
|
/// Default matches the GenericItem3D collision shape: offset (0.065) + half cylinder height (0.269).
|
||||||
|
/// </summary>
|
||||||
|
[Export] public float GroundOffset { get; set; } = 0.204f;
|
||||||
|
|
||||||
private bool _autoPickup = false;
|
private bool _autoPickup = false;
|
||||||
|
private bool _simulating = false;
|
||||||
|
private Vector3 _velocity = Vector3.Zero;
|
||||||
|
|
||||||
public bool AutoPickup => _autoPickup;
|
public bool AutoPickup => _autoPickup;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_autoPickup = LootTable.Any(x => x.AutoPickup);
|
_autoPickup = LootTable.Any(x => x.AutoPickup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies an initial velocity and enables gravity simulation so the pickup
|
||||||
|
/// arcs through the air and settles on the floor.
|
||||||
|
/// </summary>
|
||||||
|
public void Launch(Vector3 velocity)
|
||||||
|
{
|
||||||
|
_velocity = velocity;
|
||||||
|
_simulating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
if (!_simulating) return;
|
||||||
|
|
||||||
|
_velocity.Y -= GravityAccel * (float)delta;
|
||||||
|
var nextPosition = GlobalPosition + _velocity * (float)delta;
|
||||||
|
|
||||||
|
// Cast downward to detect the floor so we don't sink through it.
|
||||||
|
var spaceState = GetWorld3D()?.DirectSpaceState;
|
||||||
|
var query = PhysicsRayQueryParameters3D.Create(
|
||||||
|
GlobalPosition,
|
||||||
|
GlobalPosition + Vector3.Down * FloorProbeDistance,
|
||||||
|
// Exclude the item's own collision layer (layer 32 per scene) and hit static geometry only.
|
||||||
|
0b1
|
||||||
|
);
|
||||||
|
query.Exclude = [GetRid()];
|
||||||
|
var hit = spaceState?.IntersectRay(query) ?? [];
|
||||||
|
|
||||||
|
if (hit.Count > 0)
|
||||||
|
{
|
||||||
|
var floorY = hit["position"].AsVector3().Y;
|
||||||
|
if (nextPosition.Y - GroundOffset <= floorY)
|
||||||
|
{
|
||||||
|
nextPosition.Y = floorY + GroundOffset;
|
||||||
|
_simulating = false;
|
||||||
|
_velocity = Vector3.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalPosition = nextPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Activate(ActivationType activationType = ActivationType.Toggle)
|
public override bool Activate(ActivationType activationType = ActivationType.Toggle)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cirno.Scripts.Resources.Loot;
|
namespace Cirno.Scripts.Resources.Loot;
|
||||||
|
|
||||||
|
|
@ -11,4 +11,7 @@ public partial class LootDrop : Resource
|
||||||
|
|
||||||
[Export(PropertyHint.None, "suffix:%")]
|
[Export(PropertyHint.None, "suffix:%")]
|
||||||
public float Chance { get; set; }
|
public float Chance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>How many pickup instances to spawn when this drop is triggered.</summary>
|
||||||
|
[Export] public int Count { get; set; } = 1;
|
||||||
}
|
}
|
||||||
68
Scripts/Resources/Loot/LootDropHelper.cs
Normal file
68
Scripts/Resources/Loot/LootDropHelper.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Cirno.Scripts.Resources.Loot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shared logic for spawning and scattering <see cref="LootDrop"/> entries in the 3D world.
|
||||||
|
/// Used by both destructibles and enemies so the distribution behaviour stays consistent.
|
||||||
|
/// </summary>
|
||||||
|
public static class LootDropHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Rolls each drop's chance, then spawns the resulting pickups scattered uniformly
|
||||||
|
/// in a circle around <paramref name="center"/> with an outward + upward launch velocity
|
||||||
|
/// so they arc through the air and fall to the floor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="drops">The list of possible drops to evaluate.</param>
|
||||||
|
/// <param name="anchor">Node used as the spawn parent / sibling reference.</param>
|
||||||
|
/// <param name="center">World-space centre of the scatter circle.</param>
|
||||||
|
/// <param name="scatterRadius">Radius of the circle in which items are spread.</param>
|
||||||
|
/// <param name="launchUpSpeed">Initial upward speed applied to each pickup.</param>
|
||||||
|
/// <param name="rng">Caller-owned RNG instance so seeds are consistent per object.</param>
|
||||||
|
public static void SpawnDrops(
|
||||||
|
IEnumerable<LootDrop> drops,
|
||||||
|
Node3D anchor,
|
||||||
|
Vector3 center,
|
||||||
|
float scatterRadius,
|
||||||
|
float launchUpSpeed,
|
||||||
|
RandomNumberGenerator rng)
|
||||||
|
{
|
||||||
|
var dropList = drops?.ToList();
|
||||||
|
if (dropList is not { Count: > 0 }) return;
|
||||||
|
|
||||||
|
// Count total spawns up front so golden-angle spacing is proportional to the full set.
|
||||||
|
var totalSpawns = dropList
|
||||||
|
.Where(d => d?.Item is not null)
|
||||||
|
.Sum(d => Mathf.Max(d.Count, 1));
|
||||||
|
|
||||||
|
var itemIndex = 0;
|
||||||
|
|
||||||
|
foreach (var drop in dropList)
|
||||||
|
{
|
||||||
|
if (drop?.Item is null) continue;
|
||||||
|
|
||||||
|
var roll = rng.RandfRange(0f, 100f);
|
||||||
|
if (roll > drop.Chance) continue;
|
||||||
|
|
||||||
|
var spawnCount = Mathf.Max(drop.Count, 1);
|
||||||
|
for (var i = 0; i < spawnCount; i++)
|
||||||
|
{
|
||||||
|
// Golden-angle placement gives uniform, non-clustering distribution.
|
||||||
|
itemIndex++;
|
||||||
|
var angle = itemIndex * Mathf.Tau / 1.618033988f;
|
||||||
|
var radius = scatterRadius * Mathf.Sqrt((float)itemIndex / Mathf.Max(totalSpawns, 1));
|
||||||
|
var offset = new Vector3(Mathf.Cos(angle) * radius, 0f, Mathf.Sin(angle) * radius);
|
||||||
|
var spawnPos = center + offset;
|
||||||
|
|
||||||
|
var lateralDir = offset.LengthSquared() > 0f ? offset.Normalized() : Vector3.Right;
|
||||||
|
var launch = lateralDir * (scatterRadius * 0.5f) + Vector3.Up * launchUpSpeed;
|
||||||
|
|
||||||
|
drop.Item.Spawn3D(anchor, spawnPosition: spawnPos, launchVelocity: launch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1
Scripts/Resources/Loot/LootDropHelper.cs.uid
Normal file
1
Scripts/Resources/Loot/LootDropHelper.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cmpwy17x132io
|
||||||
|
|
@ -55,31 +55,43 @@ public partial class LootItem : Resource
|
||||||
return spawnedItem;
|
return spawnedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemPickup3D Spawn3D(Node3D sibling, bool dropAsChild = false)
|
/// <summary>
|
||||||
|
/// Spawns this item as an <see cref="ItemPickup3D"/> in the 3D world.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sibling">Reference node used to determine parent and default spawn position.</param>
|
||||||
|
/// <param name="dropAsChild">If true, spawns as a child of <paramref name="sibling"/>; otherwise as a sibling.</param>
|
||||||
|
/// <param name="spawnPosition">
|
||||||
|
/// Optional world-space position override. When null, the position of <paramref name="sibling"/> is used.
|
||||||
|
/// Pass a custom value to scatter drops instead of stacking them all at the same point.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="launchVelocity">Optional initial velocity applied to the pickup so it arcs and falls to the floor.</param>
|
||||||
|
public ItemPickup3D Spawn3D(Node3D sibling, bool dropAsChild = false, Vector3? spawnPosition = null, Vector3? launchVelocity = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(DropScenePath3D)) return null;
|
if (string.IsNullOrWhiteSpace(DropScenePath3D)) return null;
|
||||||
var itemScene = GD.Load<PackedScene>(DropScenePath3D);
|
var itemScene = GD.Load<PackedScene>(DropScenePath3D);
|
||||||
|
|
||||||
var spawnedItem = itemScene.Instantiate<ItemPickup3D>();
|
var spawnedItem = itemScene.Instantiate<ItemPickup3D>();
|
||||||
spawnedItem.Name = this.ItemKey;
|
spawnedItem.Name = this.ItemKey;
|
||||||
|
|
||||||
|
var position = spawnPosition ?? sibling.GlobalPosition;
|
||||||
|
|
||||||
if (dropAsChild)
|
if (dropAsChild)
|
||||||
{
|
{
|
||||||
CallDeferred(MethodName.DeferredSpawn3D, sibling, spawnedItem, sibling.GlobalPosition);
|
CallDeferred(MethodName.DeferredSpawn3D, sibling, spawnedItem, position);
|
||||||
//sibling.CallDeferred(Node.MethodName.AddChild, spawnedItem);
|
|
||||||
//sibling.AddChild(spawnedItem);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CallDeferred(MethodName.DeferredSpawn3D, sibling.GetParentNode3D(), spawnedItem, sibling.GlobalPosition);
|
CallDeferred(MethodName.DeferredSpawn3D, sibling.GetParentNode3D(), spawnedItem, position);
|
||||||
//sibling.GetParent().CallDeferred(Node.MethodName.AddChild, spawnedItem);
|
|
||||||
//sibling.GetParent().AddChild(spawnedItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//spawnedItem.GlobalPosition = sibling.GlobalPosition;
|
|
||||||
|
|
||||||
spawnedItem.LootTable.Add(this);
|
spawnedItem.LootTable.Add(this);
|
||||||
spawnedItem.SetSprite(InventorySprite);
|
spawnedItem.SetSprite(InventorySprite);
|
||||||
|
|
||||||
|
if (launchVelocity.HasValue)
|
||||||
|
{
|
||||||
|
spawnedItem.Launch(launchVelocity.Value);
|
||||||
|
}
|
||||||
|
|
||||||
return spawnedItem;
|
return spawnedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
shader_type spatial;
|
shader_type spatial;
|
||||||
|
|
||||||
render_mode unshaded, depth_draw_opaque, cull_back;
|
render_mode unshaded, depth_draw_opaque, cull_back;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue