538 lines
16 KiB
GDScript
538 lines
16 KiB
GDScript
class_name PopochiuResources
|
|
extends Node
|
|
|
|
enum Types {
|
|
ROOM,
|
|
CHARACTER,
|
|
INVENTORY_ITEM,
|
|
DIALOG,
|
|
# Room object types
|
|
PROP,
|
|
HOTSPOT,
|
|
REGION,
|
|
MARKER,
|
|
WALKABLE_AREA
|
|
}
|
|
enum CursorType {
|
|
NONE,
|
|
ACTIVE,
|
|
DOWN,
|
|
IDLE,
|
|
LEFT,
|
|
LOOK,
|
|
RIGHT,
|
|
SEARCH,
|
|
TALK,
|
|
UP,
|
|
USE,
|
|
WAIT,
|
|
}
|
|
enum AudioTypes {
|
|
NONE = -1,
|
|
MUSIC,
|
|
SOUND_EFFECT,
|
|
VOICE,
|
|
UI
|
|
}
|
|
|
|
# PLUGIN -------------------------------------------------------------------------------------------
|
|
const MAIN_DOCK_PATH = "res://addons/popochiu/editor/main_dock/popochiu_dock.tscn"
|
|
const MAIN_TYPES = [
|
|
Types.ROOM, Types.CHARACTER, Types.INVENTORY_ITEM, Types.DIALOG
|
|
]
|
|
const ROOM_TYPES = [Types.PROP, Types.HOTSPOT, Types.REGION, Types.MARKER, Types.WALKABLE_AREA]
|
|
const DOCUMENTATION = "https://carenalgas.github.io/popochiu/"
|
|
const CFG = "res://addons/popochiu/plugin.cfg"
|
|
const GUI_ADDON_FOLDER = "res://addons/popochiu/engine/objects/gui/"
|
|
const GUI_TEMPLATES_FOLDER = GUI_ADDON_FOLDER + "templates/"
|
|
const GUI_SCRIPT_TEMPLATES_FOLDER = "res://addons/popochiu/engine/templates/gui/"
|
|
const RETRO_RESOLUTION = Vector2(356.0, 200.0)
|
|
# SINGLETONS ---------------------------------------------------------------------------------------
|
|
const GLOBALS_SNGL = "res://game/popochiu_globals.gd"
|
|
const UTILS_SNGL = "res://addons/popochiu/engine/others/popochiu_utils.gd"
|
|
const CURSOR_SNGL = "res://addons/popochiu/engine/cursor/cursor.tscn"
|
|
const POPOCHIU_SNGL = "res://addons/popochiu/engine/popochiu.tscn"
|
|
const IROOM = "res://addons/popochiu/engine/interfaces/i_room.gd"
|
|
const ICHARACTER = "res://addons/popochiu/engine/interfaces/i_character.gd"
|
|
const IINVENTORY = "res://addons/popochiu/engine/interfaces/i_inventory.gd"
|
|
const IDIALOG = "res://addons/popochiu/engine/interfaces/i_dialog.gd"
|
|
const IGRAPHIC_INTERFACE_SNGL = "res://addons/popochiu/engine/interfaces/i_graphic_interface.gd"
|
|
const IAUDIO = "res://addons/popochiu/engine/interfaces/i_audio.gd"
|
|
const R_SNGL = "res://game/autoloads/r.gd"
|
|
const C_SNGL = "res://game/autoloads/c.gd"
|
|
const I_SNGL = "res://game/autoloads/i.gd"
|
|
const D_SNGL = "res://game/autoloads/d.gd"
|
|
const A_SNGL = "res://game/autoloads/a.gd"
|
|
const G_SNGL = "res://game/autoloads/g.gd"
|
|
# FIRST INSTALL ------------------------------------------------------------------------------------
|
|
const GI = 0
|
|
const TL = 1
|
|
const TRANSITION_LAYER_ADDON =\
|
|
"res://addons/popochiu/engine/objects/transition_layer/transition_layer.tscn"
|
|
# ENGINE -------------------------------------------------------------------------------------------
|
|
const POPOCHIU_SCENE = "res://addons/popochiu/engine/popochiu.tscn"
|
|
const AUDIO_MANAGER =\
|
|
"res://addons/popochiu/engine/audio_manager/audio_manager.tscn"
|
|
const CURSOR_TYPE =\
|
|
preload("res://addons/popochiu/engine/cursor/cursor.gd").Type
|
|
const DATA = "res://game//popochiu_data.cfg"
|
|
const ROOM_CHILDREN = ["props", "hotspots", "walkable_areas", "regions"]
|
|
const VALID_TYPES = [
|
|
TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING,
|
|
TYPE_ARRAY, TYPE_PACKED_STRING_ARRAY,
|
|
TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY,
|
|
TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY
|
|
]
|
|
const PROPS_IGNORE = [
|
|
"description",
|
|
"baseline",
|
|
"clickable",
|
|
"cursor",
|
|
"always_on_top",
|
|
"frames",
|
|
"v_frames",
|
|
"link_to_item",
|
|
"_description_code",
|
|
"last_click_button",
|
|
"_double_click_delay",
|
|
"_has_double_click",
|
|
]
|
|
const HOTSPOTS_IGNORE = [
|
|
"description",
|
|
"baseline",
|
|
"clickable",
|
|
"cursor",
|
|
"always_on_top",
|
|
"_description_code",
|
|
"last_click_button",
|
|
"_double_click_delay",
|
|
"_has_double_click",
|
|
]
|
|
const WALKABLE_AREAS_IGNORE = [
|
|
"description",
|
|
"tint"
|
|
]
|
|
const REGIONS_IGNORE = [
|
|
"description",
|
|
"tint",
|
|
"scaling",
|
|
"scale_top",
|
|
"scale_bottom"
|
|
]
|
|
const SNGL_TEMPLATE = "@tool\n" +\
|
|
"extends \"%s\"\n\n" +\
|
|
"# classes ----\n" +\
|
|
"# ---- classes\n" +\
|
|
"\n" +\
|
|
"# nodes ----\n" +\
|
|
"# ---- nodes\n" +\
|
|
"\n" +\
|
|
"# functions ----\n" +\
|
|
"# ---- functions\n" +\
|
|
"\n"
|
|
const SNGL_SETUP = {
|
|
R_SNGL : {
|
|
interface = IROOM,
|
|
section = "rooms",
|
|
"class" = "res://game/rooms/%s/room_%s.gd",
|
|
"const" = "const PR%s := preload(\"%s\")\n",
|
|
node = "var %s: PR%s : get = get_%s\n",
|
|
"func" = "func get_%s() -> PR%s: return get_runtime_room(\"%s\")\n",
|
|
prefix = "R",
|
|
},
|
|
C_SNGL : {
|
|
interface = ICHARACTER,
|
|
section = "characters",
|
|
"class" = "res://game/characters/%s/character_%s.gd",
|
|
"const" = "const PC%s := preload(\"%s\")\n",
|
|
node = "var %s: PC%s : get = get_%s\n",
|
|
"func" = "func get_%s() -> PC%s: return get_runtime_character(\"%s\")\n",
|
|
prefix = "C",
|
|
},
|
|
I_SNGL : {
|
|
interface = IINVENTORY,
|
|
section = "inventory_items",
|
|
"class" = "res://game/inventory_items/%s/inventory_item_%s.gd",
|
|
"const" = "const PII%s := preload(\"%s\")\n",
|
|
node = "var %s: PII%s : get = get_%s\n",
|
|
"func" = "func get_%s() -> PII%s: return get_item_instance(\"%s\")\n",
|
|
prefix = "I",
|
|
},
|
|
D_SNGL : {
|
|
interface = IDIALOG,
|
|
section = "dialogs",
|
|
"class" = "res://game/dialogs/%s/dialog_%s.gd",
|
|
"const" = "const PD%s := preload(\"%s\")\n",
|
|
node = "var %s: PD%s : get = get_%s\n",
|
|
"func" = "func get_%s() -> PD%s: return get_instance(\"%s\")\n",
|
|
prefix = "D",
|
|
}
|
|
}
|
|
const A_TEMPLATE = "@tool\n" +\
|
|
"extends \"%s\"\n\n" +\
|
|
"# cues ----\n" +\
|
|
"# ---- cues\n" +\
|
|
"\n"
|
|
const AUDIO_CUE_SOUND =\
|
|
"res://addons/popochiu/engine/audio_manager/audio_cue_sound.gd"
|
|
const AUDIO_CUE_MUSIC =\
|
|
"res://addons/popochiu/engine/audio_manager/audio_cue_music.gd"
|
|
const VAR_AUDIO_CUE_SOUND = "var %s: AudioCueSound = load(\"%s\")\n"
|
|
const VAR_AUDIO_CUE_MUSIC = "var %s: AudioCueMusic = load(\"%s\")\n"
|
|
# GODOT PROJECT SETTINGS ---------------------------------------------------------------------------
|
|
const DISPLAY_WIDTH = "display/window/size/viewport_width"
|
|
const DISPLAY_HEIGHT = "display/window/size/viewport_height"
|
|
const MAIN_SCENE = "application/run/main_scene"
|
|
const TEST_WIDTH = "display/window/size/window_width_override"
|
|
const TEST_HEIGHT = "display/window/size/window_height_override"
|
|
const STRETCH_MODE = "display/window/stretch/mode"
|
|
const STRETCH_ASPECT = "display/window/stretch/aspect"
|
|
# GUI TEMPLATES ------------------------------------------------------------------------------------
|
|
const GUI_CUSTOM = "custom"
|
|
const GUI_CUSTOM_SCENE = GUI_ADDON_FOLDER + "popochiu_gui.tscn"
|
|
const GUI_CUSTOM_TEMPLATE = GUI_SCRIPT_TEMPLATES_FOLDER + "custom_commands_template.gd"
|
|
# GAME ---------------------------------------------------------------------------------------------
|
|
const GAME_PATH = "res://game/"
|
|
const ROOMS_PATH = GAME_PATH + "rooms/"
|
|
const CHARACTERS_PATH = GAME_PATH + "characters/"
|
|
const INVENTORY_ITEMS_PATH = GAME_PATH + "inventory_items/"
|
|
const DIALOGS_PATH = GAME_PATH + "dialogs/"
|
|
const GUI_GAME_FOLDER = GAME_PATH + "gui/"
|
|
const GUI_GAME_SCENE = GUI_GAME_FOLDER + "gui.tscn"
|
|
const GUI_COMMANDS = GUI_GAME_FOLDER + "gui_commands.gd"
|
|
const TRANSITION_LAYER = GAME_PATH + "transition_layer/transition_layer.tscn"
|
|
|
|
|
|
#region Public #####################################################################################
|
|
# Verify if the folders (where Popochiu's objects will be) exists
|
|
static func init_file_structure() -> bool:
|
|
var is_first_install := !DirAccess.dir_exists_absolute(GAME_PATH)
|
|
|
|
# Create the folders that does not exist
|
|
for d in _get_directories().values():
|
|
if not DirAccess.dir_exists_absolute(d):
|
|
DirAccess.make_dir_recursive_absolute(d)
|
|
|
|
# ---- Create config files ---------------------------------------------------------------------
|
|
# Create .cfg file
|
|
if not FileAccess.file_exists(DATA):
|
|
_create_empty_file(DATA)
|
|
|
|
# Create Globals file
|
|
if not FileAccess.file_exists(GLOBALS_SNGL):
|
|
var globals_file = FileAccess.open(GLOBALS_SNGL, FileAccess.WRITE)
|
|
globals_file.store_string("extends Node")
|
|
globals_file.close()
|
|
|
|
# ---- Create autoload files -------------------------------------------------------------------
|
|
create_auto_loads()
|
|
|
|
return is_first_install
|
|
|
|
|
|
static func create_auto_loads() -> void:
|
|
for key in SNGL_SETUP:
|
|
if not FileAccess.file_exists(key):
|
|
var file := FileAccess.open(key, FileAccess.WRITE)
|
|
file.store_string(SNGL_TEMPLATE % SNGL_SETUP[key].interface)
|
|
file.close()
|
|
|
|
if not FileAccess.file_exists(A_SNGL):
|
|
var file := FileAccess.open(A_SNGL, FileAccess.WRITE)
|
|
file.store_string(A_TEMPLATE % IAUDIO)
|
|
file.close()
|
|
|
|
|
|
static func update_autoloads(save := false) -> void:
|
|
# ---- Create autoload files -------------------------------------------------------------------
|
|
create_auto_loads()
|
|
|
|
# ---- Update autoload files -------------------------------------------------------------------
|
|
for id in SNGL_SETUP:
|
|
if FileAccess.file_exists(id):
|
|
var s: Script = load(id)
|
|
var code := s.source_code
|
|
var modified := false
|
|
var sngl_setup: Dictionary = SNGL_SETUP[id]
|
|
|
|
if not get_data_cfg().has_section(sngl_setup.section):
|
|
continue
|
|
|
|
for key in get_data_cfg().get_section_keys(sngl_setup.section):
|
|
var var_name: String = key
|
|
var snake_name := key.to_snake_case()
|
|
|
|
if var_name[0].is_valid_int():
|
|
var_name = var_name.insert(0, sngl_setup.prefix)
|
|
|
|
if not ("var %s" % var_name) in code:
|
|
var classes_idx := code.find("# ---- classes")
|
|
var class_path: String = sngl_setup["class"] % [
|
|
snake_name, snake_name
|
|
]
|
|
|
|
code = code.insert(
|
|
classes_idx,
|
|
sngl_setup["const"] % [key, class_path]
|
|
)
|
|
|
|
var nodes_idx := code.find("# ---- nodes")
|
|
code = code.insert(
|
|
nodes_idx,
|
|
sngl_setup.node % [var_name, key, key]
|
|
)
|
|
|
|
var functions_idx := code.find("# ---- functions")
|
|
code = code.insert(
|
|
functions_idx,
|
|
sngl_setup["func"] % [key, key, key]
|
|
)
|
|
|
|
modified = true
|
|
|
|
if modified:
|
|
s.source_code = code
|
|
|
|
if save: ResourceSaver.save(s, id)
|
|
|
|
# ---- Populate the A singleton ----------------------------------------------------------------
|
|
if not get_data_cfg().has_section("audio")\
|
|
or not FileAccess.file_exists(A_SNGL):
|
|
return
|
|
|
|
# [mx_cues, sfx_cues, vo_cues, ui_cues]
|
|
var audio_groups := get_data_cfg().get_section_keys("audio")
|
|
var s: Script = load(A_SNGL)
|
|
var code := s.source_code
|
|
var modified := false
|
|
|
|
var old_audio_cues := []
|
|
|
|
# Add all the AudioCues as variables
|
|
for group in audio_groups:
|
|
for path in get_data_value("audio", group, []):
|
|
# Check if the AudioCue is of a valid type
|
|
var audio_cue: Resource = load(path)
|
|
var script_path: String = audio_cue.get_script().resource_path
|
|
|
|
if not script_path in [AUDIO_CUE_MUSIC, AUDIO_CUE_SOUND]:
|
|
# Backup the properties of the AudioCue
|
|
var values = audio_cue.get_values()
|
|
|
|
if group == "mx_cues":
|
|
audio_cue.set_script(load(AUDIO_CUE_MUSIC))
|
|
else:
|
|
audio_cue.set_script(load(AUDIO_CUE_SOUND))
|
|
|
|
# Restore the properties of the AudioCue
|
|
audio_cue.set_values(values)
|
|
old_audio_cues.append(audio_cue)
|
|
|
|
ResourceSaver.save(audio_cue, path)
|
|
|
|
var var_name := audio_cue.resource_name
|
|
|
|
if ("var %s" % var_name) in code:
|
|
continue
|
|
|
|
var cues_idx := code.find("# ---- cues")
|
|
|
|
if group == "mx_cues":
|
|
code = code.insert(
|
|
cues_idx, VAR_AUDIO_CUE_MUSIC % [var_name, path]
|
|
)
|
|
else:
|
|
code = code.insert(
|
|
cues_idx, VAR_AUDIO_CUE_SOUND % [var_name, path]
|
|
)
|
|
|
|
modified = true
|
|
|
|
if modified:
|
|
s.source_code = code
|
|
|
|
if save: ResourceSaver.save(s, A_SNGL)
|
|
|
|
# Save the script changes in the AudioCues
|
|
for cue in old_audio_cues:
|
|
ResourceSaver.call_deferred("save", cue.resource_path, cue)
|
|
|
|
|
|
static func remove_autoload_obj(id: String, script_name: String) -> void:
|
|
var sngl_setup: Dictionary = SNGL_SETUP[id]
|
|
var snake_name := script_name.to_snake_case()
|
|
var class_path: String = sngl_setup["class"] % [snake_name, snake_name]
|
|
var s: Script = load(id)
|
|
var code := s.source_code
|
|
|
|
code = code.replace(sngl_setup["const"] % [script_name, class_path], "")
|
|
code = code.replace(sngl_setup.node % [script_name, script_name, script_name], "")
|
|
code = code.replace(sngl_setup["func"] % [script_name, script_name, script_name], "")
|
|
|
|
s.source_code = code
|
|
ResourceSaver.save(s, id)
|
|
|
|
|
|
static func remove_audio_autoload(type: String, var_name: String, path: String) -> void:
|
|
var s: Script = load(A_SNGL)
|
|
var code := s.source_code
|
|
|
|
if type == "mx_cues":
|
|
code = code.replace(VAR_AUDIO_CUE_MUSIC % [var_name, path], "")
|
|
else:
|
|
code = code.replace(VAR_AUDIO_CUE_SOUND % [var_name, path], "")
|
|
|
|
s.source_code = code
|
|
ResourceSaver.save(s, A_SNGL)
|
|
|
|
|
|
# ---- GAME DATA -----------------------------------------------------------------------------------
|
|
static func get_data_cfg() -> ConfigFile:
|
|
var config := ConfigFile.new()
|
|
var err: int = config.load(DATA)
|
|
|
|
if err == OK:
|
|
return config
|
|
|
|
PopochiuUtils.print_error("Couldn't load popochiu_data.cfg")
|
|
return null
|
|
|
|
|
|
static func set_data_value(section: String, key: String, value) -> int:
|
|
var config := get_data_cfg()
|
|
|
|
config.set_value(section, key, value)
|
|
return config.save(DATA)
|
|
|
|
|
|
static func has_data_value(section: String, key: String) -> bool:
|
|
return get_data_cfg().has_section_key(section, key)
|
|
|
|
|
|
static func get_data_value(section: String, key: String, default):
|
|
var config := get_data_cfg()
|
|
|
|
if not config.has_section(section):
|
|
return default
|
|
|
|
return config.get_value(section, key, default)
|
|
|
|
|
|
static func erase_data_value(section: String, key: String) -> void:
|
|
var config := get_data_cfg()
|
|
|
|
if config.has_section_key(section, key):
|
|
config.erase_section_key(section, key)
|
|
config.save(DATA)
|
|
else:
|
|
PopochiuUtils.print_error("Can't delete %s key from %s section" % [key, section])
|
|
|
|
|
|
static func get_section(section: String) -> Array:
|
|
var config := get_data_cfg()
|
|
var resource_paths := []
|
|
|
|
if config.has_section(section):
|
|
for key in config.get_section_keys(section):
|
|
resource_paths.append(config.get_value(section, key))
|
|
|
|
return resource_paths
|
|
|
|
|
|
static func store_properties(
|
|
target: Dictionary, source: Object, ignore_too := []
|
|
) -> void:
|
|
copy_popochiu_object_properties(target, source, ignore_too)
|
|
|
|
# ---- Call custom function to store extra data ------------------------------------------------
|
|
if source.has_method("on_save"):
|
|
target.custom_data = source.on_save()
|
|
if target.custom_data.is_empty(): target.erase("custom_data")
|
|
|
|
|
|
static func copy_popochiu_object_properties(target, source: Object, ignore_too := []) -> void:
|
|
var properties_to_ignore := ["script_name", "scene"]
|
|
|
|
if not ignore_too.is_empty():
|
|
properties_to_ignore.append_array(ignore_too)
|
|
|
|
# ---- Store basic type properties -------------------------------------------------------------
|
|
# prop = {class_name, hint, hint_string, name, type, usage}
|
|
for prop in source.get_script().get_script_property_list():
|
|
if prop.name in properties_to_ignore: continue
|
|
if not prop.type in VALID_TYPES: continue
|
|
|
|
# Check if the property is a script variable (8192)
|
|
# or a export variable (8199)
|
|
if prop.usage == PROPERTY_USAGE_SCRIPT_VARIABLE or prop.usage == (
|
|
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE
|
|
):
|
|
target[prop.name] = source[prop.name]
|
|
|
|
|
|
static func has_property(source: Object, property: String) -> bool:
|
|
for prop in source.get_script().get_script_property_list():
|
|
if prop.name == property:
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
static func get_section_keys(section: String) -> Array:
|
|
var keys := []
|
|
var config := get_data_cfg()
|
|
|
|
if config.has_section(section):
|
|
keys = config.get_section_keys(section)
|
|
|
|
return keys
|
|
|
|
|
|
# ---- PLUGIN --------------------------------------------------------------------------------------
|
|
static func get_plugin_cfg() -> ConfigFile:
|
|
var config := ConfigFile.new()
|
|
var err: int = config.load(CFG)
|
|
|
|
if err == OK:
|
|
return config
|
|
|
|
PopochiuUtils.print_error("Couldn't load plugin config.")
|
|
return null
|
|
|
|
|
|
static func get_version() -> String:
|
|
return get_plugin_cfg().get_value("plugin", "version")
|
|
|
|
|
|
static func is_setup_done() -> bool:
|
|
return get_data_value("setup", "done", false)
|
|
|
|
|
|
static func is_gui_set() -> bool:
|
|
return (
|
|
!get_data_value("ui", "template", "").is_empty()
|
|
and DirAccess.dir_exists_absolute(GUI_GAME_FOLDER)
|
|
)
|
|
|
|
|
|
#endregion
|
|
|
|
#region Private ####################################################################################
|
|
static func _create_empty_file(path):
|
|
var file = FileAccess.open(path, FileAccess.WRITE)
|
|
file.store_string("")
|
|
file.close()
|
|
|
|
|
|
static func _get_directories() -> Dictionary:
|
|
return {
|
|
BASE = GAME_PATH,
|
|
AUTOLOADS = GAME_PATH + "/autoloads/",
|
|
ROOMS = ROOMS_PATH,
|
|
CHARACTERS = CHARACTERS_PATH,
|
|
INVENTORY_ITEMS = INVENTORY_ITEMS_PATH,
|
|
DIALOGS = DIALOGS_PATH,
|
|
}
|
|
|
|
|
|
#endregion
|