Initial commit of Escoria-Reloaded. Still a lot of missing stuff.

This commit is contained in:
Julian Murgia
2020-12-17 16:24:25 +01:00
commit f26d96f115
1794 changed files with 89611 additions and 0 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://addons/escoria-core/game/assets/fonts/efmi/efmi.TTF" type="DynamicFontData" id=1]
[resource]
size = 25
font_data = ExtResource( 1 )

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/no_image.png-7e4632ad2d21010b279ddaa4725bacb7.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/escoria-core/game/assets/images/no_image.png"
dest_files=[ "res://.import/no_image.png-7e4632ad2d21010b279ddaa4725bacb7.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

View File

@@ -0,0 +1,540 @@
extends Node
"""
ESC files:
Lines beginning with ":" such as :push, :say are EVENTS.
Lines in between are usually the ESC API functions calls. They are called COMMANDS.
Steps
compile_script(path/to/esc) : called once
> compile(path/to/esc, errors) : called once
> read_events() : called once
> create an ESCState, initialized with 1st line
> for each line in ESCState that corresponds to an event (:event), create a new level
> add_level(state, level, errors)
> for each state.line that belongs to same group (same indentation), create a command
> read_cmd(state, level, errors)
> get the token in state.line : this is the actual command (say, teleport, etc.)
> get the parameters next to the token
> create an ESCCommand, check it and push it into level array
> create an ESCEvent with the level created
> add it to the returned Dictionary of events
In the end, the ESCState has read all lines in the file and is deleted
Returned value is a Dictionary { event name : ESCEvent}
And ESCEvent.level is an array of ESCCommand
"""
var commands = {
"accept_input": { "min_args": 1, "types": [TYPE_STRING] },
"autosave": { "min_args": 0 },
"anim": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL] },
"camera_push": { "min_args": 1, "types": [TYPE_STRING] },
"camera_set_drag_margin_enabled": { "min_args": 2, "types": [TYPE_BOOL, TYPE_BOOL] },
"camera_set_pos": { "min_args": 3, "types": [TYPE_REAL, TYPE_INT, TYPE_INT] },
"camera_set_target": { "min_args": 1, "types": [TYPE_REAL] },
"camera_set_zoom": { "min_args": 1, "types": [TYPE_REAL] },
"camera_set_zoom_height": { "min_args": 1, "types": [TYPE_INT] },
"camera_shift": { "min_args": 2, "types": [TYPE_INT, TYPE_INT] },
"change_scene": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] },
"custom": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING] },
"cut_scene": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL] },
"debug": { "min_args": 1 },
"dec_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
"dialog_config": { "min_args": 3, "types": [TYPE_STRING, TYPE_BOOL, TYPE_BOOL] },
"enable_terrain": { "min_args": 1, "types": [TYPE_STRING]},
"game_over": { "min_args": 1, "types": [TYPE_BOOL] },
"inc_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
"inventory_add": { "min_args": 1 },
"inventory_remove": { "min_args": 1 },
"inventory_open": { "min_args": 1, "types": [TYPE_BOOL] },
"jump": { "min_args": 1 },
"play_snd": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] },
"queue_animation": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] },
"queue_resource": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] },
"repeat": true,
"set_state": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] },
"set_hud_visible": { "min_args": 1, "types": [TYPE_BOOL]},
"say": { "min_args": 2 },
"sched_event": { "min_args": 3, "types": [TYPE_REAL, TYPE_STRING, TYPE_STRING] },
"set_active": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] },
"set_angle": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
"set_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING] },
"set_globals": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] },
"set_interactive": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] },
"set_speed": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
"slide": { "min_args": 2 },
"slide_block": { "min_args": 2 },
"spawn": { "min_args": 1 },
"stop": true,
"teleport": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_INT] },
"teleport_pos": { "min_args": 3 },
"turn_to": { "min_args": 2 },
"wait": true,
"walk": { "min_args": 2 },
"walk_block": { "min_args": 2 },
"%": { "alias": "label", "min_args": 1},
"?": { "alias": "dialog"},
"!": { "alias": "end_dialog", "min_args": 0 },
">": { "alias": "branch"},
}
# Commands that can be called only by the ESC debug prompt
var debug_commands = {
"get_active": { "min_args": 1, "types": [TYPE_STRING] },
"get_global": { "min_args": 1, "types": [TYPE_STRING] },
"get_interactive": { "min_args": 1, "types": [TYPE_STRING] },
"get_state": { "min_args": 1, "types": [TYPE_STRING] },
}
# Loads a Dictionary of actions from a file, given its path.
func load_esc_file(esc_file_path : String) -> Dictionary:
var f = File.new()
if !f.file_exists(esc_file_path):
escoria.report_errors("esc_compiler.gd:load_esc_file()", ["File " + esc_file_path + " not found."])
return {}
return compile_script(esc_file_path)
# Loads the parameter script file. Can be either GDScript of ESC type.
# Returns the Dictionary of actions loaded from the file.
func compile_script(p_path : String) -> Dictionary:
var ev_table
# Script is GDScript
if p_path.find(".gd") != -1:
var res = ResourceLoader.load(p_path)
if res == null:
return {}
ev_table = res.new().get_events()
else: # Script is ESC
var errors = []
ev_table = compile(p_path, errors)
if errors.size() > 0:
escoria.call_deferred("report_errors", p_path, errors)
return ev_table
func check_command(commands_list : Dictionary, cmd : esctypes.ESCCommand, state : esctypes.ESCState, errors : Array):
if !(cmd.name in commands_list):
errors.push_back("line "+str(state.line_count)+": command "+cmd.name+" not valid.")
return false
var cmd_data = commands_list[cmd.name]
if typeof(cmd_data) == TYPE_BOOL:
return true
if "alias" in cmd_data:
cmd.name = cmd_data.alias
if "min_args" in cmd_data:
if cmd.params.size() < cmd_data.min_args:
errors.push_back("line "+str(state.line_count)+": command "+cmd.name+" takes "+str(cmd_data.min_args)+" parameters ("+str(cmd.params.size())+" were given).")
return false
var ret = true
if "types" in cmd_data:
var i = 0
for t in cmd_data.types:
if i >= cmd.params.size():
break
if t == TYPE_BOOL:
if cmd.params[i] == "true":
cmd.params[i] = true
elif cmd.params[i] == "false":
cmd.params[i] = false
else:
errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Must be 'true' or 'false'.")
ret = false
if t == TYPE_INT:
if not cmd.params[i].is_valid_integer():
errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Expected integer.")
cmd.params[i] = int(cmd.params[i])
if t == TYPE_REAL:
if not cmd.params[i].is_valid_float():
errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Expected float.")
cmd.params[i] = float(cmd.params[i])
i+=1
return ret
# Check that the given command exists and respects the right number of parameters
func check_normal_command(cmd : esctypes.ESCCommand, state : esctypes.ESCState, errors : Array):
return check_command(commands, cmd, state, errors)
func check_debug_command(cmd : esctypes.ESCCommand, state : esctypes.ESCState, errors : Array):
return check_command(debug_commands, cmd, state, errors)
# Fills the given "state" with the next line read from the file
func read_line(state : esctypes.ESCState) -> void:
while true:
if _eof_reached(state.file):
state.line = null
return
else:
state.line = _get_line(state.file)
state.line_count += 1
if !is_comment(state.line):
return
# Returns true if line is a comment (starting with #)
func is_comment(line : String) -> bool:
for i in range(0, line.length()):
var c = line[i]
if c == "#":
return true
if c != " " && c != "\t":
return false
return true
# Returns the position of the first non-blank character in given line string
func get_indent(line : String):
for i in range(0, line.length()):
if line[i] != " " && line[i] != "\t":
return i
# If the given line string is a event (begins with ":"), returns its name
# Else, return false
func is_event(line : String):
var trimmed = trim(line)
if trimmed.find(":") == 0:
return trimmed.substr(1, trimmed.length()-1)
return false
# Returns true if the given string is a flag (ie. "[.+]")
func is_flags(tk : String) -> bool:
var trimmed = trim(tk)
if trimmed.find("[") == 0 && trimmed.find("]") == trimmed.length()-1:
return true
return false
# Reads each line contained in the state (ESCState) (updates state.line)
# While the new line belongs to the same group, creates an ESCCommand from the current state
func add_level(state : esctypes.ESCState, level : Array, errors : Array):
read_line(state)
while state.line != null:
if is_event(state.line):
return
var ind_level = get_indent(state.line)
if ind_level < state.indent:
return
if ind_level > state.indent:
errors.push_back("line "+str(state.line_count)+": invalid indentation for group")
read_line(state)
continue
read_cmd(state, level, errors)
func add_dialog(state : esctypes.ESCState, level : Array, errors : Array):
read_line(state)
while typeof(state.line) != typeof(null):
if is_event(state.line):
return
var ind_level = get_indent(state.line)
if ind_level < state.indent:
return
if ind_level > state.indent:
errors.push_back("line "+str(state.line_count)+": invalid indentation for dialog")
read_line(state)
continue
read_dialog_option(state, level, errors)
func get_token(line : String, p_from : int, line_count : int, errors : Array) -> int:
while p_from < line.length():
if line[p_from] == " " || line[p_from] == "\t":
p_from += 1
else:
break
if p_from >= line.length():
return -1
var tk_end
if line[p_from] == "[":
tk_end = line.find("]", p_from)
if tk_end == -1:
errors.push_back("line "+str(line_count)+": unterminated flags")
tk_end += 1
elif line[p_from] == "\"":
tk_end = line.find("\"", p_from+1)
if tk_end == -1:
errors.push_back("line "+str(line_count)+": unterminated quotes, line '"+line+"'")
else:
tk_end = p_from
while tk_end < line.length():
if line[tk_end] == ":":
var ntk = get_token(line, tk_end+1, line_count, errors)
tk_end = ntk
break
if line[tk_end] == " " || line[tk_end] == "\t":
break
tk_end += 1
return tk_end
# Remove blank characters around p_str
func trim(p_str : String) -> String:
while p_str.length() && (p_str[0] == " " || p_str[0] == "\t"):
p_str = p_str.substr(1, p_str.length()-1)
while p_str.length() && p_str[p_str.length()-1] == " " || p_str[p_str.length()-1] == "\t":
p_str = p_str.substr(0, p_str.length()-1)
if p_str[0] == "\"":
p_str = p_str.substr(1, p_str.length()-1)
if p_str[p_str.length()-1] == "\"":
p_str = p_str.substr(0, p_str.length()-1)
return p_str
# Parses a flags string (usually defined by '[.*]') and fills the flags_list array
# and ifs variable (Dictionary containing all ifs conditions)
func parse_flags(p_flags : String, flags_list : Array, ifs : Dictionary):
var from = 1
while true:
var next = p_flags.find(",", from)
var flag
if next == -1:
flag = p_flags.substr(from, (p_flags.length()-1) - from)
else:
flag = p_flags.substr(from, next - from)
flag = trim(flag)
var list = []
if flag[0] == "!":
list.push_back(true)
flag = trim(flag.substr(1, flag.length()-1))
if flag.find("inv-") == 0:
ifs["if_not_inv"].push_back(trim(flag).substr(4, flag.length()-1))
elif flag.find("a/") == 0:
ifs["if_not_active"].push_back(trim(flag).substr(2, flag.length() - 1))
elif flag.substr(0, 3) in ["eq ", "gt ", "lt "]:
var elems = flag.split(" ", true, 2)
var comparison = "ne" if elems[0] == "eq" else "le" if elems[0] == "gt" else "ge"
ifs["if_" + comparison].push_back([elems[1], elems[2]])
else:
ifs["if_false"].push_back(trim(flag))
else:
list.push_back(false)
if flag.find("inv-") == 0:
ifs["if_inv"].push_back(trim(flag).substr(4, flag.length()-1))
elif flag.find("a/") == 0:
ifs["if_active"].push_back(trim(flag).substr(2, flag.length() - 1))
elif flag.substr(0, 3) in ["eq ", "gt ", "lt "]:
var elems = flag.split(" ", true, 2)
ifs["if_" + elems[0]].push_back([elems[1], elems[2]])
else:
ifs["if_true"].push_back(trim(flag))
if flag.find(":") >= 0:
var pos = flag.substr(0, flag.find(":"))
var inv = flag.substr(0, pos)
inv = trim(inv)
list.push_back(inv)
flag = flag.substr(pos, flag.length() - pos)
elif flag.find("inv-") == 0:
flag = trim(flag).substr(4, flag.length()-1)
list.push_back("i")
else:
list.push_back("g")
list.push_back(trim(flag))
# printt("adding flag ", list)
flags_list.push_back(list)
if next == -1:
return
from = next+1
func read_dialog_option(state : esctypes.ESCState, level : Array, errors : Array):
var tk_end = get_token(state.line, 0, state.line_count, errors)
var tk = trim(state.line.substr(0, tk_end))
if tk != "*" && tk != "-":
errors.append("line "+str(state.line_count)+": Invalid dialog option")
read_line(state)
return
# Remove inline comments
var comment_idx = state.line.find("#")
if comment_idx > -1:
state.line = state.line.substr(0, comment_idx)
tk_end += 1
# var c_start = state.line.find("\"", 0)
var c_end = state.line.find_last("\"")
var q_end = state.line.find("[", c_end)
var q_flags = null
#printt("flags before", q_flags)
if q_end == -1:
q_end = state.line.length()
else:
var f_end = state.line.find("]", q_end)
if f_end == -1:
errors.append("line "+str(state.line_count)+": unterminated flags")
else:
f_end += 1
q_flags = state.line.substr(q_end, f_end - q_end)
var question = trim(state.line.substr(tk_end, q_end - tk_end))
var cmd = { "name": "*", "params": [question, []] }
if q_flags:
var ifs = {
"if_true": [], "if_false": [], "if_inv": [], "if_not_inv": [],
"if_active": [], "if_not_active": [],
"if_eq": [], "if_ne": [], # string and integer comparison
"if_gt": [], "if_ge": [], "if_lt": [], "if_le": [] # integer comparison
}
var flag_list = []
parse_flags(q_flags, flag_list, ifs)
for key in ifs:
if ifs[key].size():
cmd.conditions[key] = ifs[key]
if flag_list.size():
cmd.flags = flag_list
state.indent += 1
add_level(state, cmd.params[1], errors)
state.indent -= 1
level.push_back(cmd)
# Read an ESCState and converts it to ESCCommand
# then puts it into level (Array of ESCCommand)
func read_cmd(state : esctypes.ESCState, level : Array, errors : Array):
var params = []
var from = 0
var tk_end = get_token(state.line, from, state.line_count, errors)
var ifs = {
"if_true": [], "if_false": [], "if_inv": [], "if_not_inv": [],
"if_active": [], "if_not_active": [],
"if_eq": [], "if_ne": [], # string and integer comparison
"if_gt": [], "if_ge": [], "if_lt": [], "if_le": [] # integer comparison
}
var flags = []
while tk_end != -1:
var tk = trim(state.line.substr(from, tk_end - from))
from = tk_end + 1
if is_flags(tk):
parse_flags(tk, flags, ifs)
else:
params.push_back(tk)
tk_end = get_token(state.line, from, state.line_count, errors)
if params.size() == 0:
errors.append("line "+str(state.line_count)+": Invalid command.")
read_line(state)
return
var cmd = esctypes.ESCCommand.new(params[0])
if params[0] == ">":
cmd.params = []
state.indent += 1
add_level(state, cmd.params, errors)
state.indent -= 1
elif params[0] == "?":
params.remove(0)
var dialog_params = []
state.indent += 1
add_dialog(state, dialog_params, errors)
cmd.params = params
cmd.params.insert(0, dialog_params)
state.indent -= 1
elif params[0] == "*":
errors.push_back("line "+str(state.line_count)+": Invalid command: dialog option outside dialog")
read_line(state)
return
else:
params.remove(0)
# Remove inline comments
var comment_idx = params.find("#")
if comment_idx > -1:
params.resize(comment_idx)
cmd.params = params
read_line(state)
for key in ifs:
if ifs[key].size():
cmd.conditions[key] = ifs[key]
if flags.size():
cmd.flags = flags
var errors_before = errors.duplicate()
var valid = check_normal_command(cmd, state, errors)
if valid:
level.push_back(cmd)
else:
var debug_valid = check_debug_command(cmd, state, errors)
if debug_valid:
errors.clear()
level.push_back(cmd)
# Read events from f (Dictionary or File) into ret Dictionary
func read_events(f, ret : Dictionary, errors : Array):
#var state = { "file": f, "line": _get_line(f), "indent": 0, "line_count": 0 }
var state = esctypes.ESCState.new(f, _get_line(f), 0, 0)
while state.line != null:
if is_comment(state.line):
read_line(state)
continue
var ev = is_event(state.line)
if typeof(ev) != typeof(null):
var level = []
var abort = add_level(state, level, errors)
var ev_flags = []
if ev is String:
if "|" in ev:
var ev_split = ev.split("|", true, 1)
ev = ev_split[0]
ev = ev.strip_edges()
if ev_split.size() > 1:
ev_split[1] = ev_split[1].strip_edges()
ev_flags = ev_split[1].split(" ")
ret[ev] = esctypes.ESCEvent.new(ev, level, Array(ev_flags))
if abort:
return abort
# If f is a File, returns the next line as String (or null)
# If f is a Dictionary, returns the next line from f.lines
func _get_line(f):
if f is Dictionary:
if f.line >= f.lines.size():
return null
var line = f.lines[f.line]
f.line += 1
#printt("reading line ", line)
return line
else:
return f.get_line()
func _eof_reached(f):
if typeof(f) == typeof({}):
return f.line >= f.lines.size()
else:
return f.eof_reached()
func compile_str(p_str : String, errors : Array):
var f = { "line": 0, "lines": p_str.split("\n") }
#printt("esc compile str ", f)
var ret = {}
read_events(f, ret, errors)
#printt("returning ", p_fname, ret)
return ret
# Returns a Dictionary of events read from p_fname filename
func compile(p_fname : String, errors : Array) -> Dictionary:
var f = File.new()
f.open(p_fname, File.READ)
if !f.is_open():
return {}
var ret = {}
read_events(f, ret, errors)
#printt("returning ", p_fname, ret)
return ret

