first commit 🎉
41
scenes/entities/components/flag_holder.tscn
Normal 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")
|
||||
20
scenes/entities/components/inventory.tscn
Normal 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)
|
||||
93
scenes/entities/components/scripts/flag_holder.gd
Normal 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()
|
||||
1
scenes/entities/components/scripts/flag_holder.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cefq7u2fatx2e
|
||||
79
scenes/entities/components/scripts/health.gd
Normal 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
|
||||
1
scenes/entities/components/scripts/health.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c2df3oj23o0wa
|
||||
23
scenes/entities/components/scripts/inventory.gd
Normal 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()
|
||||
1
scenes/entities/components/scripts/inventory.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bih0550c0aj65
|
||||
5
scenes/entities/components/scripts/item.gd
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
## This class defines an Item
|
||||
class_name Item extends Resource
|
||||
|
||||
@export var name:String
|
||||
@export var texture:Texture
|
||||
1
scenes/entities/components/scripts/item.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://borwqw2eea3uf
|
||||
37
scenes/entities/components/scripts/item_storage.gd
Normal 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
|
||||
1
scenes/entities/components/scripts/item_storage.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c782w1epbdnps
|
||||
38
scenes/entities/components/scripts/loadout.gd
Normal 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
|
||||
1
scenes/entities/components/scripts/loadout.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bh5mmolmfwj7h
|
||||
84
scenes/entities/components/scripts/old_inventory.gd
Normal 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)
|
||||
1
scenes/entities/components/scripts/old_inventory.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dagc5l6b73fr4
|
||||
BIN
scenes/entities/dummy_target/assets/dummy.glb
Normal file
37
scenes/entities/dummy_target/assets/dummy.glb.import
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://chuein4frnvwt"
|
||||
path="res://.godot/imported/dummy.glb-0a7f4e22ef0fca2f4fb61e437d6d993d.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/dummy_target/assets/dummy.glb"
|
||||
dest_files=["res://.godot/imported/dummy.glb-0a7f4e22ef0fca2f4fb61e437d6d993d.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
33
scenes/entities/dummy_target/dummy_target.gd
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# 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/>.
|
||||
class_name DummyTarget extends RigidBody3D
|
||||
|
||||
@export var respawn_time := 0.0 # seconds
|
||||
|
||||
@onready var collision_shape_3d: CollisionShape3D = $CollisionShape3D
|
||||
|
||||
var start_pos : Vector3
|
||||
|
||||
func _ready() -> void:
|
||||
start_pos = global_position
|
||||
$TargetMesh/AnimationPlayer.play("gunTwoHanded")
|
||||
|
||||
func spawn(_killer_id : int) -> void:
|
||||
hide()
|
||||
collision_shape_3d.disabled = true
|
||||
await get_tree().create_timer(respawn_time).timeout
|
||||
global_position = start_pos
|
||||
show()
|
||||
collision_shape_3d.disabled = false
|
||||
1
scenes/entities/dummy_target/dummy_target.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://rvccxslu86pf
|
||||
119
scenes/entities/dummy_target/dummy_target.tscn
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://dpnu1lvfncx6q"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://rvccxslu86pf" path="res://scenes/entities/dummy_target/dummy_target.gd" id="1_iup5v"]
|
||||
[ext_resource type="Shape3D" uid="uid://cb8esdlnottdn" path="res://scenes/entities/player/assets/collider.tres" id="2_i5k5j"]
|
||||
[ext_resource type="PackedScene" uid="uid://chuein4frnvwt" path="res://scenes/entities/dummy_target/assets/dummy.glb" id="4_fuync"]
|
||||
|
||||
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_b0142"]
|
||||
friction = 0.5
|
||||
bounce = 0.5
|
||||
|
||||
[node name="DummyTarget" type="RigidBody3D"]
|
||||
collision_mask = 2147483651
|
||||
axis_lock_angular_x = true
|
||||
axis_lock_angular_y = true
|
||||
axis_lock_angular_z = true
|
||||
mass = 75.0
|
||||
physics_material_override = SubResource("PhysicsMaterial_b0142")
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector3(0, 1, 0)
|
||||
continuous_cd = true
|
||||
script = ExtResource("1_iup5v")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
shape = ExtResource("2_i5k5j")
|
||||
|
||||
[node name="TargetMesh" parent="." instance=ExtResource("4_fuync")]
|
||||
transform = Transform3D(0.75, 0, 0, 0, 0.75, 0, 0, 0, 0.75, 0, 0, 0)
|
||||
|
||||
[node name="playerrig" parent="TargetMesh" index="2"]
|
||||
transform = Transform3D(2.56873e-07, 0, 1.31928, 0, 1.31928, 0, -1.31928, 0, 2.56873e-07, 0, 0, 0)
|
||||
|
||||
[node name="Skeleton3D" parent="TargetMesh/playerrig" index="0"]
|
||||
bones/0/position = Vector3(0, 1.0099, -0.0552)
|
||||
bones/0/scale = Vector3(1, 1, 1)
|
||||
bones/1/rotation = Quaternion(-0.0644764, 5.29396e-23, -5.29947e-23, 0.997919)
|
||||
bones/5/rotation = Quaternion(-0.16559, 0, -1.65436e-23, 0.986195)
|
||||
bones/6/rotation = Quaternion(-0.605155, -0.345862, -0.356134, 0.622363)
|
||||
bones/7/position = Vector3(0.0170569, 0.214097, -0.0587266)
|
||||
bones/7/rotation = Quaternion(0.18188, 0.221628, -0.929157, 0.233384)
|
||||
bones/8/rotation = Quaternion(0.128862, -0.709864, -0.133277, 0.679504)
|
||||
bones/8/scale = Vector3(1, 1, 1)
|
||||
bones/9/rotation = Quaternion(0.126866, -0.0346058, -0.00662178, 0.991294)
|
||||
bones/9/scale = Vector3(1, 1, 1)
|
||||
bones/10/rotation = Quaternion(0.0557877, -0.312038, -0.196665, 0.927816)
|
||||
bones/11/rotation = Quaternion(-0.0630718, -0.16283, 0.0971492, 0.979832)
|
||||
bones/12/rotation = Quaternion(0.0237033, -0.0123211, 0.0433656, 0.998702)
|
||||
bones/13/position = Vector3(-0.00420077, 0.0987895, 0.0277967)
|
||||
bones/13/rotation = Quaternion(0.0299552, -0.545663, -0.00259937, 0.837465)
|
||||
bones/13/scale = Vector3(1, 1, 1)
|
||||
bones/14/rotation = Quaternion(-0.0636278, -0.0624373, 0.0299354, 0.995569)
|
||||
bones/15/rotation = Quaternion(0.01399, 0.0251671, 0.0278526, 0.999197)
|
||||
bones/16/position = Vector3(-0.011858, 0.0987964, 0.000921253)
|
||||
bones/16/rotation = Quaternion(-0.1, -0.561252, 0.0324573, 0.82094)
|
||||
bones/17/rotation = Quaternion(0.048608, -0.0407852, 0.014286, 0.997883)
|
||||
bones/18/rotation = Quaternion(-0.0568169, 0.0249822, 0.0364248, 0.997407)
|
||||
bones/19/position = Vector3(-0.00156325, 0.0941323, -0.0270346)
|
||||
bones/19/rotation = Quaternion(-0.12453, -0.526036, 0.129609, 0.831252)
|
||||
bones/19/scale = Vector3(1, 1, 1)
|
||||
bones/20/rotation = Quaternion(-0.0225171, -0.0668488, 0.0231689, 0.99724)
|
||||
bones/20/scale = Vector3(1, 1, 1)
|
||||
bones/21/rotation = Quaternion(-0.00114938, 0.0252308, -0.0174874, 0.999528)
|
||||
bones/21/scale = Vector3(1, 1, 1)
|
||||
bones/22/position = Vector3(-0.00339956, 0.0845645, -0.0510466)
|
||||
bones/22/rotation = Quaternion(-0.192536, -0.535774, 0.0761884, 0.818579)
|
||||
bones/22/scale = Vector3(1, 1, 1)
|
||||
bones/23/rotation = Quaternion(0.0104723, -0.0704384, 0.0293146, 0.99703)
|
||||
bones/23/scale = Vector3(1, 1, 1)
|
||||
bones/24/rotation = Quaternion(0.0334518, 0.0259883, -0.0178538, 0.998943)
|
||||
bones/25/rotation = Quaternion(-0.608395, 0.196282, 0.258426, 0.724254)
|
||||
bones/25/scale = Vector3(1, 1, 1)
|
||||
bones/26/position = Vector3(-0.017057, 0.214097, -0.0587264)
|
||||
bones/26/rotation = Quaternion(0.161981, -0.321612, 0.619315, 0.697694)
|
||||
bones/26/scale = Vector3(1, 1, 1)
|
||||
bones/27/rotation = Quaternion(0.426391, 0.0868582, 0.53013, 0.727742)
|
||||
bones/27/scale = Vector3(1, 1, 1)
|
||||
bones/28/rotation = Quaternion(-0.217102, 0.0422089, 0.0230506, 0.974963)
|
||||
bones/28/scale = Vector3(1, 1, 1)
|
||||
bones/29/rotation = Quaternion(0.296043, 0.246211, 0.290544, 0.87597)
|
||||
bones/30/rotation = Quaternion(-0.0630717, 0.16283, -0.0971492, 0.979832)
|
||||
bones/31/rotation = Quaternion(0.0237032, 0.0123211, -0.0433656, 0.998702)
|
||||
bones/31/scale = Vector3(1, 1, 1)
|
||||
bones/32/rotation = Quaternion(0.243143, 0.523041, 0.164477, 0.800161)
|
||||
bones/32/scale = Vector3(1, 1, 1)
|
||||
bones/33/rotation = Quaternion(-0.0636278, 0.0624373, -0.0299353, 0.995569)
|
||||
bones/34/rotation = Quaternion(0.01399, -0.0251671, -0.0278526, 0.999197)
|
||||
bones/36/rotation = Quaternion(-0.4266, 0.0977703, 0.332942, 0.835226)
|
||||
bones/37/rotation = Quaternion(-0.632018, -0.129105, 0.241206, 0.725055)
|
||||
bones/39/rotation = Quaternion(-0.479327, 0.030236, 0.338389, 0.809212)
|
||||
bones/40/rotation = Quaternion(-0.540176, -0.0581251, 0.395197, 0.740709)
|
||||
bones/40/scale = Vector3(1, 1, 1)
|
||||
bones/41/rotation = Quaternion(0.0241847, 0.530538, 0.0849425, 0.843048)
|
||||
bones/41/scale = Vector3(1, 1, 1)
|
||||
bones/42/rotation = Quaternion(-0.451682, -0.0170105, 0.326969, 0.829931)
|
||||
bones/43/rotation = Quaternion(-0.512965, -0.0942157, 0.391943, 0.757873)
|
||||
bones/44/rotation = Quaternion(0.986515, 0, 1.35686e-25, 0.163672)
|
||||
bones/44/scale = Vector3(1, 1, 0.999997)
|
||||
bones/45/rotation = Quaternion(0.143207, -4.28623e-07, 5.19845e-06, 0.989693)
|
||||
bones/45/scale = Vector3(1, 1, 1)
|
||||
bones/46/rotation = Quaternion(-0.574131, 2.92207e-06, -4.58343e-06, 0.818763)
|
||||
bones/47/rotation = Quaternion(1.99048e-05, 0.961249, -0.27568, 4.3124e-06)
|
||||
bones/47/scale = Vector3(1, 1, 1)
|
||||
bones/48/rotation = Quaternion(0.679706, 0.679706, -0.194936, 0.194936)
|
||||
bones/49/rotation = Quaternion(0.986515, 0, 1.35686e-25, 0.163672)
|
||||
bones/49/scale = Vector3(1, 1, 0.999997)
|
||||
bones/50/rotation = Quaternion(0.143207, 4.28623e-07, -5.19845e-06, 0.989693)
|
||||
bones/50/scale = Vector3(1, 1, 1)
|
||||
bones/51/rotation = Quaternion(-0.574131, -2.92207e-06, 4.58343e-06, 0.818763)
|
||||
bones/52/rotation = Quaternion(-1.99048e-05, 0.961249, -0.27568, -4.3124e-06)
|
||||
bones/52/scale = Vector3(1, 1, 1)
|
||||
bones/53/rotation = Quaternion(0.679706, -0.679706, 0.194936, 0.194936)
|
||||
|
||||
[node name="AnimationPlayer" parent="TargetMesh" index="4"]
|
||||
autoplay = "gunTwoHanded"
|
||||
|
||||
[node name="Marker3D" type="Marker3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
|
||||
|
||||
[editable path="TargetMesh"]
|
||||
4
scenes/entities/flag/assets/capsule_shape.tres
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="CapsuleShape3D" format=3 uid="uid://cc4von3ac65kl"]
|
||||
|
||||
[resource]
|
||||
radius = 0.25
|
||||
53
scenes/entities/flag/assets/flag.blend.import
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://dlckx3u45bns5"
|
||||
path="res://.godot/imported/flag.blend-7923385f3a6a3e72dea73ad94d1f96ae.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://entities/flag/assets/flag.blend"
|
||||
dest_files=["res://.godot/imported/flag.blend-7923385f3a6a3e72dea73ad94d1f96ae.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
blender/nodes/visible=0
|
||||
blender/nodes/active_collection_only=false
|
||||
blender/nodes/punctual_lights=true
|
||||
blender/nodes/cameras=true
|
||||
blender/nodes/custom_properties=true
|
||||
blender/nodes/modifiers=1
|
||||
blender/meshes/colors=false
|
||||
blender/meshes/uvs=true
|
||||
blender/meshes/normals=true
|
||||
blender/meshes/export_geometry_nodes_instances=false
|
||||
blender/meshes/tangents=true
|
||||
blender/meshes/skins=2
|
||||
blender/meshes/export_bones_deforming_mesh_only=false
|
||||
blender/materials/unpack_enabled=true
|
||||
blender/materials/export_materials=1
|
||||
blender/animation/limit_playback=true
|
||||
blender/animation/always_sample=true
|
||||
blender/animation/group_tracks=true
|
||||
BIN
scenes/entities/flag/assets/flag.glb
Normal file
37
scenes/entities/flag/assets/flag.glb.import
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://d3l7fvbdg6m5g"
|
||||
path="res://.godot/imported/flag.glb-fe2c347435572b29cc642d8925aaeb7c.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/flag/assets/flag.glb"
|
||||
dest_files=["res://.godot/imported/flag.glb-fe2c347435572b29cc642d8925aaeb7c.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
89
scenes/entities/flag/flag.gd
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# 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/>.
|
||||
class_name Flag extends RigidBody3D
|
||||
|
||||
signal grabbed(by_peer:int)
|
||||
signal dropped(by_peer:int)
|
||||
|
||||
const scene:PackedScene = preload("res://scenes/entities/flag/flag.tscn")
|
||||
|
||||
static func instantiate() -> Flag:
|
||||
return scene.instantiate()
|
||||
|
||||
enum FlagState {
|
||||
## The flag is fresh and at its initial position
|
||||
ON_STAND,
|
||||
## The flag is dropped by a [FlagHolder]
|
||||
DROPPED,
|
||||
## The flag is taken and being carried by a [FlagHolder]
|
||||
TAKEN
|
||||
}
|
||||
|
||||
@export var state:FlagState = FlagState.ON_STAND
|
||||
@export var last_carrier:int
|
||||
|
||||
@onready var area:Area3D = $GripArea
|
||||
@onready var mesh:Node3D = $Mesh
|
||||
@onready var waypoint:Waypoint3D = $Waypoint
|
||||
|
||||
var dropped_max_duration := 30.
|
||||
## This timer is responsible for returning the flag when it is dropped for more than [member dropped_max_duration].
|
||||
var respawn_timer := Timer.new()
|
||||
|
||||
func _ready() -> void:
|
||||
area.body_entered.connect(_on_body_entered)
|
||||
grabbed.connect(_on_grabbed)
|
||||
dropped.connect(_on_dropped)
|
||||
if multiplayer.is_server():
|
||||
respawn_timer.wait_time = dropped_max_duration
|
||||
respawn_timer.one_shot = true
|
||||
add_child(respawn_timer)
|
||||
|
||||
func _process(_delta:float) -> void:
|
||||
if state == FlagState.DROPPED and not respawn_timer.is_stopped():
|
||||
waypoint.text = "%.2f s" % respawn_timer.time_left
|
||||
else:
|
||||
waypoint.text = ""
|
||||
|
||||
func _physics_process(delta:float) -> void:
|
||||
var root = get_tree().current_scene
|
||||
var distance_to_camera = global_position.distance_to(get_viewport().get_camera_3d().global_position)
|
||||
# Determine if terrain collision is active (or too far from camera when using dynamic terrain colliton mode)
|
||||
var using_fake_ground = distance_to_camera > root.map.collision_radius
|
||||
if using_fake_ground:
|
||||
global_position.y = root.map.data.get_height(global_position) # ground_height
|
||||
linear_velocity.y = 0
|
||||
|
||||
func _on_body_entered(body: Node) -> void:
|
||||
if body is Player:
|
||||
assert(body.flag_holder)
|
||||
if state < FlagState.TAKEN:
|
||||
body.flag_holder.grab(self)
|
||||
hide()
|
||||
|
||||
func _on_grabbed(peer_id:int) -> void:
|
||||
assert(state < FlagState.TAKEN)
|
||||
hide()
|
||||
state = FlagState.TAKEN
|
||||
sleeping = true
|
||||
area.set_deferred("monitoring", false)
|
||||
last_carrier = peer_id
|
||||
|
||||
func _on_dropped(_peer_id:int) -> void:
|
||||
assert(state == FlagState.TAKEN)
|
||||
sleeping = false
|
||||
area.set_deferred("monitoring", true)
|
||||
state = FlagState.DROPPED
|
||||
show()
|
||||
1
scenes/entities/flag/flag.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c8xmmi4wp0dp2
|
||||
70
scenes/entities/flag/flag.tscn
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
[gd_scene format=3 uid="uid://c88l3h0ph00c7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c8xmmi4wp0dp2" path="res://scenes/entities/flag/flag.gd" id="1_y7d3d"]
|
||||
[ext_resource type="PackedScene" uid="uid://d3l7fvbdg6m5g" path="res://scenes/entities/flag/assets/flag.glb" id="2_i78em"]
|
||||
[ext_resource type="Shape3D" uid="uid://cc4von3ac65kl" path="res://scenes/entities/flag/assets/capsule_shape.tres" id="2_ig160"]
|
||||
[ext_resource type="PackedScene" uid="uid://2350a3ce4xs8" path="res://scenes/interfaces/waypoint/waypoint3d.tscn" id="4_y8nj0"]
|
||||
|
||||
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_4ymrw"]
|
||||
friction = 0.25
|
||||
bounce = 0.5
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_lpijf"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:linear_velocity")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
properties/2/path = NodePath(".:visible")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 2
|
||||
properties/3/path = NodePath(".:sleeping")
|
||||
properties/3/spawn = true
|
||||
properties/3/replication_mode = 2
|
||||
properties/4/path = NodePath(".:state")
|
||||
properties/4/spawn = true
|
||||
properties/4/replication_mode = 2
|
||||
properties/5/path = NodePath(".:last_carrier")
|
||||
properties/5/spawn = true
|
||||
properties/5/replication_mode = 1
|
||||
|
||||
[node name="Flag" type="RigidBody3D" unique_id=99622018]
|
||||
collision_layer = 8
|
||||
collision_mask = 2147483657
|
||||
axis_lock_angular_x = true
|
||||
axis_lock_angular_y = true
|
||||
axis_lock_angular_z = true
|
||||
mass = 15.0
|
||||
physics_material_override = SubResource("PhysicsMaterial_4ymrw")
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector3(0, 1, 0)
|
||||
continuous_cd = true
|
||||
script = ExtResource("1_y7d3d")
|
||||
|
||||
[node name="Waypoint" parent="." unique_id=568468031 instance=ExtResource("4_y8nj0")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.25, 0)
|
||||
foreground_color = Color(0, 0.75, 0.75, 1)
|
||||
|
||||
[node name="Label3D" parent="Waypoint" index="0"]
|
||||
modulate = Color(0, 0.75, 0.75, 1)
|
||||
|
||||
[node name="Sprite3D" parent="Waypoint" index="1"]
|
||||
modulate = Color(0, 0.75, 0.75, 1)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=778578492]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
shape = ExtResource("2_ig160")
|
||||
|
||||
[node name="GripArea" type="Area3D" parent="." unique_id=1556910033]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GripArea" unique_id=512420425]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
shape = ExtResource("2_ig160")
|
||||
|
||||
[node name="Mesh" parent="." unique_id=1972686775 instance=ExtResource("2_i78em")]
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1011418792]
|
||||
replication_config = SubResource("SceneReplicationConfig_lpijf")
|
||||
|
||||
[editable path="Waypoint"]
|
||||
4
scenes/entities/player/assets/collider.tres
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="CapsuleShape3D" format=3 uid="uid://cb8esdlnottdn"]
|
||||
|
||||
[resource]
|
||||
radius = 0.25
|
||||
BIN
scenes/entities/player/assets/jetpackfx/Particle01.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1,35 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dmf12llra7aq5"
|
||||
path.s3tc="res://.godot/imported/Particle01.png-91f92696ba4d647b684a91370e6d4963.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/jetpackfx/Particle01.png"
|
||||
dest_files=["res://.godot/imported/Particle01.png-91f92696ba4d647b684a91370e6d4963.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
scenes/entities/player/assets/jetpackfx/smoke_01.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
35
scenes/entities/player/assets/jetpackfx/smoke_01.png.import
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ct1v5iadtpadm"
|
||||
path.s3tc="res://.godot/imported/smoke_01.png-37c016a5a51c2cceaf682437fe75284f.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/jetpackfx/smoke_01.png"
|
||||
dest_files=["res://.godot/imported/smoke_01.png-37c016a5a51c2cceaf682437fe75284f.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
scenes/entities/player/assets/jetpackfx/smoke_02.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
35
scenes/entities/player/assets/jetpackfx/smoke_02.png.import
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://doxo4vfn0bjlp"
|
||||
path.s3tc="res://.godot/imported/smoke_02.png-60fd959f44b7825f3153061fdb667fe7.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/jetpackfx/smoke_02.png"
|
||||
dest_files=["res://.godot/imported/smoke_02.png-60fd959f44b7825f3153061fdb667fe7.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
scenes/entities/player/assets/jetpackfx/smoke_05.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
34
scenes/entities/player/assets/jetpackfx/smoke_05.png.import
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dg8jhudb2pp5"
|
||||
path="res://.godot/imported/smoke_05.png-2f5c9b30713a43c1f15c13e2ffc2abef.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/jetpackfx/smoke_05.png"
|
||||
dest_files=["res://.godot/imported/smoke_05.png-2f5c9b30713a43c1f15c13e2ffc2abef.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
scenes/entities/player/assets/jetpackfx/smoke_06.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
34
scenes/entities/player/assets/jetpackfx/smoke_06.png.import
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bj3byo4v71rub"
|
||||
path="res://.godot/imported/smoke_06.png-a3676bce06542e67baa5c4b1c38f48cd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/jetpackfx/smoke_06.png"
|
||||
dest_files=["res://.godot/imported/smoke_06.png-a3676bce06542e67baa5c4b1c38f48cd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
4
scenes/entities/player/assets/surface_sensor_shape.tres
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="SphereShape3D" format=3 uid="uid://dc3sl7amb8vj7"]
|
||||
|
||||
[resource]
|
||||
radius = 0.2
|
||||
BIN
scenes/entities/player/assets/vanguard.glb
Normal file
10834
scenes/entities/player/assets/vanguard.glb.import
Normal file
BIN
scenes/entities/player/assets/vanguard_0.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
36
scenes/entities/player/assets/vanguard_0.png.import
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ia3bdpe4rm1m"
|
||||
path.bptc="res://.godot/imported/vanguard_0.png-7e4d2faeb963c948822713a4215f97e1.bptc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/vanguard_0.png"
|
||||
dest_files=["res://.godot/imported/vanguard_0.png-7e4d2faeb963c948822713a4215f97e1.bptc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=true
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
scenes/entities/player/assets/vanguard_1.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
36
scenes/entities/player/assets/vanguard_1.png.import
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bvgfmpb2l1juf"
|
||||
path.bptc="res://.godot/imported/vanguard_1.png-bd95dbe9f4f4b9a674d90ae4deb4a801.bptc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/vanguard_1.png"
|
||||
dest_files=["res://.godot/imported/vanguard_1.png-bd95dbe9f4f4b9a674d90ae4deb4a801.bptc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=true
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=1
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=1
|
||||
roughness/src_normal="res://entities/player/assets/vanguard_1.png"
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
scenes/entities/player/assets/vanguard_2.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
36
scenes/entities/player/assets/vanguard_2.png.import
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vtrbc3eja3df"
|
||||
path.bptc="res://.godot/imported/vanguard_2.png-369246b9d40329f77c1e8edd517f4717.bptc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/player/assets/vanguard_2.png"
|
||||
dest_files=["res://.godot/imported/vanguard_2.png-369246b9d40329f77c1e8edd517f4717.bptc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=true
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=1
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=1
|
||||
roughness/src_normal="res://entities/player/assets/vanguard_2.png"
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
106
scenes/entities/player/input_sync.gd
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# 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/>.
|
||||
class_name InputSynchronizer extends MultiplayerSynchronizer
|
||||
|
||||
# synchronized properties
|
||||
@export var jetting:bool = false
|
||||
@export var skiing:bool = false
|
||||
# @NOTE: make this direction a ready to use Vector3 instead
|
||||
@export var direction:Vector3
|
||||
@export var rotation:Vector3
|
||||
|
||||
signal jump
|
||||
signal primary(pressed:bool)
|
||||
signal secondary(pressed:bool)
|
||||
signal throw(pressed:bool)
|
||||
signal cycle(shift:int)
|
||||
signal select(index:int)
|
||||
|
||||
var history := Queue.new()
|
||||
|
||||
@export var id := 0 # no registered inputs yet
|
||||
|
||||
func sample() -> Dictionary:
|
||||
return {
|
||||
"id": id,
|
||||
"direction": direction,
|
||||
"rotation": rotation,
|
||||
"jetting": jetting,
|
||||
"skiing": skiing,
|
||||
}
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if is_multiplayer_authority():
|
||||
id += 1
|
||||
|
||||
func _unhandled_input(event:InputEvent) -> void:
|
||||
if is_multiplayer_authority() and owner.is_alive():
|
||||
var vec := Input.get_vector("left", "right", "forward", "backward")
|
||||
direction = Vector3(vec.x, .0, vec.y)
|
||||
jetting = Input.is_action_pressed("secondary")
|
||||
skiing = Input.is_action_pressed("ski")
|
||||
# handle mouse rotation
|
||||
if event is InputEventMouseMotion:
|
||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
# check to apply reverse y axis setting
|
||||
if Settings.get_value("controls", "inverted_y_axis", false):
|
||||
event.relative.y *= -1.0
|
||||
# update rotation with new relative mouse position since last frame
|
||||
var mouse_sensitivity:float = Settings.get_value("controls", "mouse_sensitivity", .6)
|
||||
# clamp vertical rotation (head motion)
|
||||
rotation.x -= event.relative.y * mouse_sensitivity / 100
|
||||
rotation.x = clamp(rotation.x, deg_to_rad(-90.0), deg_to_rad(90.0))
|
||||
# wrap horizontal rotation (to prevent accumulation)
|
||||
rotation.y -= event.relative.x * mouse_sensitivity / 100
|
||||
rotation.y = wrapf(rotation.y, deg_to_rad(0.0), deg_to_rad(360.0))
|
||||
return
|
||||
|
||||
if event.is_action("primary"):
|
||||
_primary.rpc(event.pressed)
|
||||
elif event.is_action_pressed("secondary"):
|
||||
_jump.rpc()
|
||||
elif event.is_action_pressed("throw"):
|
||||
_throw.rpc(event.pressed)
|
||||
elif event.is_action_pressed("wheel_up"):
|
||||
# @TODO: add settings for cycle direction
|
||||
_cycle.rpc(1)
|
||||
elif event.is_action_pressed("wheel_down"):
|
||||
_cycle.rpc(-1)
|
||||
elif event.is_action_pressed("inventory_slot_0"):
|
||||
_select.rpc(0)
|
||||
elif event.is_action_pressed("inventory_slot_1"):
|
||||
_select.rpc(1)
|
||||
elif event.is_action_pressed("inventory_slot_2"):
|
||||
_select.rpc(2)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _jump() -> void:
|
||||
jump.emit()
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _throw(pressed:bool) -> void:
|
||||
throw.emit(pressed)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _primary(pressed: bool) -> void:
|
||||
primary.emit(pressed)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _cycle(shift:int) -> void:
|
||||
cycle.emit(shift)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _select(index:int) -> void:
|
||||
select.emit(index)
|
||||
1
scenes/entities/player/input_sync.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://nj5aqjkwsya4
|
||||
147
scenes/entities/player/jetpackfx.tscn
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
[gd_scene load_steps=23 format=3 uid="uid://cry3qxi8dxwae"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://ct1v5iadtpadm" path="res://scenes/entities/player/assets/jetpackfx/smoke_01.png" id="1_ycxvc"]
|
||||
[ext_resource type="Texture2D" uid="uid://doxo4vfn0bjlp" path="res://scenes/entities/player/assets/jetpackfx/smoke_02.png" id="2_ssb7j"]
|
||||
[ext_resource type="Texture2D" uid="uid://dmf12llra7aq5" path="res://scenes/entities/player/assets/jetpackfx/Particle01.png" id="3_1luuf"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_3u0pn"]
|
||||
offsets = PackedFloat32Array(0, 0.872727)
|
||||
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
|
||||
|
||||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_wjoiw"]
|
||||
gradient = SubResource("Gradient_3u0pn")
|
||||
|
||||
[sub_resource type="Curve" id="Curve_pmb0n"]
|
||||
_limits = [0.0, 2.0, 0.0, 1.0]
|
||||
_data = [Vector2(0, 0.197802), 0.0, 0.0, 0, 0, Vector2(1, 2), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="CurveTexture" id="CurveTexture_cj8ky"]
|
||||
curve = SubResource("Curve_pmb0n")
|
||||
|
||||
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_v556h"]
|
||||
emission_shape = 1
|
||||
emission_sphere_radius = 0.1
|
||||
angle_min = -381.7
|
||||
angle_max = 381.7
|
||||
gravity = Vector3(0, -5, 0)
|
||||
tangential_accel_min = -5.0
|
||||
tangential_accel_max = 5.0
|
||||
scale_curve = SubResource("CurveTexture_cj8ky")
|
||||
color_ramp = SubResource("GradientTexture1D_wjoiw")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_27esd"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_texture = ExtResource("1_ycxvc")
|
||||
billboard_mode = 1
|
||||
billboard_keep_scale = true
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_hegkl"]
|
||||
material = SubResource("StandardMaterial3D_27esd")
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_f4nyt"]
|
||||
offsets = PackedFloat32Array(0, 0.972727)
|
||||
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
|
||||
|
||||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_ovx5q"]
|
||||
gradient = SubResource("Gradient_f4nyt")
|
||||
|
||||
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_l8e6j"]
|
||||
emission_shape = 1
|
||||
emission_sphere_radius = 0.1
|
||||
angle_min = -381.7
|
||||
angle_max = 381.7
|
||||
gravity = Vector3(0, -5, 0)
|
||||
tangential_accel_min = -5.0
|
||||
tangential_accel_max = 5.0
|
||||
scale_curve = SubResource("CurveTexture_cj8ky")
|
||||
color_ramp = SubResource("GradientTexture1D_ovx5q")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bknuu"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_texture = ExtResource("2_ssb7j")
|
||||
billboard_mode = 1
|
||||
billboard_keep_scale = true
|
||||
grow_amount = 1.506
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_aeure"]
|
||||
material = SubResource("StandardMaterial3D_bknuu")
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_t1nsw"]
|
||||
offsets = PackedFloat32Array(0, 0.627273, 0.927273)
|
||||
colors = PackedColorArray(1, 0.858824, 0.0784314, 1, 1, 0, 0, 1, 1, 0, 0, 0)
|
||||
|
||||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_x0y0j"]
|
||||
gradient = SubResource("Gradient_t1nsw")
|
||||
|
||||
[sub_resource type="Curve" id="Curve_fkrx7"]
|
||||
_limits = [0.0, 2.0, 0.0, 1.0]
|
||||
_data = [Vector2(0, 0.197802), 0.0, 0.0, 0, 0, Vector2(0.594203, 0.395604), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="CurveTexture" id="CurveTexture_frkde"]
|
||||
curve = SubResource("Curve_fkrx7")
|
||||
|
||||
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_q1vdw"]
|
||||
emission_shape = 1
|
||||
emission_sphere_radius = 0.05
|
||||
angle_min = -381.7
|
||||
angle_max = 381.7
|
||||
direction = Vector3(0, -1, 0)
|
||||
spread = 25.0
|
||||
initial_velocity_min = 0.5
|
||||
initial_velocity_max = 1.5
|
||||
gravity = Vector3(0, -5, 0)
|
||||
tangential_accel_min = -5.0
|
||||
tangential_accel_max = 5.0
|
||||
scale_min = 0.5
|
||||
scale_max = 0.75
|
||||
scale_curve = SubResource("CurveTexture_frkde")
|
||||
color_ramp = SubResource("GradientTexture1D_x0y0j")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2jwv2"]
|
||||
render_priority = -1
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_texture = ExtResource("3_1luuf")
|
||||
emission_enabled = true
|
||||
emission = Color(1, 0.823529, 0.701961, 1)
|
||||
billboard_mode = 3
|
||||
billboard_keep_scale = true
|
||||
particles_anim_h_frames = 1
|
||||
particles_anim_v_frames = 1
|
||||
particles_anim_loop = false
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_uc7ts"]
|
||||
material = SubResource("StandardMaterial3D_2jwv2")
|
||||
|
||||
[node name="JetpackFX" type="Node3D"]
|
||||
|
||||
[node name="Smoke1" type="GPUParticles3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.150648, 0)
|
||||
emitting = false
|
||||
lifetime = 0.5
|
||||
one_shot = true
|
||||
process_material = SubResource("ParticleProcessMaterial_v556h")
|
||||
draw_pass_1 = SubResource("QuadMesh_hegkl")
|
||||
|
||||
[node name="Smoke2" type="GPUParticles3D" parent="."]
|
||||
emitting = false
|
||||
lifetime = 0.5
|
||||
one_shot = true
|
||||
process_material = SubResource("ParticleProcessMaterial_l8e6j")
|
||||
draw_pass_1 = SubResource("QuadMesh_aeure")
|
||||
|
||||
[node name="Fire1" type="GPUParticles3D" parent="."]
|
||||
emitting = false
|
||||
amount = 16
|
||||
lifetime = 0.3
|
||||
one_shot = true
|
||||
speed_scale = 2.0
|
||||
process_material = SubResource("ParticleProcessMaterial_q1vdw")
|
||||
draw_pass_1 = SubResource("QuadMesh_uc7ts")
|
||||
385
scenes/entities/player/player.gd
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
# 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/>.
|
||||
class_name Player extends RigidBody3D
|
||||
|
||||
signal killed(victim: Player, killer:int)
|
||||
## Emitted when a source wants to damage this body
|
||||
signal damage(source: Node, target: Node, amount: int)
|
||||
## Emitted when the player respawns, see [member Player.respawn].
|
||||
signal respawned(player: Player)
|
||||
|
||||
## depends on hardcoded path but allows to instantiate players from anywhere in the code without the
|
||||
const scene:PackedScene = preload("res://scenes/entities/player/player.tscn")
|
||||
static func instantiate() -> Player:
|
||||
return scene.instantiate()
|
||||
|
||||
#@export var profile:Profile
|
||||
|
||||
@export var peer_id:int = 1
|
||||
@export var username:String = "Newblood"
|
||||
|
||||
@export var team_id:int = -1
|
||||
@export var input:InputSynchronizer
|
||||
|
||||
## Which side the player is holding equipped weapon (right-handed by default)
|
||||
@export_enum("Left", "Right") var dominant_laterality:int = 1
|
||||
|
||||
@export_group("Motion")
|
||||
## The ground speed of the player
|
||||
@export_range(0, 12, .01, "suffix:m/s") var ground_speed:float = 32 / 3.6 # m/s
|
||||
## The aerial acceleration is an horizontal acceleration component which a body can use to
|
||||
## slightly adjust their trajectory while not [member InputSynchronizer.jetting] but airborne
|
||||
@export_range(0, 12, 1., "suffix:m/s²") var aerial_control_acceleration:int = 4
|
||||
|
||||
@export_range(0., 90, 1., "suffix:°") var max_floor_angle:float = 60
|
||||
@export var jump_height:float = 1.0
|
||||
|
||||
@onready var spring_arm:SpringArm3D = $Pivot/SpringArm3D
|
||||
@onready var tp_head_remote_transform:RemoteTransform3D = $Pivot/Mesh/Node/Skeleton3D/Head/RT_Head
|
||||
|
||||
@export var travel_acceleration_curve:Curve
|
||||
|
||||
@export_category("Components")
|
||||
## The is player [IFF] (Indicator Friend or Foe) component.
|
||||
@export var iff:IFF
|
||||
## This is the player [Health] component.
|
||||
@export var health:Health
|
||||
## This is the player [Energy] component.
|
||||
@export var energy:Energy
|
||||
## This is the player [FlagHolder] component.
|
||||
@export var flag_holder:FlagHolder
|
||||
## This is the player [Inventory] component.
|
||||
@export var inventory:Inventory
|
||||
## This is the player [HUD] component.
|
||||
@export var hud:HUD
|
||||
## This is the player [Jetpack] component.
|
||||
@export var jetpack:Jetpack
|
||||
## This is the visual representation of the [Player]
|
||||
@export var mesh:Node3D
|
||||
|
||||
@export var ground_sensor:ShapeCast3D
|
||||
@export var pivot:Node3D
|
||||
@export var camera:Camera3D
|
||||
@onready var collider:CollisionShape3D = $CollisionShape3D
|
||||
|
||||
var gravity:float = ProjectSettings.get_setting("physics/3d/default_gravity") # in m/s²
|
||||
var _jumping:bool = false
|
||||
|
||||
## The [Queue] to store local prediction states
|
||||
var local_predictions:Queue
|
||||
|
||||
## The [Queue] to store authoritative control states
|
||||
var control_states:Queue
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func req_loadout(idx:int = 0) -> void:
|
||||
prints("req_loadout", multiplayer.get_remote_sender_id(), multiplayer.get_unique_id(), idx)
|
||||
inventory.clear()
|
||||
if is_multiplayer_authority():
|
||||
# spawn new weapons
|
||||
prints("req_loadout", self, idx)
|
||||
|
||||
func _ready() -> void:
|
||||
input.set_multiplayer_authority(peer_id)
|
||||
iff.username = username
|
||||
|
||||
local_predictions = Queue.new()
|
||||
control_states = Queue.new()
|
||||
|
||||
input.jump.connect(_jump)
|
||||
input.throw.connect(flag_holder.throw)
|
||||
|
||||
energy.changed.connect(hud.energy_bar.set_value)
|
||||
health.changed.connect(hud.health_bar.set_value)
|
||||
health.changed.connect(iff.health_bar.set_value)
|
||||
health.killed.connect(_on_killed)
|
||||
|
||||
if is_pawn():
|
||||
camera.current = true
|
||||
camera.fov = Settings.get_value("video", "fov")
|
||||
iff.hide()
|
||||
mesh.hide()
|
||||
else:
|
||||
hud.hide()
|
||||
|
||||
func _process(_delta:float) -> void:
|
||||
if not is_alive():
|
||||
return
|
||||
|
||||
if is_pawn():
|
||||
match camera.state:
|
||||
camera.CameraState.THIRD_PERSON:
|
||||
_animate_third_person()
|
||||
camera.CameraState.FIRST_PERSON:
|
||||
pass
|
||||
_input_rotations()
|
||||
else:
|
||||
_animate_third_person()
|
||||
|
||||
func _input_rotations() -> void:
|
||||
spring_arm.rotation.x = lerp_angle(spring_arm.rotation.x, input.rotation.x, .5)
|
||||
pivot.rotation.y = lerp_angle(pivot.rotation.y, input.rotation.y, .5)
|
||||
|
||||
func _physics_process(_delta:float) -> void:
|
||||
if not is_alive():
|
||||
return
|
||||
|
||||
func apply_inputs(sample:Variant) -> void:
|
||||
if not sample: return
|
||||
#if OS.is_debug_build():
|
||||
#prints("%s:" % multiplayer.get_unique_id(), "apply Player(%s) input" % peer_id, sample)
|
||||
|
||||
var local_direction:Vector3 = pivot.global_transform.basis * sample.direction
|
||||
# handle jetpack
|
||||
if sample.jetting and energy.value > 0 and not jetpack.stuttering:
|
||||
apply_force(mass * (Vector3.UP * (gravity + jetpack.vertical_acceleration) + local_direction * jetpack.horizontal_acceleration))
|
||||
|
||||
physics_material_override.friction = !sample.skiing
|
||||
linear_damp_mode = DAMP_MODE_COMBINE
|
||||
|
||||
if is_on_floor():
|
||||
# retrieve collision normal
|
||||
var normal:Vector3 = ground_sensor.get_collision_normal(0)
|
||||
if not sample.direction.is_zero_approx():
|
||||
if sample.skiing:
|
||||
# zero damping ski
|
||||
linear_damp_mode = DAMP_MODE_REPLACE
|
||||
# naive carving
|
||||
linear_velocity = linear_velocity.lerp(linear_velocity.rotated(normal, Input.get_axis("right", "left") * PI/32), .1)
|
||||
else:
|
||||
# calculate the angle between the ground normal and the up vector
|
||||
var slope_angle:float = rad_to_deg(acos(normal.dot(Vector3.UP)))
|
||||
# check if the slope angle exceeds the maximum slope angle
|
||||
if slope_angle <= max_floor_angle:
|
||||
# retrieve normalized projection of local direction on tangent plane to normal
|
||||
var tangential_direction := (local_direction - normal * local_direction.dot(normal)).normalized()
|
||||
var target_velocity := tangential_direction * ground_speed
|
||||
var target_force:Vector3 = mass * (target_velocity - linear_velocity) / get_physics_process_delta_time()
|
||||
# @TODO: smooth acceleration
|
||||
var factor := travel_acceleration_curve.sample(1.)
|
||||
apply_force(target_force * factor)
|
||||
if _jumping:
|
||||
apply_central_impulse(basis.y * mass * sqrt(2 * gravity * jump_height))
|
||||
_jumping = false
|
||||
|
||||
else:
|
||||
# handle aerial control
|
||||
if not sample.jetting:
|
||||
apply_force(mass * aerial_control_acceleration * local_direction)
|
||||
|
||||
## This rpc can be called remotly by the authority to send specified [member control_state] unreliably,
|
||||
## which simply adds it to [member control_states]
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func new_state(control_state:Dictionary) -> void:
|
||||
if not control_states.is_empty():
|
||||
var back:Dictionary = control_states.back()
|
||||
# drop late control states
|
||||
if control_state.id < back.id:
|
||||
prints("%s: new state (dropped)" % multiplayer.get_unique_id(), control_state, back, peer_id, self)
|
||||
return
|
||||
prints("%s: new state" % multiplayer.get_unique_id(), control_state, peer_id, self)
|
||||
control_states.enqueue(control_state)
|
||||
|
||||
func replay_inputs(from:int) -> void:
|
||||
# reset local_predictions since this node needs to resync, past predictions are wrong
|
||||
local_predictions = Queue.new()
|
||||
# reapply unacknowledged inputs if any
|
||||
if not input.history.is_empty():
|
||||
for i in range(input.history.size()):
|
||||
var sample:Dictionary = input.history.dequeue()
|
||||
if sample.id > from:
|
||||
prints("replaying from", from, sample)
|
||||
input.history.enqueue(sample)
|
||||
apply_inputs(sample)
|
||||
var state := PhysicsServer3D.body_get_direct_state(get_rid())
|
||||
state.integrate_forces()
|
||||
# regenerate predictions
|
||||
local_predictions.enqueue(get_state(sample.id))
|
||||
prints("local state is now", local_predictions.back())
|
||||
|
||||
func reconcile(prediction:Dictionary, control:Dictionary) -> void:
|
||||
prints("reconcile", prediction, control)
|
||||
var sigma:float = .05
|
||||
var resync := [
|
||||
(prediction.state[0] - control.state[0]).length() > sigma,
|
||||
(prediction.state[1] - control.state[1]).length() > sigma
|
||||
]
|
||||
if resync[0]:
|
||||
global_position = control.state[0]
|
||||
if resync[1]:
|
||||
apply_force(mass * (control.state[1] - linear_velocity) / get_physics_process_delta_time())
|
||||
|
||||
if false:
|
||||
spring_arm.global_rotation.x = control.state[2].x
|
||||
pivot.global_rotation.y = control.state[2].y # same as spring arm
|
||||
|
||||
if resync.max():
|
||||
prints("resync", resync)
|
||||
replay_inputs(control.id)
|
||||
|
||||
func get_state(id:int) -> Dictionary:
|
||||
return {
|
||||
"id": id,
|
||||
"state": [ global_position, linear_velocity, global_rotation ]
|
||||
}
|
||||
|
||||
var last_sent_state := -1
|
||||
var last_control := 0
|
||||
|
||||
func _integrate_forces(state:PhysicsDirectBodyState3D) -> void:
|
||||
if not is_alive():
|
||||
return
|
||||
#prints("%s:"%multiplayer.get_unique_id(), "tick", Engine.get_physics_frames(), "Player(%s)" % peer_id)
|
||||
if is_pawn():
|
||||
# local prediction for pawns
|
||||
var sample:Dictionary = input.sample()
|
||||
if multiplayer.is_server():
|
||||
apply_inputs(sample)
|
||||
else:
|
||||
input.history.enqueue(sample)
|
||||
apply_inputs(sample)
|
||||
local_predictions.enqueue(get_state(sample.id))
|
||||
if not control_states.is_empty():
|
||||
var prediction:Dictionary = local_predictions.front()
|
||||
var control:Dictionary = control_states.dequeue()
|
||||
prints("%s:" % multiplayer.get_unique_id(), "A", prediction.id, control.id)
|
||||
while prediction.id != control.id:
|
||||
if local_predictions.is_empty():
|
||||
break
|
||||
prediction = local_predictions.dequeue()
|
||||
if prediction.id == control.id:
|
||||
prints("%s:" % multiplayer.get_unique_id(), "B", prediction.id, control.id)
|
||||
reconcile(prediction, control)
|
||||
else:
|
||||
prints("%s:" % multiplayer.get_unique_id(), "B no prediction / control match, wait for next control state")
|
||||
else:
|
||||
if multiplayer.is_server():
|
||||
var sample:Dictionary = input.sample()
|
||||
# make sure synchronized inputs are processed once
|
||||
if sample.id != last_sent_state:
|
||||
last_sent_state = sample.id
|
||||
apply_inputs(sample)
|
||||
spring_arm.rotation.x = input.rotation.x
|
||||
pivot.rotation.y = input.rotation.y
|
||||
new_state.rpc(get_state(sample.id))
|
||||
|
||||
#if not (local_predictions.is_empty() or control_states.is_empty()):
|
||||
#var prediction:Dictionary = local_predictions.front()
|
||||
#var control:Dictionary = control_states.front()
|
||||
#prints("A", prediction.id, control.id)
|
||||
## dequeue predictions until we match most fresh control state
|
||||
#while prediction.id != control.id:
|
||||
#if local_predictions.is_empty():
|
||||
#break
|
||||
#prediction = local_predictions.dequeue()
|
||||
#if prediction.id == control.id:
|
||||
#reconcile(prediction, control)
|
||||
#prints("B", prediction.id, control.id)
|
||||
#else:
|
||||
#prints("B no prediction / control match, wait for next control state")
|
||||
|
||||
#else:
|
||||
#if multiplayer.is_server():
|
||||
#var sample:Dictionary = input.sample()
|
||||
#apply_inputs(sample)
|
||||
#new_state.rpc(get_state(sample.id))
|
||||
|
||||
|
||||
func apply_state(control:Dictionary, do_rotate:bool = false) -> void:
|
||||
global_position = control.state[0]
|
||||
# apply central force to reach control state velocity
|
||||
apply_force(mass * (control.state[1] - linear_velocity) / get_physics_process_delta_time())
|
||||
if do_rotate:
|
||||
spring_arm.global_rotation.x = control.state[2].x
|
||||
pivot.global_rotation.y = control.state[2].y # same as spring arm
|
||||
prints("apply_state", global_position, control.state[0])
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if is_pawn() and event.is_action("pov") and event.pressed and not event.echo:
|
||||
if camera.state == camera.CameraState.FIRST_PERSON:
|
||||
tp_head_remote_transform.update_position = false
|
||||
# @TODO: check if we can alt + mouse wheel to control the length of the spring arm
|
||||
spring_arm.spring_length = 3.
|
||||
mesh.show()
|
||||
camera.state = camera.CameraState.THIRD_PERSON
|
||||
else:
|
||||
spring_arm.spring_length = 0.
|
||||
tp_head_remote_transform.update_position = true
|
||||
mesh.animation_tree.set("parameters/locomotion/blend_position", Vector2.ZERO)
|
||||
mesh.animation_tree.set("parameters/mix/blend_amount", .0)
|
||||
mesh.hide()
|
||||
camera.state = camera.CameraState.FIRST_PERSON
|
||||
|
||||
func _animate_third_person() -> void:
|
||||
if not is_alive():
|
||||
mesh.animation_tree.set("parameters/transition/transition_request", &"dead")
|
||||
return
|
||||
|
||||
if is_on_floor():
|
||||
#mesh.animation_tree.set("parameters/transition/transition_request", &"grounded")
|
||||
if input.skiing:
|
||||
mesh.animation_tree.set("parameters/locomotion/blend_position", Vector2.ZERO)
|
||||
mesh.animation_tree.set("parameters/mix/blend_amount", .0)
|
||||
else:
|
||||
# update animation direction
|
||||
var blend_pos:Vector2 = mesh.animation_tree.get("parameters/locomotion/blend_position")
|
||||
var new_blend_pos:Vector3 = (mesh.global_basis.inverse() * linear_velocity).normalized()
|
||||
mesh.animation_tree.set("parameters/locomotion/blend_position", blend_pos.lerp(Vector2(new_blend_pos.x, new_blend_pos.z), .5))
|
||||
# update animation intensity
|
||||
var blend:float = mesh.animation_tree["parameters/mix/blend_amount"]
|
||||
mesh.animation_tree.set("parameters/mix/blend_amount", lerp(blend, input.direction.length(), .5))
|
||||
else:
|
||||
mesh.animation_tree.set("parameters/transition/transition_request", &"mid_air")
|
||||
|
||||
## This method returns wether the local system multiplayer peer has input authority over this node
|
||||
func is_pawn() -> bool:
|
||||
return multiplayer.get_unique_id() == peer_id
|
||||
|
||||
func _jump() -> void:
|
||||
_jumping = is_alive()
|
||||
|
||||
func is_on_floor() -> bool:
|
||||
return ground_sensor.is_colliding()
|
||||
|
||||
func is_alive() -> bool:
|
||||
return health.state == health.HealthState.ALIVE
|
||||
|
||||
## Whether the player [FlagHolder] holds a flag or not
|
||||
func has_flag() -> bool:
|
||||
return flag_holder.flag != null
|
||||
|
||||
func _on_killed(by_peer_id:int) -> void:
|
||||
flag_holder.drop.rpc_id(1)
|
||||
if is_pawn():
|
||||
mesh.animation_player.play("death")
|
||||
tp_head_remote_transform.update_rotation = true
|
||||
tp_head_remote_transform.update_position = true
|
||||
killed.emit(self, by_peer_id)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func respawn(location: Vector3) -> void:
|
||||
mesh.animation_player.stop()
|
||||
linear_velocity = Vector3.ZERO
|
||||
health.heal()
|
||||
global_position = location
|
||||
#for weapon: Weapon in inventory.weapons.get_children():
|
||||
#weapon.set_ammo.rpc(weapon.max_ammo)
|
||||
respawned.emit(self)
|
||||
|
||||
func _on_animation_player_animation_finished(animation_name: StringName) -> void:
|
||||
if animation_name == "death":
|
||||
tp_head_remote_transform.update_rotation = false
|
||||
tp_head_remote_transform.update_position = false
|
||||
camera.rotation = Vector3.ZERO
|
||||
1
scenes/entities/player/player.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dj0gvjeh0tvl8
|
||||
6049
scenes/entities/player/player.tscn
Normal file
44
scenes/entities/player/scripts/energy.gd
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# 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/>.
|
||||
## This class defines an Energy component for entities.
|
||||
@icon("res://assets/icons/energy.svg")
|
||||
class_name Energy extends Node
|
||||
|
||||
## Emitted when [param value] is changed ([param value] > [param 0])
|
||||
signal changed(new_value:int)
|
||||
## Emitted when [param value] is exhausted ([param value] == [param 0])
|
||||
signal exhausted
|
||||
|
||||
## The maximum energy value
|
||||
@export var max_value:float = 100.
|
||||
|
||||
## The amount of energy left to consume
|
||||
@export var value:float = 100.:
|
||||
set(new_value):
|
||||
value = clampf(new_value, 0, max_value)
|
||||
if value > 0:
|
||||
changed.emit(value)
|
||||
else:
|
||||
exhausted.emit()
|
||||
|
||||
## Wether the energy [param value] should recharge over time based on [param charge_rate]
|
||||
@export var charging:bool = true
|
||||
|
||||
## The energy charge rate per second, expressed as a percentage of [param max_value]
|
||||
@export_range(0,100, 1., "suffix:%") var charge_rate:float = 10
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if charging:
|
||||
value += charge_rate / 100 * max_value * delta
|
||||
1
scenes/entities/player/scripts/energy.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c0v6nfmjxp045
|
||||
38
scenes/entities/player/scripts/info.gd
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
@tool
|
||||
class_name Profile extends Resource
|
||||
|
||||
@export var name: String
|
||||
|
||||
var _loadouts:Array[Loadout] = []:
|
||||
set(value):
|
||||
_loadouts = value
|
||||
_loadouts.resize(3)
|
||||
notify_property_list_changed()
|
||||
|
||||
func _init() -> void:
|
||||
_loadouts.resize(3)
|
||||
notify_property_list_changed()
|
||||
|
||||
func _get_property_list() -> Array:
|
||||
var properties := []
|
||||
for i in range(_loadouts.size()):
|
||||
properties.append({
|
||||
"name": "loadout_%d" % (i + 1),
|
||||
"type": TYPE_OBJECT,
|
||||
"hint": PROPERTY_HINT_RESOURCE_TYPE,
|
||||
"hint_string": "Loadout",
|
||||
})
|
||||
return properties
|
||||
|
||||
func _get(prop:StringName) -> Variant:
|
||||
if prop.begins_with("loadout_"):
|
||||
var index := prop.get_slice("_", 1).to_int() - 1
|
||||
return _loadouts[index]
|
||||
return null
|
||||
|
||||
func _set(prop:StringName, value:Variant) -> bool:
|
||||
if prop.begins_with("loadout_"):
|
||||
var index := prop.get_slice("_", 1).to_int() - 1
|
||||
_loadouts[index] = value
|
||||
return true
|
||||
return false
|
||||
1
scenes/entities/player/scripts/info.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cnvbn183o3bgc
|
||||
62
scenes/entities/player/scripts/jetpack.gd
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# 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/>.
|
||||
class_name Jetpack extends Node3D
|
||||
|
||||
@export var input:InputSynchronizer
|
||||
@export var energy:Energy
|
||||
|
||||
## The [param energy.value] drain rate per second, as a percentage of [param energy.max_value]
|
||||
@export_range(0,100, 1., "suffix:%") var drain_rate:float = 30
|
||||
## The stutter threshold of [param energy.value] at which the jetpack needs to charge to be used again,
|
||||
## as a percentage of [param energy.max_value]
|
||||
@export_range(0,100, 1., "suffix:%") var stutter_treshold:float = 15
|
||||
## This property indicates wether the [Jetpack] is stuttering
|
||||
@export var stuttering:bool = false
|
||||
|
||||
@export_category("Thrust")
|
||||
# @NOTE: If a body want to accelerate upward on a high-gravity planet, say by 10 m/s²:
|
||||
# F = 80 * (19.6 + 10) = 80 * 29.6 = 2,368 N
|
||||
## The thrust vertical acceleration component, expressed in m/s²
|
||||
@export_range(0, 100, .01, "suffix:m/s²") var vertical_acceleration:float = 10
|
||||
# @NOTE: If a body want to accelerate horizontally, say by 15 m/s²:
|
||||
# F = 80 * 15 = 1200 N
|
||||
## The thrust horizontal acceleration component, expressed in m/s²
|
||||
@export_range(0, 100, .01, "suffix:m/s²") var horizontal_acceleration:float = 15
|
||||
|
||||
@export_category("Effects")
|
||||
## An array of [GPUParticles3D] children
|
||||
@onready var emitters:Array = find_children("*", "GPUParticles3D")
|
||||
## Wether all [GPUParticles3D] children have their [member GPUParticles3D.emitting] property set
|
||||
@export var emitting:bool:
|
||||
get: return emitters.map(func(child:Node) -> bool: return child.emitting).max()
|
||||
set(value): emitters.map(func(child:Node) -> void: child.emitting = value)
|
||||
## Wether all [GPUParticles3D] children have their [member GPUParticles3D.one_shot] property set
|
||||
@export var one_shot:bool:
|
||||
get: return emitters.map(func(child:Node) -> bool: return child.one_shot).max()
|
||||
set(value): emitters.map(func(child:Node) -> void: child.one_shot = value)
|
||||
|
||||
func _ready() -> void:
|
||||
assert(input and energy, "Jetpack requires 'input' and 'energy' properties to be assigned")
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if stuttering:
|
||||
stuttering = energy.value <= stutter_treshold / 100. * energy.max_value
|
||||
elif input.jetting and energy.value <= 0:
|
||||
stuttering = true
|
||||
energy.charging = !input.jetting or stuttering
|
||||
if !energy.charging:
|
||||
energy.value -= drain_rate / 100. * energy.max_value * delta
|
||||
if input.jetting and energy.value > 0 and not stuttering:
|
||||
emitting = true
|
||||
1
scenes/entities/player/scripts/jetpack.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://chwntu0dwwtwq
|
||||
18
scenes/entities/player/vanguard.gd
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# 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/>.
|
||||
extends Node3D
|
||||
|
||||
@onready var animation_player : AnimationPlayer = $AnimationPlayer
|
||||
@onready var animation_tree : AnimationTree = $AnimationTree
|
||||
1
scenes/entities/player/vanguard.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b0g22hqlya7wy
|
||||
230
scenes/entities/player/vanguard.tscn
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
[gd_scene load_steps=20 format=3 uid="uid://drbefw6akui2v"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://4naw661fqmjg" path="res://scenes/entities/player/assets/vanguard.glb" id="1_d2ik6"]
|
||||
[ext_resource type="Script" uid="uid://b0g22hqlya7wy" path="res://scenes/entities/player/vanguard.gd" id="2_c22xr"]
|
||||
[ext_resource type="PackedScene" uid="uid://c8co0qa2omjmh" path="res://scenes/entities/weapons/space_gun/space_gun.tscn" id="3_cfjna"]
|
||||
[ext_resource type="PackedScene" uid="uid://b0xql5hi0b52y" path="res://scenes/entities/weapons/chaingun/chaingun.tscn" id="4_0qqks"]
|
||||
[ext_resource type="PackedScene" uid="uid://cstl7yxc75572" path="res://scenes/entities/weapons/grenade_launcher/grenade_launcher.tscn" id="5_p6n3u"]
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_lw37l"]
|
||||
animation = &"death"
|
||||
|
||||
[sub_resource type="AnimationNodeOneShot" id="AnimationNodeOneShot_ncg4i"]
|
||||
|
||||
[sub_resource type="AnimationNodeTimeScale" id="AnimationNodeTimeScale_ugw6e"]
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_dyfkg"]
|
||||
animation = &"jump"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_x158p"]
|
||||
animation = &"jump_up"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_l3m4j"]
|
||||
animation = &"run_right"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_a1e0h"]
|
||||
animation = &"run_backward"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_sjcrs"]
|
||||
animation = &"run_left"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_vf3mp"]
|
||||
animation = &"run_forward"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_n6f67"]
|
||||
animation = &"idle"
|
||||
|
||||
[sub_resource type="AnimationNodeBlendSpace2D" id="AnimationNodeBlendSpace2D_oxci6"]
|
||||
blend_point_0/node = SubResource("AnimationNodeAnimation_l3m4j")
|
||||
blend_point_0/pos = Vector2(-1, 0)
|
||||
blend_point_1/node = SubResource("AnimationNodeAnimation_a1e0h")
|
||||
blend_point_1/pos = Vector2(0, -1)
|
||||
blend_point_2/node = SubResource("AnimationNodeAnimation_sjcrs")
|
||||
blend_point_2/pos = Vector2(1, 0)
|
||||
blend_point_3/node = SubResource("AnimationNodeAnimation_vf3mp")
|
||||
blend_point_3/pos = Vector2(0, 1)
|
||||
blend_point_4/node = SubResource("AnimationNodeAnimation_n6f67")
|
||||
blend_point_4/pos = Vector2(0, 0)
|
||||
sync = true
|
||||
|
||||
[sub_resource type="AnimationNodeOneShot" id="AnimationNodeOneShot_ax6or"]
|
||||
|
||||
[sub_resource type="AnimationNodeTransition" id="AnimationNodeTransition_c220w"]
|
||||
xfade_time = 0.1
|
||||
input_0/name = "grounded"
|
||||
input_0/auto_advance = false
|
||||
input_0/reset = true
|
||||
input_1/name = "mid_air"
|
||||
input_1/auto_advance = false
|
||||
input_1/reset = true
|
||||
input_2/name = "dead"
|
||||
input_2/auto_advance = false
|
||||
input_2/reset = true
|
||||
|
||||
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_1ok7t"]
|
||||
nodes/Death/node = SubResource("AnimationNodeOneShot_ncg4i")
|
||||
nodes/Death/position = Vector2(880, 500)
|
||||
"nodes/Death 2/node" = SubResource("AnimationNodeAnimation_lw37l")
|
||||
"nodes/Death 2/position" = Vector2(674.414, 529.106)
|
||||
nodes/GroundSpeed/node = SubResource("AnimationNodeTimeScale_ugw6e")
|
||||
nodes/GroundSpeed/position = Vector2(920, 20)
|
||||
nodes/JumpLoop/node = SubResource("AnimationNodeAnimation_dyfkg")
|
||||
nodes/JumpLoop/position = Vector2(500, 400)
|
||||
nodes/JumpUp/node = SubResource("AnimationNodeAnimation_x158p")
|
||||
nodes/JumpUp/position = Vector2(500, 240)
|
||||
nodes/Locomotion/node = SubResource("AnimationNodeBlendSpace2D_oxci6")
|
||||
nodes/Locomotion/position = Vector2(680, 40)
|
||||
nodes/OneShot/node = SubResource("AnimationNodeOneShot_ax6or")
|
||||
nodes/OneShot/position = Vector2(880, 240)
|
||||
nodes/Transition/node = SubResource("AnimationNodeTransition_c220w")
|
||||
nodes/Transition/position = Vector2(1200, 80)
|
||||
nodes/output/position = Vector2(1400, 0)
|
||||
node_connections = [&"Death", 0, &"Death 2", &"GroundSpeed", 0, &"Locomotion", &"OneShot", 0, &"JumpUp", &"OneShot", 1, &"JumpLoop", &"Transition", 0, &"GroundSpeed", &"Transition", 1, &"OneShot", &"Transition", 2, &"Death", &"output", 0, &"Transition"]
|
||||
|
||||
[node name="Vanguard" node_paths=PackedStringArray("spine_ik_target_attachment", "hand_attachment") instance=ExtResource("1_d2ik6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.61612e-06, 0.000176162, 0.000105232)
|
||||
script = ExtResource("2_c22xr")
|
||||
spine_ik_target_attachment = NodePath("SpineIKTarget")
|
||||
hand_attachment = NodePath("Node/Skeleton3D/HandAttachment")
|
||||
|
||||
[node name="Skeleton3D" parent="Node" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00146306, 0, -0.00178421)
|
||||
bones/0/position = Vector3(-0.0048219, 0.946668, 0.00678214)
|
||||
bones/0/rotation = Quaternion(-0.0341192, -0.409249, -0.0209221, 0.911545)
|
||||
bones/2/rotation = Quaternion(-0.00595832, -0.0014545, 0.0101407, 0.99993)
|
||||
bones/4/rotation = Quaternion(0.0691268, 0.00227406, 0.0147948, 0.997496)
|
||||
bones/6/rotation = Quaternion(0.160951, 0.00623474, 0.0096637, 0.986895)
|
||||
bones/8/rotation = Quaternion(0.675761, -0.399581, 0.409695, 0.464577)
|
||||
bones/10/rotation = Quaternion(0.212344, -0.0870591, -0.287653, 0.929831)
|
||||
bones/12/rotation = Quaternion(0.0811501, 0.206806, -0.754068, 0.618084)
|
||||
bones/14/rotation = Quaternion(0.00943715, 0.0971687, -0.145046, 0.984597)
|
||||
bones/20/rotation = Quaternion(-0.123455, 0.0248346, 0.23344, 0.964183)
|
||||
bones/24/rotation = Quaternion(0.0450683, -0.000817796, 0.0508488, 0.997689)
|
||||
bones/26/rotation = Quaternion(0.100545, -1.16532e-07, 0.00792588, 0.994901)
|
||||
bones/28/rotation = Quaternion(0.288532, -1.83796e-07, 0.0227447, 0.9572)
|
||||
bones/32/rotation = Quaternion(0.00674081, -0.0316083, 0.105474, 0.993897)
|
||||
bones/34/rotation = Quaternion(0.780684, 4.23384e-08, 0.0615407, 0.621889)
|
||||
bones/36/rotation = Quaternion(0.580978, -1.56224e-07, 0.0457976, 0.81263)
|
||||
bones/40/rotation = Quaternion(0.0913739, 0.113691, 0.100265, 0.984211)
|
||||
bones/42/rotation = Quaternion(0.836841, 4.40546e-07, 0.0659669, 0.543457)
|
||||
bones/44/rotation = Quaternion(0.633142, 6.48257e-09, 0.04991, 0.772425)
|
||||
bones/50/rotation = Quaternion(0.729888, -4.88266e-08, 0.0575362, 0.681141)
|
||||
bones/52/rotation = Quaternion(0.624011, -9.63141e-08, 0.04919, 0.779865)
|
||||
bones/54/rotation = Quaternion(-1.18924e-16, 2.81961e-21, -4.50738e-10, 1)
|
||||
bones/56/rotation = Quaternion(-0.0254885, 0.0439755, -0.00910637, 0.998666)
|
||||
bones/58/rotation = Quaternion(-0.0119802, 0.289956, -0.0527053, 0.955513)
|
||||
bones/62/rotation = Quaternion(0.689943, 0.375565, -0.410267, 0.463261)
|
||||
bones/64/rotation = Quaternion(0.337746, -0.290519, 0.159479, 0.880961)
|
||||
bones/66/rotation = Quaternion(0.540371, -0.580679, 0.406779, 0.453147)
|
||||
bones/68/position = Vector3(0.000474975, 0.275475, 0.035475)
|
||||
bones/68/rotation = Quaternion(0.0150529, -0.289001, 0.0720108, 0.954498)
|
||||
bones/70/rotation = Quaternion(0.155965, 0.0109114, -0.00107202, 0.987702)
|
||||
bones/72/rotation = Quaternion(0.563923, 4.19095e-08, -0.0577906, 0.823803)
|
||||
bones/74/rotation = Quaternion(0.285209, 0.0197164, -0.0936782, 0.953673)
|
||||
bones/78/rotation = Quaternion(0.057484, -0.0698946, -0.0100642, 0.995846)
|
||||
bones/80/rotation = Quaternion(0.433127, 5.44824e-08, -0.0443866, 0.900239)
|
||||
bones/82/rotation = Quaternion(0.274138, -1.97906e-08, -0.0280937, 0.96128)
|
||||
bones/86/rotation = Quaternion(0.244152, 0.0521336, 0.176446, 0.952123)
|
||||
bones/88/rotation = Quaternion(0.0146391, -1.48637e-07, -0.121951, 0.992428)
|
||||
bones/90/rotation = Quaternion(-0.147655, -0.0737763, 0.197847, 0.966236)
|
||||
bones/94/rotation = Quaternion(0.180682, 0.0832945, -0.00387314, 0.980001)
|
||||
bones/96/rotation = Quaternion(0.245651, -6.35919e-08, -0.0251742, 0.969032)
|
||||
bones/98/rotation = Quaternion(0.246432, 7.6252e-08, -0.0252543, 0.968831)
|
||||
bones/100/rotation = Quaternion(-6.44756e-14, -1.4372e-11, -3.12192e-10, 1)
|
||||
bones/102/rotation = Quaternion(0.179829, 0.0890365, -0.000307644, 0.97966)
|
||||
bones/104/rotation = Quaternion(0.388149, 1.28057e-07, -0.0397774, 0.920738)
|
||||
bones/106/rotation = Quaternion(0.372324, -1.37021e-07, -0.0381557, 0.927318)
|
||||
bones/110/rotation = Quaternion(-0.167577, 0.223934, 0.958827, 0.0492099)
|
||||
bones/112/rotation = Quaternion(-0.466474, -0.0088339, -0.0232928, 0.884184)
|
||||
bones/114/rotation = Quaternion(0.575696, 0.0793941, -0.0250592, 0.813414)
|
||||
bones/116/rotation = Quaternion(0.355062, 0.0493655, 0.0246355, 0.933213)
|
||||
bones/120/rotation = Quaternion(0.115252, 0.282473, 0.945749, -0.111737)
|
||||
bones/122/rotation = Quaternion(-0.494906, -0.0647935, 0.0183973, 0.866332)
|
||||
bones/124/rotation = Quaternion(0.417677, -0.0431149, 0.00625689, 0.90755)
|
||||
bones/126/rotation = Quaternion(0.397818, -0.0427722, -0.00601182, 0.916447)
|
||||
|
||||
[node name="HandAttachment" type="BoneAttachment3D" parent="Node/Skeleton3D" index="0"]
|
||||
transform = Transform3D(-0.152214, 0.0548832, 0.986823, 0.933991, 0.334546, 0.125459, -0.323252, 0.94078, -0.102183, -0.261612, 1.14328, 0.0896011)
|
||||
bone_name = "mixamorigRightHand"
|
||||
bone_idx = 14
|
||||
|
||||
[node name="SpaceGun" parent="Node/Skeleton3D/HandAttachment" index="0" instance=ExtResource("3_cfjna")]
|
||||
transform = Transform3D(-0.170368, 0.95438, -0.24522, -0.0464896, 0.240797, 0.969461, 0.984283, 0.176566, 0.00334465, 0.0409744, 0.381457, 0.0445086)
|
||||
script = null
|
||||
|
||||
[node name="grip" parent="Node/Skeleton3D/HandAttachment/SpaceGun/Mesh/Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.84217e-14, 1.19209e-07, 2.38419e-07)
|
||||
|
||||
[node name="main" parent="Node/Skeleton3D/HandAttachment/SpaceGun/Mesh/Armature/Skeleton3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.84217e-14, 1.19209e-07, 2.38419e-07)
|
||||
|
||||
[node name="sides" parent="Node/Skeleton3D/HandAttachment/SpaceGun/Mesh/Armature/Skeleton3D" index="2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.84217e-14, 1.19209e-07, 2.38419e-07)
|
||||
|
||||
[node name="ChainGun" parent="Node/Skeleton3D/HandAttachment" index="1" instance=ExtResource("4_0qqks")]
|
||||
transform = Transform3D(-0.0425921, 0.235665, -0.0717487, -0.0116224, 0.0708099, 0.23948, 0.246071, 0.0441354, -0.00110777, -0.00204359, 0.464663, 0.0585247)
|
||||
visible = false
|
||||
script = null
|
||||
|
||||
[node name="BarrelsInner" parent="Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="BarrelsOuter" parent="Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Base" parent="Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Grip" parent="Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="3"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Barrels" parent="Node/Skeleton3D/HandAttachment/ChainGun" index="3"]
|
||||
transform = Transform3D(-1, -7.45058e-09, 8.70787e-08, 8.56817e-08, 3.72529e-09, 1, 0, 1, 0, 0, -1.19209e-07, -0.55315)
|
||||
|
||||
[node name="GrenadeLauncher" parent="Node/Skeleton3D/HandAttachment" index="2" instance=ExtResource("5_p6n3u")]
|
||||
transform = Transform3D(-0.170368, 0.95438, -0.24522, -0.0464896, 0.240797, 0.969461, 0.984283, 0.176566, 0.00334465, 0.0409744, 0.381457, 0.0445086)
|
||||
visible = false
|
||||
script = null
|
||||
|
||||
[node name="barrel" parent="Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.19209e-07, 0)
|
||||
|
||||
[node name="grip" parent="Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.082471, -0.0653242)
|
||||
|
||||
[node name="main" parent="Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D" index="2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.19209e-07, 0)
|
||||
|
||||
[node name="SpineIK" type="SkeletonIK3D" parent="Node/Skeleton3D" index="3"]
|
||||
process_priority = 1
|
||||
root_bone = &"mixamorigHips"
|
||||
tip_bone = &"mixamorigSpine2"
|
||||
override_tip_basis = false
|
||||
target_node = NodePath("../../../SpineIKTarget")
|
||||
|
||||
[node name="AnimationPlayer" parent="." index="1"]
|
||||
autoplay = "idle"
|
||||
playback_default_blend_time = 0.2
|
||||
|
||||
[node name="AnimationTree" type="AnimationTree" parent="." index="2"]
|
||||
tree_root = SubResource("AnimationNodeBlendTree_1ok7t")
|
||||
anim_player = NodePath("../AnimationPlayer")
|
||||
parameters/Death/active = false
|
||||
parameters/Death/internal_active = false
|
||||
parameters/Death/request = 0
|
||||
parameters/GroundSpeed/scale = 0.0
|
||||
parameters/Locomotion/blend_position = Vector2(0.0952381, 0.280822)
|
||||
parameters/OneShot/active = false
|
||||
parameters/OneShot/internal_active = false
|
||||
parameters/OneShot/request = 0
|
||||
parameters/Transition/current_state = "grounded"
|
||||
parameters/Transition/transition_request = ""
|
||||
parameters/Transition/current_index = 0
|
||||
|
||||
[node name="SpineIKTarget" type="Marker3D" parent="." index="3"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.479842, 0)
|
||||
|
||||
[editable path="Node/Skeleton3D/HandAttachment/SpaceGun"]
|
||||
[editable path="Node/Skeleton3D/HandAttachment/SpaceGun/Mesh"]
|
||||
[editable path="Node/Skeleton3D/HandAttachment/ChainGun"]
|
||||
[editable path="Node/Skeleton3D/HandAttachment/GrenadeLauncher"]
|
||||
31
scenes/entities/projectiles/arc_projectile.gd
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# 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/>.
|
||||
class_name ArcProjectile extends ExplosiveProjectile
|
||||
|
||||
var _g: float = ProjectSettings.get_setting("physics/3d/default_gravity") # in m/s²
|
||||
var _gravity: Vector3 = _g * ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
||||
|
||||
func _physics_process(delta : float) -> void:
|
||||
# compute motion
|
||||
var previous_global_position : Vector3 = global_position
|
||||
# apply gravity
|
||||
velocity += _gravity * delta
|
||||
# compute new position
|
||||
global_position += velocity * delta
|
||||
# handle collision
|
||||
if shape_cast:
|
||||
shape_cast.target_position = to_local(previous_global_position)
|
||||
if shape_cast.is_colliding():
|
||||
destroy(shape_cast.collision_result[0].point)
|
||||
1
scenes/entities/projectiles/arc_projectile.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cle4ucefanova
|
||||
62
scenes/entities/projectiles/damages/explosive_damage.gd
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# 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/>.
|
||||
## This class defines an explosive damage area.
|
||||
##
|
||||
## It detects nearby bodies within a radius defined by a child [CollisionShape3D]
|
||||
## or [CollisionPolygon3D] and also initiates a blast that applies impulse and
|
||||
## damages to detected bodies based on their distance from the explosion.
|
||||
class_name ExplosiveDamage extends Area3D
|
||||
|
||||
## The base amount of damage.
|
||||
@export var damage: int = 1
|
||||
## The magnitude of blast force, in Newtons.
|
||||
@export var blast_force: int = 1500
|
||||
## A factor that determines how damage decreases with distance from the explosion center.
|
||||
@export var falloff: Curve
|
||||
## The entity responsible for dealing damage.
|
||||
var source: Node = null
|
||||
|
||||
func _ready() -> void:
|
||||
body_shape_entered.connect(_on_body_shape_entered)
|
||||
|
||||
# queue free area after processing few frames (enough for body detection)
|
||||
func _physics_process(_delta: float) -> void:
|
||||
await get_tree().physics_frame
|
||||
await get_tree().physics_frame
|
||||
queue_free()
|
||||
|
||||
# overlapping bodies signal handler
|
||||
func _on_body_shape_entered(_body_rid: RID, body: Node, _body_shape_idx: int, local_shape_idx: int) -> void:
|
||||
if multiplayer.is_server():
|
||||
# retrieve area shape
|
||||
var shape : SphereShape3D = shape_owner_get_shape(
|
||||
shape_find_owner(local_shape_idx), local_shape_idx)
|
||||
# retrieve vector from current node origin to body global center of mass
|
||||
var direction: Vector3 = (body.global_transform.origin + body.center_of_mass) - global_transform.origin
|
||||
# sample curve texture if any to get falloff value at current distance
|
||||
var weight := 1.0
|
||||
if falloff:
|
||||
weight = falloff.sample(direction.length() / shape.radius)
|
||||
# handle blast
|
||||
if body is RigidBody3D:
|
||||
var impulse: Vector3 = direction.normalized() * (1000 * weight)
|
||||
# apply body impulse based on distance from explosion origin
|
||||
body.apply_impulse(impulse, global_transform.origin)
|
||||
# handle blast damages
|
||||
if body is Player:
|
||||
# deal damage based on distance from explosion origin
|
||||
body.damage.emit(source, body, damage * weight)
|
||||
if body is Flag:
|
||||
body.respawn_timer.start(clampf(body.respawn_timer.time_left + 5., 0., body.dropped_duration_max))
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://catddkufiivnk
|
||||
24
scenes/entities/projectiles/explosive_projectile.gd
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# 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/>.
|
||||
class_name ExplosiveProjectile extends Projectile
|
||||
|
||||
@export var EXPLOSION:PackedScene
|
||||
|
||||
func destroy(location:Vector3 = global_position) -> void:
|
||||
var explosion : Node3D = EXPLOSION.instantiate()
|
||||
add_sibling(explosion)
|
||||
explosion.global_position = location
|
||||
explosion.source = source
|
||||
queue_free()
|
||||
1
scenes/entities/projectiles/explosive_projectile.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bisagerhaiofe
|
||||
63
scenes/entities/projectiles/projectile.gd
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# 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/>.
|
||||
## This class defines a projectile.
|
||||
class_name Projectile extends Node3D
|
||||
|
||||
# @NOTE: while it's the correct fundamental constant, we're really looking
|
||||
# for « terminal velocity » here instead because earth is not a vacuum
|
||||
const _speed_of_light := 299792458 # in m/s
|
||||
|
||||
## The initial speed when launched, measured in meters per second (m/s or mps).
|
||||
@export_range(0, _speed_of_light) var speed : float = 42
|
||||
## The time before it automatically self-destructs (never by default), measured in seconds (s).
|
||||
@export var lifespan : float = 0.
|
||||
## A knockback impulse applied to bodies it collides with, in Newtons.
|
||||
@export var knockback: int
|
||||
## The base amount of damage.
|
||||
@export var damage: int
|
||||
## The [param source] that launched this projectile.
|
||||
var source: Node
|
||||
|
||||
var deflectable:bool = false
|
||||
var inheritance:Vector3 = Vector3.ZERO
|
||||
var shape_cast : ShapeCast3D = null
|
||||
var velocity:Vector3 = Vector3.ZERO
|
||||
|
||||
func _init() -> void:
|
||||
# do not inherit transform from parent
|
||||
top_level = true
|
||||
# ensure projectile has shape
|
||||
ready.connect(func() -> void:
|
||||
for child in get_children():
|
||||
if child is ShapeCast3D:
|
||||
shape_cast = child
|
||||
assert(shape_cast)
|
||||
if lifespan > 0:
|
||||
get_tree().create_timer(lifespan).timeout.connect(destroy)
|
||||
)
|
||||
|
||||
func destroy(_location: Vector3 = global_position) -> void:
|
||||
queue_free()
|
||||
|
||||
func _physics_process(delta : float) -> void:
|
||||
# compute motion
|
||||
var previous_global_position : Vector3 = global_position
|
||||
# compute new position
|
||||
global_position += velocity * delta
|
||||
# handle collision
|
||||
if shape_cast:
|
||||
shape_cast.target_position = to_local(previous_global_position)
|
||||
if shape_cast.is_colliding():
|
||||
destroy(shape_cast.collision_result[0].point)
|
||||
1
scenes/entities/projectiles/projectile.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://buhupm2larnr7
|
||||
4
scenes/entities/weapons/assets/reticle.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="3" height="3" version="1.1" viewBox="0 0 3 3" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="1.5" cy="1.5" r="1.5" fill="#fff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 190 B |
37
scenes/entities/weapons/assets/reticle.svg.import
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bnircbm24kvdk"
|
||||
path="res://.godot/imported/reticle.svg-51e79695af4f687110b685f97f822cf6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/weapons/assets/reticle.svg"
|
||||
dest_files=["res://.godot/imported/reticle.svg-51e79695af4f687110b685f97f822cf6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
29
scenes/entities/weapons/automatic_weapon.gd
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# 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/>.
|
||||
class_name AutomaticWeapon extends Weapon
|
||||
|
||||
var _auto_trigger := Timer.new()
|
||||
|
||||
func _ready() -> void:
|
||||
super._ready()
|
||||
_auto_trigger.timeout.connect(trigger)
|
||||
add_child(_auto_trigger)
|
||||
|
||||
func _on_primary(pressed: bool) -> void:
|
||||
if pressed:
|
||||
trigger()
|
||||
_auto_trigger.start(cooldown)
|
||||
else:
|
||||
_auto_trigger.stop()
|
||||
1
scenes/entities/weapons/automatic_weapon.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bstde106q2whk
|
||||
BIN
scenes/entities/weapons/chaingun/assets/chaingun.glb
Normal file
37
scenes/entities/weapons/chaingun/assets/chaingun.glb.import
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cumv8wij5uufk"
|
||||
path="res://.godot/imported/chaingun.glb-dc64aaa327d50dfbdd146e4af1b72648.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/weapons/chaingun/assets/chaingun.glb"
|
||||
dest_files=["res://.godot/imported/chaingun.glb-dc64aaa327d50dfbdd146e4af1b72648.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
17
scenes/entities/weapons/chaingun/assets/mat_flare.tres
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://dkyb51bkvgamw"]
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_343cd"]
|
||||
load_path = "res://.godot/imported/Flare00.png-75515c1e8df86aba86a9e55bb8ca7901.s3tc.ctex"
|
||||
|
||||
[resource]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_color = Color(0.860369, 0.860369, 0.860369, 1)
|
||||
albedo_texture = SubResource("CompressedTexture2D_343cd")
|
||||
billboard_mode = 3
|
||||
billboard_keep_scale = true
|
||||
particles_anim_h_frames = 1
|
||||
particles_anim_v_frames = 1
|
||||
particles_anim_loop = false
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
[gd_resource type="ParticleProcessMaterial" load_steps=5 format=3 uid="uid://drjuqikmi1uth"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_4bco7"]
|
||||
offsets = PackedFloat32Array(0.00806452, 1)
|
||||
colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 0)
|
||||
|
||||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_gtja2"]
|
||||
gradient = SubResource("Gradient_4bco7")
|
||||
|
||||
[sub_resource type="Curve" id="Curve_d0rrq"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.980263, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="CurveTexture" id="CurveTexture_5xpsc"]
|
||||
curve = SubResource("Curve_d0rrq")
|
||||
|
||||
[resource]
|
||||
lifetime_randomness = 1.0
|
||||
emission_shape = 1
|
||||
emission_sphere_radius = 0.3
|
||||
spread = 180.0
|
||||
gravity = Vector3(0, -2, 0)
|
||||
scale_min = 0.75
|
||||
scale_max = 1.5
|
||||
scale_curve = SubResource("CurveTexture_5xpsc")
|
||||
color = Color(5, 2, 1, 1)
|
||||
color_ramp = SubResource("GradientTexture1D_gtja2")
|
||||
BIN
scenes/entities/weapons/chaingun/assets/tracer.glb
Normal file
37
scenes/entities/weapons/chaingun/assets/tracer.glb.import
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bf7v57agnxtcp"
|
||||
path="res://.godot/imported/tracer.glb-6e40e11f6e57daa531db93e9774bbee8.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/weapons/chaingun/assets/tracer.glb"
|
||||
dest_files=["res://.godot/imported/tracer.glb-6e40e11f6e57daa531db93e9774bbee8.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
55
scenes/entities/weapons/chaingun/chaingun.gd
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# 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/>.
|
||||
class_name ChainGun extends AutomaticWeapon
|
||||
|
||||
@export var _PROJECTILE : PackedScene
|
||||
## The factor of velocity from the owner of this [Weapon] inherited by this [Projectile].
|
||||
@export_range(0., 1., .01) var inheritance : float = 1.
|
||||
|
||||
@export var anim_player : AnimationPlayer
|
||||
## The angular deviation from ideal line of fire in degrees
|
||||
@export var min_spread: float
|
||||
## The maximum angle the spread will increase to during heatup.
|
||||
@export var max_spread: float
|
||||
## The amount of time it takes for the chaingun to spin up or spin down.
|
||||
@export var spin_period: float
|
||||
## The amount of time it takes for the chaingun to overheat.
|
||||
@export var heat_period: float
|
||||
#@export var heat_time: float
|
||||
## The fraction of the [member heat_period] at which the gun can start firing again
|
||||
@export var cooldown_threshold: float
|
||||
## Multiplier for the speed when calculating cooldown/heatup
|
||||
@export var speed_cooldown_factor: float
|
||||
## The [Shader] responsible for rendering heat while firing.
|
||||
@export var heat_shader: Shader
|
||||
# heat flag
|
||||
var overheated: bool
|
||||
|
||||
func _on_triggered() -> void:
|
||||
# play the fire animation
|
||||
anim_player.play("fire")
|
||||
# init projectile
|
||||
var projectile : ChainGunProjectile = _PROJECTILE.instantiate()
|
||||
projectile.velocity = projectile.speed * $Nozzle.global_basis.z.normalized() + owner.linear_velocity * inheritance
|
||||
projectile.source = owner
|
||||
owner.add_child(projectile)
|
||||
projectile.global_transform = $Nozzle.global_transform
|
||||
projectile.shape_cast.add_exception(owner)
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if self.visible:
|
||||
#anim_player.play("equip")
|
||||
#self.sounds.play("equip")
|
||||
pass
|
||||
1
scenes/entities/weapons/chaingun/chaingun.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cmic5vyi7st0d
|
||||
80
scenes/entities/weapons/chaingun/chaingun.tscn
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
[gd_scene load_steps=6 format=3 uid="uid://b0xql5hi0b52y"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cumv8wij5uufk" path="res://scenes/entities/weapons/chaingun/assets/chaingun.glb" id="1_d8tdv"]
|
||||
[ext_resource type="Script" uid="uid://cmic5vyi7st0d" path="res://scenes/entities/weapons/chaingun/chaingun.gd" id="2_qsqeh"]
|
||||
[ext_resource type="Texture2D" uid="uid://bnircbm24kvdk" path="res://scenes/entities/weapons/assets/reticle.svg" id="4_o6ohs"]
|
||||
[ext_resource type="PackedScene" uid="uid://b0sfwgilfbpx5" path="res://scenes/entities/weapons/chaingun/projectile.tscn" id="4_p63ts"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_o6ohs"]
|
||||
properties/0/path = NodePath(".:ammo")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 2
|
||||
properties/1/path = NodePath(".:visible")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 2
|
||||
|
||||
[node name="ChainGun" node_paths=PackedStringArray("anim_player") instance=ExtResource("1_d8tdv")]
|
||||
script = ExtResource("2_qsqeh")
|
||||
_PROJECTILE = ExtResource("4_p63ts")
|
||||
anim_player = NodePath("AnimationPlayer")
|
||||
min_spread = 1.0
|
||||
max_spread = 4.0
|
||||
spin_period = 0.5
|
||||
heat_period = 3.0
|
||||
cooldown_threshold = 1.7
|
||||
speed_cooldown_factor = 0.001
|
||||
ammo = 150
|
||||
max_ammo = 150
|
||||
ammo_usage = 1
|
||||
cooldown = 0.1
|
||||
|
||||
[node name="Reticle" type="TextureRect" parent="." index="0"]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -20.0
|
||||
offset_top = -20.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 20.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("4_o6ohs")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="Skeleton3D" parent="Armature" index="0"]
|
||||
bones/1/rotation = Quaternion(3.09086e-08, 0.707107, 0.707107, 3.09086e-08)
|
||||
bones/2/rotation = Quaternion(3.09086e-08, 0.707107, 0.707107, -3.09086e-08)
|
||||
bones/3/rotation = Quaternion(3.09086e-08, 0.707107, 0.707107, -3.09086e-08)
|
||||
|
||||
[node name="BarrelsInner" parent="Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="BarrelsOuter" parent="Armature/Skeleton3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Base" parent="Armature/Skeleton3D" index="2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Grip" parent="Armature/Skeleton3D" index="3"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="AnimationPlayer" parent="." index="2"]
|
||||
autoplay = "idle"
|
||||
|
||||
[node name="Barrels" type="BoneAttachment3D" parent="." index="3"]
|
||||
transform = Transform3D(-1, 0, 8.74227e-08, 8.74227e-08, 0, 1, 0, 1, 0, -2.32831e-10, 0, -0.55315)
|
||||
bone_name = "barrels"
|
||||
bone_idx = 1
|
||||
use_external_skeleton = true
|
||||
external_skeleton = NodePath("../Armature/Skeleton3D")
|
||||
|
||||
[node name="Nozzle" type="Marker3D" parent="." index="4"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.98023e-08, 0.123522, 0.477262)
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="5"]
|
||||
replication_config = SubResource("SceneReplicationConfig_o6ohs")
|
||||
|
||||
[connection signal="triggered" from="." to="." method="_on_triggered"]
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
30
scenes/entities/weapons/chaingun/projectile.gd
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# 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/>.
|
||||
class_name ChainGunProjectile extends Projectile
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var previous_global_position: Vector3 = global_position
|
||||
global_position += velocity * delta
|
||||
# handle collision
|
||||
if shape_cast:
|
||||
var local_global_position: Vector3 = to_local(previous_global_position)
|
||||
shape_cast.target_position = Vector3(
|
||||
local_global_position.x, -local_global_position.z, local_global_position.y)
|
||||
shape_cast.target_position = shape_cast.target_position
|
||||
if shape_cast.is_colliding():
|
||||
var collider: Node = shape_cast.get_collider(0)
|
||||
if collider is Player:
|
||||
collider.damage.emit(source, collider, damage)
|
||||
destroy(shape_cast.collision_result[0].point)
|
||||
1
scenes/entities/weapons/chaingun/projectile.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://181uvgr0oxgt
|
||||
23
scenes/entities/weapons/chaingun/projectile.tscn
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://b0sfwgilfbpx5"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bf7v57agnxtcp" path="res://scenes/entities/weapons/chaingun/assets/tracer.glb" id="1_p7mmn"]
|
||||
[ext_resource type="Script" uid="uid://181uvgr0oxgt" path="res://scenes/entities/weapons/chaingun/projectile.gd" id="2_jjfwk"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_dbodg"]
|
||||
radius = 0.2
|
||||
height = 2.5
|
||||
|
||||
[node name="ChainGunProjectile" instance=ExtResource("1_p7mmn")]
|
||||
script = ExtResource("2_jjfwk")
|
||||
speed = 128.0
|
||||
lifespan = 3.0
|
||||
damage = 15
|
||||
|
||||
[node name="ShapeCast3D" type="ShapeCast3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 1.25)
|
||||
shape = SubResource("CapsuleShape3D_dbodg")
|
||||
target_position = Vector3(0, 0, 0)
|
||||
collision_mask = 2147483653
|
||||
|
||||
[node name="tracerinner" parent="." index="1"]
|
||||
transform = Transform3D(0.0505494, 0, 0, 0, 0.0505494, 0, 0, 0, 0.0505494, 0, 0, 2.5)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bbp260t2qivxk"
|
||||
path="res://.godot/imported/grenade_launcher.glb-d918c597e1fc1b674a4bd43da4356e82.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/weapons/grenade_launcher/assets/models/grenade_launcher.glb"
|
||||
dest_files=["res://.godot/imported/grenade_launcher.glb-d918c597e1fc1b674a4bd43da4356e82.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[gd_resource type="ShaderMaterial" format=3 uid="uid://c80t026c2gpxx"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://d2aeepgs6xnsg" path="res://scenes/entities/weapons/grenade_launcher/assets/resources/explosion_shader.tres" id="1_dmawi"]
|
||||
[ext_resource type="Texture2D" uid="uid://dt6c44uknktrg" path="res://scenes/entities/weapons/grenade_launcher/assets/textures/explosion_texture_alb.png" id="2_4up7w"]
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_itmuc"]
|
||||
load_path = "res://.godot/imported/explosion_texture_flowmap.png-3137b307308713f172b3be796ef584b9.s3tc.ctex"
|
||||
|
||||
[resource]
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_dmawi")
|
||||
shader_parameter/albedo = Color(0.941176, 0.941176, 0.941176, 1)
|
||||
shader_parameter/texture_albedo = ExtResource("2_4up7w")
|
||||
shader_parameter/texture_flow = SubResource("CompressedTexture2D_itmuc")
|
||||
shader_parameter/particles_anim_h_frames = 5
|
||||
shader_parameter/particles_anim_v_frames = 5
|
||||
shader_parameter/particles_anim_loop = false
|
||||
shader_parameter/smoothing = true
|
||||
shader_parameter/flow_strength = 0.015
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
[gd_resource type="Shader" format=3 uid="uid://d2aeepgs6xnsg"]
|
||||
|
||||
[resource]
|
||||
code = "shader_type spatial;
|
||||
render_mode unshaded;
|
||||
|
||||
// @TODO: add support for `Keep scale` billboard option so that we can spawn
|
||||
// different scale using a curve in the particles process material
|
||||
|
||||
uniform vec4 albedo : source_color;
|
||||
uniform sampler2D texture_albedo : source_color,filter_linear_mipmap,repeat_disable;
|
||||
uniform sampler2D texture_flow : hint_normal,filter_linear_mipmap,repeat_disable;
|
||||
uniform int particles_anim_h_frames;
|
||||
uniform int particles_anim_v_frames;
|
||||
uniform bool particles_anim_loop;
|
||||
uniform bool smoothing = false;
|
||||
uniform float flow_strength = 0.015;
|
||||
|
||||
varying vec2 next_UV;
|
||||
varying float timer;
|
||||
|
||||
void vertex() {
|
||||
mat4 mat_world = mat4(normalize(INV_VIEW_MATRIX[0]), normalize(INV_VIEW_MATRIX[1]) ,normalize(INV_VIEW_MATRIX[2]), MODEL_MATRIX[3]);
|
||||
mat_world = mat_world * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
|
||||
MODELVIEW_MATRIX = VIEW_MATRIX * mat_world;
|
||||
MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);
|
||||
float h_frames = float(particles_anim_h_frames);
|
||||
float v_frames = float(particles_anim_v_frames);
|
||||
float particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);
|
||||
float particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));
|
||||
if (!particles_anim_loop) {
|
||||
particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);
|
||||
} else {
|
||||
particle_frame = mod(particle_frame, particle_total_frames);
|
||||
}
|
||||
UV /= vec2(h_frames, v_frames);
|
||||
next_UV = UV;
|
||||
UV += vec2(mod(particle_frame, h_frames) / h_frames, floor((particle_frame + 0.5) / h_frames) / v_frames);
|
||||
next_UV += vec2(mod(particle_frame + 1.0, h_frames) / h_frames, floor((particle_frame + 1.0 + 0.5) / h_frames) / v_frames);
|
||||
timer = fract(INSTANCE_CUSTOM.y * h_frames * v_frames);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec4 albedo_tex;
|
||||
if (smoothing) {
|
||||
vec2 flow_tex = texture(texture_flow, UV).rg;
|
||||
flow_tex -= 0.5;
|
||||
flow_tex *= 2.0;
|
||||
vec2 flow_uv = UV + flow_tex * timer * -flow_strength;
|
||||
vec2 reverse_flow_uv = next_UV + flow_tex * (1.0 - timer) * flow_strength;
|
||||
albedo_tex = mix(texture(texture_albedo, flow_uv), texture(texture_albedo, reverse_flow_uv), timer);
|
||||
} else {
|
||||
albedo_tex = texture(texture_albedo, UV);
|
||||
}
|
||||
albedo_tex *= COLOR;
|
||||
ALBEDO = albedo.rgb * albedo_tex.rgb;
|
||||
ALPHA *= albedo.a * albedo_tex.a;
|
||||
}"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="CompressedTexture2D" format=3 uid="uid://b0003clv8jv3n"]
|
||||
|
||||
[resource]
|
||||
load_path = "res://.godot/imported/explosion_texture_alb.png-1ff7964ed65c51040c0956ec9ef6a893.s3tc.ctex"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="CompressedTexture2D" format=3 uid="uid://ddlahr6k2hg5g"]
|
||||
|
||||
[resource]
|
||||
load_path = "res://.godot/imported/explosion_texture_flowmap.png-3137b307308713f172b3be796ef584b9.s3tc.ctex"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3 uid="uid://bbqjhgs44rnss"]
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_uiuwk"]
|
||||
load_path = "res://.godot/imported/Flare00.png-75515c1e8df86aba86a9e55bb8ca7901.s3tc.ctex"
|
||||
|
||||
[resource]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_color = Color(0.860369, 0.860369, 0.860369, 1)
|
||||
albedo_texture = SubResource("CompressedTexture2D_uiuwk")
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[gd_resource type="ParticleProcessMaterial" format=3 uid="uid://cykwajkj5aaqx"]
|
||||
|
||||
[sub_resource type="Curve" id="Curve_2oi53"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="CurveTexture" id="CurveTexture_ml1t5"]
|
||||
curve = SubResource("Curve_2oi53")
|
||||
|
||||
[resource]
|
||||
gravity = Vector3(0, 0, 0)
|
||||
scale_min = 4.0
|
||||
scale_max = 4.0
|
||||
scale_curve = SubResource("CurveTexture_ml1t5")
|
||||
color = Color(2.5, 1, 0.5, 1)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
[gd_resource type="ParticleProcessMaterial" format=3 uid="uid://v7s5w3uw4hmb"]
|
||||
|
||||
[sub_resource type="Curve" id="Curve_ei0ls"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="Curve" id="Curve_q68ud"]
|
||||
_limits = [0.0, 5.0, 0.0, 1.0]
|
||||
_data = [Vector2(0, 5), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="Curve" id="Curve_26exw"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="CurveXYZTexture" id="CurveXYZTexture_eoauk"]
|
||||
curve_x = SubResource("Curve_ei0ls")
|
||||
curve_y = SubResource("Curve_q68ud")
|
||||
curve_z = SubResource("Curve_26exw")
|
||||
|
||||
[resource]
|
||||
particle_flag_align_y = true
|
||||
emission_shape = 1
|
||||
emission_sphere_radius = 0.2
|
||||
direction = Vector3(0, 1, 0)
|
||||
spread = 90.0
|
||||
initial_velocity_min = 15.0
|
||||
initial_velocity_max = 25.0
|
||||
radial_velocity_min = 10.0
|
||||
radial_velocity_max = 10.0
|
||||
gravity = Vector3(0, 0, 0)
|
||||
scale_min = 0.2
|
||||
scale_curve = SubResource("CurveXYZTexture_eoauk")
|
||||
color = Color(5, 2, 1, 1)
|
||||
turbulence_influence_min = 0.01
|
||||
turbulence_influence_max = 0.01
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="SphereShape3D" format=3 uid="uid://b8d4jwso2dcdu"]
|
||||
|
||||
[resource]
|
||||
radius = 0.062
|
||||
|
After Width: | Height: | Size: 406 KiB |
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://df1te2nwndirs"
|
||||
path="res://.godot/imported/Circle00.png-834849120bcb2061949246a9fd054178.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://scenes/entities/weapons/grenade_launcher/assets/textures/Circle00.png"
|
||||
dest_files=["res://.godot/imported/Circle00.png-834849120bcb2061949246a9fd054178.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 114 KiB |