First commit 🎉

This commit is contained in:
Tony Bark 2025-07-17 01:49:18 -04:00
commit 43ea213f9b
728 changed files with 37080 additions and 0 deletions

View file

@ -0,0 +1,44 @@
class_name PopochiuIAudio
extends Node
## Provides access to the [PopochiuAudioCue]s in the game. Access with [b]A[/b] (e.g.
## [code]A.sfx_woosh.play()[/code]).
##
## Interface class that can be used to access all the audio cues in the game in order to play
## sound effects and music.[br][br]
## Use examples:[br]
## [codeblock]
## func _on_click() -> void:
## await A.sfx_tv_on.play()
## await E.queue([
## A.mx_toon_town.queue_play(),
## A.vo_scream.queue_play(true), # Wait for the audio to finish
## A.sfx_boing.queue_play(),
## ])
## A.mx_house.play()
## [/codeblock]
## Used to convert the value of the pitch set on [member PopochiuAudioCue.pitch] to the
## corresponding value needed for the [code]pitch_scale[/code] property of the audio stream players.
var twelfth_root_of_two := pow(2, (1.0 / 12))
#region Godot ######################################################################################
func _init() -> void:
Engine.register_singleton(&"A", self)
#endregion
#region Public #####################################################################################
## Transforms [param pitch] to a value that can be used to modify the
## [member AudioStreamPlayer.pitch_scale] or [member AudioStreamPlayer2D.pitch_scale].
func semitone_to_pitch(pitch: float) -> float:
return pow(twelfth_root_of_two, pitch)
## Returns [code]true[/code] if the [PopochiuAudioCue] identified by [param cue_name] is playing.
func is_playing_cue(cue_name: String) -> bool:
return PopochiuUtils.e.am.is_playing_cue(cue_name)
#endregion

View file

@ -0,0 +1 @@
uid://b0sr3r3nlem0t

View file