View File

@@ -0,0 +1,630 @@
extends Node
# This is the script that runs in background, checking for events in the events stack
# and executing them.
# Events are managed using 2 structures:
# - event_queue: a queue for scheduled events. On each iteration, every event in the queue is updated
# according time delta. If an event time occurs, it is run (see check_event_queue()).
# - levels_stack: stack of events to be run immediately (from an event in ESC file usually)
signal global_changed(global_name)
signal inventory_changed
signal open_inventory
signal saved
signal run_event(ev_name, ev_data)
signal event_done(ev_name)
signal music_volume_changed
signal action_changed
signal paused(p_paused)
onready var resource_cache = load("res://addons/escoria-core/game/core-scripts/resource_queue.gd").new()
#save_data = load(ProjectSettings.get_setting("escoria/internals/save_data")).new()
onready var save_data = load("res://addons/escoria-core/game/core-scripts/save_data/save_data.gd").new()
# Cached scenes
var scenes_cache_list : Array = []
var scenes_cache : Dictionary = {} # this will eventually have everything in scenes_cache_list forever
# Event currently running
var running_event
# Events queue for timed events
var event_queue : Array = []
# Events stack
var levels_stack : Array = []
var reserved_globals = [
"ESC_LAST_SCENE"
]
# Dictionary of global variables
var globals : Dictionary = {}
# Dictionary of active objects
## Active == visible to the player
## Inactive == invisible to the player
var actives : Dictionary = {}
# Dictionary of all objects
## in the form : { "object global_id" : object }
var objects : Dictionary = {}
# Dictionary of all objects' states
## in the form : { "object_name" : "state_name" }
var states : Dictionary = {}
var interactives : Dictionary = {}
# Array containing the event table for each registered object
var objects_events_table : Dictionary
var continue_enabled : bool = true
var loading_game : bool = false
var game
# VERBS & TOOLS
# to be used like this:
# <current_action> <current_tool> with (target item or hotspot)
# eg: "use" "wrench" (with) (target item or hotspot)
# Current verb used
var current_action : String = "" setget set_current_action
# Current tool (ESCItem/ESCInventoryItem) used
var current_tool
## If true, we are accepting inputs
#var accept_input : bool
#enum ACCEPTABLE_INPUT {
# INPUT_NONE
#}
func _ready():
save_data.start()
get_tree().set_auto_accept_quit(ProjectSettings.get('escoria/main/force_quit'))
randomize()
save_data.load_settings([self, "settings_loaded"])
printt("calling res cache start")
resource_cache.start()
if !ProjectSettings.get_setting("escoria/platform/skip_cache"):
scenes_cache_list.push_back(ProjectSettings.get_setting("escoria/main/curtain"))
scenes_cache_list.push_back(ProjectSettings.get_setting("escoria/main/hud"))
printt("cache list ", scenes_cache_list)
for s in scenes_cache_list:
if s != null:
resource_cache.queue_resource(s, false, true)
set_process(true)
func _process(delta : float):
check_event_queue(delta)
run()
check_autosave()
# Process the event queue
func check_event_queue(delta : float):
# Update every event's time in the queue
for e in event_queue:
if e.qe_time > 0:
e.qe_time -= delta
# if !can_interact() or running_event:
# return
var i = event_queue.size()
while i:
i -= 1
var queued_next = event_queue[i]
print(queued_next)
# if queued_next.qe_time <= 0:
# var obj = get_object(queued_next.qe_objname)
# run_event(obj.event_table[queued_next.qe_event])
# event_queue.remove(i)
# break
func is_debug_command(p_event):
if p_event["ev_name"] != "debug":
return false
if p_event["ev_level"].size() != 1:
return false
if !(p_event["ev_level"][0].name in escoria.esc_compiler.debug_commands):
return false
return true
# Called by run_game()
func run_event(p_event):
"""
Run an event
"""
if is_debug_command(p_event):
return run_debug_command(p_event)
else:
running_event = p_event
add_level(p_event, true)
func run_debug_command(p_event):
return callv(p_event["ev_level"][0].name, p_event["ev_level"][0].params)
func add_level(p_event, p_root : bool):
"""
Add an ESCEvent to events stack
p_event: the ESCEvent
p_root: bool
"""
levels_stack.push_back(instance_level(p_event, p_root))
return esctypes.EVENT_LEVEL_STATE.CALL
func instance_level(p_event : esctypes.ESCEvent, p_root : bool):
var new_level = {
"ip": 0,
"instructions": p_event.ev_level,
"waiting": false,
"break_stop": p_root,
"labels": {},
"flags": p_event.ev_flags
}
for i in range(p_event.ev_level.size()):
if p_event.ev_level[i].name == "label":
var lname = p_event.ev_level[i].params[0]
new_level.labels[lname] = i
return new_level
func run():
if levels_stack.size() == 0:
# Constantly run in _process: we may have an empty levels_stack and no event
if running_event:
emit_signal("event_done", running_event.ev_name)
running_event = null
return
while levels_stack.size() > 0:
var ret = run_top()
if ret == esctypes.EVENT_LEVEL_STATE.YIELD:
return
if ret == esctypes.EVENT_LEVEL_STATE.BREAK:
while levels_stack.size() > 0 && !(levels_stack[levels_stack.size()-1].break_stop):
levels_stack.remove(levels_stack.size()-1)
levels_stack.remove(levels_stack.size()-1)
func run_top():
var top = levels_stack[levels_stack.size()-1]
# printt("-----> TOP:", top)
var ret = $esc_level_runner.resume(top)
if ret == esctypes.EVENT_LEVEL_STATE.RETURN || ret == esctypes.EVENT_LEVEL_STATE.BREAK:
levels_stack.remove(levels_stack.size()-1)
return ret
func test(cmd):
if "if_true" in cmd.conditions.keys():
for flag in cmd.conditions.if_true:
if !get_global(flag):
return false
if "if_false" in cmd.conditions.keys():
for flag in cmd.conditions.if_false:
if get_global(flag):
return false
if "if_inv" in cmd.conditions.keys():
for flag in cmd.conditions.if_inv:
if !inventory_has(flag):
return false
if "if_not_inv" in cmd.conditions.keys():
for flag in cmd.conditions.if_not_inv:
if inventory_has(flag):
return false
if "if_active" in cmd.conditions.keys():
for flag in cmd.conditions.if_active:
if not flag in actives or not actives[flag]:
return false
if "if_not_active" in cmd.conditions.keys():
for flag in cmd.conditions.if_not_active:
if flag in actives and actives[flag]:
return false
if "if_eq" in cmd.conditions.keys():
for flag in cmd.conditions.if_eq:
if !is_global_equal_to(flag[0], flag[1]):
return false
if "if_ne" in cmd.conditions.keys():
for flag in cmd.conditions.if_ne:
if is_global_equal_to(flag[0], flag[1]):
return false
if "if_gt" in cmd.conditions.keys():
for flag in cmd.conditions.if_gt:
if !is_global_greater_than(flag[0], flag[1]):
return false
if "if_ge" in cmd.conditions.keys():
for flag in cmd.conditions.if_ge:
if is_global_less_than(flag[0], flag[1]):
return false
if "if_lt" in cmd.conditions.keys():
for flag in cmd.conditions.if_lt:
if !is_global_less_than(flag[0], flag[1]):
return false
if "if_le" in cmd.conditions.keys():
for flag in cmd.conditions.if_le:
if is_global_greater_than(flag[0], flag[1]):
return false
return true
func inventory_has(p_obj):
return get_global("i/"+p_obj)
func items_in_inventory():
var items = []
for glob in globals.keys():
if glob.begins_with("i/") and globals[glob] == "true":
items.push_back(glob.rsplit("i/", false)[0])
return items
func get_global(name):
# If no value or looks like boolean, return boolean for backwards compatibility
if not name in globals or globals[name].to_lower() == "false":
return false
if globals[name].to_lower() == "true":
return true
return globals[name]
# Set global state 'name' to 'value' (can be true or false)
# 
func set_global(name, val, force_change_reserved : bool = false):
if name in reserved_globals and !force_change_reserved:
escoria.report_warnings("esc_runner.gd:set_global()",
["Global " + name + " is reserved. Value not modified."])
return
globals[name] = val
# printt("global changed at global_vm, emitting for ", name, val)
emit_signal("global_changed", name)
func dec_global(name, diff):
var global = get_global(name)
global = int(global) if global else 0
set_global(name, str(global - diff))
func inc_global(name, diff):
var global = get_global(name)
global = int(global) if global else 0
set_global(name, str(global + diff))
func set_globals(pat, val):
for key in globals:
if key.match(pat):
globals[key] = val
emit_signal("global_changed", key)
func is_global_equal_to(name, val):
var global = get_global(name)
if global and val and global == val:
return true
func is_global_greater_than(name, val):
var global = get_global(name)
if global and val and int(global) > int(val):
return true
func is_global_less_than(name, val):
var global = get_global(name)
if global and val and int(global) < int(val):
return true
func check_autosave():
pass
func set_current_action(action : String):
if ! action is String:
escoria.report_errors("esc_runner.gd",
["Trying to set_current_action: " + str(typeof(action))])
if action != current_action:
clear_current_tool()
current_action = action
emit_signal("action_changed")
func clear_current_action():
set_current_action("")
func clear_current_tool():
current_tool = null
func change_scene(params, context, run_events=true):
printt("change scene to ", params[0], " with run_events ", run_events)
# check_cache()
# main.clear_scene()
# camera = null
event_queue = []
# Regular events need to be reset immediately, so we don't
# accidentally `yield()` on them, for performance reasons.
# This does not affect `stack` so execution is fine anyway.
if running_event and running_event.ev_name != "load":
emit_signal("event_done", running_event.ev_name)
running_event = null
var res_room = resource_cache.get_resource(params[0])
var res_game = resource_cache.get_resource(ProjectSettings.get_setting("escoria/ui/game_scene"))
if !res_room:
escoria.report_errors("esc_runner.gd:change_scene()",
["Resource not found: " + params[0]])
if !res_game:
escoria.report_errors("esc_runner.gd:change_scene()",
["Resource not found: " + ProjectSettings.get_setting("escoria/ui/game_scene")])
resource_cache.clear()
# Load game scene
var game_scene = res_game.instance()
if !game_scene:
escoria.report_errors("esc_runner.gd:change_scene()",
["Failed loading scene " + ProjectSettings.get_setting("escoria/ui/game_scene")])
# Load room scene
var room_scene = res_room.instance()
if room_scene:
room_scene.add_child(game_scene)
room_scene.move_child(game_scene, 0)
escoria.main.set_scene(room_scene, run_events)
escoria.inputs_manager.is_hotspot_focused = false
if !scenes_cache_list.has(params[0]):
scenes_cache_list.push_back(params[0])
scenes_cache[room_scene.global_id] = params[0]
else:
escoria.report_errors("esc_runner.gd:change_scene()",
["Failed loading scene " + params[0]])
if context != null:
context.waiting = false
# Re-apply actives
for active in actives:
set_active(active, actives[active])
# cam_target = null
# autosave_pending = true
func run_game(actions : Dictionary):
set_process(true)
# `load` and `ready` are exclusive because you probably don't want to
# reset the game state when a scene becomes ready, and `ready` is
# redundant when `load`ing state anyway.
# `start` is used only in your `escoria/platform/game_start_script` .esc
# file to start the game.
if "start" in actions:
clear()
run_event(actions["start"])
escoria.main_menu_instance.hide()
elif "load" in actions:
clear()
run_event(actions["load"])
elif "ready" in actions:
run_event(actions["ready"])
func clear():
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "game_cleared")
levels_stack = []
globals = {}
objects = {}
states = {}
actives = {}
interactives = {}
event_queue = []
continue_enabled = true
loading_game = false
func register_object(name : String, val : Object, force : bool = false):
if !name:
escoria.report_errors("esc_runner.gd:register_object()",
["global_id not given for " + val.get_class() + " " + val.name])
if name in objects and not force:
escoria.report_errors("esc_runner.gd:register_object()",
["Trying to register already registered object " + name + ": " \
+ val.get_class() + " (" + val.name + ")"])
objects[name] = val
if not val.is_connected("tree_exited", self, "object_exit_scene"):
val.connect("tree_exited", self, "object_exit_scene", [name])
# Most objects have states/animations, but don't count on it
# if val.has_method("set_state"):
if val is ESCItem or val is ESCPlayer or val is ESCCharacter or val is ESCHotspot:
if name in states:
set_state(name, [states[name], true])
else:
set_state(name, [esctypes.OBJ_DEFAULT_STATE])
if val is ESCItem or val is ESCHotspot:
if val.is_interactive:
set_interactive(name, true)
# if val.has_method("set_active"):
# if name in actives:
# val.set_active(actives[name])
# if val.has_method("set_interactive"):
# if name in interactives:
# val.set_interactive(interactives[name])
if val.get("esc_script") != null and !val.get("esc_script").empty():
objects_events_table[name] = escoria.esc_compiler.load_esc_file(val.esc_script)
func get_object(name):
if !(name in objects):
return null
return objects[name]
# Activates the action for given params
# p_action String Action to execute (defined in attached ESC file and in action verbs UI)
# - eg: arrived, use, look, pickup...
# p_params Array
# - 0 Object Target object
func activate(p_action : String, p_param : Array):
printt("Action", p_action, "with params", p_param)
# if p_param[0].global_id:
# printt("("+p_param[0].global_id+")")
var what = p_param[0]
# If we're using an action which item requires to combine
if what is ESCItem and p_action in what.combine_if_action_used_among:
# Check if object must be in inventory to be used
if what is ESCItem and what.use_from_inventory_only:
if !inventory_has(what.global_id):
# TODO Either use fallback here, or run pickup action before use
escoria.report_warnings("esc_runner.gd:activate()", ["Trying to "
+ p_action + " on object " + what.global_id
+ " but item must be in inventory."])
return esctypes.EVENT_LEVEL_STATE.YIELD
else:
# Player has item in inventory, we check the element to use on
if p_param.size() > 1:
var combine_with = p_param[1]
var do_combine = false
if combine_with is ESCItem and combine_with.use_from_inventory_only:
if inventory_has(combine_with.global_id):
do_combine = true
else:
do_combine = true
if do_combine:
if objects_events_table[what.global_id].has(p_action + " " + combine_with.global_id):
run_event(objects_events_table[what.global_id][p_action + " " + combine_with.global_id])
return esctypes.EVENT_LEVEL_STATE.RETURN
elif objects_events_table[combine_with.global_id].has(p_action + " " + what.global_id) \
and !combine_with.combine_is_one_way:
run_event(objects_events_table[combine_with.global_id][p_action + " " + what.global_id])
return esctypes.EVENT_LEVEL_STATE.RETURN
else:
var errors = ["Attempted to execute inexisting action " + \
p_action + " between item " + combine_with.global_id + " and item " + what.global_id]
if combine_with.combine_is_one_way:
errors.append("Reason: " + combine_with.global_id + "'s item interaction is one-way.")
escoria.report_warnings("esc_runner.gd:activate()", errors)
return esctypes.EVENT_LEVEL_STATE.YIELD
else:
# TODO Use fallback here
pass
else:
# We're missing a target here.
# Tell the Label to add a conjunction and wait for another click
# to add the target to p_param. Until then, return false.
current_tool = what
return esctypes.EVENT_LEVEL_STATE.YIELD
if what.global_id in objects_events_table:
if p_action in objects_events_table[what.global_id]:
run_event(objects_events_table[what.global_id][p_action])
else:
escoria.report_warnings("esc_runner.gd:activate()",
["Action '" + p_action + "' requested on object '" \
+ what.global_id + "' but action doesn't exist in attached ESC file.",
"TODO: manage fallbacks."])
return esctypes.EVENT_LEVEL_STATE.RETURN
else:
escoria.report_warnings("esc_runner.gd:activate()",
["Action '" + p_action + "' requested on object '" + what.global_id \
+ "' but object does not exist in objects_events_table.", \
"Does object " + what.global_id + " have an attached ESC file?"])
return esctypes.EVENT_LEVEL_STATE.RETURN
return esctypes.EVENT_LEVEL_STATE.RETURN
func get_state(name : String):
return states[name]
func get_active(name : String) -> bool:
if actives.has(name):
return actives[name]
return false
"""
Return the interactive object given its global_id.
"""
func get_interactive(global_id : String):
if interactives.has(global_id):
return interactives[global_id]
return false
"""
Change an object state and play its animation (if it has one)
p_params[] :
- String state : the state name
- bool immediate (default=false) : if true, the animation is not played and immediately goes to the last frame
"""
func set_state(global_id : String, p_params : Array):
var obj = get_object(global_id)
states[global_id] = p_params[0]
var immediate : bool = false
if p_params.size() > 1:
immediate = p_params[1]
# A Hotspot can have a child item, if this item has an empty sprite
# (the hotspot is there to get the user input)
var animation_node
if obj is ESCItem:
#if obj.get("animation") != null:
# animation_node = obj.get("animation")
if obj.get_animation_player() != null:
animation_node = obj.get_animation_player()
elif obj is ESCHotspot and obj.get_item_child_if_any() != null:
#if obj.get_item_child_if_any().get("animation") != null:
# animation_node = obj.get_item_child_if_any().animation
if obj.get_item_child_if_any().get_animation_player() != null:
animation_node = obj.get_item_child_if_any().get_animation_player()
if animation_node:
animation_node.stop()
if animation_node.has_animation(p_params[0]):
if !immediate:
animation_node.play(p_params[0])
else:
# The animation is not played, we directly set it at its last frame
animation_node.current_animation = p_params[0]
var animation = animation_node.get_animation(p_params[0])
var animation_length = animation.length
animation_node.seek(animation_length)
"""
When object is active, it is VISIBLE.
When object is inactive, it is HIDDEN.
"""
func set_active(name : String, active):
if objects[name] is ESCInventoryItem:
return
actives[name] = active
if objects.has(name) and is_instance_valid(objects[name]):
if active:
objects[name].show()
else:
objects[name].hide()
"""
When object is interactive, it can be focused
When object is not interactive, it cannot be focused and used
"""
func set_interactive(name : String, active):
interactives[name] = active
"""
Callback called by ESCItems when it emits "tree_exit", ie. removed from scene.
Item is kept in objects[] array if it is in inventory.
"""
func object_exit_scene(name : String):
# If object is in inventory, save it before it's destroyed so we still have
# its data in objects[]
if inventory_has(name):
objects[name] = objects[name].duplicate()
else:
printt("Object " + name + " removed from scene.")
objects.erase(name)

