feat: implement procedural humanoid animations, modular weapon system, and dummy entity base classes
This commit is contained in:
@@ -161,7 +161,12 @@ func _shoot_hitscan() -> void:
|
||||
hits_remaining -= 1
|
||||
current_damage_mult *= penetration_damage_penalty
|
||||
final_target = result.position
|
||||
_spawn_tracer(origin, final_target)
|
||||
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||
player.server_play_fire_effects.rpc_id(1, origin, final_target, name, true)
|
||||
|
||||
func _spawn_tracer(_origin: Vector3, final_target: Vector3) -> void:
|
||||
# Spawn cosmetic tracer
|
||||
var tracer = Node3D.new()
|
||||
tracer.set_script(load("res://weapons/hitscan_tracer.gd"))
|
||||
|
||||
@@ -120,6 +120,9 @@ func _shoot_projectile() -> void:
|
||||
var fire_dir = (forward + offset_dir).normalized()
|
||||
|
||||
_spawn_custom_projectile(origin, fire_dir)
|
||||
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||
player.server_play_fire_effects.rpc_id(1, origin, fire_dir, weapon_name, false)
|
||||
|
||||
func _spawn_custom_projectile(_origin: Vector3, _fire_dir: Vector3) -> void:
|
||||
# Virtual method for custom projectiles (e.g., bouncing, homing)
|
||||
|
||||
@@ -137,6 +137,8 @@ func _on_hit(result: Dictionary) -> void:
|
||||
if not result.collider.has_method("take_damage"):
|
||||
if result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
|
||||
elif not owner_player or owner_player.is_multiplayer_authority():
|
||||
result.collider.take_damage(damage, result.position, owner_player)
|
||||
_explode(result.position)
|
||||
destroy()
|
||||
|
||||
@@ -198,10 +200,14 @@ func _explode(pos: Vector3) -> void:
|
||||
if col == owner_player and not can_self_damage:
|
||||
# Skip self damage, but we still applied the knockback!
|
||||
pass
|
||||
else:
|
||||
col.take_damage(final_damage, target_pos, owner_player)
|
||||
elif not owner_player or owner_player.is_multiplayer_authority():
|
||||
col.take_damage(final_damage, target_pos, owner_player, dir * final_knockback)
|
||||
|
||||
# Visual Effect
|
||||
# Broadcast Visual Explosion Effect to other peers
|
||||
if owner_player and owner_player.has_method("server_play_explosion") and owner_player.multiplayer.has_multiplayer_peer() and owner_player.multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||
owner_player.server_play_explosion.rpc_id(1, pos, explosion_radius)
|
||||
|
||||
# Local Visual Effect
|
||||
var visual = MeshInstance3D.new()
|
||||
var s_mesh = SphereMesh.new()
|
||||
s_mesh.radius = explosion_radius
|
||||
|
||||
@@ -58,7 +58,8 @@ func _on_hit(result: Dictionary) -> void:
|
||||
final_damage = lerpf(damage, min_damage, falloff_factor)
|
||||
|
||||
if result.collider.has_method("take_damage"):
|
||||
result.collider.take_damage(final_damage, result.position, owner_player)
|
||||
if not owner_player or owner_player.is_multiplayer_authority():
|
||||
result.collider.take_damage(final_damage, result.position, owner_player)
|
||||
elif result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||
var size = 0.4 if impact_type == "plasma" else 0.15
|
||||
ImpactSpawner.spawn(get_tree(), impact_type, result.position, result.normal, size)
|
||||
|
||||
@@ -140,6 +140,9 @@ func _fire() -> void:
|
||||
|
||||
var fire_dir = (base_dir + right * x_dir + up * y_dir).normalized()
|
||||
_spawn_custom_projectile(proj_origin, fire_dir)
|
||||
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||
player.server_play_fire_effects.rpc_id(1, proj_origin, fire_dir, weapon_name, false)
|
||||
|
||||
var _current_aim_dir: Vector3 = Vector3.ZERO
|
||||
|
||||
|
||||
@@ -61,6 +61,15 @@ func _process(_delta: float) -> void:
|
||||
vm_camera.fov = camera.fov
|
||||
|
||||
func _build_loadout() -> void:
|
||||
var is_auth = false
|
||||
if player:
|
||||
is_auth = player.is_multiplayer_authority()
|
||||
else:
|
||||
is_auth = is_multiplayer_authority()
|
||||
|
||||
if not is_auth:
|
||||
return # Let the player_movement_controller's remote _process call _build_remote_loadout
|
||||
|
||||
# Clear existing weapons
|
||||
for w in weapons.values():
|
||||
if is_instance_valid(w):
|
||||
@@ -69,6 +78,16 @@ func _build_loadout() -> void:
|
||||
|
||||
var l = LoadoutManager.get_active_loadout()
|
||||
|
||||
if player:
|
||||
player.synced_loadout_p1 = l["primary_1"]
|
||||
player.synced_loadout_p2 = l["primary_2"]
|
||||
player.synced_loadout_sp = l["special"]
|
||||
if l.has("melee"):
|
||||
player.synced_loadout_melee = l["melee"]
|
||||
else:
|
||||
player.synced_loadout_melee = ""
|
||||
player.synced_loadout_ready = true
|
||||
|
||||
_spawn_weapon(1, l["primary_1"])
|
||||
_spawn_weapon(2, l["primary_2"])
|
||||
_spawn_weapon(3, l["special"])
|
||||
@@ -77,6 +96,20 @@ func _build_loadout() -> void:
|
||||
|
||||
_equip_slot(1)
|
||||
|
||||
func _build_remote_loadout(p1: String, p2: String, sp: String, melee: String) -> void:
|
||||
for w in weapons.values():
|
||||
if is_instance_valid(w):
|
||||
w.queue_free()
|
||||
weapons.clear()
|
||||
|
||||
_spawn_weapon(1, p1)
|
||||
_spawn_weapon(2, p2)
|
||||
_spawn_weapon(3, sp)
|
||||
if melee != "":
|
||||
_spawn_weapon(4, melee)
|
||||
|
||||
_equip_slot(1)
|
||||
|
||||
func _spawn_weapon(slot: int, weapon_id: String) -> void:
|
||||
if weapon_id == "" or weapon_id == "none" or not LoadoutManager.weapon_db.has(weapon_id):
|
||||
return
|
||||
@@ -184,6 +217,9 @@ func _equip_slot(slot: int) -> void:
|
||||
var humanoid = player.get_node("HumanoidModel")
|
||||
if humanoid.has_method("set_weapon") and w.has_meta("script_path"):
|
||||
humanoid.set_weapon(w.get_meta("script_path"))
|
||||
# Update the synced variable so remote peers pick it up
|
||||
if "synced_weapon_path" in player:
|
||||
player.synced_weapon_path = w.get_meta("script_path")
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||
|
||||
Reference in New Issue
Block a user