@ -0,0 +1,176 @@
class_name PopochiuICharacter
extends Node
## Provides access to the [PopochiuCharacter]s in the game. Access with [b]C[/b] (e.g.
## [code]C.player.say("What a wonderful plugin")[/code]).
##
## Use it to manipulate Characters. Its script is [b]i_character.gd[/b].[br][br]
##
## Some things you can do with it:[br][br]
## [b]•[/b] Access the Player-controlled Character (PC) directly [code]C.player[/code].[br]
## [b]•[/b] Access any character (with autocompletion based on its name).[br]
## [b]•[/b] Make characters move or say something.[br][br]
##
## Example:
## [codeblock]
## func on_click() -> void:
## await C.walk_to_clicked() # Make the PC move to the clicked object
## await C.face_clicked() # Make the PC look at the clicked object
## await C.player.say("It's a three-headed monkey!!!") # The PC says something
## await C.Popsy.say("Don't tell me...") # Another character says something
## [/codeblock]
## Emitted when [param character] says [param message].
signal character_spoke(character: PopochiuCharacter, message: String)
## Access to the [PopochiuCharacter] that is the current Player-controlled Character (PC).
var player: PopochiuCharacter : set = set_player
## Access to the [PopochiuCharacter] that is owning the camera.
var camera_owner: PopochiuCharacter
## Stores data about the state of each [PopochiuCharacter] in the game. The key of each entry is the
## [member PopochiuCharacter.script_name] of the character.
var characters_states := {}
var _characters := {}
#region Godot ######################################################################################
func _init() -> void:
Engine.register_singleton(&"C", self)
#endregion
#region Public #####################################################################################
## Makes the Player-controlled Character (PC) move (NON-BLOCKING) to the
## [member PopochiuClickable.walk_to_point] position of the last clicked [PopochiuClickable] (i.e. a
## [PopochiuProp], a [PopochiuHotspot], or another [PopochiuCharacter]) in the room. You can set an
## [param offset] relative to the target position.
func walk_to_clicked(offset := Vector2.ZERO) -> void:
await player.walk_to_clicked(offset)
## Makes the Player-controlled Character (PC) move (NON-BLOCKING) to the
## [member PopochiuClickable.walk_to_point] position of the last clicked [PopochiuClickable] (i.e. a
## [PopochiuProp], a [PopochiuHotspot], or another [PopochiuCharacter]) in the room. You can set an
## [param offset] relative to the target position.
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_walk_to_clicked(offset := Vector2.ZERO) -> Callable:
return func (): await walk_to_clicked(offset)
## Similar to [method walk_to_clicked] but BLOCKING the GUI to prevent players from clicking other
## objects or any point in the room.
func walk_to_clicked_blocking(offset := Vector2.ZERO) -> void:
await player.walk_to_clicked_blocking(offset)
## Similar to [method walk_to_clicked] but BLOCKING the GUI to prevent players from clicking other
## objects or any point in the room.
func queue_walk_to_clicked_blocking(offset := Vector2.ZERO) -> Callable:
return func (): await walk_to_clicked_blocking(offset)
## Makes the Player-controlled Character (PC) look at the last clicked [PopochiuClickable].
func face_clicked() -> void:
await player.face_clicked()
## Makes the Player-controlled Character (PC) look at the last clicked [PopochiuClickable].[br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_face_clicked() -> Callable:
return func (): await face_clicked()
## Makes the camera follow [param c].
func change_camera_owner(c: PopochiuCharacter) -> void:
if PopochiuUtils.e.cutscene_skipped:
camera_owner = c
await PopochiuUtils.e.get_tree().process_frame
return
camera_owner = c
await PopochiuUtils.e.get_tree().process_frame
## Makes the camera follow [param c].[br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_change_camera_owner(c: PopochiuCharacter) -> Callable:
return func (): await change_camera_owner(c)
## Returns the instance of the [PopochiuCharacter] identified with [param script_name]. If the
## character doesn't exists, then [code]null[/code] is returned.[br][br]
## This method is used by [b]res://game/autoloads/c.gd[/b] to load the instance of each character
## (present in that script as a variable for code autocompletion) in runtime.
func get_runtime_character(script_name: String) -> PopochiuCharacter:
var character: PopochiuCharacter = null
if _characters.has(script_name):
character = _characters[script_name]
else:
PopochiuUtils.print_error("Character %s is not in the room" % script_name)
return character
## Returns [code]true[/code] if [param script_name] is equal to [code]player[/code] or exist in
## [member characters].
func is_valid_character(script_name: String) -> bool:
var is_valid := false
if script_name.to_lower() == "player":
is_valid = true
else:
is_valid = _characters.has(script_name)
return is_valid
## Gets a [PopochiuCharacter] identified with [param script_name]. If the instance doesn't exist in
## [member characters], then one is created, added to the array, and returned.
func get_character(script_name: String) -> PopochiuCharacter:
var character: PopochiuCharacter = null
if script_name.is_empty():
return character
if (
script_name.to_lower() == "player"
or (is_instance_valid(player) and player.script_name.to_lower() == script_name)
):
character = player
elif _characters.has(script_name):
character = _characters[script_name]
else:
# If the character doesn't exist, try to instantiate it from the list of characters (Resource)
# in popochiu_data.cfg
character = get_instance(script_name)
if character:
_characters[character.script_name] = character
set(character.script_name, character)
return character
## Gets the instance of the [PopochiuCharacter] identified with [param script_name].
func get_instance(script_name: String) -> PopochiuCharacter:
var tres_path: String = PopochiuResources.get_data_value("characters", script_name, "")
if not tres_path:
PopochiuUtils.print_error("Character [b]%s[/b] doesn't exist in the project" % script_name)
return null
return load(load(tres_path).scene).instantiate()
#endregion
#region SetGet #####################################################################################
func set_player(value: PopochiuCharacter) -> void:
player = value
camera_owner = value
#endregion

View file

@ -0,0 +1 @@
uid://b4102ianyo7fo

View file