View File

@@ -0,0 +1,463 @@
extends Node
# This script runs the ESCCommands contained in the ESCEvent.
var current_context
onready var esc_runner = get_parent()
func _ready():
pass
func finished(context = null):
if context != null:
context.waiting = false
else:
current_context.waiting = false
func check_obj(name, cmd):
var obj = escoria.esc_runner.get_object(name)
if obj == null:
escoria.report_errors("", ["Global id "+name+" not found for " + cmd])
return false
return true
func resume(context):
current_context = context
if context.waiting:
return esctypes.EVENT_LEVEL_STATE.YIELD
var count = context.instructions.size()
while context.ip < count:
var top = esc_runner.levels_stack.size()
var ret = run(context)
context.ip += 1
if top < esc_runner.levels_stack.size():
return esctypes.EVENT_LEVEL_STATE.CALL
if ret == esctypes.EVENT_LEVEL_STATE.YIELD:
return esctypes.EVENT_LEVEL_STATE.YIELD
if ret == esctypes.EVENT_LEVEL_STATE.CALL:
return esctypes.EVENT_LEVEL_STATE.CALL
if ret == esctypes.EVENT_LEVEL_STATE.BREAK:
if context.break_stop:
break
else:
return esctypes.EVENT_LEVEL_STATE.BREAK
if ret == esctypes.EVENT_LEVEL_STATE.REPEAT:
context.ip = 0
if ret == esctypes.EVENT_LEVEL_STATE.JUMP:
return esctypes.EVENT_LEVEL_STATE.JUMP
context.ip = 0
return esctypes.EVENT_LEVEL_STATE.RETURN
func run(context):
var cmd = context.instructions[context.ip]
if cmd.name == "label":
return esctypes.EVENT_LEVEL_STATE.RETURN
if !esc_runner.test(cmd):
return esctypes.EVENT_LEVEL_STATE.RETURN
#print("name is ", cmd.name)
#if !(cmd.name in self):
# esc_runner.report_errors("", ["Unexisting command "+cmd.name])
return call(cmd.name, cmd.params)
"""
Automatically called when a dialog line is said.
"""
func dialog_line_finished() -> void:
# escoria.esc_runner.get_node("esc_level_runner").finished()
finished()
escoria.dialog_player.is_speaking = false
escoria.current_state = escoria.GAME_STATE.DEFAULT
"""
"""
func accept_input():
pass
"""
"""
func autosave():
pass
"""
"""
func anim():
pass
"""
"""
func branch(command_params : Array):
var branch_ev = esctypes.ESCEvent.new("branch", command_params, [])
return escoria.esc_runner.add_level(branch_ev, false)
"""
"""
func camera_push():
pass
"""
"""
func camera_set_drag_margin_enabled():
pass
"""
"""
func camera_set_pos():
pass
"""
"""
func camera_set_target():
pass
"""
"""
func camera_set_zoom():
pass
"""
"""
func camera_set_zoom_height():
pass
"""
"""
func camera_shift():
pass
"""
"""
func change_scene(params):
# Savegames must have events disabled, so saving the game adds a false to params
var run_events = true
if params.size() == 2:
run_events = bool(params[1])
# looking for localized string format in scene. this should be somewhere else
var sep = params[0].find(":\"")
if sep >= 0:
var path = params[0].substr(sep + 2, params[0].length() - (sep + 2))
escoria.esc_runner.call_deferred("change_scene", [path], current_context, run_events)
else:
escoria.esc_runner.call_deferred("change_scene", params, current_context, run_events)
current_context.waiting = true
return esctypes.EVENT_LEVEL_STATE.YIELD
"""
"""
func custom():
pass
"""
"""
func cut_scene():
pass
"""
"""
func debug():
pass
"""
"""
func dec_global():
pass
"""
"""
func inc_global():
pass
"""
"""
func dialog(command_params : Array):
current_context.waiting = true
current_context.in_dialog = true
escoria.current_state = escoria.GAME_STATE.DIALOG
if !escoria.dialog_player:
escoria.dialog_player = escoria.main.current_scene.get_node("game/ui/dialog_layer/dialog_player")
var options = command_params.slice(1, command_params.size())
escoria.dialog_player.start_dialog_choices(command_params[0], options)
return esctypes.EVENT_LEVEL_STATE.YIELD
"""
"""
func dialog_config():
pass
"""
Enable the ESCTerrain's NavigationPolygonInstance defined by given node name.
Disables previously activated NavigationPolygonInstance.
"""
func enable_terrain(command_params : Array):
var name : String = command_params[0]
if escoria.room_terrain.has_node(name):
var new_active_navigation_instance = escoria.room_terrain.get_node(name)
escoria.room_terrain.current_active_navigation_instance.enabled = false
escoria.room_terrain.current_active_navigation_instance = new_active_navigation_instance
escoria.room_terrain.current_active_navigation_instance.enabled = true
"""
"""
func game_over(command_params : Array):
pass
"""
"""
func inventory_add(command_params : Array):
pass
"""
"""
func inventory_remove(command_params : Array):
pass
"""
"""
func inventory_open(command_params : Array):
pass
"""
"""
func jump(command_params : Array):
pass
"""
"""
func play_snd(command_params : Array):
pass
"""
"""
func queue_animation(command_params : Array):
pass
"""
"""
func queue_resource(command_params : Array):
pass
"""
"""
func repeat(command_params : Array):
pass
"""
Make a character say one line.
Usage: say object_id line [dialog_ui_name]
"""
func say(command_params : Array) -> esctypes:
current_context.waiting = true
var dict : Dictionary
var dialog_scene_name = ProjectSettings.get_setting("escoria/ui/default_dialog_scene")
if dialog_scene_name.empty():
escoria.report_errors("level_esc_runners.gd:say()", ["Project setting 'escoria/ui/default_dialog_scene' is not set. Please set a default dialog scene."])
var file = dialog_scene_name.get_file()
var extension = dialog_scene_name.get_extension()
dialog_scene_name = file.rstrip("." + extension)
# Manage specific dialog scene
if command_params.size() > 2:
dialog_scene_name = command_params[2]
dict = {
"line": command_params[1],
"ui": dialog_scene_name
#"ui": "dialog_label"
#"ui": "dialog_box_inset"
}
escoria.current_state = escoria.GAME_STATE.DIALOG
if !escoria.dialog_player:
escoria.dialog_player = escoria.main.current_scene.get_node("game/ui/dialog_layer/dialog_player")
escoria.dialog_player.say(command_params[0], dict)
return esctypes.EVENT_LEVEL_STATE.YIELD
"""
Sets object as active or inactive. Active objects are displayed in scene and respond
to inputs. Inactives are hidden.
"""
func set_active(command_params : Array):
if !check_obj(command_params[0], "set_active"):
return esctypes.EVENT_LEVEL_STATE.RETURN
var name : String = command_params[0]
var value = command_params[1]
escoria.esc_runner.set_active(name, value)
"""
Set the angle of an object.
Usage: set_angle object_id angle_degrees
"""
func set_angle(params : Array):
if !check_obj(params[0], "set_angle"):
return esctypes.EVENT_LEVEL_STATE.RETURN
var obj = escoria.esc_runner.get_object(params[0])
obj.set_angle(int(params[1]))
return esctypes.EVENT_LEVEL_STATE.RETURN
"""
"""
func set_state(command_params : Array):
var global_id : String = command_params[0]
var p_params : Array = command_params.slice(1, command_params.size())
escoria.esc_runner.set_state(global_id, p_params)
"""
"""
func set_hud_visible(command_params : Array):
pass
"""
"""
func sched_event(command_params : Array):
pass
"""
"""
func set_global(command_params : Array):
var name : String = command_params[0]
var value = command_params[1]
escoria.esc_runner.set_global(name, value)
"""
"""
func set_globals(command_params : Array):
pass
"""
"""
func set_interactive(command_params : Array):
var name : String = command_params[0]
var value = command_params[1]
escoria.esc_runner.set_interactive(name, value)
"""
"""
func set_speed(command_params : Array):
pass
"""
"""
func slide(command_params : Array):
pass
"""
"""
func slide_block(command_params : Array):
pass
"""
"""
func spawn(command_params : Array):
pass
"""
"""
func stop(command_params : Array):
return esctypes.EVENT_LEVEL_STATE.BREAK
"""
Teleports obj1 at obj2's position. If angle_degrees is set (int), sets obj1's
angle to angle_degrees.
Usage: teleport obj1 obj2 [angle_degrees]
"""
func teleport(params):
if !check_obj(params[0], "teleport"):
return esctypes.EVENT_LEVEL_STATE.RETURN
if !check_obj(params[1], "teleport"):
return esctypes.EVENT_LEVEL_STATE.RETURN
var angle
if params.size() > 2:
angle = int(params[2])
escoria.esc_runner.get_object(params[0]).teleport(escoria.esc_runner.get_object(params[1]), angle)
return esctypes.EVENT_LEVEL_STATE.RETURN
"""
"""
func teleport_pos(command_params : Array):
pass
"""
"""
func turn_to(command_params : Array):
pass
"""
Wait for given time in seconds.
Usage: wait time_in_seconds
"""
func wait(command_params : Array):
escoria.current_state = escoria.GAME_STATE.WAIT
var time = float(command_params[0])
if time <= 0:
return esctypes.EVENT_LEVEL_STATE.RETURN
# get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "wait", time, p_level)
escoria.main.wait(command_params, current_context)
current_context.waiting = true
return esctypes.EVENT_LEVEL_STATE.YIELD
"""
Make object1 walk towards object2. This command is not blocking (user input not disabled)
Usage: walk object_id1 object_id2
"""
func walk(command_params : Array):
escoria.do("walk", command_params)
"""
"""
func walk_block(command_params : Array):
pass

View File

@@ -0,0 +1,97 @@
tool
extends TextureRect
class_name ESCBackground
func get_class():
return "ESCBackground"
signal double_left_click_on_bg(position)
signal left_click_on_bg(position)
signal right_click_on_bg(position)
signal mouse_moved
export(String, FILE, "*.esc") var esc_script = ""
# Actual size of the scene
var size : Vector2
"""
ESCBackground purpose is to display a background image and receive input events
on the background. More precisely, the TextureRect under ESCBackground does not
receive events itself - if it did, it would also eat all events like hotspot
focusing and such. Instead, we set the TextureRect mouse filter to
MOUSE_FILTER_IGNORE, and we use an Area2D node to receive the input events.
If ESCBackground doesn't contain a texture, it is important that its rect_size
is set over the whole scene, because its rect_size is then used to create the
Area2D node under it. If the rect_size is wrongly set, the background may
receive no input.
"""
# PRIVATE VARS
var area : Area2D
var actual_click_position : Vector2
# Godot doesn't do doubleclicks so we must
var last_lmb_dt = 0
var waiting_dblclick = null # null or [pos, event]
func _enter_tree():
# Use size of background texture to calculate collision shape if any
if get_texture():
size = get_texture().get_size()
else:
size = rect_size
area = Area2D.new()
var shape = RectangleShape2D.new()
var sid = area.create_shape_owner(area)
# Move origin of Area2D to center of Sprite
var transform = area.shape_owner_get_transform(sid)
transform.origin = size / 2
area.shape_owner_set_transform(sid, transform)
# Set extents of RectangleShape2D to cover entire Sprite
shape.set_extents(size / 2)
area.shape_owner_add_shape(sid, shape)
add_child(area)
func _ready():
mouse_filter = MOUSE_FILTER_IGNORE
area.connect("input_event", self, "manage_input")
connect("gui_input", self, "manage_input_texturerect")
if !Engine.is_editor_hint():
connect("left_click_on_bg", escoria.inputs_manager, "_on_left_click_on_bg")
connect("right_click_on_bg", escoria.inputs_manager, "_on_right_click_on_bg")
connect("double_left_click_on_bg", escoria.inputs_manager, "_on_double_left_click_on_bg")
# connect("mouse_moved_on_bg", escoria.inputs_manager, "_on_mouse_moved_on_bg")
func manage_input(_viewport, event, _shape_idx):
if event is InputEventMouseButton:
var p = get_global_mouse_position()
if event.doubleclick:
if event.button_index == BUTTON_LEFT:
emit_signal("double_left_click_on_bg", p)
else:
if event.is_pressed():
if event.button_index == BUTTON_LEFT:
emit_signal("left_click_on_bg", p)
if event.button_index == BUTTON_RIGHT:
emit_signal("right_click_on_bg", p)
# elif event is InputEventMouseMotion:
# emit_signal("mouse_moved_on_bg")
func manage_input_texturerect(event):
if event is InputEventMouseButton and event.is_pressed():
if event.button_index == BUTTON_LEFT:
emit_signal("left_click_on_bg", event.position)
if event.button_index == BUTTON_RIGHT:
emit_signal("right_click_on_bg", event.position)
else:
pass

