Extensive work done on basically everything. Attacks prototyped, animations prototyped, pawnbody split out, all traps but pitfall implemented, UI hooked up more.
This commit is contained in:
10
scripts/character_select.gd
Normal file
10
scripts/character_select.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
extends Control
|
||||
|
||||
var pawns = [
|
||||
preload("res://data/pawns/van_reily.tres"),
|
||||
preload("res://data/pawns/lou_riche.tres"),
|
||||
preload("res://data/pawns/tico.tres"),
|
||||
preload("res://data/pawns/john_bishous.tres"),
|
||||
preload("res://data/pawns/abdoll_relin.tres"),
|
||||
preload("res://data/pawns/tenrou_ugetsu.tres")
|
||||
]
|
||||
1
scripts/character_select.gd.uid
Normal file
1
scripts/character_select.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://grs3nyom325o
|
||||
1
scripts/combat_target.gd
Normal file
1
scripts/combat_target.gd
Normal file
@@ -0,0 +1 @@
|
||||
class_name CombatTarget extends StaticBody3D
|
||||
1
scripts/combat_target.gd.uid
Normal file
1
scripts/combat_target.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://clqnjqolkujea
|
||||
18
scripts/control_display.gd
Normal file
18
scripts/control_display.gd
Normal file
@@ -0,0 +1,18 @@
|
||||
class_name ControlDisplay extends Control
|
||||
|
||||
@onready var range_attack_button : TextureButton = $RangedAttack
|
||||
@onready var melee_attack_button : TextureButton = $MeleeAttack
|
||||
@onready var trap_button : TextureButton = $Trap
|
||||
@onready var detect_button : TextureButton = $Detect
|
||||
@onready var switch_button : TextureButton = $Switch
|
||||
|
||||
func _on_melee_range_changed(melee : bool) -> void:
|
||||
range_attack_button.visible = !melee
|
||||
melee_attack_button.visible = melee
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
trap_button.set_pressed_no_signal(Input.is_action_pressed("lay trap"))
|
||||
detect_button.set_pressed_no_signal(Input.is_action_pressed("detect"))
|
||||
switch_button.set_pressed_no_signal(Input.is_action_pressed("detonate"))
|
||||
melee_attack_button.set_pressed_no_signal(Input.is_action_pressed("attack"))
|
||||
range_attack_button.set_pressed_no_signal(Input.is_action_pressed("attack"))
|
||||
1
scripts/control_display.gd.uid
Normal file
1
scripts/control_display.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ci7vc41m5alq5
|
||||
@@ -65,7 +65,7 @@ func start_disarming() -> void:
|
||||
disarm_button_container.add_child(icon)
|
||||
confirmation_dialog.visible = false
|
||||
disarm_window.visible = true
|
||||
timer.start(3 + (2 * len(disarm_buttons) * randf_range(.75, 1.25) ))
|
||||
timer.start(2 + (len(disarm_buttons) * randf_range(.75, 1.25) ))
|
||||
|
||||
func try_advance(btn : int) -> void:
|
||||
|
||||
@@ -91,7 +91,9 @@ func button_pressed(event : InputEventAction) -> void:
|
||||
match(event.action):
|
||||
"detonate":
|
||||
start_disarming()
|
||||
"attack":
|
||||
"attack":
|
||||
var trap = Game.level.get_square_trap(square)
|
||||
trap.disarming = false
|
||||
Game.player.close_modal()
|
||||
_: return
|
||||
|
||||
|
||||
46
scripts/gas_emitter.gd
Normal file
46
scripts/gas_emitter.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
extends Node3D
|
||||
|
||||
var gas_square_template = preload("res://templates/gas_square.tscn")
|
||||
var cycles : int = 3
|
||||
var square : Vector3i = Vector3i(2,0,0)
|
||||
var squares : Dictionary[Vector3i, bool] = {}
|
||||
var last_spread : Array[Vector3i]
|
||||
var trap_owner : int
|
||||
var damage : int = 4
|
||||
|
||||
func _ready() -> void:
|
||||
emit_gas.call_deferred(square)
|
||||
last_spread = [square]
|
||||
squares[square] = true
|
||||
|
||||
func _on_timeout() -> void:
|
||||
cycles -= 1
|
||||
if cycles <= 0:
|
||||
queue_free()
|
||||
return
|
||||
spread()
|
||||
|
||||
func spread() -> void:
|
||||
var new_squares : Dictionary[Vector3i, bool] = {}
|
||||
for square in last_spread:
|
||||
for i in range(-1, 2):
|
||||
for j in range(-1, 2):
|
||||
for k in range(-1, 2):
|
||||
var ts = square + Vector3i(i, j, k)
|
||||
new_squares[square + Vector3i(i, j, k)] = true
|
||||
last_spread = []
|
||||
for square in new_squares.keys():
|
||||
if squares.has(square):
|
||||
continue
|
||||
var floor_square = square + Vector3i(0,-1,0)
|
||||
if(Game.level.floor_layer.get_cell_item(floor_square) == GridMap.INVALID_CELL_ITEM
|
||||
and Game.level.ramp_layer.get_cell_item(floor_square) == GridMap.INVALID_CELL_ITEM):
|
||||
continue
|
||||
last_spread.append(square)
|
||||
squares[square] = true
|
||||
emit_gas(square)
|
||||
|
||||
func emit_gas(square) -> void:
|
||||
var gas = gas_square_template.instantiate()
|
||||
gas.setup(trap_owner, damage)
|
||||
Game.level.add_vfx(gas, square)
|
||||
1
scripts/gas_emitter.gd.uid
Normal file
1
scripts/gas_emitter.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b06ywr7w0wigy
|
||||
27
scripts/gas_square.gd
Normal file
27
scripts/gas_square.gd
Normal file
@@ -0,0 +1,27 @@
|
||||
extends Area3D
|
||||
|
||||
@onready var particles : GPUParticles3D = %ParticleFX
|
||||
var trap_owner : int
|
||||
var damage : int
|
||||
var time_remaining : float
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
particles.emitting = true
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
for body in get_overlapping_bodies():
|
||||
body.poison(damage, 2.0)
|
||||
time_remaining -= delta
|
||||
if time_remaining <= 0:
|
||||
queue_free()
|
||||
|
||||
func setup(owner, damage) -> void:
|
||||
self.trap_owner = trap_owner
|
||||
self.damage = damage
|
||||
time_remaining = 2.0
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if body.can_hurt():
|
||||
body.hurt(damage)
|
||||
body.poison(damage, 2.0)
|
||||
1
scripts/gas_square.gd.uid
Normal file
1
scripts/gas_square.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://6xmp5cbmjrbw
|
||||
@@ -1,6 +1,7 @@
|
||||
class_name HealthBar extends TextureProgressBar
|
||||
|
||||
|
||||
@export var healthy_gradient : GradientTexture1D
|
||||
@export var poisoned_gradient : GradientTexture1D
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
@@ -13,3 +14,6 @@ func _process(delta: float) -> void:
|
||||
func _on_health_changed(current : int, max : int) -> void:
|
||||
max_value = max
|
||||
value = current
|
||||
|
||||
func _on_poison_status_changed(poisoned : bool) -> void:
|
||||
texture_progress = poisoned_gradient if poisoned else healthy_gradient
|
||||
|
||||
@@ -2,12 +2,26 @@ class_name HUD extends Control
|
||||
|
||||
@onready var trap_display : TrapDisplay = %TrapDisplay
|
||||
@onready var p1_healthbar : HealthBar = %P1HealthBar
|
||||
@onready var control_display : ControlDisplay = %ControlDisplay
|
||||
@onready var ammo_label : Label = %AmmoLabel
|
||||
func _ready() -> void:
|
||||
Game.hud = self
|
||||
|
||||
func register_player(player : Player) -> void:
|
||||
player.health_changed.connect(p1_healthbar._on_health_changed)
|
||||
player.poison_status_changed.connect(p1_healthbar._on_poison_status_changed)
|
||||
trap_display._on_trap_list_changed(player.data.traps, player.data.active_trap)
|
||||
player.trap_cycled.connect(trap_display._on_trap_cycled)
|
||||
player.trap_quantity_changed.connect(trap_display._on_trap_quantity_changed)
|
||||
player.trap_list_changed.connect(trap_display._on_trap_list_changed)
|
||||
player.combat_target_changed.connect(control_display._on_melee_range_changed)
|
||||
player.ammo_changed.connect(_on_ammo_changed)
|
||||
set_ammo(player.ammo)
|
||||
|
||||
func set_ammo(ammo : int) -> void:
|
||||
ammo_label.text = str(ammo)
|
||||
ammo_label.modulate = Color.DIM_GRAY if ammo == 0 else Color.WHITE
|
||||
|
||||
func _on_ammo_changed(current : int, max : int) -> void:
|
||||
set_ammo(current)
|
||||
|
||||
|
||||
@@ -1,19 +1,48 @@
|
||||
extends Camera3D
|
||||
class_name PlayerCamera extends Camera3D
|
||||
|
||||
var target
|
||||
var offset : Vector3
|
||||
var player_offset : Vector3
|
||||
|
||||
@export var decay = 0.9 # How quickly the shaking stops [0, 1].
|
||||
@export var max_offset = Vector2(2, 1.5) # Maximum hor/ver shake in pixels.
|
||||
|
||||
@export var noise : FastNoiseLite
|
||||
var noise_y = 0
|
||||
|
||||
var trauma = 0.0 # Current shake strength.
|
||||
var trauma_power = 2 # Trauma exponent. Use [2, 3].
|
||||
|
||||
func _enter_tree() -> void:
|
||||
call_deferred("register_player")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
randomize()
|
||||
noise.seed = randi()
|
||||
|
||||
|
||||
func register_player() -> void:
|
||||
target = Game.player
|
||||
target.camera = self
|
||||
if target:
|
||||
offset = global_position - target.global_position
|
||||
|
||||
player_offset = global_position - target.global_position
|
||||
|
||||
func add_trauma(amount):
|
||||
trauma = min(trauma + amount, 1.0)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if target:
|
||||
global_position = target.global_position + offset
|
||||
global_position = target.global_position + player_offset
|
||||
if trauma:
|
||||
trauma = max(trauma - decay * delta, 0)
|
||||
shake()
|
||||
else:
|
||||
h_offset = 0
|
||||
v_offset = 0
|
||||
|
||||
func shake():
|
||||
noise_y += .1
|
||||
var amount = pow(trauma, trauma_power)
|
||||
var n_val = noise.get_noise_2d(noise.seed*2, noise_y)
|
||||
h_offset = max_offset.x * amount * randf_range(-1, 1)
|
||||
v_offset = max_offset.y * amount * randf_range(-1, 1)
|
||||
print("%f %f" % [h_offset, v_offset])
|
||||
|
||||
10
scripts/pawn_body.gd
Normal file
10
scripts/pawn_body.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
class_name PawnBody extends Node3D
|
||||
|
||||
@export var projectile_template : PackedScene
|
||||
|
||||
@onready var anim_player : AnimationPlayer = %AnimationPlayer
|
||||
@onready var ranged_point : Node3D = %RangedPoint
|
||||
@onready var material : StandardMaterial3D = $Mesh.material
|
||||
|
||||
func play_animation(anim_name : String) -> void:
|
||||
anim_player.play(anim_name)
|
||||
1
scripts/pawn_body.gd.uid
Normal file
1
scripts/pawn_body.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://wye3mat5y5yg
|
||||
@@ -1,46 +1,131 @@
|
||||
class_name Player extends CharacterBody3D
|
||||
|
||||
enum State {
|
||||
NORMAL,
|
||||
KNOCKDOWN,
|
||||
KNOCKUP,
|
||||
FLUNG,
|
||||
BOUND,
|
||||
DEAD
|
||||
}
|
||||
|
||||
const trap_template = preload("res://templates/trap.tscn")
|
||||
const remove_trap_modal = preload("res://templates/remove_trap_modal.tscn")
|
||||
const disarm_trap_modal = preload("res://templates/disarm_trap_modal.tscn")
|
||||
|
||||
@export var speed : float = 10
|
||||
@onready var body = $Body
|
||||
@onready var body : PawnBody = $PawnBody
|
||||
@onready var data : PlayerData = $Data
|
||||
@onready var trap_sound : AudioStreamPlayer3D = $TrapSound
|
||||
@onready var detonate_sound : AudioStreamPlayer3D = $DetonateSound
|
||||
@onready var detect_sound : AudioStreamPlayer3D = $DetectSound
|
||||
@onready var reload_sound : AudioStreamPlayer3D = $PawnBody/ReloadSound
|
||||
@onready var detect_icon : Sprite3D = $DetectIcon
|
||||
|
||||
var id : int = 1
|
||||
|
||||
var state : State
|
||||
var button_actions : Dictionary[int, String]
|
||||
var current_square : Vector3i
|
||||
var facing : Vector3
|
||||
var detecting : bool = false
|
||||
var detect_squares : Dictionary[Vector3i, bool] = {}
|
||||
var detect_tween : Tween = null
|
||||
var fling_direction : Vector3
|
||||
var fling_speed : float
|
||||
|
||||
var poison_strength : int = 0
|
||||
var poison_time_remaining : float = 0
|
||||
var poison_pulse_timer : float
|
||||
|
||||
var melee_range : float = 3.0
|
||||
var ranged_range : float = 6
|
||||
var attack_timer : float = 0
|
||||
var melee_recovery_time : float = .75
|
||||
var ranged_recovery_time : float = .2
|
||||
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 input_locked : bool = false
|
||||
var action_tween : Tween = null
|
||||
|
||||
|
||||
var camera : PlayerCamera = null
|
||||
|
||||
var modal = null
|
||||
|
||||
signal trap_cycled(trap_index)
|
||||
signal trap_quantity_changed(trap_index, quantity)
|
||||
signal trap_cycled(trap_index : int)
|
||||
signal trap_quantity_changed(trap_index : int, quantity : int)
|
||||
signal trap_list_changed(traps)
|
||||
signal health_changed(current : int, max : int)
|
||||
|
||||
signal poison_status_changed(poisoned : bool)
|
||||
signal combat_target_changed(melee : bool)
|
||||
signal ammo_changed(current : int, max : int)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if attack_timer > 0:
|
||||
attack_timer -= delta
|
||||
check_attack_target()
|
||||
if is_poisoned():
|
||||
poison_time_remaining -= delta
|
||||
poison_pulse_timer += delta
|
||||
if poison_pulse_timer >= 1.0:
|
||||
poison_pulse_timer -= 1.0
|
||||
hurt(poison_strength)
|
||||
if poison_time_remaining <= 0:
|
||||
var pshader : ShaderMaterial = body.material.next_pass
|
||||
pshader.set_shader_parameter("strength", 0)
|
||||
poison_status_changed.emit(false)
|
||||
|
||||
var dir = Input.get_vector("west", "east", "north", "south")
|
||||
dir = Vector3(dir.x, 0, dir.y)
|
||||
if input_locked or modal != null:
|
||||
dir = Vector3.ZERO
|
||||
if dir.length_squared() > 0:
|
||||
if state == State.FLUNG:
|
||||
dir = fling_direction
|
||||
body.look_at(body.global_position - dir)
|
||||
var down = 0
|
||||
if !is_on_floor():
|
||||
down = velocity.y + get_gravity().y * delta
|
||||
velocity = fling_speed * dir
|
||||
#Raycast for a wall
|
||||
var space_state = get_world_3d().direct_space_state
|
||||
# use global coordinates, not local to node
|
||||
var start = global_position + Vector3(0,0.25,0)
|
||||
var end = start + velocity.normalized() * 0.25 + velocity * 2 * delta
|
||||
var query = PhysicsRayQueryParameters3D.create(start, end, 1|2, [self])
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result:
|
||||
var opp : Player = result.collider as Player
|
||||
if opp:
|
||||
opp.knockdown(fling_direction)
|
||||
opp.hurt(10)
|
||||
knockdown(-fling_direction)
|
||||
hurt(10)
|
||||
else:
|
||||
velocity += Vector3(0, down, 0)
|
||||
|
||||
move_and_slide()
|
||||
elif dir.length_squared() > 0:
|
||||
facing = dir.normalized()
|
||||
var down = 0
|
||||
if !is_on_floor():
|
||||
down = velocity.y + get_gravity().y * delta
|
||||
body.look_at(body.global_position - dir)
|
||||
velocity = speed * dir
|
||||
if is_poisoned():
|
||||
velocity *= 0.5
|
||||
if detecting:
|
||||
velocity /= 3
|
||||
if !is_on_floor():
|
||||
velocity += get_gravity()
|
||||
velocity *= .33
|
||||
velocity += Vector3(0, down, 0)
|
||||
move_and_slide()
|
||||
elif !is_on_floor():
|
||||
velocity = get_gravity()
|
||||
velocity += get_gravity() * delta
|
||||
move_and_slide()
|
||||
|
||||
if detecting:
|
||||
@@ -60,18 +145,101 @@ func _physics_process(delta: float) -> void:
|
||||
evt.action = button
|
||||
evt.pressed = true
|
||||
modal.button_pressed(evt)
|
||||
else:
|
||||
elif state == State.NORMAL:
|
||||
if Input.is_action_just_pressed("left cycle trap"):
|
||||
cycle_active_trap(-1)
|
||||
if Input.is_action_just_pressed("right cycle trap"):
|
||||
cycle_active_trap(1)
|
||||
if Input.is_action_just_pressed("detect"):
|
||||
if Input.is_action_just_pressed("detonate"):
|
||||
detonate()
|
||||
elif Input.is_action_just_pressed("detect"):
|
||||
start_detecting()
|
||||
elif Input.is_action_just_released("detect"):
|
||||
stop_detecting()
|
||||
if !detecting and Input.is_action_just_pressed("lay trap"):
|
||||
elif !detecting and Input.is_action_just_pressed("lay trap"):
|
||||
try_lay_trap()
|
||||
elif Input.is_action_pressed("attack"):
|
||||
attack()
|
||||
|
||||
func attack() -> void:
|
||||
if attack_timer > 0:
|
||||
return
|
||||
if meleeing:
|
||||
body.play_animation("melee")
|
||||
attack_timer = melee_recovery_time
|
||||
else:
|
||||
if ammo <= 0:
|
||||
reload_sound.play()
|
||||
ammo = max_ammo
|
||||
attack_timer = ranged_reload_time
|
||||
ammo_changed.emit(ammo, max_ammo)
|
||||
return
|
||||
|
||||
ammo-=1
|
||||
ammo_changed.emit(ammo, max_ammo)
|
||||
attack_timer = ranged_recovery_time
|
||||
body.play_animation("shoot")
|
||||
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 fire_ranged() -> void:
|
||||
var shot = body.projectile_template.instantiate()
|
||||
var tdir : Vector3 = Vector3.ZERO
|
||||
shot.speed = projectile_speed
|
||||
tdir = body.ranged_point.global_position.direction_to(combat_target.global_position) if combat_target else facing
|
||||
shot.direction = tdir
|
||||
shot.damage = projectile_damage
|
||||
shot.position = body.ranged_point.global_position
|
||||
Game.level.add_projectile(shot)
|
||||
|
||||
func check_attack_target() -> void:
|
||||
var ranged_closest = null
|
||||
var ranged_d_sq = 99999999
|
||||
var melee_closest = null
|
||||
var melee_d_sq = 99999999
|
||||
var ranged_sq = ranged_range * ranged_range
|
||||
var melee_sq = melee_range * melee_range
|
||||
var space_state = get_world_3d().direct_space_state
|
||||
|
||||
combat_target = null
|
||||
# use global coordinates, not local to node
|
||||
for target : Node3D in get_tree().get_nodes_in_group("combat"):
|
||||
if target == self:
|
||||
continue
|
||||
#Check to see if they're within the correct direction
|
||||
var angle = abs(facing.angle_to(target.global_position - global_position))
|
||||
if angle > PI / 4.0:
|
||||
continue
|
||||
#Determine if they're within shot range
|
||||
var d_sq = global_position.distance_squared_to(target.global_position)
|
||||
if(d_sq > ranged_sq or(ranged_closest != null and ranged_d_sq <= d_sq)):
|
||||
continue
|
||||
|
||||
#Raycast to see if they're a valid target
|
||||
var start = global_position + Vector3(0,1,0)
|
||||
var end = target.global_position + Vector3(0,1,0)
|
||||
var query = PhysicsRayQueryParameters3D.create(start, end, 1|2, [self])
|
||||
var result = space_state.intersect_ray(query)
|
||||
if !result or (result.collider is not Player and result.collider is not CombatTarget):
|
||||
return
|
||||
|
||||
if d_sq < melee_sq:
|
||||
melee_closest = target
|
||||
melee_d_sq = d_sq
|
||||
else:
|
||||
ranged_closest = target
|
||||
ranged_d_sq = d_sq
|
||||
if melee_closest != null:
|
||||
meleeing = true
|
||||
combat_target = melee_closest
|
||||
else:
|
||||
meleeing = false
|
||||
if ranged_closest != null:
|
||||
combat_target = ranged_closest
|
||||
combat_target_changed.emit(melee_closest != null)
|
||||
|
||||
func try_lay_trap() -> void:
|
||||
if !is_on_floor():
|
||||
return
|
||||
@@ -93,8 +261,9 @@ func try_lay_trap() -> void:
|
||||
func lay_trap(square : Vector3i, idx : int) -> void:
|
||||
var type : Trap.Type = data.traps[idx].type
|
||||
var trap = trap_template.instantiate()
|
||||
trap.setup(type, Multiplayer.id)
|
||||
trap.setup(type, facing, id)
|
||||
trap.disarmed.connect(_on_trap_disarmed)
|
||||
trap.activated.connect(_on_trap_activated)
|
||||
data.traps[idx].quantity -= 1
|
||||
trap_quantity_changed.emit(idx, data.traps[idx].quantity)
|
||||
Game.level.add_trap(trap, square)
|
||||
@@ -125,12 +294,19 @@ func update_detecting() -> void:
|
||||
|
||||
|
||||
var remove_list = []
|
||||
var trap_detected : bool = false
|
||||
for sq in new_squares.keys():
|
||||
if detect_squares.has(sq):
|
||||
continue
|
||||
if !Game.level.detect_square(sq, true):
|
||||
remove_list.append(sq)
|
||||
|
||||
else:
|
||||
var trap = Game.level.get_square_trap(sq + Vector3i(0,1,0))
|
||||
if trap and trap.is_just_revealed():
|
||||
trap_detected = true
|
||||
if trap_detected:
|
||||
detect_alert()
|
||||
|
||||
detect_squares = new_squares
|
||||
for key in remove_list:
|
||||
detect_squares.erase(key)
|
||||
@@ -140,9 +316,20 @@ func update_detecting() -> void:
|
||||
if trap.trap_owner == Multiplayer.id:
|
||||
show_remove_trap_modal()
|
||||
else:
|
||||
trap.disarming = true
|
||||
show_disarm_trap_modal()
|
||||
|
||||
|
||||
func detect_alert() -> void:
|
||||
detect_sound.play()
|
||||
detect_icon.visible = true
|
||||
if detect_tween != null:
|
||||
detect_tween.stop()
|
||||
detect_tween = create_tween()
|
||||
detect_tween.tween_interval(.75)
|
||||
detect_tween.tween_property(detect_icon, "visible", false, 0)
|
||||
detect_tween.tween_property(self, "detect_tween", null, 0)
|
||||
|
||||
func close_modal() -> void:
|
||||
modal.queue_free()
|
||||
modal = null
|
||||
@@ -175,11 +362,16 @@ func start_detecting() -> void:
|
||||
detect_squares[sq] = true
|
||||
|
||||
var remove_list = []
|
||||
|
||||
var trap_detected : bool = false
|
||||
for sq in detect_squares.keys():
|
||||
if !Game.level.detect_square(sq, true):
|
||||
remove_list.append(sq)
|
||||
|
||||
else:
|
||||
var trap = Game.level.get_square_trap(sq + Vector3i(0,1,0))
|
||||
if trap and trap.is_just_revealed():
|
||||
trap_detected = true
|
||||
if trap_detected:
|
||||
detect_alert()
|
||||
for key in remove_list:
|
||||
detect_squares.erase(key)
|
||||
|
||||
@@ -221,6 +413,9 @@ func cycle_active_trap(dir) -> void:
|
||||
if prev != data.active_trap:
|
||||
trap_cycled.emit(data.active_trap)
|
||||
|
||||
func can_hurt() -> bool:
|
||||
return true
|
||||
|
||||
func hurt(damage : int) -> void:
|
||||
data.life = max(0, data.life - damage)
|
||||
health_changed.emit(data.life, data.max_life)
|
||||
@@ -232,3 +427,58 @@ func _on_trap_disarmed(type : Trap.Type) -> void:
|
||||
d.max -= 1
|
||||
trap_quantity_changed.emit(i, d.quantity)
|
||||
break
|
||||
|
||||
func _on_trap_activated(type : Trap.Type) -> void:
|
||||
for i in range(len(data.traps)):
|
||||
var d = data.traps[i]
|
||||
if d.type == type:
|
||||
d.quantity = min(d.max, d.quantity+ 1)
|
||||
trap_quantity_changed.emit(i, d.quantity)
|
||||
break
|
||||
|
||||
func detonate() -> void:
|
||||
var switch_list = []
|
||||
for trap : Trap in Game.level.traps.values():
|
||||
if trap.type == Trap.Type.SWITCH and trap.trap_owner == id:
|
||||
switch_list.append(trap)
|
||||
detonate_sound.play()
|
||||
|
||||
for trap : Trap in switch_list:
|
||||
trap.activate()
|
||||
|
||||
func is_poisoned() -> bool:
|
||||
return poison_time_remaining > 0
|
||||
|
||||
func poison(damage : int, length : float) -> void:
|
||||
if is_poisoned():
|
||||
if damage > poison_strength:
|
||||
poison_strength = damage
|
||||
if length > poison_time_remaining:
|
||||
poison_time_remaining = length
|
||||
else:
|
||||
poison_strength = damage
|
||||
poison_time_remaining = length
|
||||
poison_pulse_timer = 0
|
||||
var pshader : ShaderMaterial = body.material.next_pass
|
||||
pshader.set_shader_parameter("strength", 0.5)
|
||||
poison_status_changed.emit(true)
|
||||
|
||||
func fling(direction : Vector3, speed : float) -> void:
|
||||
state = State.FLUNG
|
||||
fling_direction = direction
|
||||
fling_speed = speed
|
||||
|
||||
func knockdown(direction : Vector3) -> void:
|
||||
state = State.KNOCKDOWN
|
||||
input_locked = true
|
||||
body.look_at(Vector3(0,1,0), direction)
|
||||
var knockdown_tween = create_tween()
|
||||
knockdown_tween.tween_interval(1.5)
|
||||
knockdown_tween.tween_property(self, "state", State.NORMAL, 0)
|
||||
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:
|
||||
state = State.KNOCKUP
|
||||
input_locked = true
|
||||
|
||||
|
||||
23
scripts/projectile.gd
Normal file
23
scripts/projectile.gd
Normal file
@@ -0,0 +1,23 @@
|
||||
class_name Projectile extends RigidBody3D
|
||||
|
||||
var direction : Vector3
|
||||
var speed : float
|
||||
var damage
|
||||
var time_remaining : float
|
||||
func _ready() -> void:
|
||||
linear_velocity = speed * direction
|
||||
time_remaining = 2
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
time_remaining -= delta
|
||||
if time_remaining <= 0:
|
||||
queue_free()
|
||||
|
||||
func _on_body_entered(body: Node) -> void:
|
||||
queue_free()
|
||||
|
||||
|
||||
|
||||
func _on_hit_area_entered(body: Node3D) -> void:
|
||||
if body is Player:
|
||||
body.hurt(damage)
|
||||
1
scripts/projectile.gd.uid
Normal file
1
scripts/projectile.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dqmdowebyfwbq
|
||||
@@ -7,6 +7,6 @@ func button_pressed(event : InputEventAction) -> void:
|
||||
"detonate":
|
||||
Game.player.remove_trap_at(square)
|
||||
Game.player.close_modal()
|
||||
"attack":
|
||||
"attack":
|
||||
Game.player.close_modal()
|
||||
_: return
|
||||
|
||||
@@ -4,6 +4,7 @@ const player_controller = preload("res://templates/singleplayer_pc.tscn")
|
||||
const trap_template = preload("res://templates/trap.tscn")
|
||||
|
||||
@onready var floor_layer : GridMap = %Floor
|
||||
@onready var ramp_layer : GridMap = %Ramps
|
||||
@onready var marker_layer : GridMap = %Markers
|
||||
|
||||
@export var difficulty : int = 1
|
||||
@@ -14,12 +15,14 @@ func _ready() -> void:
|
||||
Game.level = self
|
||||
var player = player_controller.instantiate()
|
||||
var traps : Array[PlayerData.TrapData] = [
|
||||
PlayerData.TrapData.new(Trap.Type.FORCE_PANEL, 3, 3),
|
||||
PlayerData.TrapData.new(Trap.Type.BOMB, 3, 3),
|
||||
PlayerData.TrapData.new(Trap.Type.SWITCH, 3, 3),
|
||||
PlayerData.TrapData.new(Trap.Type.MINE, 3, 3)
|
||||
PlayerData.TrapData.new(Trap.Type.MINE, 3, 3),
|
||||
PlayerData.TrapData.new(Trap.Type.GAS, 3, 3)
|
||||
]
|
||||
add_child(player)
|
||||
generate_trap(Trap.Type.MINE, Vector3i(5,0,2))
|
||||
generate_trap(Trap.Type.FORCE_PANEL, Vector3(-1, 0, 0), Vector3i(5,0,2))
|
||||
player.setup(traps)
|
||||
|
||||
func is_square_detected(crd) -> bool:
|
||||
@@ -44,7 +47,12 @@ func add_trap(trap : Trap, crd : Vector3i) -> void:
|
||||
traps[crd] = trap
|
||||
trap.global_position = Vector3(crd) + Vector3(.5, 0, .5)
|
||||
add_child(trap)
|
||||
pass
|
||||
|
||||
func add_projectile(shot : Projectile) -> void:
|
||||
add_child(shot)
|
||||
|
||||
func remove_trap_square(crd : Vector3i) -> void:
|
||||
traps.erase(crd)
|
||||
|
||||
func get_square_trap(crd : Vector3i) -> Trap:
|
||||
if traps.has(crd):
|
||||
@@ -52,6 +60,8 @@ func get_square_trap(crd : Vector3i) -> Trap:
|
||||
else:
|
||||
return null
|
||||
|
||||
|
||||
|
||||
func is_valid_trap_square(crd : Vector3i) -> bool:
|
||||
if floor_layer.get_cell_item(crd + Vector3i(0,-1,0)) == GridMap.INVALID_CELL_ITEM:
|
||||
return false
|
||||
@@ -68,9 +78,9 @@ func activate_trap(crd : Vector3i) -> void:
|
||||
#if trap:
|
||||
|
||||
|
||||
func generate_trap(type : Trap.Type, square : Vector3i):
|
||||
func generate_trap(type : Trap.Type, dir : Vector3, square : Vector3i):
|
||||
var trap = trap_template.instantiate()
|
||||
trap.setup(type, -1)
|
||||
trap.setup(type, dir, -1)
|
||||
add_trap(trap, square)
|
||||
|
||||
func disarm_trap(crd : Vector3i) -> void:
|
||||
|
||||
112
scripts/trap.gd
112
scripts/trap.gd
@@ -26,40 +26,64 @@ const trap_icons : Dictionary = {
|
||||
Trap.Type.MINE : preload("res://visuals/images/icons/t-mine.png"),
|
||||
}
|
||||
|
||||
const explosion_template = preload("res://templates/explosion.tscn")
|
||||
const bomb_explosion_template = preload("res://templates/explosion.tscn")
|
||||
const mine_explosion_template = preload("res://templates/explosion.tscn")
|
||||
const switch_explosion_template = preload("res://templates/explosion.tscn")
|
||||
const gas_emitter_template = preload("res://templates/gas_emitter.tscn")
|
||||
|
||||
@onready var range_area : Area3D = %RangeArea
|
||||
@onready var range_shape : BoxShape3D = %RangeShape.shape
|
||||
@onready var model : MeshInstance3D = %Model
|
||||
@onready var icon : Sprite3D = %Icon
|
||||
@onready var force_strip : Sprite3D = %ForceStrip
|
||||
@onready var material : StandardMaterial3D = model.get_surface_override_material(0)
|
||||
@onready var reveal_timer : Timer = %RevealTimer
|
||||
@onready var activation_timer : Timer = %ActivationTimer
|
||||
|
||||
var type : Type
|
||||
var square : Vector3i
|
||||
var trap_owner : int
|
||||
var direction : Vector3
|
||||
|
||||
var disarming : bool
|
||||
var disarm_id : int
|
||||
|
||||
var delayed_trigger_tween : Tween = null
|
||||
|
||||
var damage : int = 10
|
||||
|
||||
var just_revealed : bool = false
|
||||
|
||||
signal disarmed(type : Trap.Type)
|
||||
signal activated(type : Trap.Type)
|
||||
|
||||
|
||||
func setup(type : Type, trap_owner : int) -> void:
|
||||
func setup(type : Type, direction : Vector3, trap_owner : int) -> void:
|
||||
self.type = type
|
||||
self.trap_owner = trap_owner
|
||||
if type == Type.FORCE_PANEL:
|
||||
var r : float = atan2(direction.z, direction.x)
|
||||
var cardinal : float = roundi(r * 2 / PI) * PI / 2
|
||||
self.direction = Vector3(cos(cardinal), 0, sin(cardinal))
|
||||
|
||||
func disarm() -> void:
|
||||
disarmed.emit(type)
|
||||
queue_free()
|
||||
|
||||
func reveal() -> void:
|
||||
if model.visible:
|
||||
return
|
||||
model.visible = true
|
||||
reveal_timer.start(5)
|
||||
just_revealed = true
|
||||
|
||||
func is_just_revealed() -> bool:
|
||||
return just_revealed
|
||||
|
||||
func is_revealed() -> bool:
|
||||
return model.visible
|
||||
|
||||
func _on_reveal_timeout() -> void:
|
||||
if Game.level.is_square_detected(square):
|
||||
if Game.level.is_square_detected(square) or disarming:
|
||||
reveal_timer.start(5)
|
||||
else:
|
||||
model.visible = false
|
||||
@@ -71,15 +95,87 @@ func _ready() -> void:
|
||||
icon.visible = owns_trap
|
||||
range_shape.size = range_shapes[type]
|
||||
material.albedo_color = Color.YELLOW if owns_trap else Color.RED
|
||||
match(type):
|
||||
Type.BOMB:
|
||||
damage = 15
|
||||
Type.MINE:
|
||||
damage = 10
|
||||
Type.SWITCH:
|
||||
damage = 5
|
||||
Type.GAS:
|
||||
activation_timer.start()
|
||||
Type.FORCE_PANEL:
|
||||
var r : float = atan2(direction.z, -direction.x) + PI/2
|
||||
force_strip.rotate_y(r)
|
||||
force_strip.visible = true
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
just_revealed = false
|
||||
|
||||
func blast(body : Player) -> void:
|
||||
body.hurt(damage)
|
||||
|
||||
func activate() -> void:
|
||||
var exp = explosion_template.instantiate()
|
||||
Game.level.add_vfx(exp, square)
|
||||
var explode : bool = false
|
||||
match(type):
|
||||
Type.FORCE_PANEL:
|
||||
for body in get_overlapping_bodies():
|
||||
body.fling(direction, 5.0)
|
||||
Type.MINE:
|
||||
var exp = mine_explosion_template.instantiate()
|
||||
Game.level.add_vfx(exp, square)
|
||||
explode = true
|
||||
Type.SWITCH:
|
||||
var exp = switch_explosion_template.instantiate()
|
||||
Game.level.add_vfx(exp, square)
|
||||
explode = true
|
||||
Type.BOMB:
|
||||
var exp = bomb_explosion_template.instantiate()
|
||||
Game.level.add_vfx(exp, square)
|
||||
explode = true
|
||||
Type.GAS:
|
||||
var emitter = gas_emitter_template.instantiate()
|
||||
emitter.trap_owner = trap_owner
|
||||
emitter.square = square
|
||||
emitter.damage = 4
|
||||
Game.level.add_vfx(emitter, square)
|
||||
|
||||
if explode:
|
||||
trigger_adjacent_bombs()
|
||||
blast_players()
|
||||
|
||||
Game.level.remove_trap_square(square)
|
||||
activated.emit(type)
|
||||
queue_free()
|
||||
|
||||
func trigger_adjacent_bombs() -> void:
|
||||
for trap : Trap in range_area.get_overlapping_areas():
|
||||
if trap.type == Type.BOMB:
|
||||
trap.delay_trigger()
|
||||
|
||||
func blast_players() -> void:
|
||||
for body in range_area.get_overlapping_bodies():
|
||||
body.hurt(damage)
|
||||
#match(type):
|
||||
blast(body)
|
||||
|
||||
func delay_trigger() -> void:
|
||||
if delayed_trigger_tween != null:
|
||||
return
|
||||
delayed_trigger_tween = create_tween()
|
||||
delayed_trigger_tween.tween_interval(.25)
|
||||
delayed_trigger_tween.tween_callback(activate)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if type == Type.GAS:
|
||||
return
|
||||
|
||||
if body.id == trap_owner:
|
||||
return
|
||||
|
||||
if !disarming or body.id != disarm_id:
|
||||
if !body.detecting:
|
||||
activate()
|
||||
|
||||
|
||||
func _on_activation_timer_timeout() -> void:
|
||||
activate()
|
||||
|
||||
@@ -15,6 +15,10 @@ func proximal_shake(distance : float) -> void:
|
||||
if !Game.player:
|
||||
return
|
||||
var d = global_position.distance_squared_to(Game.player.global_position)
|
||||
if d <= distance * distance:
|
||||
Input.start_joy_vibration(0, 1,1,1000)
|
||||
var d_sq = distance * distance
|
||||
if d <= d_sq:
|
||||
var str : float = (d_sq - d) / d_sq
|
||||
var l_val : float = lerp(0.0, 1.0, str)
|
||||
Input.start_joy_vibration(0, l_val,l_val,lerp(0.5, 1.0, str))
|
||||
Game.player.camera.add_trauma(l_val)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user