@ -0,0 +1,158 @@
class_name PopochiuIDialog
extends Node
## Provides access to the [PopochiuDialog]s in the game. Access with [b]D[/b] (e.g.
## [code]D.AskAboutLoom.start()[/code]).
##
## Use it to work with branching dialogs and listen to options selection. Its script is
## [b]i_dialog.gd[/b].[br][br]
##
## Some things you can do with it:[br][br]
## [b]•[/b] Start a branching dialog.[br]
## [b]•[/b] Know when a dialog has finished, or an option in the current list of options is
## selected.[br]
## [b]•[/b] Create a list of options on the fly.[br][br]
##
## Example:
## [codeblock]
## func on_click() -> void:
## # Create a dialog with 3 options
## var opt: PopochiuDialogOption = await D.show_inline_dialog([
## "Ask Popsy something", "Give Popsy a hug", "Do nothing"
## ])
##
## # The options IDs will go from 0 to the size - 1 of the array passed to D.show_inline_dialog
## match opt.id:
## "0": # "Ask Popsy something" was selected
## D.ChatWithPopsy.start() # Start the ChatWithPopsy dialog
## "1": # "Give Popsy a hug" was selected
## await C.walk_to_clicked()
## await C.player.play_hug()
## "2": # "Do nothing" was selected
## await C.player.say("Maybe later...")
## [/codeblock]
## Emitted when [param dlg] starts.
signal dialog_started(dlg: PopochiuDialog)
## Emitted when an [param opt] is selected in the current dialog.
signal option_selected(opt: PopochiuDialogOption)
## Emitted when [param dlg] finishes.
signal dialog_finished(dlg: PopochiuDialog)
## Emitted when the list of available [param options] in the current dialog is requested.
signal dialog_options_requested(options: Array[PopochiuDialogOption])
## Emitted when an inline dialog is created based on a list of [param options].
signal inline_dialog_requested(options: Array)
## Whether a dialog is playing.
var active := false
## Stores data about the state of each [PopochiuDialog] in the game. The key of each entry is the
## [member PopochiuDialog.script_name] of the dialog.
var trees := {}
## Provides access to the dialog that is currently playing.
var current_dialog: PopochiuDialog = null : set = set_current_dialog
## Provides access to the currently selected option in the dialog that is currently playing.
var selected_option: PopochiuDialogOption = null
## Provides access to the branching dialog that was played before the current one. I.e. Could be
## used to return to the previous dialog after exhausting the options in the currently playing one.
var prev_dialog: PopochiuDialog = null
#region Godot ######################################################################################
func _init() -> void:
Engine.register_singleton(&"D", self)
#endregion
#region Public #####################################################################################
## Displays a list of [param options], similar to a branching dialog, and returns the selected
## [PopochiuDialogOption].
func show_inline_dialog(options: Array) -> PopochiuDialogOption:
active = true
if current_dialog:
PopochiuUtils.d.option_selected.disconnect(current_dialog._on_option_selected)
inline_dialog_requested.emit(options)
var pdo: PopochiuDialogOption = await option_selected
if current_dialog:
PopochiuUtils.d.option_selected.connect(current_dialog._on_option_selected)
else:
active = false
PopochiuUtils.g.unblock()
return pdo
## Halts the currently playing [PopochiuDialog].
func finish_dialog() -> void:
dialog_finished.emit(current_dialog)
## Makes the Player-controlled Character (PC) to say the selected option in a branching dialog.
func say_selected() -> void:
await PopochiuUtils.c.player.say(selected_option.text)
## Transforms any text to gibberish preserving bbcode tags
func create_gibberish(input_string: String) -> String:
var output_text := ""
var bbcode := false
var letters := [
"a","e","i","o","u",
"y","b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","z"
]
for chr in input_string:
if(chr == "["):
bbcode = true
elif(chr == "]"):
output_text += chr
bbcode = false
continue
if (!bbcode):
if (chr != " "):
output_text += letters[randi_range(0,letters.size()-1)]
else:
output_text += " "
else:
output_text += chr
return output_text
## @deprecated
## Now it is [method get_instance].
func get_dialog_instance(script_name: String) -> PopochiuDialog:
return get_instance(script_name)
## Gets the instance of the [PopochiuDialog] identified with [param script_name].
func get_instance(script_name: String) -> PopochiuDialog:
var tres_path: String = PopochiuResources.get_data_value("dialogs", script_name, "")
if not tres_path:
PopochiuUtils.print_error("Dialog [b]%s[/b] doesn't exist in the project" % script_name)
return null
return load(tres_path)
#endregion
#region SetGet #####################################################################################
func set_current_dialog(value: PopochiuDialog) -> void:
current_dialog = value
active = true
await self.dialog_finished
# Save the state of the dialog
trees[current_dialog.script_name] = current_dialog
active = false
current_dialog = null
selected_option = null
#endregion

View file

@ -0,0 +1 @@
uid://dtjt7q7s51g0e

View file