View File

@@ -0,0 +1,9 @@
tool
extends Node
class_name ESCCharacter
export(String) var character_id
export(String, FILE, ".esc") var esc_script = ""
func _ready():
pass

View File

@@ -0,0 +1,148 @@
tool
extends Area2D
class_name ESCHotspot
func get_class():
return "ESCHotspot"
"""
ESCHotspot is an Area2D (hotspot).
A hotspot is a simple area that can be defined by the user and is thus invisible
Usually, hotspots are used to define areas of the background that the player can
look at.
"""
signal mouse_entered_hotspot(global_id)
signal mouse_exited_hotspot
signal mouse_left_clicked_hotspot(global_id, click_position)
signal mouse_double_left_clicked_hotspot(global_id, click_position)
signal mouse_right_clicked_hotspot(global_id, click_position)
export(String) var global_id
export(bool) var is_exit
export(String, FILE, "*.esc") var esc_script
export(bool) var is_interactive = true
export(bool) var player_orients_on_arrival = true
export(ESCPlayer.Directions) var interaction_direction
export(String) var tooltip_name
export(String) var default_action
# If action used by player is in the list, game will wait for a second click on another item
# to combine objects together (typical USE <X> WITH <Y>, GIVE <X> TO <Y>)
export(PoolStringArray) var combine_if_action_used_among = []
export(Color) var dialog_color = ColorN("white")
# Detected interact position set by a Position2D node OUTSIDE OF THE HOTSPOT SCENE.
# You have to add a child to the INSTANCED HOTSPOT SCENE, IN THE ROOM SCENE.
export(Dictionary) var interact_positions : Dictionary = { "default": null}
var collision
var terrain : ESCTerrain
# If the terrain node type is scalenodes
var terrain_is_scalenodes : bool
var check_maps = true
var pose_scale : int
var last_scale : Vector2
func _ready():
if !Engine.is_editor_hint():
escoria.register_object(self)
connect("mouse_entered_hotspot", escoria.inputs_manager, "_on_mouse_entered_hotspot")
connect("mouse_exited_hotspot", escoria.inputs_manager, "_on_mouse_exited_hotspot")
connect("mouse_left_clicked_hotspot", escoria.inputs_manager, "_on_mouse_left_clicked_hotspot")
connect("mouse_right_clicked_hotspot", escoria.inputs_manager, "_on_mouse_right_clicked_hotspot")
connect("mouse_entered", self, "_on_mouse_entered")
connect("mouse_exited", self, "_on_mouse_exited")
connect("input_event", self, "manage_input")
init_interact_position_with_node()
terrain = escoria.room_terrain
update_terrain()
func init_interact_position_with_node():
"""
Initialize the interact_position attribute by searching for a Position2D
node in children nodes.
If any is found, the first one is used as interaction position with this hotspot.
If none is found, we use the CollisionShape2D or CollisionPolygon2D child node's
position instead.
"""
for c in get_children():
if c is Position2D:
# If the position2D node is part of the hotspot, it means it is not an interact position
# but a dialog position for example. Interact position node must be set in the room scene.
if c.get_owner() == self:
continue
interact_positions.default = c.global_position
break
if c is CollisionShape2D or c is CollisionPolygon2D:
interact_positions.default = c.global_position
func manage_input(viewport : Viewport, event : InputEvent, shape_idx : int):
if event is InputEventMouseButton:
# var p = get_global_mouse_position()
if event.doubleclick:
if event.button_index == BUTTON_LEFT:
emit_signal("mouse_double_left_clicked_hotspot", global_id, event)
else:
if event.is_pressed():
if event.button_index == BUTTON_LEFT:
emit_signal("mouse_left_clicked_hotspot", global_id, event)
if event.button_index == BUTTON_RIGHT:
emit_signal("mouse_right_clicked_hotspot", global_id, event)
func _on_mouse_entered():
emit_signal("mouse_entered_hotspot", global_id)
func _on_mouse_exited():
emit_signal("mouse_exited_hotspot")
func get_item_child_if_any():
for c in get_children():
if c is ESCItem:
return c
func update_terrain(on_event_finished_name = null):
if !terrain:
return
if on_event_finished_name != null and on_event_finished_name != "setup":
return
var pos = position
z_index = pos.y if pos.y <= VisualServer.CANVAS_ITEM_Z_MAX else VisualServer.CANVAS_ITEM_Z_MAX
var color
if terrain_is_scalenodes:
last_scale = terrain.get_terrain(pos)
self.scale = last_scale
elif check_maps:
color = terrain.get_terrain(pos)
var scal = terrain.get_scale_range(color.b)
if scal != get_scale():
last_scale = scal
self.scale = last_scale
# Do not flip the entire player character, because that would conflict
# with shadows that expect to be siblings of $"sprite"
if pose_scale == -1 and $"sprite".scale.x > 0:
$"sprite".scale.x *= pose_scale
collision.scale.x *= pose_scale
elif pose_scale == 1 and $"sprite".scale.x < 0:
$"sprite".scale.x *= -1
collision.scale.x *= -1
# if check_maps:
# color = terrain.get_light(pos)
#
# if color:
# for s in sprites:
# s.set_modulate(color)

View File

@@ -0,0 +1,123 @@
tool
extends Sprite
class_name ESCItem
func get_class():
return "ESCItem"
"""
ESCItem is a Sprite that defines an item, potentially interactive
"""
signal mouse_entered_item(global_id)
signal mouse_exited_item
signal mouse_left_clicked_item(global_id)
signal mouse_double_left_clicked_item(global_id)
signal mouse_right_clicked_item(global_id)
export(String) var global_id
export(String, FILE, "*.esc") var esc_script
# If true, the ESC script may have an ":exit_scene" event to manage scene changes
export(bool) var is_exit
export(bool) var is_interactive = true
export(bool) var player_orients_on_arrival = true
export(ESCPlayer.Directions) var interaction_direction
export(String) var tooltip_name
export(String) var default_action
# If action used by player is in the list, game will wait for a second click on another item
# to combine objects together (typical USE <X> WITH <Y>, GIVE <X> TO <Y>)
export(PoolStringArray) var combine_if_action_used_among = []
# If true, combination must be done in the way it is written in ESC script
# ie. :use ON_ITEM
# If false, combination will be tried in the other way.
export(bool) var combine_is_one_way = false
# If use_from_inventory_only is true, then the object must have been picked up before using it.
# A false value is useful for items in the background, such as buttons.
export(bool) var use_from_inventory_only = false
# Scene used in inventory for the object if it is picked up
export(PackedScene) var inventory_item_scene_file : PackedScene
export(Color) var dialog_color = ColorN("white")
# Animation node (null if none was found)
var animation
onready var interact_positions : Dictionary = { "default": null}
# PRIVATE VARS
var area : Area2D
# Size of the item
var size : Vector2
func _ready():
for n in get_children():
if n is AnimationPlayer:
animation = n
continue
if n is Area2D:
area = n
continue
if area:
area.connect("mouse_entered", self, "_on_mouse_entered")
area.connect("mouse_exited", self, "_on_mouse_exited")
area.connect("input_event", self, "manage_input")
init_interact_position_with_node()
if !Engine.is_editor_hint():
escoria.register_object(self)
connect("mouse_entered_item", escoria.inputs_manager, "_on_mouse_entered_item")
connect("mouse_exited_item", escoria.inputs_manager, "_on_mouse_exited_item")
connect("mouse_left_clicked_item", escoria.inputs_manager, "_on_mouse_left_clicked_item")
connect("mouse_double_left_clicked_item", escoria.inputs_manager, "_on_mouse_left_double_clicked_item")
connect("mouse_right_clicked_item", escoria.inputs_manager, "_on_mouse_right_clicked_item")
func get_animation_player():
if animation == null:
for n in get_children():
if n is AnimationPlayer:
animation = n
return animation
"""
Initialize the interact_position attribute by searching for a Position2D
node in children nodes.
If any is found, the first one is used as interaction position with this hotspot.
If none is found, we use the CollisionShape2D or CollisionPolygon2D child node's
position instead.
"""
func init_interact_position_with_node():
for c in get_children():
if c is Position2D:
interact_positions.default = c.global_position
break
if c is CollisionShape2D or c is CollisionPolygon2D:
interact_positions.default = c.global_position
if interact_positions.default == null:
interact_positions.default = self.global_position
func manage_input(viewport : Viewport, event : InputEvent, shape_idx : int):
if event is InputEventMouseButton:
# var p = get_global_mouse_position()
if event.doubleclick:
if event.button_index == BUTTON_LEFT:
emit_signal("mouse_double_left_clicked_item", global_id, event)
else:
if event.is_pressed():
if event.button_index == BUTTON_LEFT:
emit_signal("mouse_left_clicked_item", global_id, event)
if event.button_index == BUTTON_RIGHT:
emit_signal("mouse_right_clicked_item", global_id, event)
func _on_mouse_entered():
emit_signal("mouse_entered_item", global_id)
func _on_mouse_exited():
emit_signal("mouse_exited_item")

View File

@@ -0,0 +1,86 @@
extends Node
const OBJ_DEFAULT_STATE = "default"
## Custom nodes:
#var ESCBackground = preload("res://addons/escoria-core/game/core-scripts/escbackground.gd")
#var ESCCharacter = preload("res://addons/escoria-core/game/core-scripts/esccharacter.gd")
#var ESCHotspot = preload("res://addons/escoria-core/game/core-scripts/eschotspot.gd")
#var ESCItem = preload("res://addons/escoria-core/game/core-scripts/escitem.gd")
#var ESCItemsInventory = preload("res://addons/escoria-core/game/core-scripts/items_inventory.gd")
#var ESCInventoryItem = preload("res://addons/escoria-core/game/core-scripts/inventory_item.gd")
#var ESCPlayer = preload("res://addons/escoria-core/game/core-scripts/escplayer.gd")
#var ESCRoom = preload("res://addons/escoria-core/game/core-scripts/escroom.gd")
#var ESCTerrain = preload("res://addons/escoria-core/game/core-scripts/escterrain.gd")
#var ESCTriggerZone = preload("res://addons/escoria-core/game/core-scripts/esctriggerzone.gd")
enum EVENT_LEVEL_STATE {
RETURN, # 0
YIELD, # 1
BREAK, # 2
REPEAT, # 3
CALL, # 4
JUMP # 5
}
"""
ESCState is a helper class used to read ESC files. Once the ESC file is read and
decoded into ESCEvents and ESCCommands, the ESCState instance is removed.
"""
class ESCState:
var file # File or Dictionary
var line # String, can be null
var indent : int
var line_count : int
func _init(p_file, p_line, p_indent, p_line_count):
file = p_file
line = p_line
indent = p_indent
line_count = p_line_count
func _to_string():
return """ESCState: {
file: """ + file + """,
line: """ + line + """,
indent: """ + indent + """,
line_count: """ + line_count + """
}"""
class ESCEvent:
var ev_name : String
var ev_level : Array
var ev_flags : Array
func _init(p_name, p_level, p_flags):
ev_name = p_name
ev_level = p_level
ev_flags = p_flags
func _to_string():
return """ESCEvent: {
ev_name: """ + ev_name + """,
ev_level: """ + String(ev_level) + """,
ev_flags: """ + String(ev_flags) + """
}"""
class ESCCommand:
var name : String
var params : Array
var conditions : Dictionary
var flags : Array
func _init(p_name):
name = p_name
params = []
func _to_string():
return """ESCCommand: {
name: """ + name + """,
params: """ + String(params) + """,
conditions: """ + String(conditions) + """,
flags: """ + String(flags) + """
}"""

View File

