Work on pickups and multiplayer

This commit is contained in:
2026-03-19 23:50:29 -04:00
parent d128501f7c
commit 9d931a3b46
42 changed files with 771 additions and 324 deletions

View File

@@ -2,6 +2,11 @@ class_name PawnController extends CharacterBody3D
enum State {
NORMAL,
DETECTING,
INSTALLING,
UNINSTALLING,
MELEE_ATTACKING,
RANGED_ATTACKING,
KNOCKDOWN,
KNOCKUP,
FLUNG,
@@ -26,7 +31,7 @@ const range_sphere_template = preload("res://templates/range_sphere.tscn")
@onready var detect_sound : AudioStreamPlayer3D = $DetectSound
@onready var fling_sound : AudioStreamPlayer3D = $FlingSound
@onready var crash_sound : AudioStreamPlayer3D = $CrashSound
@onready var reload_sound : AudioStreamPlayer3D = $PawnBody/ReloadSound
@onready var reload_sound : AudioStreamPlayer3D
@onready var detect_icon : Sprite3D = $DetectIcon
@export var id : int = 1
@@ -35,9 +40,6 @@ const range_sphere_template = preload("res://templates/range_sphere.tscn")
var button_actions : Dictionary[int, String]
var current_square : Vector3i
var facing : Vector3
@export var detecting : bool = false
var installing : bool = false
var uninstalling : bool = false
var range_sphere : RangeSphere
var detect_squares : Dictionary[Vector3i, bool] = {}
var detect_tween : Tween = null
@@ -51,18 +53,17 @@ var poison_time_remaining : float = 0
var poison_pulse_timer : float
var melee_range : float = 3.0
var can_melee : bool = false
var ranged_range : float = 6
var attack_timer : float = 0
var melee_recovery_time : float = .75
var ranged_recovery_time : float = .2
var ranged_recovery_time : float = 0.25
var ranged_reload_time : float = 1
var projectile_speed : float = 10.0
var projectile_damage : int = 4
var ammo = 5
var max_ammo = 5
var combat_target
var meleeing : bool = false
var shooting : bool = false
var reloading : bool = false
var take_shot : bool = false
var flinch : float = 0
@@ -98,7 +99,22 @@ func _exit_tree() -> void:
#Game.level.evaluate_outcome()
#Game.evaluate
func calculate_pawn_velocity(dir : Vector3) -> Vector3:
var y = velocity.y
var result = speed * dir
if is_poisoned():
result *= 0.5
if is_crouching():
result *= .33
result.y = y
return result
func play_footsteps(spd : float, loud : bool) -> void:
if loud:
body.play_footsteps(spd)
func stop_footsteps() -> void:
body.stop_footsteps()
func _physics_process(delta: float) -> void:
if attack_timer > 0:
@@ -142,26 +158,16 @@ func _physics_process(delta: float) -> void:
knockdown(-fling_direction)
hurt(10)
moving = false
State.NORMAL:
State.NORMAL, State.DETECTING, State.INSTALLING:
can_fall = true
if dir.length_squared() > 0:
moving = true
velocity = calculate_pawn_velocity(dir)
facing = dir.normalized()
body.look_at(body.global_position - dir)
var y = velocity.y
velocity = speed * dir
if is_poisoned():
velocity *= 0.5
if is_crouching():
velocity *= .33
velocity.y = y
if !detecting:
body.play_footsteps(lerp(.78, .33, dir.length()))
else:
body.stop_footsteps()
elif body != null:
body.stop_footsteps()
play_footsteps(lerp(.78, .33, dir.length()), true)
else:
stop_footsteps()
if body != null:
body.set_animation_parameter("parameters/Motion/blend_position", dir.length())
body.set_animation_parameter("parameters/Crouch/blend_position", dir.length())
@@ -171,12 +177,9 @@ func _physics_process(delta: float) -> void:
else:
moving = true
can_fall = true
State.KNOCKDOWN,State.BOUND,State.DEAD:
return
if can_fall:
var down = 0
if !is_on_floor():
velocity.y = velocity.y + get_gravity().y * delta
moving = true
@@ -184,8 +187,9 @@ func _physics_process(delta: float) -> void:
if moving:
move_and_slide()
if detecting:
if is_detecting():
update_detect_region.rpc(true)
if range_sphere:
update_range_sphere()
#Deal with the rest of the buttons
@@ -200,7 +204,7 @@ func _physics_process(delta: float) -> void:
for button in buttons:
if input.is_action_just_pressed(button):
modal.button_pressed(button)
elif state == State.NORMAL:
else:
update_actions()
@rpc("call_local", "reliable")
@@ -213,15 +217,17 @@ func set_pawn_body(pb : PawnBody) -> void:
body = pb
pb.reloading.connect(reload_ranged)
pb.shooting.connect(fire_ranged)
reload_sound = body.find_child("ReloadSound")
add_child(body)
body.set_animation_length("Ranged Fire", ranged_recovery_time)
struggling.connect(body._on_struggle_changed)
func attack() -> void:
if attack_timer > 0:
return
if meleeing:
body.play_animation.rpc("melee")
if can_melee:
state = State.MELEE_ATTACKING
attack_timer = melee_recovery_time
else:
if ammo <= 0:
@@ -229,14 +235,14 @@ func attack() -> void:
return
attack_timer = ranged_recovery_time
shooting = true
state = State.RANGED_ATTACKING
take_shot = true
if combat_target != null:
var v = body.global_position.direction_to(combat_target.global_position)
v.y = 0
body.look_at(body.global_position - v)
func reload_ranged() -> void:
reloading = false
reload_sound.play()
@@ -245,41 +251,44 @@ func reload_ranged() -> void:
ammo_changed.emit(ammo, max_ammo)
func update_actions() -> void:
if attack_timer <= 0 and shooting and !input.is_action_pressed("attack"):
shooting = false
if state == State.DEAD:
return
if attack_timer <= 0 and is_attacking():
if !input.is_action_pressed("attack"):
state = State.NORMAL
elif is_shooting():
if ammo <= 0:
reloading = true
else:
take_shot = true
if input.is_action_just_pressed("left cycle hack"):
cycle_active_hack(-1)
if input.is_action_just_pressed("right cycle hack"):
cycle_active_hack(1)
if input.is_action_just_pressed("detonate"):
detonate()
elif !installing:
if input.is_action_just_pressed("detect"):
start_detecting()
elif input.is_action_just_released("detect"):
stop_detecting()
if!detecting:
if input.is_action_just_pressed("install"):
installing = true
if range_sphere != null:
range_sphere.queue_free()
range_sphere = range_sphere_template.instantiate()
Game.level.add_child(range_sphere)
#Set the range sphere size based upon the current hack
update_range_sphere()
if input.is_action_just_released("install"):
installing = false
range_sphere.queue_free()
range_sphere = null
try_install_hack()
match(state):
State.NORMAL:
if input.is_action_just_pressed("detonate"):
detonate()
if(input.is_action_just_pressed("detect")
and input.is_action_pressed("detect")):
start_detecting()
return
if(input.is_action_just_pressed("install")
and input.is_action_pressed("install")):
start_installing()
return
elif input.is_action_pressed("attack"):
attack()
elif input.is_action_just_released("install"):
installing = false
range_sphere.queue_free()
range_sphere = null
try_install_hack()
State.DETECTING:
if !input.is_action_pressed("detect"):
stop_detecting()
State.INSTALLING:
if !input.is_action_pressed("install"):
stop_installing()
try_install_hack()
func update_range_sphere() -> void:
var new_pos = (global_position - Vector3.ONE * .5).round() + Vector3(0.5, 0, 0.5)
@@ -324,6 +333,7 @@ func fire_ranged() -> void:
ammo-=1
ammo_changed.emit(ammo, max_ammo)
shot.speed = projectile_speed
shot.id = id
tdir = body.ranged_point.global_position.direction_to(combat_target.global_position) if combat_target else facing
shot.direction = tdir
shot.damage = projectile_damage
@@ -368,10 +378,10 @@ func check_attack_target() -> void:
ranged_closest = target
ranged_d_sq = d_sq
if melee_closest != null:
meleeing = true
can_melee = true
combat_target = melee_closest
else:
meleeing = false
can_melee = false
if ranged_closest != null:
combat_target = ranged_closest
combat_target_changed.emit(melee_closest != null)
@@ -423,21 +433,21 @@ func detect_alert() -> void:
detect_tween.tween_callback(func(): detect_tween = null)
func close_modal() -> void:
uninstalling = false
state = State.NORMAL
if modal != null:
modal.queue_free()
modal = null
func show_uninstall_hack_modal() -> void:
stop_detecting()
uninstalling = true
state = State.UNINSTALLING
modal = uninstall_hack_modal.instantiate()
modal.square = current_square
Game.level.add_child(modal)
func show_decompile_hack_modal() -> void:
stop_detecting()
uninstalling = true
state = State.UNINSTALLING
modal = decompile_hack_modal.instantiate()
modal.difficulty = Game.level.difficulty
modal.square = current_square
@@ -451,8 +461,23 @@ func show_decompile_hack_modal() -> void:
Game.level.add_child(modal)
func start_detecting() -> void:
detecting = true
update_detect_region.rpc(false)
state = State.DETECTING
update_detect_region(false)
func start_installing() -> void:
state = State.INSTALLING
if id == Multiplayer.id:
if range_sphere != null:
range_sphere.queue_free()
range_sphere = range_sphere_template.instantiate()
Game.level.add_child(range_sphere)
#Set the range sphere size based upon the current hack
update_range_sphere()
func stop_installing() -> void:
state = State.NORMAL
range_sphere.queue_free()
range_sphere = null
@rpc("authority", "call_local")
func update_detect_region(update : bool) -> void:
@@ -477,13 +502,14 @@ func update_detect_region(update : bool) -> void:
var remove_list = []
var hack_detected : bool = false
var hack : Hack
for sq in new_squares.keys():
if update and detect_squares.has(sq):
continue
if !Game.level.detect_square(sq, true):
remove_list.append(sq)
else:
var hack = Game.level.get_square_hack(sq + Vector3i(0,1,0))
hack = Game.level.get_square_hack(sq + Vector3i(0,1,0))
if hack and hack.is_just_revealed():
hack_detected = true
@@ -495,7 +521,7 @@ func update_detect_region(update : bool) -> void:
for key in remove_list:
detect_squares.erase(key)
var hack : Hack = Game.level.get_square_hack(current_square)
hack = Game.level.get_square_hack(current_square)
if hack != null:
if hack.hack_owner == Multiplayer.id:
show_uninstall_hack_modal()
@@ -504,7 +530,7 @@ func update_detect_region(update : bool) -> void:
show_decompile_hack_modal()
func stop_detecting() -> void:
detecting = false
state = State.NORMAL
clear_detect_region.rpc()
@rpc("authority", "call_local")
@@ -516,8 +542,8 @@ func clear_detect_region() -> void:
detect_squares = {}
@rpc("authority", "call_local", "reliable")
func setup(id : int, pawn : StringName) -> void:
self.id = id
func setup(p_id : int, pawn : StringName) -> void:
id = p_id
var base_data : PawnBaseData = Game.pawns[pawn]
var hacklist : Array[PawnLevelData.HackData] = []
for hack in base_data.starting_hacks:
@@ -564,9 +590,11 @@ func hurt(damage : int) -> void:
health_changed.emit(data.life, data.max_life)
if data.life == 0:
die.rpc()
@rpc("any_peer", "call_local")
@rpc("authority", "call_local")
func die() -> void:
state = State.DEAD
body.travel_animation("Death")
input_locked = true
if id == Multiplayer.id:
var death_tween = create_tween()
@@ -578,21 +606,15 @@ func _on_hack_decompiled(type : Hack.Type) -> void:
for i in range(len(data.hacks)):
var d = data.hacks[i]
if d.type == type:
d.max -= 1
d.max_quantity -= 1
hack_quantity_changed.emit(i, d.quantity)
break
#hurt
#blast
#blast_players
#activate
#activate_hack
#fail
#on_hack_failed
func _on_hack_activated(type : Hack.Type) -> void:
for i in range(len(data.hacks)):
var d = data.hacks[i]
if d.type == type:
d.quantity = min(d.max, d.quantity+ 1)
d.quantity = min(d.max_quantity, d.quantity+ 1)
hack_quantity_changed.emit(i, d.quantity)
break
@@ -623,10 +645,10 @@ func poison(damage : int, length : float) -> void:
pshader.set_shader_parameter("strength", 0.5)
poison_status_changed.emit(true)
func fling(direction : Vector3, speed : float) -> void:
func fling(direction : Vector3, spd : float) -> void:
state = State.FLUNG
fling_direction = direction
fling_speed = speed
fling_speed = spd
fling_sound.play()
func knockdown(direction : Vector3) -> void:
@@ -641,14 +663,14 @@ func knockdown(direction : Vector3) -> void:
knockdown_tween.tween_property(self, "input_locked", false, 0)
knockdown_tween.tween_callback(Callable(body.look_at).bind(facing, Vector3(0,1,0)))
func knockup(velocity : Vector3) -> void:
func knockup(vel : Vector3) -> void:
if state != State.KNOCKUP:
state = State.KNOCKUP
input_locked = true
if is_on_floor():
self.velocity = Vector3.UP * .1
velocity = Vector3.UP * .1
move_and_slide()
self.velocity = velocity
velocity = vel
func knockback(direction : Vector3, impact : float) -> void:
if state != State.NORMAL:
@@ -672,14 +694,46 @@ func start_pitfall(square : Vector3, duration : float) -> void:
tween.tween_property(self, "input_locked", false, 0)
tween.tween_callback(pitfall.bind(duration))
func _on_melee_hit(body : Node3D) -> void:
func _on_melee_hit(_body : Node3D) -> void:
#TODO: WRITE THE MELEE DAMAGE CODE
pass
func is_attacking() -> bool:
return state == State.RANGED_ATTACKING or state == State.MELEE_ATTACKING
func is_meleeing() -> bool:
return state == State.MELEE_ATTACKING
func is_shooting() -> bool:
return state == State.RANGED_ATTACKING
func is_dead() -> bool:
return state == State.DEAD
func is_detecting() -> bool:
return detecting
return state == State.DETECTING
func is_installing() -> bool:
return state == State.INSTALLING
func is_uninstalling() -> bool:
return state == State.UNINSTALLING
func is_crouching() -> bool:
return detecting or installing or uninstalling
var result : bool = false
match(state):
State.DETECTING: result = true
State.INSTALLING: result = true
State.UNINSTALLING: result = true
return result
func add_random_hack(advanced : bool) -> void:
#TODO: Add random hack spawning using RPCs
pass
func pickup(type : Pickup.Type) -> void:
if id != Multiplayer.id:
return
match(type):
Pickup.Type.DATABLOCK: print("Datablock picked up!")
Pickup.Type.BASIC_HACK: add_random_hack(false)