@ -0,0 +1,167 @@
class_name PopochiuIGraphicInterface
extends Node
## Provides access to the in-game [PopochiuGraphicInterface] (GUI). Access with [b]G[/b] (e.g.
## [code]G.block()[/code]).[br][br]
##
## Use it to manage the GUI. Its script is [b]i_graphic_interface.gd[/b].[br][br]
##
## Some things you can do with it:[br][br]
##
## [b]•[/b] Show messages in the middle of the screen (like a narrator or a game message).[br]
## [b]•[/b] Show info about hovered objects in the game.[br]
## [b]•[/b] Show, hide, block or unblock the GUI.[br][br]
##
## Examples:
## [codeblock]
## G.show_info('Click this to open the main menu')
## G.display('There are no actions set for this object')
## G.hide_interface()
## G.connect('inventory_shown', self, '_play_inventory_sfx')
## [/codeblock]
## Emitted when [method block] is called. [PopochiuGraphicInterface] connects to this signal in
## order to block the GUI.
signal blocked
## Emitted when [method unblock] is called. [PopochiuGraphicInterface] connects to this signal in
## order to unblock the GUI.
signal unblocked
## Emitted when [method hide_interface] is called. [PopochiuGraphicInterface] connects to this
## signal in order to hide the GUI.
signal hidden
## Emitted when [method show_interface] is called. [PopochiuGraphicInterface] connects to this
## signal in order to show the GUI.
signal shown
## Emitted when the cursor enters (hover) a [param clickable].
signal mouse_entered_clickable(clickable: PopochiuClickable)
## Emitted when the cursor exits a [param clickable].
signal mouse_exited_clickable(clickable: PopochiuClickable)
## Emitted when the cursor enters (hover) a [param inventory_item].
signal mouse_entered_inventory_item(inventory_item: PopochiuInventoryItem)
## Emitted when the cursor exits a [param inventory_item].
signal mouse_exited_inventory_item(inventory_item: PopochiuInventoryItem)
## Emitted when a [PopochiuCharacter] begins to say a dialogue line.
signal dialog_line_started
## Emitted when a [PopochiuCharacter] finishes saying a dialogue line.
signal dialog_line_finished
## Emitted when [method show_hover_text] so the GUI can show [param message] in the hover text.
## I.e. when a [PopochiuClickable] is hovered.
signal hover_text_shown(message: String)
## Emitted when [method show_system_text] so the GUI can show [param message] as a system text.
signal system_text_shown(message: String)
## Emitted when the system text disappears after a click on the screen.
signal system_text_hidden
## Emitted when the [PopochiuPopup] identified by [member PopochiuPopup.script_name] is opened.
signal popup_requested(script_name: StringName)
# NOTE: Maybe add some signals for clicking objects and items
#signal clicked_clickable(clickable: PopochiuClickable)
#signal clicked_inventory_item(inventory_item: PopochiuInventoryItem)
## Emitted when the dialog options of the running [PopochiuDialog] are shown.
signal dialog_options_shown
## Emitted when a game is loaded and the GUI has shown (or not shown) a notification to the player.
signal load_feedback_finished
## Whether the GUI is blocked or not.
var is_blocked := false
## Provides access to the identifier of the GUI template used by the game.
var template := ""
## Provides access to the [PopochiuGraphicInterface] of the GUI template used by the game.
var gui: PopochiuGraphicInterface
#region Godot ######################################################################################
func _init() -> void:
Engine.register_singleton(&"G", self)
func _ready():
template = PopochiuResources.get_data_value("ui", "template", "")
#endregion
#region Public #####################################################################################
## Displays [param msg] at the center of the screen, useful for narration, instructions, or warnings
## to players. Temporarily blocks the GUI until players click anywhere on the game window, causing
## the text to disappear.
func show_system_text(msg: String) -> void:
# NOTE: Not sure if this logic should happen here. Perhaps it could trigger a signal to which
# the in-game graphic interface connects, allowing it to handle the logic.
if not PopochiuUtils.e.playing_queue and gui.popups_stack.is_empty():
block()
if PopochiuUtils.e.cutscene_skipped:
await get_tree().process_frame
return
system_text_shown.emit(PopochiuUtils.e.get_text(msg))
await system_text_hidden
if not PopochiuUtils.e.playing_queue and gui.popups_stack.is_empty():
unblock()
## Displays [param msg] at the center of the screen, useful for narration, instructions, or warnings
## to players. Temporarily blocks the GUI until players click anywhere on the game window, causing
## the text to disappear.[br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_show_system_text(msg: String) -> Callable:
return func (): await show_system_text(msg)
## Displays [param msg] in the game window without blocking interactions. Used to show players the
## name of objects where the cursor is positioned (i.e., a [PopochiuClickable]). It could also be
## used to show players what will happen if they use the left click or right click.
func show_hover_text(msg := '') -> void:
hover_text_shown.emit(msg)
## Causes the in-game graphic interface (GUI) to be blocked. This prevents players from interacting
## with the game elements.
func block() -> void:
is_blocked = true
blocked.emit()
## Causes the in-game graphic interface (GUI) to be unblocked.
func unblock(wait := false) -> void:
is_blocked = false
if wait:
await get_tree().create_timer(0.1).timeout
if is_blocked: return
unblocked.emit()
## Makes the in-game graphic interface (GUI) to hide.
func hide_interface() -> void:
hidden.emit()
## Makes the in-game graphic interface (GUI) to hide.[br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_hide_interface() -> Callable:
return func(): hide_interface()
## Makes the in-game graphic interface (GUI) to show.
func show_interface() -> void:
shown.emit()
## Makes the in-game graphic interface (GUI) to show.[br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_show_interface() -> Callable:
return func(): show_interface()
## Returns the name of the cursor texture to show.
func get_cursor_name() -> String:
if not is_instance_valid(gui): return ""
return gui.get_cursor_name()
#endregion

View file

@ -0,0 +1 @@
uid://bv4x1t3ugm6vk

View file