@@ -0,0 +1,401 @@
tool
extends KinematicBody2D
class_name ESCPlayer
func get_class():
return "ESCPlayer"
signal arrived
export var global_id : String
var params_queue : Array
var terrain : ESCTerrain
var camera : ESCCamera
# If the terrain node type is scalenodes
var terrain_is_scalenodes : bool
var check_maps = true
var walk_path : Array = []
var walk_destination : Vector2
var walk_context
var target_object : Object = null
var moved : bool
var path_ofs : float
export(int) var speed : int = 300
export(float) var v_speed_damp : float = 1.0
var orig_speed : float
enum PLAYER_TASKS {
NONE,
WALK,
SLIDE
}
var task # type PLAYER_TASKS
# State machine defining the current interact state of the player
enum INTERACT_STATES {
INTERACT_STARTED, # 
INTERACT_NONE, #
INTERACT_WALKING # Player is walking
}
var interact_status # Current interact status, type INTERACT_STATES
enum Directions {
NORTH = 0, # 0
NORTHEAST = 1, # 1
EAST = 2, # 2
SOUTHEAST = 3, # 3
SOUTH = 4, # 4
SOUTHWEST = 5, # 5
WEST = 6, # 6
NORTHWEST = 7, # 7
TOP = 0,
TOP_RIGHT = 1
RIGHT = 2,
BOTTOM_RIGHT = 3,
BOTTOM = 4,
BOTTOM_LEFT = 5,
LEFT = 6,
TOP_LEFT = 7,
}
var last_deg : int
var last_dir : int
var last_scale : Vector2
var pose_scale : int
export(Script) var animations
# AnimatedSprite node (if any)
var animation_sprite
# AnimationPlayer node (if any)
## NOT USED YET
#var animation
var collision
# Dialogs parameters
export(NodePath) var dialog_position_node
export(Color) var dialog_color = ColorN("white")
# Camera parameters
export(NodePath) var camera_position_node
func _ready():
# Connect the player to the event_done signal, so we can react to a finished
# ":setup" event. In this case, we need to run update_terrain()
escoria.esc_runner.connect("event_done", self, "update_terrain")
# assert(is_angle_in_interval(0, [340,40])) # true
# assert(is_angle_in_interval(359, [340,40])) # true
# assert(is_angle_in_interval(1, [340,40])) # true
# assert(!is_angle_in_interval(90, [340,40])) # false
#
# assert(is_angle_in_interval(90, [70,40])) #true
# assert(!is_angle_in_interval(180, [70,40])) #false
#
# assert(is_angle_in_interval(179, [160, 40])) #true
# assert(is_angle_in_interval(180, [160, 40])) #true
# assert(is_angle_in_interval(181, [160, 40])) #true
# assert(!is_angle_in_interval(0, [160, 40])) #false
#
# assert(is_angle_in_interval(270, [250, 40])) # true
# assert(!is_angle_in_interval(270, [70,40])) #false
for n in get_children():
if n is AnimatedSprite:
animation_sprite = n
# for sprite_child in n.get_children():
# if sprite_child is AnimationPlayer:
# animation = sprite_child
# break
if n is CollisionShape2D or n is CollisionPolygon2D:
collision = n
animation_sprite.connect("animation_finished", self, "anim_finished")
if Engine.is_editor_hint():
return
terrain = escoria.room_terrain
last_scale = scale
set_process(true)
func _process(time):
$debug.text = str(z_index)
if task == PLAYER_TASKS.WALK or task == PLAYER_TASKS.SLIDE:
var pos = get_position()
var old_pos = pos
var next
if walk_path.size() > 1:
next = walk_path[path_ofs + 1]
else:
next = walk_path[path_ofs]
var dist = speed * time * pow(last_scale.x, 2) * terrain.player_speed_multiplier
if walk_context and "fast" in walk_context and walk_context.fast:
dist *= terrain.player_doubleclick_speed_multiplier
var dir = (next - pos).normalized()
# assume that x^2 + y^2 == 1, apply v_speed_damp the y axis
#printt("dir before", dir)
dir = dir * (dir.x * dir.x + dir.y * dir.y * v_speed_damp)
#printt("dir after", dir, dist)
var new_pos
if pos.distance_to(next) < dist:
new_pos = next
path_ofs += 1
else:
new_pos = pos + dir * dist
if path_ofs >= walk_path.size() - 1:
walk_stop(walk_destination)
return
pos = new_pos
var angle = (old_pos.angle_to_point(pos))
set_position(pos)
if task == PLAYER_TASKS.WALK:
last_deg = escoria.utils._get_deg_from_rad(angle)
last_dir = _get_dir_deg(last_deg, animations)
var current_animation = ""
if animation_sprite != null:
current_animation = animation_sprite.animation
# elif animation != null:
# current_animation = animation.current_animation
if current_animation != animations.directions[last_dir][0]:
animation_sprite.play(animations.directions[last_dir][0])
pose_scale = animations.directions[last_dir][1]
update_terrain()
else:
moved = false
set_process(false)
func update_terrain(on_event_finished_name = null):
if !terrain:
return
if on_event_finished_name != null and on_event_finished_name != "setup":
return
var pos = position
z_index = pos.y if pos.y <= VisualServer.CANVAS_ITEM_Z_MAX else VisualServer.CANVAS_ITEM_Z_MAX
var color
if terrain_is_scalenodes:
last_scale = terrain.get_terrain(pos)
self.scale = last_scale
elif check_maps:
color = terrain.get_terrain(pos)
var scal = terrain.get_scale_range(color.b)
if scal != get_scale():
last_scale = scal
self.scale = last_scale
# Do not flip the entire player character, because that would conflict
# with shadows that expect to be siblings of $"sprite"
if pose_scale == -1 and $"sprite".scale.x > 0:
$"sprite".scale.x *= pose_scale
collision.scale.x *= pose_scale
elif pose_scale == 1 and $"sprite".scale.x < 0:
$"sprite".scale.x *= -1
collision.scale.x *= -1
# if check_maps:
# color = terrain.get_light(pos)
#
# if color:
# for s in sprites:
# s.set_modulate(color)
# Sets player angle and plays according animation.
func set_angle(deg):
if deg < 0 or deg > 360:
escoria.report_errors("player.gd:set_angle()", ["Invalid degree to turn to " + str(deg)])
moved = true
last_deg = deg
last_dir = _get_dir_deg(deg, animations)
# The player may have a state animation from before, which would be
# resumed, so we immediately force the correct idle animation
if animation_sprite.animation != animations.idles[last_dir][0]:
animation_sprite.play(animations.idles[last_dir][0])
pose_scale = animations.idles[last_dir][1]
update_terrain()
func teleport(target, angle : Object = null) -> void:
"""
Teleports the player on target position.
target can be Vector2 or Object
"""
if typeof(target) == TYPE_VECTOR2:
printt("Player teleported at position", target, "with angle", angle)
position = target
elif typeof(target) == TYPE_OBJECT:
if target.get("interact_positions") != null:
position = target.interact_positions.default #.global_position
else:
position = target.position
printt("Player teleported at", target.name, "position", position, "with angle", angle)
else:
escoria.report_errors("escplayer.gd", ["target to teleport player to is null or unusable (" + target + ")"])
# PUBLIC FUNCTION
func walk_to(pos : Vector2, p_walk_context = null):
if not terrain:
return walk_stop(get_position())
if interact_status == INTERACT_STATES.INTERACT_WALKING:
return
if interact_status == INTERACT_STATES.INTERACT_STARTED:
interact_status = INTERACT_STATES.INTERACT_WALKING
walk_path = terrain.get_terrain_path(get_position(), pos)
walk_context = p_walk_context
if walk_path.size() == 0:
task = PLAYER_TASKS.NONE
walk_stop(get_position())
set_process(false)
return
moved = true
walk_destination = walk_path[walk_path.size()-1]
if terrain.is_solid(pos):
walk_destination = walk_path[walk_path.size()-1]
path_ofs = 0.0
task = PLAYER_TASKS.WALK
set_process(true)
# PRIVATE FUNCTION
func walk(target_pos, p_speed, context = null):
if p_speed:
orig_speed = speed
speed = p_speed
walk_to(target_pos, context)
# PRIVATE FUNCTION
func walk_stop(pos):
position = pos
interact_status = INTERACT_STATES.INTERACT_NONE
walk_path = []
if orig_speed:
speed = orig_speed
orig_speed = 0.0
task = PLAYER_TASKS.NONE
moved = false
set_process(false)
if params_queue != null && !params_queue.empty():
if animations.dir_angles.size() > 0:
if params_queue[0].interact_angle == -1:
escoria.tools.resolve_angle_to(params_queue[0])
else:
last_dir = _get_dir_deg(params_queue[0].interact_angle, animations)
animation_sprite.play(animations.idles[last_dir][0])
pose_scale = animations.idles[last_dir][1]
update_terrain()
else:
animation_sprite.play(animations.idles[last_dir][0])
pose_scale = animations.idles[last_dir][1]
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "interact", params_queue)
# Clear params queue to prevent the same action from being triggered again
params_queue = []
else:
# If we're heading to an object and reached its interaction position,
# orient towards the defined interaction direction set on the object (if any)
if walk_context.has("target_object") and walk_context.target_object.player_orients_on_arrival \
and escoria.esc_runner.get_interactive(walk_context.target_object.global_id):
var orientation = walk_context["target_object"].interaction_direction
animation_sprite.play(animations.idles[orientation][0])
pose_scale = animations.idles[orientation][1]
else:
animation_sprite.play(animations.idles[last_dir][0])
pose_scale = animations.idles[last_dir][1]
update_terrain()
if walk_context != null:
escoria.esc_level_runner.finished(walk_context)
walk_context = null
emit_signal("arrived")
func anim_finished():
pass
func get_camera_pos():
if camera_position_node and get_node(camera_position_node):
return get_node(camera_position_node).global_position
return global_position
func get_animations_list() -> PoolStringArray:
return animation_sprite.get_sprite_frames().get_animation_names()
func _get_dir(angle : float, animations) -> int:
var deg = escoria.utils._get_deg_from_rad(angle)
return _get_dir_deg(deg, animations)
func _get_dir_deg(deg : int, animations) -> int:
# We turn the angle by -90° because angle_to_point gives the angle against X axis, not Y
deg = wrapi(deg - 90, 0, 360)
var dir = -1
var i = 0
for arr_angle_zone in animations.dir_angles:
if is_angle_in_interval(deg, arr_angle_zone):
dir = i
break
else:
i += 1
continue
# It's an error to have the animations misconfigured
if dir == -1:
escoria.report_errors("player", ["No direction found for " + str(deg)])
return dir
# Returns true if given angle is inside the interval given by a starting_angle and the size.
# @param angle : Angle to test
# @param: interval : Array of size 2, containing the starting angle, and the size of interval
# eg: [90, 40] corresponds to angle between 90° and 130°
func is_angle_in_interval(angle: float, interval : Array) -> bool:
angle = wrapi(angle, 0, 360)
if angle == 0:
angle = 360
var start_angle = wrapi(interval[0], 0, 360)
var angle_area = interval[1]
var end_angle = wrapi(interval[0] + angle_area, 0, 360)
if (angle >= 270 and angle <= 360) or (angle >= 0 and angle <= 90):
if wrapi(angle+180, 0, 360) > wrapi(interval[0]+ 180, 0, 360) \
&& wrapi(angle+180, 0, 360) <= wrapi(interval[0] + angle_area + 180, 0, 360):
return true
else:
if wrapi(angle, 0, 360) > start_angle && wrapi(angle, 0, 360) <= end_angle:
return true
return false

View File

@@ -0,0 +1,27 @@
extends Node2D
class_name ESCRoom
func get_class():
return "ESCRoom"
export(String) var global_id = ""
export(String, FILE, "*.esc") var esc_script = ""
export(PackedScene) var player_scene
export(Rect2) var camera_limits = Rect2()
var player
onready var game = $game
func _ready():
if player_scene:
player = player_scene.instance()
add_child(player)
escoria.register_object(player)
game.get_node("camera").set_target(player)
if has_node("player_start"):
escoria.register_object($player_start)
if global_id.empty():
global_id = name

View File

@@ -0,0 +1,63 @@
tool
extends "res://addons/escoria-core/game/core-scripts/escterrain_base.gd"
class_name ESCTerrain
func get_class():
return "ESCTerrain"
export var scale_min = 0.3
export var scale_max = 1.0
var current_active_navigation_instance : NavigationPolygonInstance
func _ready():
var navigation_enabled_found = false
for n in get_children():
if n is NavigationPolygonInstance:
if n.enabled:
if navigation_enabled_found:
escoria.report_errors("escterrain.gd:_ready()", ["Multiple NavigationPolygonInstances enabled at the same time."])
navigation_enabled_found = true
current_active_navigation_instance = n
if !Engine.is_editor_hint():
escoria.register_object(self)
#path = ImagePathFinder.new()
_update_texture()
func get_scale_range(r):
r = scale_min + (scale_max - scale_min) * r
return Vector2(r, r)
func get_terrain(pos):
if scales == null || scales.get_data().is_empty():
return Color(1, 1, 1, 1)
return get_pixel(pos, scales.get_data())
func get_pixel(pos, p_image):
if pos.x + 1 >= p_image.get_width() || pos.y + 1 >= p_image.get_height() || pos.x < 0 || pos.y < 0:
return Color(1.0, 0.0, 0.0)
# `get_pixel()` is slow; this is accurate enough
# without interpolating neighboring pixels and accounting for fractions
p_image.lock()
var pixel = p_image.get_pixel(pos.x, pos.y)
p_image.unlock()
return pixel
func _draw():
if typeof(texture) == typeof(null):
return
if !Engine.is_editor_hint():
return
if debug_mode == 0:
return
var scale_vect = bitmaps_scale
var src = Rect2(0, 0, texture.get_width(), texture.get_height())
var dst = Rect2(0, 0, texture.get_width() * scale_vect.x, texture.get_height() * scale_vect.y)
draw_texture_rect_region(texture, dst, src)
#draw_texture(texture, Vector2(0, 0))

View File

@@ -0,0 +1,177 @@
tool
extends Navigation2D
export(Texture) var scales setget set_scales,get_scales
export var bitmaps_scale = Vector2(1,1) setget set_bm_scale,get_bm_scale
export(Texture) var lightmap setget set_lightmap,get_lightmap
var lightmap_data
#warning-ignore:unused_class_variable
export var player_speed_multiplier = 1.0 # Override player speed in current scene
#warning-ignore:unused_class_variable
export var player_doubleclick_speed_multiplier = 1.5 # Make the player move faster when doubleclicked
export var lightmap_modulate = Color(1, 1, 1, 1)
export(int, "None", "Scales", "Lightmap") var debug_mode = 1 setget debug_mode_updated
var texture
var img_area
var _texture_dirty = false
func set_bm_scale(p_scale):
bitmaps_scale = p_scale
_update_texture()
func get_bm_scale():
return bitmaps_scale
func set_lightmap(p_lightmap):
var need_init = (lightmap != p_lightmap) or (lightmap and not lightmap_data)
lightmap = p_lightmap
# It's bad enough a new copy is created when reading a pixel, we don't
# also need to get the data for every read to make yet another copy
if need_init:
if lightmap_data:
lightmap_data.unlock()
lightmap_data = lightmap.get_data()
lightmap_data.lock()
_update_texture()
func get_lightmap():
return lightmap
func set_scales(p_scales):
scales = p_scales
_update_texture()
func get_scales():
return scales
func debug_mode_updated(p_mode):
debug_mode = p_mode
_update_texture()
func _update_texture():
if _texture_dirty:
return
_texture_dirty = true
call_deferred("_do_update_texture")
func _do_update_texture():
_texture_dirty = false
if !is_inside_tree():
return
if !Engine.is_editor_hint():
return
if debug_mode == 0:
update()
return
texture = ImageTexture.new()
if debug_mode == 1:
if scales != null:
#texture.create_from_image(scales)
texture = scales
else:
if lightmap != null:
#texture.create_from_image(lightmap)
texture = lightmap
update()
func make_local(pos):
pos = pos - get_position()
pos = pos * 1.0 / get_scale()
pos = get_closest_point(pos)
return pos
func make_global(pos):
pos = pos * get_scale()
pos = pos + get_position()
return pos
func get_terrain_path(p_src, p_dest):
# printt("get path ", p_src, p_dest)
p_src = make_local(p_src)
p_dest = make_local(p_dest)
var r_path = get_simple_path(p_src, p_dest, true)
r_path = Array(r_path)
for i in range(0, r_path.size()):
r_path[i] = make_global(r_path[i])
return r_path
func is_solid(pos):
pos = pos - get_position()
pos = pos * 1.0 / get_scale()
var closest = get_closest_point(pos)
return pos == closest
func _color_mul(a, b):
var c = Color()
c.r = a.r * b.r
c.g = a.g * b.g
c.b = a.b * b.b
c.a = a.a * b.a
return c
func get_light(pos):
if not lightmap or lightmap.get_data().is_empty():
return
return _color_mul(get_pixel(pos, lightmap_data), lightmap_modulate)
func get_pixel(pos, p_image):
p_image.lock()
pos = make_local(pos)
pos = pos * 1.0 / bitmaps_scale
if pos.x + 1 >= p_image.get_width() || pos.y + 1 >= p_image.get_height() || pos.x < 0 || pos.y < 0:
return Color()
var ll = p_image.get_pixel(pos.x, pos.y)
var ndif = Vector2()
ndif.x = pos.x - floor(pos.x)
ndif.y = pos.y - floor(pos.y)
var ur
img_area = Rect2(0, 0, p_image.get_width(), p_image.get_height())
var lr = ll
if ndif.x > 0 && img_area.has_point(Vector2(pos.x+1, pos.y)):
lr = p_image.get_pixel(pos.x+1, pos.y)
#if lr.a < 128:
# lr = ll
ur = lr
var ul = ll
if ndif.y > 0 && img_area.has_point(Vector2(pos.x, pos.y+1)):
ul = p_image.get_pixel(pos.x, pos.y+1)
#if ul.a < 128:
# ul = ll
ur = ul
if ndif.x > 0 && ndif.y > 0 && img_area.has_point(Vector2(pos.x+1, pos.y+1)):
var pix = p_image.get_pixel(pos.x+1, pos.y+1)
#if pix.a > 128:
ur = pix
var bottom = ll.linear_interpolate(lr, ndif.x)
var top
if ur != null:
top = ul.linear_interpolate(ur, ndif.x)
else:
top = ul
var final = bottom.linear_interpolate(top, ndif.y)
p_image.unlock()
return final

View File

