Initial commit of Escoria-Reloaded. Still a lot of missing stuff.
This commit is contained in:
540
addons/escoria-core/game/core-scripts/esc/esc_compiler.gd
Normal file
540
addons/escoria-core/game/core-scripts/esc/esc_compiler.gd
Normal 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
|
||||
630
addons/escoria-core/game/core-scripts/esc/esc_runner.gd
Normal file
630
addons/escoria-core/game/core-scripts/esc/esc_runner.gd
Normal 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)
|
||||
|
||||
463
addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd
Normal file
463
addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
97
addons/escoria-core/game/core-scripts/escbackground.gd
Normal file
97
addons/escoria-core/game/core-scripts/escbackground.gd
Normal 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
|
||||
9
addons/escoria-core/game/core-scripts/esccharacter.gd
Normal file
9
addons/escoria-core/game/core-scripts/esccharacter.gd
Normal 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
|
||||
148
addons/escoria-core/game/core-scripts/eschotspot.gd
Normal file
148
addons/escoria-core/game/core-scripts/eschotspot.gd
Normal 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)
|
||||
123
addons/escoria-core/game/core-scripts/escitem.gd
Normal file
123
addons/escoria-core/game/core-scripts/escitem.gd
Normal 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")
|
||||
|
||||
86
addons/escoria-core/game/core-scripts/escoria_types.gd
Normal file
86
addons/escoria-core/game/core-scripts/escoria_types.gd
Normal 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) + """
|
||||
}"""
|
||||
401
addons/escoria-core/game/core-scripts/escplayer.gd
Normal file
401
addons/escoria-core/game/core-scripts/escplayer.gd
Normal 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
|
||||
|
||||
|
||||
27
addons/escoria-core/game/core-scripts/escroom.gd
Normal file
27
addons/escoria-core/game/core-scripts/escroom.gd
Normal 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
|
||||
|
||||
63
addons/escoria-core/game/core-scripts/escterrain.gd
Normal file
63
addons/escoria-core/game/core-scripts/escterrain.gd
Normal 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))
|
||||
|
||||
|
||||
177
addons/escoria-core/game/core-scripts/escterrain_base.gd
Normal file
177
addons/escoria-core/game/core-scripts/escterrain_base.gd
Normal 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
|
||||
@@ -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)
|
||||
31
addons/escoria-core/game/core-scripts/esctriggerzone.gd
Normal file
31
addons/escoria-core/game/core-scripts/esctriggerzone.gd
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
41
addons/escoria-core/game/core-scripts/inventory_item.gd
Normal file
41
addons/escoria-core/game/core-scripts/inventory_item.gd
Normal 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")
|
||||
9
addons/escoria-core/game/core-scripts/items_inventory.gd
Normal file
9
addons/escoria-core/game/core-scripts/items_inventory.gd
Normal 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
|
||||
9
addons/escoria-core/game/core-scripts/log/logging.gd
Normal file
9
addons/escoria-core/game/core-scripts/log/logging.gd
Normal 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)
|
||||
69
addons/escoria-core/game/core-scripts/old/background.gd
Normal file
69
addons/escoria-core/game/core-scripts/old/background.gd
Normal 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")
|
||||
|
||||
5
addons/escoria-core/game/core-scripts/old/scalenode.gd
Normal file
5
addons/escoria-core/game/core-scripts/old/scalenode.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends Position2D
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
export(Vector2) var target_scale = Vector2(1.0, 1.0)
|
||||
|
||||
191
addons/escoria-core/game/core-scripts/resource_queue.gd
Normal file
191
addons/escoria-core/game/core-scripts/resource_queue.gd
Normal 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")
|
||||
202
addons/escoria-core/game/core-scripts/save_data/save_data.gd
Normal file
202
addons/escoria-core/game/core-scripts/save_data/save_data.gd
Normal 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
|
||||
4
addons/escoria-core/game/core-scripts/scalenode.gd
Normal file
4
addons/escoria-core/game/core-scripts/scalenode.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
extends Position2D
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
export(Vector2) var target_scale = Vector2(1.0, 1.0)
|
||||
10
addons/escoria-core/game/core-scripts/utils/utils.gd
Normal file
10
addons/escoria-core/game/core-scripts/utils/utils.gd
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user