first commit 🎉

This commit is contained in:
anyreso 2026-02-17 23:36:57 -05:00
commit 6e724f67fe
805 changed files with 62098 additions and 0 deletions

View file

@ -0,0 +1,41 @@
[gd_scene load_steps=5 format=3 uid="uid://ciwnbmkjqyf4d"]
[ext_resource type="Script" uid="uid://cefq7u2fatx2e" path="res://scenes/entities/components/scripts/flag_holder.gd" id="1_3kspm"]
[ext_resource type="PackedScene" uid="uid://d3l7fvbdg6m5g" path="res://scenes/entities/flag/assets/flag.glb" id="2_p6h8o"]
[ext_resource type="Texture2D" uid="uid://1mwcg0cd0msw" path="res://scenes/interfaces/hud/throw_force_texture.svg" id="3_l5ucd"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_sh8bn"]
properties/0/path = NodePath(".:visible")
properties/0/spawn = true
properties/0/replication_mode = 2
[node name="FlagHolder" type="Node3D"]
visible = false
script = ExtResource("1_3kspm")
[node name="ThrowTimer" type="Timer" parent="."]
one_shot = true
[node name="ThrowForce" type="TextureProgressBar" parent="."]
visible = false
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
value = 100.0
fill_mode = 4
radial_initial_angle = 293.0
radial_fill_degrees = 135.0
texture_progress = ExtResource("3_l5ucd")
[node name="Mesh" parent="." instance=ExtResource("2_p6h8o")]
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_sh8bn")

View file

@ -0,0 +1,20 @@
[gd_scene load_steps=4 format=3 uid="uid://53egh2236e1u"]
[ext_resource type="Script" uid="uid://bih0550c0aj65" path="res://scenes/entities/components/scripts/inventory.gd" id="1_u5gxb"]
[ext_resource type="PackedScene" uid="uid://c8co0qa2omjmh" path="res://scenes/entities/weapons/space_gun/space_gun.tscn" id="2_3ti0l"]
[ext_resource type="PackedScene" uid="uid://cstl7yxc75572" path="res://scenes/entities/weapons/grenade_launcher/grenade_launcher.tscn" id="3_qgqo0"]
[node name="Inventory" type="Node3D"]
script = ExtResource("1_u5gxb")
[node name="WeaponSpawner" type="MultiplayerSpawner" parent="."]
_spawnable_scenes = PackedStringArray("uid://c8co0qa2omjmh", "uid://b0xql5hi0b52y", "uid://cstl7yxc75572")
spawn_path = NodePath("../Weapons")
spawn_limit = 3
[node name="Weapons" type="Node3D" parent="."]
[node name="SpaceGun" parent="Weapons" instance=ExtResource("2_3ti0l")]
[node name="GrenadeLauncher" parent="Weapons" instance=ExtResource("3_qgqo0")]
transform = Transform3D(-0.675636, -0.322973, 0.0696285, 0.0630001, -0.282458, -0.692878, 0.323936, -0.618694, 0.279978, 0.138702, 0.0479742, -0.137894)

View file

@ -0,0 +1,93 @@
# This file is part of sunder.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
@icon("res://assets/icons/flag_holder.svg")
## This component is a unified system to manage [Flag] ownership, interaction
## logic, and visual displays for the owning Entity.
class_name FlagHolder extends Node3D
@export var throw_force:int = 16
## The radial progress bar to display the throwing force
@onready var texture_progress:TextureProgressBar = $ThrowForce
@onready var timer:Timer = $ThrowTimer
## The flag mesh to display when the owner carries the flag
@onready var mesh: Node3D = $Mesh
## The reference to a [Flag] node, useful when [member Flag.state] is [member Flag.FlagState.TAKEN]
var flag:Flag
func _process(_delta: float) -> void:
if is_visible_in_tree() and not timer.is_stopped():
var time_elapsed: float = timer.wait_time - timer.time_left
texture_progress.value = time_elapsed / timer.wait_time * 100
## This method grabs a [Flag] which set the mesh visible, holds a reference to it
## and emits the [Flag.grabbed] signal to handle further state changes
func grab(p_flag:Flag) -> void:
if p_flag.state < Flag.FlagState.TAKEN:
show()
flag = p_flag
flag.grabbed.emit(owner.peer_id)
@rpc("any_peer", "call_local", "reliable")
func drop() -> void:
if multiplayer.is_server() and flag and flag.state == flag.FlagState.TAKEN:
flag.dropped.emit(owner.peer_id)
if owner:
# set flag mesh to face velocity
flag.global_position = owner.global_position
flag.global_rotation.y = -flag.global_basis.z.dot(owner.linear_velocity)
# apply impulse to wake up the flag
flag.apply_impulse.call_deferred(flag.mass * owner.linear_velocity)
flag = null
hide()
## This method send the throw input to the multiplayer authority while
## displaying a throw force progress on the [HUD] of the client.
## When the throw action is released (before an abort timeout) it will also
## send that input to the authority and stop displaying the throw force progress
## on the [HUD] of the client.
func throw(pressed:bool) -> void:
prints("flag_holder._throw", owner.peer_id, multiplayer.get_unique_id(), pressed, flag)
if flag and flag.state == flag.FlagState.TAKEN:
if pressed:
timer.start()
texture_progress.visible = true
else:
timer.stop()
texture_progress.visible = false
#if is_multiplayer_authority():
#var time_elapsed:float = timer.wait_time - timer.time_left
#var pumped_force:float = clampf(time_elapsed / timer.wait_time * throw_force, 6., throw_force)
#var throw_velocity:Vector3 = -owner.spring_arm.global_basis.z * pumped_force
#var total_velocity:Vector3 = owner.linear_velocity + throw_velocity
#
#flag.dropped.emit(owner.peer_id)
#
#var impulse:Vector3 = flag.mass * total_velocity
## set flag mesh to face velocity
#var aim_offset:Vector3 = -owner.spring_arm.global_basis.z
#aim_offset.y = 1. if aim_offset.y < 1. else aim_offset.y
#
#flag.global_position = owner.global_position + aim_offset
##flag.global_position = owner.global_position + -owner.spring_arm.global_basis.z
#flag.global_rotation.y = -flag.global_basis.z.dot(impulse)
## apply impulse to wake up the flag
#flag.apply_impulse.call_deferred(flag.mass * (owner.linear_velocity + throw_velocity))
#
#flag = null
#hide()

View file

@ -0,0 +1 @@
uid://cefq7u2fatx2e

View file

@ -0,0 +1,79 @@
# This file is part of sunder.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
@tool
## This class defines a Health component for entities.
class_name Health extends Area3D
## An entity with this component can be either dead or alive.
enum HealthState {
## The entity is dead
DEAD,
## The entity is alive ([member value] > 0)
ALIVE,
## The entity is wounded ([member value] < 128)
DEGRADED,
## Stop it, get help ([member value] < 32)
CRITICAL
}
@export var state : HealthState = HealthState.ALIVE
@export var max_value:int = 255
@export var value:int = 255:
set(new_value):
value = clampi(new_value, 0, max_value)
changed.emit(value)
@export var shape:Shape3D:
set(new_shape):
shape = new_shape
if is_node_ready():
collider.shape = new_shape
@onready var collider: CollisionShape3D = $CollisionShape3D
## Emitted when a peer damaged this component too much.
signal killed(by_peer_id:int)
## Emitted when value is exhausted.
signal exhausted()
## Emitted when the value is changed.
signal changed(new_value:int)
## Emitted when the health component is damaged.
signal damaged(source: Node, target: Node, amount: int)
func _ready() -> void:
# only collide with the layer 3 named "Damage", disable monitoring completely
collision_layer = 0b00000000_00000000_00000000_00000100
collision_mask = 0
monitoring = false
func _get_configuration_warnings() -> PackedStringArray:
if not collider.shape:
return ["A shape must be provided for Health to be damaged. Please create or load a shape resource."]
return []
@rpc("authority", "call_local", "reliable")
func damage(amount:int, by_peer_id:int) -> void:
value -= amount
if value == 0 and state != HealthState.DEAD:
state = HealthState.DEAD
killed.emit(by_peer_id)
exhausted.emit(owner)
@rpc("authority", "call_local", "reliable")
func heal(amount:int = max_value) -> void:
# add strictly positive amount to value
value += clampi(amount, 1, max_value)
state = HealthState.ALIVE

View file

@ -0,0 +1 @@
uid://c2df3oj23o0wa

View file

@ -0,0 +1,23 @@
class_name Inventory extends Node3D
# weapons, tuple of 3 unique weapons
# flag, object
# grenades, typed array of max 2 grenade resources
@export_range(-1, 2) var cursor: int = -1
@export var loadout:Loadout
@onready var weapons:Node3D = $Weapons
func _ready() -> void:
if loadout and multiplayer.is_server():
for weapon in loadout.weapons:
weapons.add_child(weapon.instantiate())
#func _on_primary() -> void:
#equipped.trigger()
func clear() -> void:
for child in weapons.get_children():
child.queue_free()

View file

@ -0,0 +1 @@
uid://bih0550c0aj65

View file

@ -0,0 +1,5 @@
## This class defines an Item
class_name Item extends Resource
@export var name:String
@export var texture:Texture

View file

@ -0,0 +1 @@
uid://borwqw2eea3uf

View file

@ -0,0 +1,37 @@
@tool
class_name ItemStorage extends Resource
## An array of [Item]
var items: Array[Item] = []
@export var size:int = 3:
set = set_size
func set_size(new_size:int) -> void:
size = new_size
items.resize(size)
notify_property_list_changed()
func _get_property_list() -> Array:
var properties := []
for i in range(items.size()):
properties.append({
"name": "slot_%d" % i,
"type": TYPE_DICTIONARY,
#"hint": PROPERTY_HINT_DICTIONARY_TYPE,
"hint_string": "Item"
})
return properties
func _get(name:StringName) -> Item:
if name.begins_with("slot_"):
var index := name.get_slice("_", 1).to_int()
return items[index]
return null
func _set(name:StringName, value:Variant) -> bool:
if name.begins_with("slot_"):
var index := name.get_slice("_", 1).to_int()
items[index] = value
return true
return false

View file

@ -0,0 +1 @@
uid://c782w1epbdnps

View file

@ -0,0 +1,38 @@
@tool
class_name Loadout extends Resource
@export_enum("Light", "Medium", "Heavy") var armor_type: int = 0
var weapons:Array[PackedScene] = []:
set(value):
weapons = value
weapons.resize(3)
notify_property_list_changed()
func _init() -> void:
weapons.resize(3)
notify_property_list_changed()
func _get_property_list() -> Array:
var properties := []
for i in range(weapons.size()):
properties.append({
"name": "weapon_%d" % i,
"type": TYPE_OBJECT,
"hint": PROPERTY_HINT_RESOURCE_TYPE,
"hint_string": "PackedScene"
})
return properties
func _get(prop:StringName) -> Variant:
if prop.begins_with("weapon_"):
var index := prop.get_slice("_", 1).to_int()
return weapons[index]
return null
func _set(prop:StringName, value:Variant) -> bool:
if prop.begins_with("weapon_"):
var index := prop.get_slice("_", 1).to_int()
weapons[index] = value
return true
return false

View file

@ -0,0 +1 @@
uid://bh5mmolmfwj7h

View file

@ -0,0 +1,84 @@
# This file is part of sunder.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
@tool
## This component allows its entity to interact with items.
class_name OldInventory extends Node3D
signal selection_changed(index : int)
signal selected(node: Node)
signal unselected(node: Node)
const _max_items = 3
## This is the total capacity of the inventory.
@export_range(0, _max_items) var capacity : int = _max_items:
set(value):
capacity = clamp(value, 1, _max_items)
@export_range(0, _max_items - 1) var cursor : int = 0:
set = set_cursor
# Function to set the cursor position in the inventory
func set_cursor(new_cursor: int) -> void:
# Ensure the cursor is within the valid range
if new_cursor >= 0 and new_cursor < _max_items:
# emit unselected signal
_emit_child(unselected, cursor)
# update cursor position
cursor = new_cursor
# emit selected signal
_emit_child(selected, cursor)
# helper function to emit the selected signal for the current cursor position
func _emit_child(sig: Signal, idx: int) -> void:
var child : Node = get_child(idx)
if child:
sig.emit(child)
func _ready() -> void:
unselected.connect(_on_unselected)
selected.connect(_on_selected)
func _on_unselected(node: Node) -> void:
node.hide()
func _on_selected(node: Node) -> void:
node.show()
## This method overrides [method Node.add_child] to make sure not to exceed
## inventory capacity.
func _add_child(node: Node, force_readable_name: bool = false,
internal: InternalMode = InternalMode.INTERNAL_MODE_DISABLED) -> void:
if get_child_count() <= capacity:
super.add_child(node, force_readable_name, internal)
else:
push_warning("inventory is full")
## This method moves the [member selection] cursor to specified item index.
func select(index : int) -> void:
cursor = wrapi(index, 0, get_child_count())
## This method cycles through items in the inventory.
func cycle(shift : int = 1) -> void:
select(cursor + shift)
@rpc("any_peer", "call_local", "reliable")
func req_loadout(_index:int = 0) -> void:
if is_multiplayer_authority():
rcv_loadout.rpc_id(multiplayer.get_remote_sender_id(), "")
@rpc("authority", "call_local", "reliable")
func rcv_loadout(loadout:String) -> void:
print(loadout)

View file

@ -0,0 +1 @@
uid://dagc5l6b73fr4