@@ -0,0 +1,92 @@
tool
extends "terrain_base.gd"
const DIST_EPSILON = 0.000001
var scale_nodes = []
onready var scale_min = $"scale_min"
onready var scale_max = $"scale_max"
func debug_mode_updated(p_mode):
debug_mode = p_mode
._update_texture()
func _do_update_texture():
_texture_dirty = false
if !is_inside_tree():
return
if !Engine.is_editor_hint():
return
if debug_mode == 0:
update()
return
texture = ImageTexture.new()
if lightmap != null:
#texture.create_from_image(lightmap)
texture = lightmap
update()
static func sort_by_y(a, b):
return a.global_position.y < b.global_position.y
# Return a "scale range" immediately based on the interpolated scale size
func get_terrain(pos):
# printt("Called", pos)
var prev
var next
var prev_target
var node_target
for i in range(1, scale_nodes.size()):
prev = scale_nodes[i - 1]
next = scale_nodes[i]
if prev.global_position.y < pos.y and pos.y < next.global_position.y:
# printt("1:", prev.global_position.y, " < ", pos.y, " and ", pos.y, " < ", next.global_position.y)
prev_target = prev.target_scale.y
node_target = next.target_scale.y
break
var nodes_dist = next.global_position.y - prev.global_position.y
if nodes_dist < DIST_EPSILON:
nodes_dist = DIST_EPSILON
var interp_dist = (pos.y - prev.global_position.y) / nodes_dist
var y_1 = Vector2(0, prev_target)
var y_2 = Vector2(0, node_target)
var interp = y_1.linear_interpolate(y_2, interp_dist)
return Vector2(interp.y, interp.y)
func get_pixel(pos, p_image):
if pos.x + 1 >= p_image.get_width() || pos.y + 1 >= p_image.get_height() || pos.x < 0 || pos.y < 0:
return Color(1.0, 0.0, 0.0)
# `get_pixel()` is slow; this is accurate enough
# without interpolating neighboring pixels and accounting for fractions
return p_image.get_pixel(pos.x, pos.y)
func _draw():
if not texture:
return
if debug_mode == 0:
return
draw_texture(texture, Vector2(0, 0))
func _ready():
for c in get_children():
if c is preload("scalenode.gd"):
scale_nodes.push_back(c)
scale_nodes.sort_custom(self, "sort_by_y")
scale_nodes.push_front(scale_min)
scale_nodes.push_back(scale_max)

View File

@@ -0,0 +1,31 @@
tool
extends Area2D
class_name ESCTriggerZone
signal left_click_on_trigger
signal left_dblclick_on_trigger
signal right_click_on_trigger
signal mouse_enter_trigger
signal mouse_exit_trigger
func mouse_enter():
emit_signal("mouse_enter_trigger", self)
func mouse_exit():
emit_signal("mouse_exit_trigger", self)
func body_entered(body):
# if body is esc_type.PLAYER:
# if self.visible:
# run_event("enter")
pass
func body_exited(body):
# if body is esc_type.PLAYER:
# if self.visible:
# run_event("exit")
pass

View File

@@ -0,0 +1,41 @@
extends TextureButton
class_name ESCInventoryItem
func get_class():
return "ESCInventoryItem"
export(String) var global_id
#export(String, FILE, "*.esc") var esc_script
signal mouse_left_inventory_item(item_id)
signal mouse_right_inventory_item(item_id)
signal mouse_double_left_inventory_item(item_id)
signal inventory_item_focused(item_id)
signal inventory_item_unfocused()
func _ready():
connect("gui_input", self, "_on_inventory_item_gui_input")
connect("mouse_entered", self, "_on_inventory_item_mouse_enter")
connect("mouse_exited", self, "_on_inventory_item_mouse_exit")
func _on_inventory_item_gui_input(event : InputEvent):
if event is InputEventMouseButton:
# var p = get_global_mouse_position()
if event.doubleclick:
if event.button_index == BUTTON_LEFT:
emit_signal("mouse_double_left_inventory_item", global_id, event)
else:
if event.is_pressed():
if event.button_index == BUTTON_LEFT:
emit_signal("mouse_left_inventory_item", global_id, event)
if event.button_index == BUTTON_RIGHT:
emit_signal("mouse_right_inventory_item", global_id, event)
func _on_inventory_item_mouse_enter():
# Notify UI that item is focused (room.game.ui.Label UI)
emit_signal("inventory_item_focused", global_id)
func _on_inventory_item_mouse_exit():
# Notify UI that item is unfocused (room.game.ui.Label UI)
emit_signal("inventory_item_unfocused")

View File

@@ -0,0 +1,9 @@
extends Node
func get_inventory_item(item_id : String) -> ESCInventoryItem:
for c in get_children():
if c.global_id == item_id:
if c.inventory_item_scene_file:
return c.inventory_item_scene_file.instance()
return null

View File

@@ -0,0 +1,9 @@
func warning(string : String):
printerr("(W)\t" + string)
func info(string : String):
print("(I)\t" + string)
func error(string : String):
printerr("(E)\t" + string)

View File

@@ -0,0 +1,69 @@
extends Sprite
signal left_click_on_bg
signal right_click_on_bg # Connect this in your game/signal_script
export var action = "walk"
var area
# Godot doesn't do doubleclicks so we must
var last_lmb_dt = 0
var waiting_dblclick = null # null or [pos, event]
func input(_viewport, event, _shape_idx):
if event is InputEventMouseButton and event.pressed:
# If we are hovering items, do not allow background to receive a click
# and let the items sort out who's on top and gets to be `clicked`
if vm.hover_stack:
return
if event.is_action("game_general"):
last_lmb_dt = 0
waiting_dblclick = [get_global_mouse_position(), event]
elif event.is_action("game_rmb"):
emit_signal("right_click_on_bg", self, get_global_mouse_position(), event)
func get_action():
return action
func _physics_process(dt):
last_lmb_dt += dt
if waiting_dblclick and last_lmb_dt > vm.DOUBLECLICK_TIMEOUT:
emit_signal("left_click_on_bg", self, waiting_dblclick[0], waiting_dblclick[1])
last_lmb_dt = 0
waiting_dblclick = null
func _enter_tree():
# Use size of background texture to calculate collision shape
var size = get_texture().get_size()
area = Area2D.new()
var shape = RectangleShape2D.new()
var sid = area.create_shape_owner(area)
# Move origin of Area2D to center of Sprite
var transform = area.shape_owner_get_transform(sid)
transform.origin = size / 2
area.shape_owner_set_transform(sid, transform)
# Set extents of RectangleShape2D to cover entire Sprite
shape.set_extents(size / 2)
area.shape_owner_add_shape(sid, shape)
add_child(area)
func _ready():
var conn_err
conn_err = area.connect("input_event", self, "input")
if conn_err:
vm.report_errors("item", ["area.input_event -> input error: " + String(conn_err)])
conn_err = connect("left_click_on_bg", $"/root/scene/game", "ev_left_click_on_bg")
if conn_err:
vm.report_errors("item", ["left_click_on_bg -> ev_left_click_on_bg error: " + String(conn_err)])
add_to_group("background")

View File

@@ -0,0 +1,5 @@
extends Position2D
#warning-ignore:unused_class_variable
export(Vector2) var target_scale = Vector2(1.0, 1.0)

View File

@@ -0,0 +1,191 @@
var thread : Thread
var mutex : Mutex
var sem : Semaphore
#warning-ignore:unused_class_variable
var time_max = 100 # msec
var queue : Array = []
var pending : Dictionary = {}
signal resource_loading_progress(path, progress)
signal resource_loading_done(path)
signal resource_queue_progress(queue_size)
#warning-ignore:unused_argument
func _lock(caller):
mutex.lock()
#warning-ignore:unused_argument
func _unlock(caller):
mutex.unlock()
#warning-ignore:unused_argument
func _post(caller):
sem.post()
#warning-ignore:unused_argument
func _wait(caller):
sem.wait()
func queue_resource(path : String, p_in_front : bool = false, p_permanent : bool = false):
_lock("queue_resource")
if path in pending:
_unlock("queue_resource")
return
elif ResourceLoader.has(path):
var res = ResourceLoader.load(path)
pending[path] = { "res": res, "permanent": p_permanent }
_unlock("queue_resource")
return
else:
var res = ResourceLoader.load_interactive(path)
res.set_meta("path", path)
if p_in_front:
queue.insert(0, res)
else:
queue.push_back(res)
pending[path] = { "res": res, "permanent": p_permanent }
_post("queue_resource")
_unlock("queue_resource")
return
func cancel_resource(path):
_lock("cancel_resource")
if path in pending:
if pending[path].res is ResourceInteractiveLoader:
queue.erase(pending[path].res)
pending.erase(path)
_unlock("cancel_resource")
func clear():
_lock("clear")
for p in pending.keys():
if pending[p].permanent:
continue
cancel_resource(p)
#queue = []
#pending = {}
_unlock("clear")
func get_progress(path):
_lock("get_progress")
var ret = -1
if path in pending:
if pending[path].res is ResourceInteractiveLoader:
ret = float(pending[path].res.get_stage()) / float(pending[path].res.get_stage_count())
else:
ret = 1.0
emit_signal("resource_loading_done", path)
emit_signal("resource_loading_progress", path, ret)
_unlock("get_progress")
return ret
func is_ready(path):
var ret
_lock("is_ready")
if path in pending:
ret = !(pending[path].res is ResourceInteractiveLoader)
else:
ret = false
_unlock("is_ready")
return ret
func _wait_for_resource(res, path):
_unlock("wait_for_resource")
while true:
#VisualServer.call("sync") # workaround because sync is a keyword
VisualServer.force_sync()
OS.delay_usec(16000) # wait 1 frame
_lock("wait_for_resource")
if queue.size() == 0 || queue[0] != res:
return pending[path].res
_unlock("wait_for_resource")
func get_resource(path):
_lock("get_resource")
if path in pending:
if pending[path].res is ResourceInteractiveLoader:
var res = pending[path].res
if res != queue[0]:
var pos = queue.find(res)
queue.remove(pos)
queue.insert(0, res)
res = _wait_for_resource(res, path)
if !pending[path].permanent:
pending.erase(path)
_unlock("return")
return res
else:
var res = pending[path].res
if !pending[path].permanent:
pending.erase(path)
_unlock("return")
return res
else:
_unlock("return")
return ResourceLoader.load(path)
func thread_process():
_wait("thread_process")
_lock("process")
while queue.size() > 0:
var res = queue[0]
_unlock("process_poll")
var ret = res.poll()
_lock("process_check_queue")
var path = res.get_meta("path")
if ret == ERR_FILE_EOF || ret != OK:
printt("finished loading ", path)
if path in pending: # else it was already retrieved
pending[res.get_meta("path")].res = res.get_resource()
queue.erase(res) # something might have been put at the front of the queue while we polled, so use erase instead of remove
emit_signal("resource_queue_progress", queue.size())
get_progress(path)
_unlock("process")
#warning-ignore:unused_argument
func thread_func(u):
while true:
thread_process()
func print_progress(p_path, p_progress):
printt(p_path, "loading", round(p_progress * 100), "%")
func res_loaded(p_path):
printt("loaded resource", p_path)
func print_queue_progress(p_queue_size):
printt("queue size:", p_queue_size)
func start():
mutex = Mutex.new()
sem = Semaphore.new()
thread = Thread.new()
thread.start(self, "thread_func", 0)
## Uncomment these for debug, or wait for someone to implement log levels
# connect("resource_loading_progress", self, "print_progress")
# connect("resource_loading_done", self, "res_loaded")
# connect("resource_queue_progress", self, "print_queue_progress")

View File

@@ -0,0 +1,202 @@
const DATA_STRING = 0
const DATA_STRING_ARRAY = 1
const DATA_VARIANT = 2
var base = "user://esc_saves"
var slots = {}
var max_slots = 3
var settings
func save_settings(p_data, p_callback):
var f = File.new()
f.open("user://settings.bin", File.WRITE)
f.store_var(p_data)
f.close()
if typeof(p_callback) != typeof(null):
p_callback[0].call_deferred(p_callback[1], OK)
return OK
func load_settings(p_callback):
var f = File.new()
f.open("user://settings.bin", File.READ)
if !f.is_open():
if typeof(p_callback) != typeof(null):
p_callback[0].call_deferred(p_callback[1], null)
return FAILED
settings = f.get_var()
f.close()
if typeof(p_callback) != typeof(null):
p_callback[0].call_deferred(p_callback[1], settings)
return OK
func _get_fname(p_slot):
var date = OS.get_date()
var time = OS.get_time()
var day = str(date.day)
if date.day < 10:
day = "0"+day
var hour = str(time.hour)
if time.hour < 10:
hour = "0"+hour
var minute = str(time.minute)
if time.minute < 10:
minute = "0"+minute
var second = str(time.second)
if time.second < 10:
second = "0"+second
var fname = str(p_slot) + "-"
fname = fname + day + "-" + str(date.month) + "-" + str(date.year) + " " + hour+"."+minute+"."+second+".esc"
return fname
func save_game(p_data, p_slot, p_callback):
if p_slot < 0 || p_slot >= max_slots:
return FAILED
var fname = _get_fname(p_slot)
var ret = _do_save(base + "/" + fname, p_data)
if ret != OK:
if typeof(p_callback) != typeof(null):
p_callback[0].call_deferred(p_callback[1], FAILED)
return FAILED
if p_slot in slots:
var old_fname = slots[p_slot].fname
var d = Directory.new()
d.open(base)
d.remove(old_fname)
if typeof(p_callback) != typeof(null):
p_callback[0].call_deferred(p_callback[1], OK)
return OK
func _do_save(fname, p_data):
var f = File.new()
var ret = f.open(fname, File.WRITE)
if ret or not f.is_open():
print("Unable to open file for save ", fname)
return FAILED
if typeof(p_data) == typeof([]):
for s in p_data:
f.store_string(s)
else:
f.store_string(p_data)
f.close()
printt("Saved game to " + fname)
return OK
func load_slot(p_slot, p_callback):
if p_callback == null:
return FAILED
if !(p_slot in slots):
return FAILED
var data = _do_load(slots[p_slot].fname)
if !data:
return FAILED
p_callback[0].call_deferred(p_callback[1], data)
return OK
func load_autosave(p_callback):
if p_callback == null:
return FAILED
var data = _do_load("user://quick_save.esc")
if data == null:
return FAILED
p_callback[0].call_deferred(p_callback[1], data)
return OK
func _do_load(fname):
var f = File.new()
if !f.file_exists(fname):
return null
f.open(fname, File.READ)
var data = f.get_as_text()
f.close()
return data
func autosave(p_data, p_callback):
var err = _do_save("user://quick_save.esc", p_data)
if typeof(p_callback) != typeof(null):
p_callback[0].call_deferred(p_callback[1], err)
return err
func get_slots_available(p_callback):
if p_callback == null:
return FAILED
var d = Directory.new()
d.open("user://")
if !d.dir_exists(base):
d.make_dir(base)
d.open(base)
d.list_dir_begin()
var f = d.get_next()
while f != "":
if f.find(".esc") < 0 || f.find("-") < 0:
f = d.get_next()
continue
var sep = f.find("-")
var n = int(f.substr(0, sep))
if n >= max_slots:
f = d.get_next()
continue
var t = f.replace(".esc", "")
t = t.substr(2, t.length()-2)
var l = t.split(" ")
var h = l[1]
var date = l[0]
slots[n] = { "n": n, "fname": base + "/" + f, "date": date, "hour": h }
f = d.get_next()
d.list_dir_end()
p_callback[0].call_deferred(p_callback[1], slots)
return OK
func autosave_available():
var f = File.new()
return f.file_exists("user://quick_save.esc")
func start():
pass

View File

@@ -0,0 +1,4 @@
extends Position2D
#warning-ignore:unused_class_variable
export(Vector2) var target_scale = Vector2(1.0, 1.0)

View File

@@ -0,0 +1,10 @@
# Helpers to deal with player's and items' angles
func _get_deg_from_rad(rad_angle : float):
var deg = rad2deg(rad_angle)
if deg >= 360.0:
deg = clamp(deg, 0.0, 360.0)
if deg == 360.0:
deg = 0.0
return deg

View File