@ -0,0 +1,203 @@
class_name PopochiuIInventory
extends Node
## Provides access to the [PopochiuInventoryItem]s in the game. Access with [b]I[/b] (e.g.,
## [code]I.Key.add()[/code]).
##
## Use it to manage the inventory. Its script is [b]i_inventory.gd[/b].[br][br]
##
## Some things you can do with it:[br][br]
## [b]•[/b] Add and remove items in the inventory.[br]
## [b]•[/b] Change the cursor to the appearance of an inventory item.[br]
## [b]•[/b] Detect when an item has been added or removed.[br][br]
##
## Examples:
## [codeblock]
## # Add the DeckOfCards item to the inventory.
## I.DeckOfCards.add_now(false)
##
## # Add the Key item to the inventory and make it the selected one.
## I.Key.add_as_active()
##
## # Remove the Card item from the inventory. Inside an E.run([])
## I.Card.remove()
##
## # Add the ToyCar item after some dialog lines
## E.queue([
## "Player: Oh, is the toy car I need",
## I.ToyCar.queue_add(),
## "Player: Now I will be able to enter the private club",
## ])
## [/codeblock]
## Emitted when [param item] is added to the inventory. [param animate] can be utilized by the GUI
## to display an animation of the item entering the inventory.
signal item_added(item: PopochiuInventoryItem, animate: bool)
## Emitted when the [param item] has completed entering the inventory, signifying the end of the GUI
## animation.
signal item_add_done(item: PopochiuInventoryItem)
## Emitted when [param item] is removed from the inventory. [param animate] can be employed by the
## GUI to display an animation of the item leaving the inventory.
signal item_removed(item: PopochiuInventoryItem, animate: bool)
## Emitted when the [param item] has completed leaving the inventory, indicating the end of the GUI
## animation.
signal item_remove_done(item: PopochiuInventoryItem)
## Emitted when [param item] is replaced in the inventory by [param new_item]. Useful for handling
## inventory item combinations.
signal item_replaced(item: PopochiuInventoryItem, new_item: PopochiuInventoryItem)
## Emitted when an item replacement has finished.
signal item_replace_done
## Emitted when the [param item] has finished leaving the inventory (i.e. when the GUI animation
## is complete).
signal item_discarded(item: PopochiuInventoryItem)
## Emitted when [param item] is selected in the inventory.
signal item_selected(item: PopochiuInventoryItem)
## Emitted when the inventory is about to be displayed. You can specify the duration it remains
## visible with [param time] in seconds.
signal inventory_show_requested(time: float)
## Emitted once the animation that displays the inventory has finished.
signal inventory_shown
## Emitted when you want to hide the inventory. [param use_anim] can be used to determine whether or
## not to use an animation in the GUI.
signal inventory_hide_requested(use_anim: bool)
## Provides access to the inventory item that is currently selected.
var active: PopochiuInventoryItem : set = set_active
## Provides access to the inventory item that was clicked.
var clicked: PopochiuInventoryItem
# ---- Used for saving/loading the game ------------------------------------------------------------
## [Array] containing instances of the currently held [PopochiuInventoryItem]s.
var items := []
## Stores data about the state of each [PopochiuInventoryItem] in the game. The key of each entry is
## the [member PopochiuInventoryItem.script_name] of the item.
var items_states := {}
# ------------------------------------------------------------ Used for saving/loading the game ----
var _item_instances := {}
#region Godot ######################################################################################
func _init() -> void:
Engine.register_singleton(&"I", self)
#endregion
#region Public #####################################################################################
## Removes all the items that are currently in the inventory. If [param in_bg] is [code]true[/code],
## then the items are removed without calling [method PopochiuInventoryItem.discard] for each item.
func clean_inventory(in_bg := false) -> void:
items.clear()
for instance in _item_instances:
var pii: PopochiuInventoryItem = _item_instances[instance]
if not pii.in_inventory: continue
if not in_bg: await pii.discard()
pii.remove(!in_bg)
## Displays the inventory for a duration of [param time] seconds.
func show_inventory(time := 1.0) -> void:
if PopochiuUtils.e.cutscene_skipped:
await get_tree().process_frame
return
inventory_show_requested.emit(time)
await self.inventory_shown
## Displays the inventory for a duration of [param time] seconds.[br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_show_inventory(time := 1.0) -> Callable:
return func (): await show_inventory(time)
## Hides the inventory. If [param use_anim] is set to [code]true[/code], a GUI animation is applied.
func hide_inventory(use_anim := true) -> void:
inventory_hide_requested.emit(use_anim)
await get_tree().process_frame
## Hides the inventory. If [param use_anim] is set to [code]true[/code], a GUI animation is applied.
## [br][br]
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
func queue_hide_inventory(use_anim := true) -> Callable:
return func (): await hide_inventory(use_anim)
## Returns the instance of the [PopochiuInventoryItem] identified with [param item_name]. If the
## item doesn't exists, then [code]null[/code] is returned.[br][br]
## This method is used by [b]res://game/autoloads/i.gd[/b] to load the instace of each item
## (present in that script as a variable for code autocompletion) in runtime.
func get_item_instance(item_name: String) -> PopochiuInventoryItem:
var item: PopochiuInventoryItem = null
if _item_instances.has(item_name):
item = _item_instances[item_name]
else:
# If the item is not in the list of items, then try to instantiate it
item = get_instance(item_name)
if item:
_item_instances[item.script_name] = item
set(item.script_name, item)
return item
## Gets the instance of the [PopochiuInventoryItem] identified with [param script_name].
func get_instance(script_name: String) -> PopochiuInventoryItem:
var tres_path: String = PopochiuResources.get_data_value("inventory_items", script_name, "")
if not tres_path:
PopochiuUtils.print_error(
"Inventory item [b]%s[/b] doesn't exist in the project" % script_name
)
return null
return load(load(tres_path).scene).instantiate()
## Sets the cursor to use the texture of [param item].
func set_active_item(item: PopochiuInventoryItem = null) -> void:
if is_instance_valid(item):
active = item
else:
active = null
## Verifies if the item identified as [param item_name] is in the inventory.
func is_item_in_inventory(item_name: String) -> bool:
var i: PopochiuInventoryItem = get_item_instance(item_name)
return is_instance_valid(i) and i.in_inventory
## Checks whether the inventory has reached its limit.
func is_full() -> bool:
return (
PopochiuUtils.e.settings.inventory_limit > 0
and PopochiuUtils.e.settings.inventory_limit == items.size()
)
## Deselects the [member active] item.
func deselect_active() -> void:
active = null
#endregion
#region SetGet #####################################################################################
func set_active(value: PopochiuInventoryItem) -> void:
if is_instance_valid(active):
active.unselected.emit()
active = value
item_selected.emit(active)
#endregion

