Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock Interaction Component & Dual Interaction Rework #292

Merged
merged 11 commits into from
Oct 9, 2024
85 changes: 55 additions & 30 deletions COGITO/CogitoObjects/cogito_door.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class_name CogitoDoor
extends Node3D

signal object_state_updated(interaction_text:String)
signal lock_state_updated(lock_interaction_text:String)
signal door_state_changed(is_open:bool)
signal lock_state_changed(is_locked:bool)
signal damage_received(damage_value:float)
Expand All @@ -22,12 +23,15 @@ enum DoorType {
## Sound that plays when attempting to open while locked.
@export var rattle_sound : AudioStream
@export var unlock_sound : AudioStream
@export var lock_sound : AudioStream

@export_group("Door Settings")
## Start state of the door (usually closed).
@export var is_open : bool = false
## Text that appears on the interaction prompt when locked.
@export var interaction_text_when_locked : String = "Unlock"
## Text that appears on the interaction prompt when locked.
@export var interaction_text_when_unlocked : String = "Lock"
## Text that appears on the interaction prompt when closed.
@export var interaction_text_when_closed : String = "Open"
## Text that appears on the interaction prompt when open.
Expand All @@ -39,6 +43,8 @@ enum DoorType {
@export var key : InventoryItemPD
## Hint that is displayed if the player attempts to open the door but doesn't have the key item.
@export var key_hint : String
##Alternate item resource that can unlock this door
@export var lockpick : InventoryItemPD
## Other doors that should sync their state (locked, unlocked, open, closed) with this door. Useful for double-doors, etc.
@export var doors_to_sync_with : Array[NodePath]

Expand Down Expand Up @@ -93,6 +99,7 @@ var anim_player : AnimationPlayer
var is_moving : bool = false
var target_rotation_rad : float
var interaction_text
var lock_interaction_text
var close_timer : Timer #Used for auto-close

var interaction_nodes : Array[Node]
Expand All @@ -117,15 +124,19 @@ func _ready():
target_rotation_rad = rotation.z
else:
target_rotation_rad = rotation.y

if is_open:
interaction_text = interaction_text_when_open
elif is_locked:
interaction_text = interaction_text_when_locked
else:
interaction_text = interaction_text_when_closed

if is_locked:
lock_interaction_text = interaction_text_when_locked
else:
lock_interaction_text = interaction_text_when_unlocked

object_state_updated.emit(interaction_text)

lock_state_updated.emit(lock_interaction_text)

func interact(interactor: Node3D):
player_interaction_component = interactor
Expand All @@ -145,11 +156,12 @@ func interact(interactor: Node3D):
var object = get_node(nodepath)
object.close_door(interactor)
else:
audio_stream_player_3d.stream = rattle_sound
audio_stream_player_3d.play()

check_for_key(interactor)
door_rattle(interactor)

func door_rattle(interactor):
audio_stream_player_3d.stream = rattle_sound
audio_stream_player_3d.play()
interactor.send_hint(null,"I can't open it")

func _physics_process(_delta):
if door_type == DoorType.ROTATING:
Expand All @@ -165,32 +177,35 @@ func _physics_process(_delta):
is_moving = false


func check_for_key(interactor):
var inventory = interactor.get_parent().inventory_data
for slot_data in inventory.inventory_slots:
if slot_data != null and slot_data.inventory_item == key:
interactor.send_hint(null, key.name + " used.") # Sends a hint with the key item name.
if slot_data.inventory_item.discard_after_use:
inventory.remove_item_from_stack(slot_data)
# inventory.remove_slot_data(slot_data) (removed on 20240913, leaving line just in case there's bugs.
unlock_door()

for nodepath in doors_to_sync_with:
if nodepath != null:
var object = get_node(nodepath)
object.unlock_door()
return

if key_hint != "":
interactor.send_hint(null,key_hint) # Sends the key hint with the default hint icon.


func lock_unlock_switch():
if is_locked:
unlock_door()

else:
lock_door()

for nodepath in doors_to_sync_with:
if nodepath != null:
var object = get_node(nodepath)
object.lock_unlock_switch()

func unlock_door():

audio_stream_player_3d.stream = unlock_sound
audio_stream_player_3d.play()
is_locked = false
interaction_text = interaction_text_when_closed
object_state_updated.emit(interaction_text)
lock_interaction_text = interaction_text_when_unlocked
lock_state_updated.emit(lock_interaction_text)
lock_state_changed.emit(is_locked)

func lock_door():

audio_stream_player_3d.stream = lock_sound
audio_stream_player_3d.play()
is_locked = true
lock_interaction_text = interaction_text_when_locked
lock_state_updated.emit(lock_interaction_text)
lock_state_changed.emit(is_locked)


Expand Down Expand Up @@ -270,9 +285,13 @@ func set_state():
set_to_closed_position()
interaction_text = interaction_text_when_closed
if is_locked:
interaction_text = interaction_text_when_locked
lock_interaction_text = interaction_text_when_locked
else:
lock_interaction_text = interaction_text_when_unlocked

object_state_updated.emit(interaction_text)

lock_state_updated.emit(lock_interaction_text)
print(lock_interaction_text)

func set_to_open_position():
if door_type == DoorType.SLIDING:
Expand All @@ -293,6 +312,12 @@ func set_to_closed_position():
else:
rotation.y = deg_to_rad(closed_rotation_deg)

#alternate interaction for locking/unlocking
func interact2(interactor: Node3D):
if not is_open:
lock_unlock_switch()
else:
interactor.send_hint(null,"Can't lock an open door")

func save():
var state_dict = {
Expand Down
1 change: 1 addition & 0 deletions COGITO/Components/Interactions/BasicInteraction.gd
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ func interact(_player_interaction_component):

func _on_object_state_change(_interaction_text: String):
interaction_text = _interaction_text

56 changes: 47 additions & 9 deletions COGITO/Components/Interactions/DualInteraction.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,80 @@ signal is_being_held(time_left:float)

signal on_quick_press(player_interaction_component:PlayerInteractionComponent)
signal on_hold_complete(player_interaction_component:PlayerInteractionComponent)
signal interaction_complete(player_interaction_component: PlayerInteractionComponent)

##Time for hold to complete
@export var hold_time : float = 3.0
## Buffer time until the hold is first registered, prevents showing Hold UI for presses
@export var buffer_time : float = 0.1

@onready var press_interaction_text: String
@onready var hold_interaction_text: String
##Text that joins Press and Hold interaction text, for example: " | (HOLD) " in "Open | (HOLD) Unlock"
@export var interaction_text_joiner: String = " | (HOLD) "
##Hold node location, used for "start_hold_check" function which must return true if a hold can start. Used to prevent hold interaction without Key
@export var hold_node : Node

@onready var parent_node = get_parent() #Grabbing reference to door
@onready var hold_timer: Timer = $HoldTimer
@onready var hold_ui: Control = $HoldUi
@onready var progress_bar: ProgressBar = $HoldUi/ProgressBar


var is_holding : bool = false
var player_interaction_component



func _ready() -> void:
if parent_node.has_signal("object_state_updated"):
parent_node.object_state_updated.connect(_on_object_state_change)
if parent_node.has_signal("lock_state_updated"):
parent_node.lock_state_updated.connect(_lock_state_updated)

hold_ui.hide()
hold_timer.timeout.connect(_on_hold_complete)
hold_timer.wait_time = hold_time
progress_bar.value = hold_timer.time_left / hold_time * 100


func interact(_player_interaction_component):
was_interacted_with.emit(interaction_text,input_map_action)
player_interaction_component = _player_interaction_component
if !is_holding:
is_holding = true
hold_ui.show()
hold_timer.start()
check_before_hold_start(_player_interaction_component)

#Runs check before allowing hold to start, currently used to stop lock/unlock hold interaction if key not present.
func check_before_hold_start(_player_interaction_component):

func _on_object_state_change(_interaction_text: String):
interaction_text = _interaction_text
if hold_node and hold_node.has_method("start_hold_check"):
if hold_node.start_hold_check(_player_interaction_component):
if not is_holding:
is_holding = true
hold_timer.start()
else:
parent_node.door_rattle(_player_interaction_component)
else:
if not is_holding: # Node/Method doesn't exist, proceed with holding
is_holding = true
hold_timer.start()


func _on_object_state_change(_interaction_text: String):
press_interaction_text = _interaction_text
update_interaction_text()

func _lock_state_updated(lock_interaction_text: String):
hold_interaction_text = lock_interaction_text
update_interaction_text()

func update_interaction_text():
interaction_text = press_interaction_text + interaction_text_joiner + hold_interaction_text
interaction_complete.emit(player_interaction_component) #Signal to rebuild interaction prompt after text updated

func _process(_delta: float) -> void:
if is_holding:
if hold_timer.time_left < hold_timer.wait_time-buffer_time:
hold_ui.show()
is_being_held.emit(hold_timer.time_left)
progress_bar.value = hold_timer.time_left / hold_time * 100

Expand All @@ -47,16 +88,13 @@ func _process(_delta: float) -> void:
hold_ui.hide()
is_holding = false


func _input(event):
if is_holding and event.is_action_released(input_map_action):
hold_timer.stop()
hold_ui.hide()
is_holding = false
on_quick_press.emit(player_interaction_component)



func _on_hold_complete():
hold_timer.stop()
hold_ui.hide()
Expand Down
49 changes: 49 additions & 0 deletions COGITO/Components/Interactions/LockInteraction.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
extends InteractionComponent

@onready var parent_node = get_parent() #Grabbing reference to parent

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
if parent_node.has_signal("lock_state_updated"):
parent_node.lock_state_updated.connect(_lock_state_updated)

func interact(_player_interaction_component):
if check_for_item(_player_interaction_component, parent_node.key):
if parent_node.has_method("interact2"):
parent_node.interact2(_player_interaction_component)
was_interacted_with.emit(interaction_text, input_map_action)
elif check_for_item(_player_interaction_component, parent_node.lockpick):
if parent_node.has_method("interact2"):
parent_node.interact2(_player_interaction_component)
was_interacted_with.emit(interaction_text, input_map_action)
#start_lockpick(_player_interaction_component)
else:
_player_interaction_component.send_hint(null, parent_node.key_hint)

func _lock_state_updated(lock_interaction_text: String):
interaction_text = lock_interaction_text

func check_for_item(interactor, item) -> bool:
var inventory = interactor.get_parent().inventory_data
for slot_data in inventory.inventory_slots:
if slot_data != null and slot_data.inventory_item == item:
if slot_data.inventory_item.has_method("discard_after_use"):
if slot_data.inventory_item.discard_after_use:
inventory.remove_item_from_stack(slot_data)
return true
return false

#func start_lockpick(_player_interaction_component):
#Lockpick UI/Minigame can be initiated here
#pass

##Used for dual interaction component. This function checks keys presence before allowing a hold to start.
func start_hold_check(interactor) -> bool:
var keyResult: bool = check_for_item(interactor, parent_node.key)#
var lockpickResult: bool = check_for_item(interactor, parent_node.lockpick)

if keyResult or lockpickResult == true:
return true
else:
return false

8 changes: 8 additions & 0 deletions COGITO/Components/Interactions/LockInteraction.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://bbt5twbnrlcfj"]

[ext_resource type="Script" path="res://COGITO/Components/Interactions/LockInteraction.gd" id="3_3fqsx"]

[node name="LockInteraction" type="Node3D"]
script = ExtResource("3_3fqsx")
input_map_action = "interact2"
interaction_text = "Unlock"
11 changes: 8 additions & 3 deletions COGITO/Components/PlayerInteractionComponent.gd
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ func _handle_interaction(action: String) -> void:
if node.input_map_action == action and not node.is_disabled:
if !node.ignore_open_gui and get_parent().is_showing_ui:
return

node.interact(self)
# Update the prompts after an interaction. This is especially crucial for doors and switches.
_rebuild_interaction_prompts()

#Dual interaction components need to await signal to update correctly
if node is DualInteraction:
await node.interaction_complete # Wait until the interaction_complete signal is recieved
_rebuild_interaction_prompts() # rebuild prompt after signal is received
else:
# Update the prompts after an interaction. This is especially crucial for doors and switches.
_rebuild_interaction_prompts()
break


Expand Down
Loading