@@ -0,0 +1,249 @@
extends Node
# Scripts
onready var esc_compiler = $esc_compiler
onready var logger = load("res://addons/escoria-core/game/core-scripts/log/logging.gd").new()
onready var main = $main
onready var esc_runner = $esc_runner
onready var esc_level_runner = $esc_runner/esc_level_runner
onready var inputs_manager = $inputs_manager
onready var utils = load("res://addons/escoria-core/game/core-scripts/utils/utils.gd").new()
# INSTANCES
var main_menu_instance
## Dialog player instantiator. This instance is called directly for dialogs.
var dialog_player
## Inventory scene
var inventory
# Game variables
var room_terrain
enum GAME_STATE {
DEFAULT,
DIALOG,
WAIT
}
onready var current_state = GAME_STATE.DEFAULT
##################################################################################
func _ready():
pass
# Called by Main menu "start new game"
func new_game():
var actions = esc_compiler.load_esc_file(ProjectSettings.get_setting("escoria/main/game_start_script"))
$esc_runner.run_game(actions)
func change_scene_path(scene_path):
var scene = load(scene_path).instance()
get_tree().get_root().call_deferred("add_child", scene)
return scene
func set_main_menu(scene):
main_menu_instance = scene
func report_warnings(p_path : String, warnings : Array) -> void:
var text = "Warnings in file "+p_path+"\n"
for w in warnings:
if w is Array:
text += str(w)+"\n"
else:
text += w+"\n"
printerr("warning is: ", text)
func report_errors(p_path : String, errors : Array) -> void:
var text = "Errors in file "+p_path+"\n"
for e in errors:
if e is Array:
text += str(e)+"\n"
else:
text += e+"\n"
printerr("error is: ", text)
if ProjectSettings.get_setting("escoria/debug/terminate_on_errors"):
print_stack()
assert(false)
# If your game stopped here, you may want to look at the Output tab and check for
# the error that caused the game to stop.
"""
Add object to the environement.
"""
func register_object(object : Object):
var object_id
if object.get("global_id"):
object_id = object.global_id
else:
object_id = object.name
if object is ESCDialogsPlayer:
dialog_player = object
if object is ESCPlayer:
$esc_runner.register_object(object_id, object, true)
if object is ESCHotspot or object is Position2D:
$esc_runner.register_object(object_id, object, true)
if object is ESCItem:
$esc_runner.register_object(object_id, object, true)
if object is ESCTerrain:
room_terrain = object
if object is ESCCamera:
$esc_runner.register_object(object_id, object, true)
if object is ESCInventory:
inventory = object
"""
Generic action function that runs an action on an element of the room (eg player walk)
action: type of the action ()
"""
func do(action : String, params : Array = []) -> void:
if current_state == GAME_STATE.DEFAULT:
match action:
"walk":
# Reset current action
esc_runner.set_current_action("")
# Walk to position2D
if params[1] is Vector2:
var target_position = params[1]
var is_fast : bool = false
if params.size() > 2 and params[2] == true:
is_fast = true
var walk_context = {"fast": is_fast}
main.current_scene.player.walk_to(target_position, walk_context)
# Walk to object from its id
elif params[1] is String:
var object = escoria.esc_runner.get_object(params[1])
if object:
var target_position : Vector2 = object.interact_position
if params[0] == main.current_scene.player.global_id:
var is_fast : bool = false
if params.size() > 2 and params[2] == true:
is_fast = true
var walk_context = {"fast": is_fast, "target_object" : object}
main.current_scene.player.walk_to(target_position, walk_context)
else:
report_errors("escoria.gd: do() > walk", ["TODO: code NPC walking"])
"hotspot_left_click", "item_left_click":
if params[0] is String:
printt("escoria.do : item_left_click on item ", params[0])
# call : ev_left_click_on_item()
ev_left_click_on_item($esc_runner.get_object(params[0]), params[1])
"hotspot_right_click", "item_right_click":
if params[0] is String:
printt("escoria.do : item_right_click on item ", params[0])
# call : ev_left_click_on_item()
ev_left_click_on_item($esc_runner.get_object(params[0]), params[1], true)
_:
# $esc_runner.activate(action, params[0])
report_warnings("escoria.gd:do()", ["Action received:", action, "with params ", params])
elif current_state == GAME_STATE.DIALOG:
dialog_player.finish_fast()
elif current_state == GAME_STATE.WAIT:
pass
# PRIVATE
func ev_left_click_on_item(obj, event, default_action = false):
"""
Event occurring when an object/item is left clicked
obj : object that was left clicked
event :
"""
if obj is String:
obj = esc_runner.objects[obj]
printt(obj.global_id, "left-clicked with", event)
var need_combine = false
# Check if current_action and current_tool are already set
if esc_runner.current_action:
if esc_runner.current_tool:
if esc_runner.current_action in esc_runner.current_tool.combine_if_action_used_among:
need_combine = true
else:
esc_runner.current_tool = obj
else:
if default_action:
esc_runner.current_action = obj.default_action
elif esc_runner.current_action in obj.combine_if_action_used_among:
esc_runner.current_tool = obj
var action = "walk"
# Don't interact after player movement towards object (because object is inactive for example)
var dont_interact = false
var destination_position : Vector2 = main.current_scene.player.global_position
# Create walk context
var walk_context = {"fast": event.doubleclick, "target_object" : obj}
# If object not in inventory, player walks towards it
if !esc_runner.inventory_has(obj.global_id):
var clicked_object_has_interact_position = false
if esc_runner.get_interactive(obj.global_id):
if obj.interact_positions.default != null:
destination_position = obj.interact_positions.default#.global_position
clicked_object_has_interact_position = true
else:
destination_position = obj.position
else:
destination_position = event.position
dont_interact = true
main.current_scene.player.walk_to(destination_position, walk_context)
# Wait for the player to arrive before continuing with action.
yield(main.current_scene.player, "arrived")
# If no interaction should happen after player has arrived, leave immediately.
if dont_interact:
return
var player_global_pos = main.current_scene.player.global_position
var clicked_position = event.position
# If player has arrived at the position he was supposed to reach so he can interact
if player_global_pos == destination_position:
# Manage exits
if obj.is_exit and $esc_runner.current_action == "" or $esc_runner.current_action == "walk":
var params = [obj]
$esc_runner.activate("exit_scene", params)
else:
# Manage movements towards object before activating it
if $esc_runner.current_action == "" or $esc_runner.current_action == "walk":
if destination_position != clicked_position \
and !esc_runner.inventory_has(obj.global_id):
esc_runner.activate("arrived", [obj])
# Manage action on object
elif $esc_runner.current_action != "" and $esc_runner.current_action != "walk":
# If apply_interact, perform combine between items
if need_combine:
esc_runner.activate(esc_runner.current_action, [esc_runner.current_tool, obj])
else:
esc_runner.activate(esc_runner.current_action, [obj])
else:
# escoria.fallback("")
pass

View File

@@ -0,0 +1,30 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/esc_runner.gd" type="Script" id=1]
[ext_resource path="res://addons/escoria-core/game/main.tscn" type="PackedScene" id=2]
[ext_resource path="res://addons/escoria-core/game/escoria.gd" type="Script" id=3]
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd" type="Script" id=4]
[ext_resource path="res://addons/escoria-core/game/inputs_manager.gd" type="Script" id=5]
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/esc_compiler.gd" type="Script" id=6]
[node name="escoria" type="Node"]
script = ExtResource( 3 )
[node name="inputs_manager" type="Node" parent="."]
script = ExtResource( 5 )
[node name="esc_compiler" type="Node" parent="."]
script = ExtResource( 6 )
[node name="esc_runner" type="Node" parent="."]
script = ExtResource( 1 )
[node name="esc_level_runner" type="Node" parent="esc_runner"]
script = ExtResource( 4 )
[node name="main" parent="." instance=ExtResource( 2 )]
[node name="Viewport" type="Viewport" parent="."]
usage = 0
[editable path="main"]

View File

@@ -0,0 +1,101 @@
tool
extends Node
var is_hotspot_focused : bool
func _ready():
set_process_input(true)
func _input(event):
if event.is_action_pressed("esc_show_debug_prompt"):
escoria.main.get_node("layers/debug_layer/esc_prompt_popup").popup()
###################################################################################
func _on_left_click_on_bg(position : Vector2):
if !is_hotspot_focused:
printt("Left click on background at ", str(position))
escoria.main.current_scene.game.left_click_on_bg(position)
func _on_double_left_click_on_bg(position : Vector2):
if !is_hotspot_focused:
printt("Double left click on background at ", str(position))
escoria.main.current_scene.game.left_double_click_on_bg(position)
func _on_right_click_on_bg(position : Vector2):
if !is_hotspot_focused:
printt("Right click on background at ", str(position))
escoria.main.current_scene.game.right_click_on_bg(position)
##################################################################################
func _on_mouse_entered_hotspot(hotspot_global_id : String) -> void:
printt("Hotspot focused : ", hotspot_global_id)
is_hotspot_focused = true
escoria.main.current_scene.game.element_focused(hotspot_global_id)
func _on_mouse_exited_hotspot() -> void:
print("Hotspot unfocused")
is_hotspot_focused = false
escoria.main.current_scene.game.element_unfocused()
func _on_mouse_left_clicked_hotspot(hotspot_global_id : String, event : InputEvent) -> void:
printt("Hotspot left clicked", hotspot_global_id, event)
escoria.main.current_scene.game.left_click_on_hotspot(hotspot_global_id, event)
func _on_mouse_right_clicked_hotspot(hotspot_global_id : String, event : InputEvent) -> void:
printt("Hotspot right clicked", hotspot_global_id, event)
escoria.main.current_scene.game.right_click_on_hotspot(hotspot_global_id, event)
func _on_mouse_left_double_clicked_hotspot(hotspot_global_id : String, event : InputEvent) -> void:
printt("Hotspot right clicked", hotspot_global_id, event)
escoria.main.current_scene.game.left_double_click_on_hotspot(hotspot_global_id, event)
##################################################################################
func _on_mouse_left_click_inventory_item(inventory_item_global_id, event : InputEvent) -> void:
printt("Inventory item left clicked ", inventory_item_global_id)
escoria.main.current_scene.game.left_click_on_inventory_item(inventory_item_global_id, event)
func _on_mouse_right_click_inventory_item(inventory_item_global_id, event : InputEvent) -> void:
printt("Inventory item right clicked ", inventory_item_global_id)
escoria.main.current_scene.game.right_click_on_inventory_item(inventory_item_global_id, event)
func _on_mouse_double_left_click_inventory_item(inventory_item_global_id, event : InputEvent) -> void:
printt("Inventory item double left clicked ", inventory_item_global_id)
escoria.main.current_scene.game.double_left_click_on_inventory_item(inventory_item_global_id, event)
func _on_mouse_entered_inventory_item(inventory_item_global_id) -> void:
printt("Inventory item focused ", inventory_item_global_id)
escoria.main.current_scene.game.inventory_item_focused(inventory_item_global_id)
func _on_mouse_exited_inventory_item() -> void:
printt("Inventory item unfocused")
escoria.main.current_scene.game.inventory_item_unfocused()
##################################################################################
func _on_mouse_entered_item(item_global_id : String) -> void:
printt("Item focused : ", item_global_id)
is_hotspot_focused = true
escoria.main.current_scene.game.element_focused(item_global_id)
func _on_mouse_exited_item() -> void:
print("Item unfocused")
is_hotspot_focused = false
escoria.main.current_scene.game.element_unfocused()
func _on_mouse_left_clicked_item(item_global_id : String, event : InputEvent) -> void:
printt("Item left clicked", item_global_id, event)
escoria.main.current_scene.game.left_click_on_item(item_global_id, event)
func _on_mouse_left_double_clicked_item(item_global_id : String, event : InputEvent) -> void:
printt("Item left double clicked", item_global_id, event)
escoria.main.current_scene.game.left_double_click_on_item(item_global_id, event)
func _on_mouse_right_clicked_item(item_global_id : String, event : InputEvent) -> void:
printt("Item right clicked", item_global_id, event)
escoria.main.current_scene.game.right_click_on_item(item_global_id, event)

View File

@@ -0,0 +1,158 @@
extends Node
# This script is basically the scene-switcher.
# Global id of the last scene the player was before current scene
var last_scene_global_id
# Current scene room being displayed
var current_scene
var wait_level
var screen_ofs = Vector2(0, 0)
func _ready():
$layers/wait_timer.connect("timeout", self, "_on_wait_finished")
func set_scene(p_scene, run_events=true):
"""
Sets p_scene as current scene
If run_events=true, plays the events defined in :setup event
"""
if !p_scene:
escoria.report_errors("main", ["Trying to set empty scene"])
# Ensure we don't have a regular event running when changing scenes
if escoria.esc_runner.running_event:
assert(escoria.esc_runner.running_event.ev_name == "load")
if "esc_script" in p_scene and p_scene.esc_script and run_events:
var events = escoria.esc_compiler.load_esc_file(p_scene.esc_script)
# :setup is pretty much required in the code, but fortunately
# we can help out with cases where one isn't necessary otherwise
if not "setup" in events:
var fake_setup = escoria.esc_compiler.compile_str(":setup\n")
events["setup"] = fake_setup["setup"]
escoria.esc_runner.run_event(events["setup"])
# If scene was never visited, run "ready" event
if !escoria.esc_runner.scenes_cache.has(p_scene.global_id) \
and "ready" in events:
escoria.esc_runner.run_event(events["ready"])
if current_scene != null:
clear_scene()
# var game_scene =
get_node("/root").add_child(p_scene)
set_current_scene(p_scene, run_events)
set_camera_limits()
func set_current_scene(p_scene, run_events=true):
current_scene = p_scene
$"/root".move_child(current_scene, 0)
# Loading a save game must set the scene but not run events
if "events_path" in current_scene and current_scene.events_path and run_events:
if escoria.esc_runner.game:
# Having a game with `:setup` means we must wait for it to finish
if "setup" in escoria.esc_runner.game:
if not escoria.esc_runner.running_event:
escoria.report_errors("main.gd:set_current_scene()", ["escoria.esc_runner.game has setup but no running_event"])
if escoria.esc_runner.running_event.ev_name != "setup":
escoria.report_errors("main.gd:set_current_scene()", ["escoria.esc_runner.game has setup but it is not running: " + escoria.esc_runner.running_event.ev_name])
yield(escoria.esc_runner, "event_done")
else:
escoria.esc_compiler.load_file(current_scene.events_path)
# For a new game, we must run `:setup` if available
# and wait for it to finish
if "setup" in escoria.esc_runner.game:
escoria.esc_runner.run_event(escoria.esc_runner.game["setup"])
yield(escoria.esc_runner, "event_done")
# Because 1) changing a scene and 2) having a scene become ready
# both call `set_current_scene`, we don't want to duplicate thing
if not escoria.esc_runner.running_event:
escoria.esc_runner.run_game()
escoria.esc_runner.register_object("_scene", p_scene, true) # Force overwrite of global
func clear_scene():
if current_scene == null:
return
escoria.esc_runner.clear_current_action()
escoria.esc_runner.clear_current_tool()
# escoria.esc_runner.hover_clear_stack()
# escoria.clear_inventory()
last_scene_global_id = current_scene.global_id
escoria.esc_runner.set_global("ESC_LAST_SCENE", last_scene_global_id, true)
get_node("/root").remove_child(current_scene)
current_scene.free()
current_scene = null
func wait(params : Array, level):
wait_level = level
$layers/wait_timer.set_wait_time(float(params[0]))
$layers/wait_timer.set_one_shot(true)
$layers/wait_timer.start()
func _on_wait_finished():
escoria.esc_level_runner.finished(wait_level)
func set_camera_limits():
var limits = {}
var scene_camera_limits = current_scene.camera_limits
if scene_camera_limits.size.x == 0 and scene_camera_limits.size.y == 0:
var area = Rect2()
for child in current_scene.get_children():
if child is ESCBackground:
var pos = child.get_global_position()
var size : Vector2
if child.get_texture():
size = child.get_texture().get_size()
else:
size = child.rect_size
if child.rect_scale.x != 1 or child.rect_scale.y != 1:
size.x *= child.rect_scale.x
size.y *= child.rect_scale.y
area = area.expand(pos)
area = area.expand(pos + size)
break
# if the background is smaller than the viewport, we want the camera to stick centered on the background
if area.size.x == 0 or area.size.y == 0 or area.size < get_viewport().size:
printt("No limit area! Using viewport")
area.size = get_viewport().size
printt("setting camera limits from scene ", area)
limits = {
"limit_left": area.position.x,
"limit_right": area.position.x + area.size.x,
"limit_top": area.position.y,
"limit_bottom": area.position.y + area.size.y,
"set_default": true,
}
else:
limits = {
"limit_left": scene_camera_limits.position.x,
"limit_right": scene_camera_limits.position.x + scene_camera_limits.size.x,
"limit_top": scene_camera_limits.position.y,
"limit_bottom": scene_camera_limits.position.y + scene_camera_limits.size.y + screen_ofs.y * 2,
"set_default": true,
}
printt("setting camera limits from parameter ", scene_camera_limits)
escoria.esc_runner.get_object("camera").set_limits(limits)
escoria.esc_runner.get_object("camera").set_offset(screen_ofs * 2)