View file

@ -0,0 +1 @@
uid://b3yk4isvjtpx3

View file

@ -0,0 +1,354 @@
class_name PopochiuIRoom
extends Node
## Provides access to the [PopochiuRoom]s in the game. Access with [b]R[/b] (e.g.
## [code]R.House.get_prop("Drawer")[/code]).
##
## Use it to access props, hotspots, regions and walkable areas in the current room; or to access to
## data from other rooms. Its script is [b]i_room.gd[/b].[br][br]
##
## Some things you can do with it:[br][br]
## [b]•[/b] Access objects inside the current room.[br]
## [b]•[/b] Access the state of any room.[br]
## [b]•[/b] Move to another room.[br][br]
##
## Examples:
## [codeblock]
## R.get_prop("Scissors").modulate.a = 1.0 # Get Scissors prop and make it visible
## R.Outside.state.is_raining # Access the is_raining property in the Outside room
## [/codeblock]
## Provides access to the current [PopochiuRoom].
var current: PopochiuRoom = null : set = set_current
## Stores the state of each [PopochiuRoom] in the game. The key of each room is its
## [member PopochiuRoom.script_name], and each value is a [Dictionary] with its properties and the
## data of all its [PopochiuProp]s, [PopochiuHotspot]s, [PopochiuWalkableArea]s, [PopochiuRegion]s,
## and some data related with the [PopochiuCharacter]s in it. For more info about the data stored,
## check the documentation for [PopochiuRoomData].
var rooms_states := {}
var _room_instances := {}
var _use_transition_on_room_change := true
#region Godot ######################################################################################
func _init() -> void:
Engine.register_singleton(&"R", self)
#endregion
#region Public #####################################################################################
## Retrieves the [PopochiuProp] with a [member PopochiuClickable.script_name] matching
## [param prop_name].
func get_prop(prop_name: String) -> PopochiuProp:
return current.get_prop(prop_name)
## Retrieves the [PopochiuHotspot] with a [member PopochiuClickable.script_name] matching
## [param hotspot_name].
func get_hotspot(hotspot_name: String) -> PopochiuHotspot:
return current.get_hotspot(hotspot_name)
## Retrieves the [PopochiuRegion] with a [member PopochiuRegion.script_name] matching
## [param region_name].
func get_region(region_name: String) -> PopochiuRegion:
return current.get_region(region_name)
## Retrieves the [PopochiuWalkableArea] with a [member PopochiuWalkableArea.script_name] matching
## [param walkable_area_name].
func get_walkable_area(walkable_area_name: String) -> PopochiuWalkableArea:
return current.get_walkable_area(walkable_area_name)
## Retrieves the [Marker2D] with a [member Node.name] matching [param marker_name].
func get_marker(marker_name: String) -> Marker2D:
return current.get_marker(marker_name)
## Retrieves the [b]global position[/b] of the [Marker2D] with a [member Node.name] matching
## [param marker_name].
func get_marker_position(marker_name: String) -> Vector2:
return current.get_marker_position(marker_name)
## Returns all the [PopochiuProp]s in the room.
func get_props() -> Array:
return get_tree().get_nodes_in_group("props")
## Returns all the [PopochiuHotspot]s in the room.
func get_hotspots() -> Array:
return get_tree().get_nodes_in_group("hotspots")
## Returns all the [PopochiuRegion]s in the room.
func get_regions() -> Array:
return get_tree().get_nodes_in_group("regions")
## Returns all the [PopochiuWalkableArea]s in the room.
func get_walkable_areas() -> Array:
return get_tree().get_nodes_in_group("walkable_areas")
## Returns all the [Marker2D]s in the room.
func get_markers() -> Array:
return current.get_markers()
## Returns the instance of the [PopochiuRoom] identified with [param script_name]. If the room
## doesn't exists, then [code]null[/code] is returned.[br][br]
## This method is used by [b]res://game/autoloads/r.gd[/b] to load the instance of each room (present
## in that script as a variable for code autocompletion) in runtime.
func get_runtime_room(script_name: String) -> PopochiuRoom:
var room: PopochiuRoom = null
if _room_instances.has(script_name):
room = _room_instances[script_name]
else:
room = get_instance(script_name)
if room:
_room_instances[room.script_name] = room
return room
## Gets the instance of the [PopochiuRoom] identified with [param script_name].
func get_instance(script_name: String) -> PopochiuRoom:
# Fix #328 by returning the instance of the current room if it matches the instance that the
# plugin is looking for
if is_instance_valid(current) and current.script_name == script_name:
return current
var tres_path: String = PopochiuResources.get_data_value("rooms", script_name, "")
if not tres_path:
PopochiuUtils.print_error("Room [b]%s[/b] doesn't exist in the project" % script_name)
return null
return load(load(tres_path).scene).instantiate()
## Clears all the [PopochiuRoom] instances to free memory and orphan children.
func clear_instances() -> void:
for r in _room_instances:
(_room_instances[r] as PopochiuRoom).free()
_room_instances.clear()
## Loads the room with [param script_name]. [param use_transition] can be used to trigger a [i]fade
## out[/i] animation before loading the room, and a [i]fade in[/i] animation once it is ready.
## If [param store_state] is [code]true[/code] the state of the room will be stored in memory.
## [param ignore_change] is used internally by Popochiu to know if it's the first time the room is
## loaded when starting the game.
func goto_room(
script_name := "",
use_transition := true,
store_state := true,
ignore_change := false
) -> void:
if not PopochiuUtils.e.in_room: return
PopochiuUtils.e.in_room = false
PopochiuUtils.g.block()
_use_transition_on_room_change = use_transition
# Never fade the TL in, if we are entering the first room at game start
if use_transition and Engine.get_process_frames() > 0:
PopochiuUtils.e.tl.play_transition(PopochiuUtils.e.tl.FADE_IN)
await PopochiuUtils.e.tl.transition_finished
elif Engine.get_process_frames() > 0:
PopochiuUtils.e.tl.show_curtain()
# Prevent the GUI from showing info coming from the previous room
PopochiuUtils.g.show_hover_text()
PopochiuUtils.cursor.show_cursor()
if is_instance_valid(PopochiuUtils.c.player) and Engine.get_process_frames() > 0:
PopochiuUtils.c.player.last_room = current.script_name
# Store the room state
if store_state:
rooms_states[current.script_name] = current.state
current.state.save_children_states()
# Remove PopochiuCharacter nodes from the room so they are not deleted
if Engine.get_process_frames() > 0:
current.exit_room()
# Reset camera config
PopochiuUtils.e.camera.restore_default_limits()
if ignore_change:
return
var rp: String = PopochiuResources.get_data_value("rooms", script_name, "")
if rp.is_empty():
PopochiuUtils.print_error(
"Can't go to room [b]%s[/b] because it doesn't exist" % script_name
)
return
if Engine.get_process_frames() == 0:
await get_tree().process_frame
clear_instances()
PopochiuUtils.e.clear_hovered()
PopochiuUtils.e.get_tree().change_scene_to_file(load(rp).scene)
## Called once the loaded [param room] is "ready" ([method Node._ready]).
func room_readied(room: PopochiuRoom) -> void:
if not is_instance_valid(current):
current = room
# When running from the Editor the first time, use goto_room
if Engine.get_process_frames() == 0:
await get_tree().process_frame
PopochiuUtils.e.in_room = true
# Calling this will make the camera be set to its default values and will store the state of
# the main room (the last parameter will prevent Popochiu from changing the scene to the
# same that is already loaded).
# Also, use the transition layer to fade in the room, if the setting is enabled.
await goto_room(room.script_name, PopochiuUtils.e.settings.show_tl_in_first_room, true, true)
# Make the camera be ready for the room
current.setup_camera()
# Update the core state
if PopochiuUtils.e.loaded_game:
PopochiuUtils.c.player = PopochiuUtils.c.get_character(PopochiuUtils.e.loaded_game.player.id)
else:
current.state.visited = true
current.state.visited_times += 1
current.state.visited_first_time = current.state.visited_times == 1
# Add the PopochiuCharacter instances to the room
if (rooms_states[room.script_name]["characters"] as Dictionary).is_empty():
# Store the initial state of the characters in the room
current.state.save_characters()
current.clean_characters()
# Load the state of characters in the room
for chr_script_name: String in rooms_states[room.script_name]["characters"]:
var chr_dic: Dictionary = rooms_states[room.script_name]["characters"][chr_script_name]
var chr: PopochiuCharacter = PopochiuUtils.c.get_character(chr_script_name)
if not chr: continue
chr.position = Vector2(chr_dic.x, chr_dic.y)
chr._looking_dir = chr_dic.facing
chr.visible = chr_dic.visible
chr.modulate = Color.from_string(chr_dic.modulate, Color.WHITE)
chr.self_modulate = Color.from_string(chr_dic.self_modulate, Color.WHITE)
chr.light_mask = chr_dic.light_mask
chr.baseline = chr_dic.baseline
if chr_dic.has("walk_to_point"):
chr.walk_to_point = PopochiuUtils.unpack_vector_2(chr_dic.walk_to_point)
if chr_dic.has("look_at_point"):
chr.look_at_point = PopochiuUtils.unpack_vector_2(chr_dic.look_at_point)
current.add_character(chr)
# If the room must have the player character but it is not part of its $Characters node, then
# add the PopochiuCharacter to the room
if (
current.has_player
and is_instance_valid(PopochiuUtils.c.player)
and not current.has_character(PopochiuUtils.c.player.script_name)
):
current.add_character(PopochiuUtils.c.player)
# Place the PC in the middle of the room
PopochiuUtils.c.player.position = Vector2(PopochiuUtils.e.width, PopochiuUtils.e.height) / 2.0
await PopochiuUtils.c.player.idle()
# Load the state of Props, Hotspots, Regions and WalkableAreas
for type in PopochiuResources.ROOM_CHILDREN:
for script_name in rooms_states[room.script_name][type]:
var node: Node2D = current.callv(
"get_" + type.trim_suffix("s"),
[(script_name as String).to_pascal_case()]
)
if not is_instance_valid(node):
# Fix #320 by ignoring the object if it doesn't exist inside the Room
continue
var node_dic: Dictionary =\
rooms_states[room.script_name][type][script_name]
for property in node_dic:
if not PopochiuResources.has_property(node, property): continue
node[property] = node_dic[property]
for c in get_tree().get_nodes_in_group("PopochiuClickable"):
c.room = current
await current._on_room_entered()
if PopochiuUtils.e.loaded_game:
PopochiuUtils.c.player.global_position = Vector2(
PopochiuUtils.e.loaded_game.player.position.x,
PopochiuUtils.e.loaded_game.player.position.y
)
if _use_transition_on_room_change:
PopochiuUtils.e.tl.play_transition(PopochiuUtils.e.tl.FADE_OUT)
await PopochiuUtils.e.tl.transition_finished
await PopochiuUtils.e.wait(0.3)
else:
PopochiuUtils.e.tl.hide_curtain()
await get_tree().process_frame
if not current.hide_gui:
PopochiuUtils.g.unblock()
if PopochiuUtils.e.hovered:
PopochiuUtils.g.mouse_entered_clickable.emit(PopochiuUtils.e.hovered)
PopochiuUtils.e.in_room = true
if PopochiuUtils.e.loaded_game:
PopochiuUtils.e.game_loaded.emit(PopochiuUtils.e.loaded_game)
await PopochiuUtils.g.load_feedback_finished
PopochiuUtils.e.loaded_game = {}
# This enables the room to listen input events
current.is_current = true
await current._on_room_transition_finished()
# Fix #219: Update visited_first_time state once _on_room_transition_finished() finishes
current.state.visited_first_time = false
func store_states() -> void:
# Store the default state of rooms in the game
for room_tres in PopochiuResources.get_section("rooms"):
var res: PopochiuRoomData = load(room_tres)
rooms_states[res.script_name] = res
res.save_children_states()
#endregion
#region SetGet #####################################################################################
func set_current(value: PopochiuRoom) -> void:
if not value.is_inside_tree():
goto_room(value.script_name)
else:
current = value
#endregion

View file

@ -0,0 +1 @@
uid://ban86wtmbkxi2