View File

@@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://addons/escoria-core/game/main.gd" type="Script" id=1]
[ext_resource path="res://addons/escoria-core/game/scenes/esc_prompt/esc_prompt_popup.tscn" type="PackedScene" id=2]
[node name="main" type="Node"]
script = ExtResource( 1 )
[node name="layers" type="Node" parent="."]
[node name="curtain" type="CanvasLayer" parent="layers"]
[node name="menu" type="CanvasLayer" parent="layers"]
[node name="wait_timer" type="Timer" parent="layers"]
[node name="debug_layer" type="CanvasLayer" parent="layers"]
[node name="esc_prompt_popup" parent="layers/debug_layer" instance=ExtResource( 2 )]

View File

@@ -0,0 +1,10 @@
extends Node
# Main_scene is the entry point for Godot Engine.
# This scene sets up the main menu scene to load.
func _ready():
var main_menu_path = ProjectSettings.get_setting("escoria/main/main_menu_scene")
var main_menu = escoria.change_scene_path(main_menu_path)
escoria.set_main_menu(main_menu)

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/escoria-core/game/main_scene.gd" type="Script" id=1]
[node name="main_scene" type="Node"]
script = ExtResource( 1 )

View File

@@ -0,0 +1,11 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/escoria-core/game/scenes/camera_player/esccamera.gd" type="Script" id=1]
[node name="camera" type="Camera2D"]
current = true
drag_margin_h_enabled = true
drag_margin_v_enabled = true
script = ExtResource( 1 )
[node name="tween" type="Tween" parent="."]

View File

@@ -0,0 +1,166 @@
extends Camera2D
class_name ESCCamera
onready var tween = $"tween"
var default_limits = {} # This does not change once set
var speed = 0.0
var target
var target_pos
var zoom_time
var zoom_target
# This is needed to adjust dialog positions and such, see dialog_instance.gd
var zoom_transform
func set_limits(kwargs=null):
if not kwargs:
kwargs = {
"limit_left": -10000,
"limit_right": 10000,
"limit_top": -10000,
"limit_bottom": 10000,
"set_default": false,
}
print_stack()
self.limit_left = kwargs["limit_left"]
self.limit_right = kwargs["limit_right"]
self.limit_top = kwargs["limit_top"]
self.limit_bottom = kwargs["limit_bottom"]
if "set_default" in kwargs and kwargs["set_default"] and not default_limits:
default_limits = kwargs
func resolve_target_pos():
if typeof(target) == TYPE_VECTOR2:
target_pos = target
elif typeof(target) == TYPE_ARRAY:
var count = 0
for obj in target:
target_pos += obj.get_camera_pos()
count += 1
# Let the error in if an empty array was passed (divzero)
target_pos = target_pos / count
else:
target_pos = target.get_camera_pos()
return target_pos
func set_drag_margin_enabled(p_dm_h_enabled, p_dm_v_enabled):
self.drag_margin_h_enabled = p_dm_h_enabled
self.drag_margin_v_enabled = p_dm_v_enabled
func set_target(p_target, p_speed : float = 0.0):
speed = p_speed
target = p_target
resolve_target_pos()
if speed == 0.0:
self.global_position = target_pos
else:
var time = self.global_position.distance_to(target_pos) / speed
if tween.is_active():
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
escoria.report_warnings("camera.gd:set_target()", ["Tween still active running camera_set_target: " + tweenstat])
tween.emit_signal("tween_completed")
tween.interpolate_property(self, "global_position", self.global_position, target_pos, time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
func set_camera_zoom(p_zoom_level, p_time):
if p_zoom_level <= 0.0:
escoria.report_errors("camera.gd:set_camera_zoom()", ["Tried to set negative or zero zoom level"])
zoom_time = p_time
zoom_target = Vector2(1, 1) * p_zoom_level
if zoom_time == 0:
self.zoom = zoom_target
else:
if tween.is_active():
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
escoria.report_warnings("camera", ["Tween still active running camera_set_zoom: " + tweenstat])
tween.emit_signal("tween_completed")
tween.interpolate_property(self, "zoom", self.zoom, zoom_target, zoom_time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
func push(p_target, p_time, p_type):
var time = float(p_time)
var type = "TRANS_" + p_type
target = p_target
var camera_pos
var camera_pos_coords
if target.has_node("camera_pos"):
camera_pos = target.get_node("camera_pos")
camera_pos_coords = camera_pos.global_position
else:
camera_pos_coords = target.global_position
if time == 0:
self.global_position = camera_pos_coords
if camera_pos and camera_pos is Camera2D:
self.zoom = camera_pos.zoom
else:
if tween.is_active():
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
escoria.report_warnings("camera", ["Tween still active running camera_push: " + tweenstat])
tween.emit_signal("tween_completed")
if camera_pos and camera_pos is Camera2D:
tween.interpolate_property(self, "zoom", self.zoom, camera_pos.zoom, time, tween.get(type), Tween.EASE_IN_OUT)
tween.interpolate_property(self, "global_position", self.global_position, camera_pos_coords, time, tween.get(type), Tween.EASE_IN_OUT)
tween.start()
func shift(p_x, p_y, p_time, p_type):
var x = int(p_x)
var y = int(p_y)
var time = float(p_time)
var type = "TRANS_" + p_type
var new_pos = self.global_position + Vector2(x, y)
target = new_pos
if tween.is_active():
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
escoria.report_warnings("camera", ["Tween still active running camera_shift: " + tweenstat])
tween.emit_signal("tween_completed")
tween.interpolate_property(self, "global_position", self.global_position, new_pos, time, tween.get(type), Tween.EASE_IN_OUT)
tween.start()
func target_reached(_obj=null, _key=null):
tween.stop_all()
func _process(_delta):
zoom_transform = self.get_canvas_transform()
if target and not tween.is_active():
if typeof(target) == TYPE_VECTOR2 or typeof(target) == TYPE_ARRAY:
self.global_position = resolve_target_pos()
elif "moved" in target and target.moved:
self.global_position = resolve_target_pos()
func _ready():
if not target:
target = Vector2(0, 0)
tween.connect("tween_completed", self, "target_reached")
escoria.register_object(self)

View File

@@ -0,0 +1,74 @@
tool
extends ResourcePreloader
class_name ESCDialogsPlayer
func get_class():
return "ESCDialogsPlayer"
# This scene is in charge of ALL dialogs management :
# - characters sayings
# - player dialog options panel display/hiding and choices
var path_to_dialog_scenes : String
var is_speaking = false
var dialog_ui = null
var dialog_chooser_ui = null
func _ready():
if !Engine.is_editor_hint():
escoria.register_object(self)
preload_resources(ProjectSettings.get_setting("escoria/ui/dialogs_folder"))
func preload_resources(path : String):
path_to_dialog_scenes = path
var dialog_folder := Directory.new()
if !path_to_dialog_scenes.empty() and dialog_folder.open(path_to_dialog_scenes) == OK:
dialog_folder.list_dir_begin()
var file_name = dialog_folder.get_next()
while file_name != "":
if !dialog_folder.current_is_dir() and file_name.get_extension() == "tscn":
var extension = "." + file_name.get_extension()
var basename = file_name.replace(extension, "")
if !has_resource(basename):
var file_path = dialog_folder.get_current_dir() + "/" + file_name
var dialog_scene = load(file_path)
if dialog_scene != null:
add_resource(basename, dialog_scene)
file_name = dialog_folder.get_next()
else:
escoria.report_errors("dialog_player.gd:preload_resources()", ["An error occurred when trying to access the path: {_}.".format(path)])
func say(character : String, params : Dictionary):
is_speaking = true
dialog_ui = get_resource(params.ui).instance()
get_parent().add_child(dialog_ui)
dialog_ui.say(character, params)
func finish_fast():
dialog_ui.finish_fast()
# Options:
# type: (default value "default") the type of dialog menu to use. All types are in the "dd_player" scene.
# avatar: (default value "default") the avatar to use in the dialog ui.
# timeout: (default value 0) timeout to select an option. After the time has passed, the "timeout_option" will be selected automatically. If the value is 0, there's no timeout.
# timeout_option: (default value 0) option selected when timeout is reached.
func start_dialog_choices(answers : Array, options : Array):
if answers.empty():
escoria.report_errors("dialog_player.gd:start_dialog_choices()", ["Received answers array was empty."])
dialog_chooser_ui = get_resource("text_dialog_choice").instance()
get_parent().add_child(dialog_chooser_ui)
dialog_chooser_ui.set_answers(answers)
func play_dialog_option_chosen(level_to_run : Array):
# escoria.esc_runner.finished(context)
var ev_level = level_to_run
var ev = esctypes.ESCEvent.new("dialog_choice_done", ev_level, [])
escoria.esc_runner.add_level(ev, false)
dialog_chooser_ui.hide()
# stop()

View File

@@ -0,0 +1,10 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://game/ui/commons/dialogs/dialog_label.tscn" type="PackedScene" id=1]
[ext_resource path="res://game/ui/commons/dialogs/text_dialog_choice.tscn" type="PackedScene" id=2]
[ext_resource path="res://addons/escoria-core/game/scenes/dialogs/dialog_player.gd" type="Script" id=3]
[ext_resource path="res://game/ui/commons/dialogs/dialog_box_inset.tscn" type="PackedScene" id=4]
[node name="dialog_player" type="ResourcePreloader"]
resources = [ PoolStringArray( "dialog_box_inset", "dialog_label", "text_dialog_choice" ), [ ExtResource( 4 ), ExtResource( 1 ), ExtResource( 2 ) ] ]
script = ExtResource( 3 )

View File

@@ -0,0 +1,40 @@
extends WindowDialog
onready var past_actions = $VBoxContainer/past_actions
onready var command = $VBoxContainer/command
var last_event_done := true
func _on_command_text_entered(p_command_str : String):
if p_command_str.empty():
return
last_event_done = false
command.text = ""
past_actions.text += "\n"
past_actions.text += "# " + p_command_str
past_actions.text += "\n"
var actual_command = ":debug\n" + p_command_str + "\n"
var errors = []
var events = escoria.esc_compiler.compile_str(actual_command, errors)
if errors.empty():
#past_actions.text += str(events)
var ret = escoria.esc_runner.run_event(events["debug"])
if ret != null:
past_actions.text += str(ret)
else:
# Display first error only
past_actions.text += str(errors[0].split(":")[1].strip_edges())
func _on_event_done(event_name : String):
if event_name == "debug" and !last_event_done:
last_event_done = true
# past_actions.text += "\nDone.\n"
func _on_esc_prompt_popup_about_to_show():
command.grab_focus()

View File

@@ -0,0 +1,42 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/escoria-core/game/scenes/esc_prompt/esc_prompt_popup.gd" type="Script" id=1]
[node name="esc_prompt_popup" type="WindowDialog"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 64.0
margin_top = 68.0
margin_right = -617.0
margin_bottom = -456.0
window_title = "ESC debug prompt"
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="past_actions" type="TextEdit" parent="VBoxContainer"]
margin_right = 599.0
margin_bottom = 240.0
size_flags_vertical = 3
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
margin_top = 244.0
margin_right = 599.0
margin_bottom = 248.0
[node name="command" type="LineEdit" parent="VBoxContainer"]
margin_top = 252.0
margin_right = 599.0
margin_bottom = 276.0
caret_blink = true
[connection signal="about_to_show" from="." to="." method="_on_esc_prompt_popup_about_to_show"]
[connection signal="text_entered" from="VBoxContainer/command" to="." method="_on_command_text_entered"]

View File

@@ -0,0 +1,101 @@
extends Control
class_name ESCInventory
func get_class():
return "ESCInventory"
# Define the actual container node to add items as children of. Should be a Container.
export(NodePath) var items_container
onready var all_items = $all_items
# Methods available for selecting an item
enum ITEM_SELECTION_METHODS {
VERB_ACTION, # Use a verb action, such as use or give, on inventory item
ONE_CLICK, # One click on inventory item selects it (eventually put it on cursor)
DRAG_N_DROP # (Useful for mobile) Drag n drop item on another or on background to use/give it
}
export(ITEM_SELECTION_METHODS) var selection_method
var items_ids_in_inventory : Dictionary = {} # { item_id : TextureButton}
func _ready():
# # For debugging scene only. These 2 lines should remain commented on normal run.
# if !Engine.is_editor_hint():
# return
for item_id in escoria.esc_runner.items_in_inventory():
call_deferred("add_new_item_by_id", item_id)
escoria.register_object(self)
if items_container == null or items_container.is_empty():
escoria.report_errors(self.get_path(), ["Items container is empty."])
return
for c in get_node(items_container).get_items():
items_ids_in_inventory[c.item_id] = c
# c.connect("pressed", escoria.inputs_manager, "_on_inventory_item_pressed", [c.item_id])
escoria.esc_runner.connect("global_changed", self, "_on_escoria_global_changed")
# add item to Inventory UI using its id set in its scene
func add_new_item_by_id(item_id : String) -> void:
if item_id.begins_with("i/"):
item_id = item_id.rsplit("i/", false)[0]
if !items_ids_in_inventory.has(item_id):
var item_inventory_button = all_items.get_inventory_item(item_id).duplicate()
items_ids_in_inventory[item_id] = item_inventory_button
get_node(items_container).add_item(item_inventory_button)
# Add the item to inventory
if !escoria.esc_runner.objects.has(item_id):
escoria.esc_runner.register_object(item_id, item_inventory_button)
item_inventory_button.visible = true
# connect this new item TextureButton's signals to our inventory UI
item_inventory_button.connect("mouse_left_inventory_item",
escoria.inputs_manager, "_on_mouse_left_click_inventory_item")
item_inventory_button.connect("mouse_double_left_inventory_item",
escoria.inputs_manager, "_on_mouse_double_left_click_inventory_item")
item_inventory_button.connect("mouse_right_inventory_item",
escoria.inputs_manager, "_on_mouse_right_click_inventory_item")
item_inventory_button.connect("inventory_item_focused",
escoria.inputs_manager, "_on_mouse_entered_inventory_item")
item_inventory_button.connect("inventory_item_unfocused",
escoria.inputs_manager, "_on_mouse_exited_inventory_item")
# remove item fromInventory UI using its id set in its scene
func remove_item_by_id(item_id : String) -> void:
if items_ids_in_inventory.has(item_id):
var item_inventory_button = items_ids_in_inventory[item_id]
item_inventory_button.disconnect("mouse_left_inventory_item",
escoria.inputs_manager, "_on_mouse_left_click_inventory_item")
item_inventory_button.disconnect("mouse_double_left_inventory_item",
escoria.inputs_manager, "_on_mouse_double_left_click_inventory_item")
item_inventory_button.disconnect("mouse_right_inventory_item",
escoria.inputs_manager, "_on_mouse_right_click_inventory_item")
item_inventory_button.disconnect("inventory_item_focused",
escoria.inputs_manager, "_on_mouse_entered_inventory_item")
item_inventory_button.disconnect("inventory_item_unfocused",
escoria.inputs_manager, "_on_mouse_exited_inventory_item")
get_node(items_container).remove_item(item_inventory_button)
item_inventory_button.queue_free()
items_ids_in_inventory.erase(item_id)
func _on_escoria_global_changed(global : String) -> void:
if !global.begins_with("i/"):
return
var item = global.rsplit("i/", false)
if item.size() == 1:
if escoria.esc_runner.globals[global] == "true":
add_new_item_by_id(item[0])
elif escoria.esc_runner.globals[global] == "false":
remove_item_by_id(item[0])
else:
escoria.report_warnings("inventory_ui.gd:_on_escoria_global_changed()", \
["Inventory global " + global + " is neither 'true' nor 'false' (was " + escoria.esc_runner.globals[global] + "). "])
else:
escoria.report_errors("inventory_ui.gd:_on_escoria_global_changed()", ["Global must contain 1 item name.", "(received: " + global + ")"])