Initial commit of Escoria-Reloaded. Still a lot of missing stuff.
This commit is contained in:
BIN
addons/escoria-core/game/assets/fonts/efmi/efmi.TTF
Executable file
BIN
addons/escoria-core/game/assets/fonts/efmi/efmi.TTF
Executable file
Binary file not shown.
66299
addons/escoria-core/game/assets/fonts/efmi/efmi.inc
Executable file
66299
addons/escoria-core/game/assets/fonts/efmi/efmi.inc
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/assets/fonts/efmi/efmi.TTF" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
size = 25
|
||||
font_data = ExtResource( 1 )
|
||||
BIN
addons/escoria-core/game/assets/fonts/onesize/ONESIZE_.TTF
Executable file
BIN
addons/escoria-core/game/assets/fonts/onesize/ONESIZE_.TTF
Executable file
Binary file not shown.
BIN
addons/escoria-core/game/assets/fonts/onesize/ONESR___.TTF
Executable file
BIN
addons/escoria-core/game/assets/fonts/onesize/ONESR___.TTF
Executable file
Binary file not shown.
BIN
addons/escoria-core/game/assets/fonts/onesize/Readme1st.doc
Executable file
BIN
addons/escoria-core/game/assets/fonts/onesize/Readme1st.doc
Executable file
Binary file not shown.
BIN
addons/escoria-core/game/assets/images/no_image.png
Normal file
BIN
addons/escoria-core/game/assets/images/no_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
34
addons/escoria-core/game/assets/images/no_image.png.import
Normal file
34
addons/escoria-core/game/assets/images/no_image.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/no_image.png-7e4632ad2d21010b279ddaa4725bacb7.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/escoria-core/game/assets/images/no_image.png"
|
||||
dest_files=[ "res://.import/no_image.png-7e4632ad2d21010b279ddaa4725bacb7.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
BIN
addons/escoria-core/game/assets/images/no_image.xcf
Normal file
BIN
addons/escoria-core/game/assets/images/no_image.xcf
Normal file
Binary file not shown.
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
|
||||
|
||||
249
addons/escoria-core/game/escoria.gd
Normal file
249
addons/escoria-core/game/escoria.gd
Normal file
@@ -0,0 +1,249 @@
|
||||
extends Node
|
||||
|
||||
# Scripts
|
||||
onready var esc_compiler = $esc_compiler
|
||||
onready var logger = load("res://addons/escoria-core/game/core-scripts/log/logging.gd").new()
|
||||
onready var main = $main
|
||||
onready var esc_runner = $esc_runner
|
||||
onready var esc_level_runner = $esc_runner/esc_level_runner
|
||||
onready var inputs_manager = $inputs_manager
|
||||
onready var utils = load("res://addons/escoria-core/game/core-scripts/utils/utils.gd").new()
|
||||
|
||||
# INSTANCES
|
||||
var main_menu_instance
|
||||
## Dialog player instantiator. This instance is called directly for dialogs.
|
||||
var dialog_player
|
||||
## Inventory scene
|
||||
var inventory
|
||||
|
||||
# Game variables
|
||||
var room_terrain
|
||||
|
||||
enum GAME_STATE {
|
||||
DEFAULT,
|
||||
DIALOG,
|
||||
WAIT
|
||||
}
|
||||
onready var current_state = GAME_STATE.DEFAULT
|
||||
|
||||
##################################################################################
|
||||
func _ready():
|
||||
pass
|
||||
|
||||
|
||||
# Called by Main menu "start new game"
|
||||
func new_game():
|
||||
var actions = esc_compiler.load_esc_file(ProjectSettings.get_setting("escoria/main/game_start_script"))
|
||||
$esc_runner.run_game(actions)
|
||||
|
||||
|
||||
func change_scene_path(scene_path):
|
||||
var scene = load(scene_path).instance()
|
||||
get_tree().get_root().call_deferred("add_child", scene)
|
||||
return scene
|
||||
|
||||
|
||||
func set_main_menu(scene):
|
||||
main_menu_instance = scene
|
||||
|
||||
func report_warnings(p_path : String, warnings : Array) -> void:
|
||||
var text = "Warnings in file "+p_path+"\n"
|
||||
for w in warnings:
|
||||
if w is Array:
|
||||
text += str(w)+"\n"
|
||||
else:
|
||||
text += w+"\n"
|
||||
printerr("warning is: ", text)
|
||||
|
||||
|
||||
func report_errors(p_path : String, errors : Array) -> void:
|
||||
var text = "Errors in file "+p_path+"\n"
|
||||
for e in errors:
|
||||
if e is Array:
|
||||
text += str(e)+"\n"
|
||||
else:
|
||||
text += e+"\n"
|
||||
printerr("error is: ", text)
|
||||
if ProjectSettings.get_setting("escoria/debug/terminate_on_errors"):
|
||||
print_stack()
|
||||
assert(false)
|
||||
# If your game stopped here, you may want to look at the Output tab and check for
|
||||
# the error that caused the game to stop.
|
||||
|
||||
|
||||
"""
|
||||
Add object to the environement.
|
||||
"""
|
||||
func register_object(object : Object):
|
||||
var object_id
|
||||
if object.get("global_id"):
|
||||
object_id = object.global_id
|
||||
else:
|
||||
object_id = object.name
|
||||
|
||||
if object is ESCDialogsPlayer:
|
||||
dialog_player = object
|
||||
|
||||
if object is ESCPlayer:
|
||||
$esc_runner.register_object(object_id, object, true)
|
||||
|
||||
if object is ESCHotspot or object is Position2D:
|
||||
$esc_runner.register_object(object_id, object, true)
|
||||
|
||||
if object is ESCItem:
|
||||
$esc_runner.register_object(object_id, object, true)
|
||||
|
||||
if object is ESCTerrain:
|
||||
room_terrain = object
|
||||
|
||||
if object is ESCCamera:
|
||||
$esc_runner.register_object(object_id, object, true)
|
||||
|
||||
if object is ESCInventory:
|
||||
inventory = object
|
||||
|
||||
|
||||
"""
|
||||
Generic action function that runs an action on an element of the room (eg player walk)
|
||||
action: type of the action ()
|
||||
"""
|
||||
func do(action : String, params : Array = []) -> void:
|
||||
if current_state == GAME_STATE.DEFAULT:
|
||||
match action:
|
||||
"walk":
|
||||
# Reset current action
|
||||
esc_runner.set_current_action("")
|
||||
|
||||
# Walk to position2D
|
||||
if params[1] is Vector2:
|
||||
var target_position = params[1]
|
||||
var is_fast : bool = false
|
||||
if params.size() > 2 and params[2] == true:
|
||||
is_fast = true
|
||||
var walk_context = {"fast": is_fast}
|
||||
main.current_scene.player.walk_to(target_position, walk_context)
|
||||
# Walk to object from its id
|
||||
elif params[1] is String:
|
||||
var object = escoria.esc_runner.get_object(params[1])
|
||||
if object:
|
||||
var target_position : Vector2 = object.interact_position
|
||||
|
||||
if params[0] == main.current_scene.player.global_id:
|
||||
var is_fast : bool = false
|
||||
if params.size() > 2 and params[2] == true:
|
||||
is_fast = true
|
||||
var walk_context = {"fast": is_fast, "target_object" : object}
|
||||
|
||||
main.current_scene.player.walk_to(target_position, walk_context)
|
||||
else:
|
||||
report_errors("escoria.gd: do() > walk", ["TODO: code NPC walking"])
|
||||
|
||||
"hotspot_left_click", "item_left_click":
|
||||
if params[0] is String:
|
||||
printt("escoria.do : item_left_click on item ", params[0])
|
||||
|
||||
# call : ev_left_click_on_item()
|
||||
ev_left_click_on_item($esc_runner.get_object(params[0]), params[1])
|
||||
|
||||
"hotspot_right_click", "item_right_click":
|
||||
if params[0] is String:
|
||||
printt("escoria.do : item_right_click on item ", params[0])
|
||||
|
||||
# call : ev_left_click_on_item()
|
||||
ev_left_click_on_item($esc_runner.get_object(params[0]), params[1], true)
|
||||
|
||||
_:
|
||||
# $esc_runner.activate(action, params[0])
|
||||
report_warnings("escoria.gd:do()", ["Action received:", action, "with params ", params])
|
||||
elif current_state == GAME_STATE.DIALOG:
|
||||
dialog_player.finish_fast()
|
||||
elif current_state == GAME_STATE.WAIT:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# PRIVATE
|
||||
func ev_left_click_on_item(obj, event, default_action = false):
|
||||
"""
|
||||
Event occurring when an object/item is left clicked
|
||||
obj : object that was left clicked
|
||||
event :
|
||||
"""
|
||||
if obj is String:
|
||||
obj = esc_runner.objects[obj]
|
||||
printt(obj.global_id, "left-clicked with", event)
|
||||
|
||||
var need_combine = false
|
||||
# Check if current_action and current_tool are already set
|
||||
if esc_runner.current_action:
|
||||
if esc_runner.current_tool:
|
||||
if esc_runner.current_action in esc_runner.current_tool.combine_if_action_used_among:
|
||||
need_combine = true
|
||||
else:
|
||||
esc_runner.current_tool = obj
|
||||
else:
|
||||
if default_action:
|
||||
esc_runner.current_action = obj.default_action
|
||||
elif esc_runner.current_action in obj.combine_if_action_used_among:
|
||||
esc_runner.current_tool = obj
|
||||
|
||||
|
||||
var action = "walk"
|
||||
# Don't interact after player movement towards object (because object is inactive for example)
|
||||
var dont_interact = false
|
||||
var destination_position : Vector2 = main.current_scene.player.global_position
|
||||
|
||||
# Create walk context
|
||||
var walk_context = {"fast": event.doubleclick, "target_object" : obj}
|
||||
|
||||
# If object not in inventory, player walks towards it
|
||||
if !esc_runner.inventory_has(obj.global_id):
|
||||
var clicked_object_has_interact_position = false
|
||||
|
||||
if esc_runner.get_interactive(obj.global_id):
|
||||
if obj.interact_positions.default != null:
|
||||
destination_position = obj.interact_positions.default#.global_position
|
||||
clicked_object_has_interact_position = true
|
||||
else:
|
||||
destination_position = obj.position
|
||||
else:
|
||||
destination_position = event.position
|
||||
dont_interact = true
|
||||
|
||||
main.current_scene.player.walk_to(destination_position, walk_context)
|
||||
|
||||
# Wait for the player to arrive before continuing with action.
|
||||
yield(main.current_scene.player, "arrived")
|
||||
|
||||
# If no interaction should happen after player has arrived, leave immediately.
|
||||
if dont_interact:
|
||||
return
|
||||
|
||||
var player_global_pos = main.current_scene.player.global_position
|
||||
var clicked_position = event.position
|
||||
|
||||
# If player has arrived at the position he was supposed to reach so he can interact
|
||||
if player_global_pos == destination_position:
|
||||
# Manage exits
|
||||
if obj.is_exit and $esc_runner.current_action == "" or $esc_runner.current_action == "walk":
|
||||
var params = [obj]
|
||||
$esc_runner.activate("exit_scene", params)
|
||||
|
||||
else:
|
||||
# Manage movements towards object before activating it
|
||||
if $esc_runner.current_action == "" or $esc_runner.current_action == "walk":
|
||||
if destination_position != clicked_position \
|
||||
and !esc_runner.inventory_has(obj.global_id):
|
||||
esc_runner.activate("arrived", [obj])
|
||||
# Manage action on object
|
||||
elif $esc_runner.current_action != "" and $esc_runner.current_action != "walk":
|
||||
# If apply_interact, perform combine between items
|
||||
if need_combine:
|
||||
esc_runner.activate(esc_runner.current_action, [esc_runner.current_tool, obj])
|
||||
|
||||
else:
|
||||
esc_runner.activate(esc_runner.current_action, [obj])
|
||||
|
||||
else:
|
||||
# escoria.fallback("")
|
||||
pass
|
||||
30
addons/escoria-core/game/escoria.tscn
Normal file
30
addons/escoria-core/game/escoria.tscn
Normal file
@@ -0,0 +1,30 @@
|
||||
[gd_scene load_steps=7 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/esc_runner.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/escoria-core/game/main.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://addons/escoria-core/game/escoria.gd" type="Script" id=3]
|
||||
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd" type="Script" id=4]
|
||||
[ext_resource path="res://addons/escoria-core/game/inputs_manager.gd" type="Script" id=5]
|
||||
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/esc_compiler.gd" type="Script" id=6]
|
||||
|
||||
[node name="escoria" type="Node"]
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="inputs_manager" type="Node" parent="."]
|
||||
script = ExtResource( 5 )
|
||||
|
||||
[node name="esc_compiler" type="Node" parent="."]
|
||||
script = ExtResource( 6 )
|
||||
|
||||
[node name="esc_runner" type="Node" parent="."]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="esc_level_runner" type="Node" parent="esc_runner"]
|
||||
script = ExtResource( 4 )
|
||||
|
||||
[node name="main" parent="." instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Viewport" type="Viewport" parent="."]
|
||||
usage = 0
|
||||
|
||||
[editable path="main"]
|
||||
101
addons/escoria-core/game/inputs_manager.gd
Normal file
101
addons/escoria-core/game/inputs_manager.gd
Normal file
@@ -0,0 +1,101 @@
|
||||
tool
|
||||
extends Node
|
||||
|
||||
var is_hotspot_focused : bool
|
||||
|
||||
func _ready():
|
||||
set_process_input(true)
|
||||
|
||||
func _input(event):
|
||||
if event.is_action_pressed("esc_show_debug_prompt"):
|
||||
escoria.main.get_node("layers/debug_layer/esc_prompt_popup").popup()
|
||||
|
||||
###################################################################################
|
||||
|
||||
func _on_left_click_on_bg(position : Vector2):
|
||||
if !is_hotspot_focused:
|
||||
printt("Left click on background at ", str(position))
|
||||
escoria.main.current_scene.game.left_click_on_bg(position)
|
||||
|
||||
|
||||
func _on_double_left_click_on_bg(position : Vector2):
|
||||
if !is_hotspot_focused:
|
||||
printt("Double left click on background at ", str(position))
|
||||
escoria.main.current_scene.game.left_double_click_on_bg(position)
|
||||
|
||||
|
||||
func _on_right_click_on_bg(position : Vector2):
|
||||
if !is_hotspot_focused:
|
||||
printt("Right click on background at ", str(position))
|
||||
escoria.main.current_scene.game.right_click_on_bg(position)
|
||||
|
||||
##################################################################################
|
||||
|
||||
func _on_mouse_entered_hotspot(hotspot_global_id : String) -> void:
|
||||
printt("Hotspot focused : ", hotspot_global_id)
|
||||
is_hotspot_focused = true
|
||||
escoria.main.current_scene.game.element_focused(hotspot_global_id)
|
||||
|
||||
func _on_mouse_exited_hotspot() -> void:
|
||||
print("Hotspot unfocused")
|
||||
is_hotspot_focused = false
|
||||
escoria.main.current_scene.game.element_unfocused()
|
||||
|
||||
func _on_mouse_left_clicked_hotspot(hotspot_global_id : String, event : InputEvent) -> void:
|
||||
printt("Hotspot left clicked", hotspot_global_id, event)
|
||||
escoria.main.current_scene.game.left_click_on_hotspot(hotspot_global_id, event)
|
||||
|
||||
func _on_mouse_right_clicked_hotspot(hotspot_global_id : String, event : InputEvent) -> void:
|
||||
printt("Hotspot right clicked", hotspot_global_id, event)
|
||||
escoria.main.current_scene.game.right_click_on_hotspot(hotspot_global_id, event)
|
||||
|
||||
func _on_mouse_left_double_clicked_hotspot(hotspot_global_id : String, event : InputEvent) -> void:
|
||||
printt("Hotspot right clicked", hotspot_global_id, event)
|
||||
escoria.main.current_scene.game.left_double_click_on_hotspot(hotspot_global_id, event)
|
||||
|
||||
##################################################################################
|
||||
|
||||
func _on_mouse_left_click_inventory_item(inventory_item_global_id, event : InputEvent) -> void:
|
||||
printt("Inventory item left clicked ", inventory_item_global_id)
|
||||
escoria.main.current_scene.game.left_click_on_inventory_item(inventory_item_global_id, event)
|
||||
|
||||
func _on_mouse_right_click_inventory_item(inventory_item_global_id, event : InputEvent) -> void:
|
||||
printt("Inventory item right clicked ", inventory_item_global_id)
|
||||
escoria.main.current_scene.game.right_click_on_inventory_item(inventory_item_global_id, event)
|
||||
|
||||
func _on_mouse_double_left_click_inventory_item(inventory_item_global_id, event : InputEvent) -> void:
|
||||
printt("Inventory item double left clicked ", inventory_item_global_id)
|
||||
escoria.main.current_scene.game.double_left_click_on_inventory_item(inventory_item_global_id, event)
|
||||
|
||||
func _on_mouse_entered_inventory_item(inventory_item_global_id) -> void:
|
||||
printt("Inventory item focused ", inventory_item_global_id)
|
||||
escoria.main.current_scene.game.inventory_item_focused(inventory_item_global_id)
|
||||
|
||||
func _on_mouse_exited_inventory_item() -> void:
|
||||
printt("Inventory item unfocused")
|
||||
escoria.main.current_scene.game.inventory_item_unfocused()
|
||||
|
||||
|
||||
##################################################################################
|
||||
|
||||
func _on_mouse_entered_item(item_global_id : String) -> void:
|
||||
printt("Item focused : ", item_global_id)
|
||||
is_hotspot_focused = true
|
||||
escoria.main.current_scene.game.element_focused(item_global_id)
|
||||
|
||||
func _on_mouse_exited_item() -> void:
|
||||
print("Item unfocused")
|
||||
is_hotspot_focused = false
|
||||
escoria.main.current_scene.game.element_unfocused()
|
||||
|
||||
func _on_mouse_left_clicked_item(item_global_id : String, event : InputEvent) -> void:
|
||||
printt("Item left clicked", item_global_id, event)
|
||||
escoria.main.current_scene.game.left_click_on_item(item_global_id, event)
|
||||
|
||||
func _on_mouse_left_double_clicked_item(item_global_id : String, event : InputEvent) -> void:
|
||||
printt("Item left double clicked", item_global_id, event)
|
||||
escoria.main.current_scene.game.left_double_click_on_item(item_global_id, event)
|
||||
|
||||
func _on_mouse_right_clicked_item(item_global_id : String, event : InputEvent) -> void:
|
||||
printt("Item right clicked", item_global_id, event)
|
||||
escoria.main.current_scene.game.right_click_on_item(item_global_id, event)
|
||||
158
addons/escoria-core/game/main.gd
Normal file
158
addons/escoria-core/game/main.gd
Normal file
@@ -0,0 +1,158 @@
|
||||
extends Node
|
||||
|
||||
# This script is basically the scene-switcher.
|
||||
|
||||
# Global id of the last scene the player was before current scene
|
||||
var last_scene_global_id
|
||||
# Current scene room being displayed
|
||||
var current_scene
|
||||
|
||||
var wait_level
|
||||
|
||||
var screen_ofs = Vector2(0, 0)
|
||||
|
||||
func _ready():
|
||||
$layers/wait_timer.connect("timeout", self, "_on_wait_finished")
|
||||
|
||||
func set_scene(p_scene, run_events=true):
|
||||
"""
|
||||
Sets p_scene as current scene
|
||||
If run_events=true, plays the events defined in :setup event
|
||||
"""
|
||||
if !p_scene:
|
||||
escoria.report_errors("main", ["Trying to set empty scene"])
|
||||
|
||||
# Ensure we don't have a regular event running when changing scenes
|
||||
if escoria.esc_runner.running_event:
|
||||
assert(escoria.esc_runner.running_event.ev_name == "load")
|
||||
|
||||
if "esc_script" in p_scene and p_scene.esc_script and run_events:
|
||||
var events = escoria.esc_compiler.load_esc_file(p_scene.esc_script)
|
||||
|
||||
# :setup is pretty much required in the code, but fortunately
|
||||
# we can help out with cases where one isn't necessary otherwise
|
||||
if not "setup" in events:
|
||||
var fake_setup = escoria.esc_compiler.compile_str(":setup\n")
|
||||
events["setup"] = fake_setup["setup"]
|
||||
|
||||
escoria.esc_runner.run_event(events["setup"])
|
||||
|
||||
# If scene was never visited, run "ready" event
|
||||
if !escoria.esc_runner.scenes_cache.has(p_scene.global_id) \
|
||||
and "ready" in events:
|
||||
escoria.esc_runner.run_event(events["ready"])
|
||||
|
||||
if current_scene != null:
|
||||
clear_scene()
|
||||
|
||||
# var game_scene =
|
||||
|
||||
get_node("/root").add_child(p_scene)
|
||||
set_current_scene(p_scene, run_events)
|
||||
set_camera_limits()
|
||||
|
||||
|
||||
func set_current_scene(p_scene, run_events=true):
|
||||
current_scene = p_scene
|
||||
$"/root".move_child(current_scene, 0)
|
||||
|
||||
# Loading a save game must set the scene but not run events
|
||||
if "events_path" in current_scene and current_scene.events_path and run_events:
|
||||
if escoria.esc_runner.game:
|
||||
# Having a game with `:setup` means we must wait for it to finish
|
||||
if "setup" in escoria.esc_runner.game:
|
||||
if not escoria.esc_runner.running_event:
|
||||
escoria.report_errors("main.gd:set_current_scene()", ["escoria.esc_runner.game has setup but no running_event"])
|
||||
|
||||
if escoria.esc_runner.running_event.ev_name != "setup":
|
||||
escoria.report_errors("main.gd:set_current_scene()", ["escoria.esc_runner.game has setup but it is not running: " + escoria.esc_runner.running_event.ev_name])
|
||||
|
||||
yield(escoria.esc_runner, "event_done")
|
||||
else:
|
||||
escoria.esc_compiler.load_file(current_scene.events_path)
|
||||
# For a new game, we must run `:setup` if available
|
||||
# and wait for it to finish
|
||||
if "setup" in escoria.esc_runner.game:
|
||||
escoria.esc_runner.run_event(escoria.esc_runner.game["setup"])
|
||||
yield(escoria.esc_runner, "event_done")
|
||||
|
||||
# Because 1) changing a scene and 2) having a scene become ready
|
||||
# both call `set_current_scene`, we don't want to duplicate thing
|
||||
if not escoria.esc_runner.running_event:
|
||||
escoria.esc_runner.run_game()
|
||||
|
||||
escoria.esc_runner.register_object("_scene", p_scene, true) # Force overwrite of global
|
||||
|
||||
|
||||
func clear_scene():
|
||||
if current_scene == null:
|
||||
return
|
||||
|
||||
escoria.esc_runner.clear_current_action()
|
||||
escoria.esc_runner.clear_current_tool()
|
||||
# escoria.esc_runner.hover_clear_stack()
|
||||
# escoria.clear_inventory()
|
||||
|
||||
last_scene_global_id = current_scene.global_id
|
||||
escoria.esc_runner.set_global("ESC_LAST_SCENE", last_scene_global_id, true)
|
||||
get_node("/root").remove_child(current_scene)
|
||||
current_scene.free()
|
||||
current_scene = null
|
||||
|
||||
func wait(params : Array, level):
|
||||
wait_level = level
|
||||
$layers/wait_timer.set_wait_time(float(params[0]))
|
||||
$layers/wait_timer.set_one_shot(true)
|
||||
$layers/wait_timer.start()
|
||||
|
||||
func _on_wait_finished():
|
||||
escoria.esc_level_runner.finished(wait_level)
|
||||
|
||||
|
||||
func set_camera_limits():
|
||||
var limits = {}
|
||||
var scene_camera_limits = current_scene.camera_limits
|
||||
if scene_camera_limits.size.x == 0 and scene_camera_limits.size.y == 0:
|
||||
var area = Rect2()
|
||||
for child in current_scene.get_children():
|
||||
if child is ESCBackground:
|
||||
var pos = child.get_global_position()
|
||||
var size : Vector2
|
||||
if child.get_texture():
|
||||
size = child.get_texture().get_size()
|
||||
else:
|
||||
size = child.rect_size
|
||||
|
||||
if child.rect_scale.x != 1 or child.rect_scale.y != 1:
|
||||
size.x *= child.rect_scale.x
|
||||
size.y *= child.rect_scale.y
|
||||
|
||||
area = area.expand(pos)
|
||||
area = area.expand(pos + size)
|
||||
break
|
||||
|
||||
# if the background is smaller than the viewport, we want the camera to stick centered on the background
|
||||
if area.size.x == 0 or area.size.y == 0 or area.size < get_viewport().size:
|
||||
printt("No limit area! Using viewport")
|
||||
area.size = get_viewport().size
|
||||
|
||||
printt("setting camera limits from scene ", area)
|
||||
limits = {
|
||||
"limit_left": area.position.x,
|
||||
"limit_right": area.position.x + area.size.x,
|
||||
"limit_top": area.position.y,
|
||||
"limit_bottom": area.position.y + area.size.y,
|
||||
"set_default": true,
|
||||
}
|
||||
else:
|
||||
limits = {
|
||||
"limit_left": scene_camera_limits.position.x,
|
||||
"limit_right": scene_camera_limits.position.x + scene_camera_limits.size.x,
|
||||
"limit_top": scene_camera_limits.position.y,
|
||||
"limit_bottom": scene_camera_limits.position.y + scene_camera_limits.size.y + screen_ofs.y * 2,
|
||||
"set_default": true,
|
||||
}
|
||||
printt("setting camera limits from parameter ", scene_camera_limits)
|
||||
|
||||
escoria.esc_runner.get_object("camera").set_limits(limits)
|
||||
escoria.esc_runner.get_object("camera").set_offset(screen_ofs * 2)
|
||||
19
addons/escoria-core/game/main.tscn
Normal file
19
addons/escoria-core/game/main.tscn
Normal file
@@ -0,0 +1,19 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/main.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/escoria-core/game/scenes/esc_prompt/esc_prompt_popup.tscn" type="PackedScene" id=2]
|
||||
|
||||
[node name="main" type="Node"]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="layers" type="Node" parent="."]
|
||||
|
||||
[node name="curtain" type="CanvasLayer" parent="layers"]
|
||||
|
||||
[node name="menu" type="CanvasLayer" parent="layers"]
|
||||
|
||||
[node name="wait_timer" type="Timer" parent="layers"]
|
||||
|
||||
[node name="debug_layer" type="CanvasLayer" parent="layers"]
|
||||
|
||||
[node name="esc_prompt_popup" parent="layers/debug_layer" instance=ExtResource( 2 )]
|
||||
10
addons/escoria-core/game/main_scene.gd
Normal file
10
addons/escoria-core/game/main_scene.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
extends Node
|
||||
|
||||
# Main_scene is the entry point for Godot Engine.
|
||||
# This scene sets up the main menu scene to load.
|
||||
|
||||
func _ready():
|
||||
var main_menu_path = ProjectSettings.get_setting("escoria/main/main_menu_scene")
|
||||
var main_menu = escoria.change_scene_path(main_menu_path)
|
||||
escoria.set_main_menu(main_menu)
|
||||
|
||||
6
addons/escoria-core/game/main_scene.tscn
Normal file
6
addons/escoria-core/game/main_scene.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/main_scene.gd" type="Script" id=1]
|
||||
|
||||
[node name="main_scene" type="Node"]
|
||||
script = ExtResource( 1 )
|
||||
11
addons/escoria-core/game/scenes/camera_player/camera.tscn
Normal file
11
addons/escoria-core/game/scenes/camera_player/camera.tscn
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/scenes/camera_player/esccamera.gd" type="Script" id=1]
|
||||
|
||||
[node name="camera" type="Camera2D"]
|
||||
current = true
|
||||
drag_margin_h_enabled = true
|
||||
drag_margin_v_enabled = true
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="tween" type="Tween" parent="."]
|
||||
166
addons/escoria-core/game/scenes/camera_player/esccamera.gd
Normal file
166
addons/escoria-core/game/scenes/camera_player/esccamera.gd
Normal file
@@ -0,0 +1,166 @@
|
||||
extends Camera2D
|
||||
class_name ESCCamera
|
||||
|
||||
onready var tween = $"tween"
|
||||
|
||||
var default_limits = {} # This does not change once set
|
||||
|
||||
var speed = 0.0
|
||||
var target
|
||||
var target_pos
|
||||
|
||||
var zoom_time
|
||||
var zoom_target
|
||||
|
||||
# This is needed to adjust dialog positions and such, see dialog_instance.gd
|
||||
var zoom_transform
|
||||
|
||||
func set_limits(kwargs=null):
|
||||
if not kwargs:
|
||||
kwargs = {
|
||||
"limit_left": -10000,
|
||||
"limit_right": 10000,
|
||||
"limit_top": -10000,
|
||||
"limit_bottom": 10000,
|
||||
"set_default": false,
|
||||
}
|
||||
print_stack()
|
||||
|
||||
self.limit_left = kwargs["limit_left"]
|
||||
self.limit_right = kwargs["limit_right"]
|
||||
self.limit_top = kwargs["limit_top"]
|
||||
self.limit_bottom = kwargs["limit_bottom"]
|
||||
|
||||
if "set_default" in kwargs and kwargs["set_default"] and not default_limits:
|
||||
default_limits = kwargs
|
||||
|
||||
func resolve_target_pos():
|
||||
if typeof(target) == TYPE_VECTOR2:
|
||||
target_pos = target
|
||||
elif typeof(target) == TYPE_ARRAY:
|
||||
var count = 0
|
||||
|
||||
for obj in target:
|
||||
target_pos += obj.get_camera_pos()
|
||||
count += 1
|
||||
|
||||
# Let the error in if an empty array was passed (divzero)
|
||||
target_pos = target_pos / count
|
||||
else:
|
||||
target_pos = target.get_camera_pos()
|
||||
|
||||
return target_pos
|
||||
|
||||
func set_drag_margin_enabled(p_dm_h_enabled, p_dm_v_enabled):
|
||||
self.drag_margin_h_enabled = p_dm_h_enabled
|
||||
self.drag_margin_v_enabled = p_dm_v_enabled
|
||||
|
||||
func set_target(p_target, p_speed : float = 0.0):
|
||||
speed = p_speed
|
||||
target = p_target
|
||||
|
||||
resolve_target_pos()
|
||||
|
||||
if speed == 0.0:
|
||||
self.global_position = target_pos
|
||||
else:
|
||||
var time = self.global_position.distance_to(target_pos) / speed
|
||||
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
escoria.report_warnings("camera.gd:set_target()", ["Tween still active running camera_set_target: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
tween.interpolate_property(self, "global_position", self.global_position, target_pos, time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func set_camera_zoom(p_zoom_level, p_time):
|
||||
if p_zoom_level <= 0.0:
|
||||
escoria.report_errors("camera.gd:set_camera_zoom()", ["Tried to set negative or zero zoom level"])
|
||||
|
||||
zoom_time = p_time
|
||||
zoom_target = Vector2(1, 1) * p_zoom_level
|
||||
|
||||
if zoom_time == 0:
|
||||
self.zoom = zoom_target
|
||||
else:
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
escoria.report_warnings("camera", ["Tween still active running camera_set_zoom: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
tween.interpolate_property(self, "zoom", self.zoom, zoom_target, zoom_time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func push(p_target, p_time, p_type):
|
||||
var time = float(p_time)
|
||||
var type = "TRANS_" + p_type
|
||||
|
||||
target = p_target
|
||||
|
||||
var camera_pos
|
||||
var camera_pos_coords
|
||||
if target.has_node("camera_pos"):
|
||||
camera_pos = target.get_node("camera_pos")
|
||||
camera_pos_coords = camera_pos.global_position
|
||||
else:
|
||||
camera_pos_coords = target.global_position
|
||||
|
||||
if time == 0:
|
||||
self.global_position = camera_pos_coords
|
||||
|
||||
if camera_pos and camera_pos is Camera2D:
|
||||
self.zoom = camera_pos.zoom
|
||||
else:
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
escoria.report_warnings("camera", ["Tween still active running camera_push: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
if camera_pos and camera_pos is Camera2D:
|
||||
tween.interpolate_property(self, "zoom", self.zoom, camera_pos.zoom, time, tween.get(type), Tween.EASE_IN_OUT)
|
||||
|
||||
tween.interpolate_property(self, "global_position", self.global_position, camera_pos_coords, time, tween.get(type), Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func shift(p_x, p_y, p_time, p_type):
|
||||
var x = int(p_x)
|
||||
var y = int(p_y)
|
||||
var time = float(p_time)
|
||||
var type = "TRANS_" + p_type
|
||||
|
||||
var new_pos = self.global_position + Vector2(x, y)
|
||||
|
||||
target = new_pos
|
||||
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
escoria.report_warnings("camera", ["Tween still active running camera_shift: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
tween.interpolate_property(self, "global_position", self.global_position, new_pos, time, tween.get(type), Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func target_reached(_obj=null, _key=null):
|
||||
tween.stop_all()
|
||||
|
||||
func _process(_delta):
|
||||
zoom_transform = self.get_canvas_transform()
|
||||
|
||||
if target and not tween.is_active():
|
||||
if typeof(target) == TYPE_VECTOR2 or typeof(target) == TYPE_ARRAY:
|
||||
self.global_position = resolve_target_pos()
|
||||
elif "moved" in target and target.moved:
|
||||
self.global_position = resolve_target_pos()
|
||||
|
||||
func _ready():
|
||||
if not target:
|
||||
target = Vector2(0, 0)
|
||||
|
||||
tween.connect("tween_completed", self, "target_reached")
|
||||
escoria.register_object(self)
|
||||
|
||||
74
addons/escoria-core/game/scenes/dialogs/dialog_player.gd
Normal file
74
addons/escoria-core/game/scenes/dialogs/dialog_player.gd
Normal file
@@ -0,0 +1,74 @@
|
||||
tool
|
||||
extends ResourcePreloader
|
||||
class_name ESCDialogsPlayer
|
||||
|
||||
func get_class():
|
||||
return "ESCDialogsPlayer"
|
||||
|
||||
# This scene is in charge of ALL dialogs management :
|
||||
# - characters sayings
|
||||
# - player dialog options panel display/hiding and choices
|
||||
|
||||
var path_to_dialog_scenes : String
|
||||
|
||||
var is_speaking = false
|
||||
var dialog_ui = null
|
||||
var dialog_chooser_ui = null
|
||||
|
||||
func _ready():
|
||||
if !Engine.is_editor_hint():
|
||||
escoria.register_object(self)
|
||||
preload_resources(ProjectSettings.get_setting("escoria/ui/dialogs_folder"))
|
||||
|
||||
func preload_resources(path : String):
|
||||
path_to_dialog_scenes = path
|
||||
|
||||
var dialog_folder := Directory.new()
|
||||
if !path_to_dialog_scenes.empty() and dialog_folder.open(path_to_dialog_scenes) == OK:
|
||||
dialog_folder.list_dir_begin()
|
||||
var file_name = dialog_folder.get_next()
|
||||
while file_name != "":
|
||||
if !dialog_folder.current_is_dir() and file_name.get_extension() == "tscn":
|
||||
var extension = "." + file_name.get_extension()
|
||||
var basename = file_name.replace(extension, "")
|
||||
|
||||
if !has_resource(basename):
|
||||
var file_path = dialog_folder.get_current_dir() + "/" + file_name
|
||||
var dialog_scene = load(file_path)
|
||||
|
||||
if dialog_scene != null:
|
||||
add_resource(basename, dialog_scene)
|
||||
file_name = dialog_folder.get_next()
|
||||
else:
|
||||
escoria.report_errors("dialog_player.gd:preload_resources()", ["An error occurred when trying to access the path: {_}.".format(path)])
|
||||
|
||||
|
||||
func say(character : String, params : Dictionary):
|
||||
is_speaking = true
|
||||
dialog_ui = get_resource(params.ui).instance()
|
||||
get_parent().add_child(dialog_ui)
|
||||
dialog_ui.say(character, params)
|
||||
|
||||
func finish_fast():
|
||||
dialog_ui.finish_fast()
|
||||
|
||||
# Options:
|
||||
# type: (default value "default") the type of dialog menu to use. All types are in the "dd_player" scene.
|
||||
# avatar: (default value "default") the avatar to use in the dialog ui.
|
||||
# timeout: (default value 0) timeout to select an option. After the time has passed, the "timeout_option" will be selected automatically. If the value is 0, there's no timeout.
|
||||
# timeout_option: (default value 0) option selected when timeout is reached.
|
||||
func start_dialog_choices(answers : Array, options : Array):
|
||||
if answers.empty():
|
||||
escoria.report_errors("dialog_player.gd:start_dialog_choices()", ["Received answers array was empty."])
|
||||
dialog_chooser_ui = get_resource("text_dialog_choice").instance()
|
||||
get_parent().add_child(dialog_chooser_ui)
|
||||
dialog_chooser_ui.set_answers(answers)
|
||||
|
||||
func play_dialog_option_chosen(level_to_run : Array):
|
||||
# escoria.esc_runner.finished(context)
|
||||
var ev_level = level_to_run
|
||||
var ev = esctypes.ESCEvent.new("dialog_choice_done", ev_level, [])
|
||||
escoria.esc_runner.add_level(ev, false)
|
||||
dialog_chooser_ui.hide()
|
||||
# stop()
|
||||
|
||||
10
addons/escoria-core/game/scenes/dialogs/dialog_player.tscn
Normal file
10
addons/escoria-core/game/scenes/dialogs/dialog_player.tscn
Normal file
@@ -0,0 +1,10 @@
|
||||
[gd_scene load_steps=5 format=2]
|
||||
|
||||
[ext_resource path="res://game/ui/commons/dialogs/dialog_label.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://game/ui/commons/dialogs/text_dialog_choice.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://addons/escoria-core/game/scenes/dialogs/dialog_player.gd" type="Script" id=3]
|
||||
[ext_resource path="res://game/ui/commons/dialogs/dialog_box_inset.tscn" type="PackedScene" id=4]
|
||||
|
||||
[node name="dialog_player" type="ResourcePreloader"]
|
||||
resources = [ PoolStringArray( "dialog_box_inset", "dialog_label", "text_dialog_choice" ), [ ExtResource( 4 ), ExtResource( 1 ), ExtResource( 2 ) ] ]
|
||||
script = ExtResource( 3 )
|
||||
@@ -0,0 +1,40 @@
|
||||
extends WindowDialog
|
||||
|
||||
onready var past_actions = $VBoxContainer/past_actions
|
||||
onready var command = $VBoxContainer/command
|
||||
|
||||
var last_event_done := true
|
||||
|
||||
func _on_command_text_entered(p_command_str : String):
|
||||
if p_command_str.empty():
|
||||
return
|
||||
|
||||
last_event_done = false
|
||||
command.text = ""
|
||||
past_actions.text += "\n"
|
||||
past_actions.text += "# " + p_command_str
|
||||
past_actions.text += "\n"
|
||||
|
||||
var actual_command = ":debug\n" + p_command_str + "\n"
|
||||
|
||||
var errors = []
|
||||
var events = escoria.esc_compiler.compile_str(actual_command, errors)
|
||||
|
||||
if errors.empty():
|
||||
#past_actions.text += str(events)
|
||||
var ret = escoria.esc_runner.run_event(events["debug"])
|
||||
if ret != null:
|
||||
past_actions.text += str(ret)
|
||||
else:
|
||||
# Display first error only
|
||||
past_actions.text += str(errors[0].split(":")[1].strip_edges())
|
||||
|
||||
|
||||
func _on_event_done(event_name : String):
|
||||
if event_name == "debug" and !last_event_done:
|
||||
last_event_done = true
|
||||
# past_actions.text += "\nDone.\n"
|
||||
|
||||
|
||||
func _on_esc_prompt_popup_about_to_show():
|
||||
command.grab_focus()
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/scenes/esc_prompt/esc_prompt_popup.gd" type="Script" id=1]
|
||||
|
||||
[node name="esc_prompt_popup" type="WindowDialog"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 64.0
|
||||
margin_top = 68.0
|
||||
margin_right = -617.0
|
||||
margin_bottom = -456.0
|
||||
window_title = "ESC debug prompt"
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="past_actions" type="TextEdit" parent="VBoxContainer"]
|
||||
margin_right = 599.0
|
||||
margin_bottom = 240.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
||||
margin_top = 244.0
|
||||
margin_right = 599.0
|
||||
margin_bottom = 248.0
|
||||
|
||||
[node name="command" type="LineEdit" parent="VBoxContainer"]
|
||||
margin_top = 252.0
|
||||
margin_right = 599.0
|
||||
margin_bottom = 276.0
|
||||
caret_blink = true
|
||||
|
||||
[connection signal="about_to_show" from="." to="." method="_on_esc_prompt_popup_about_to_show"]
|
||||
[connection signal="text_entered" from="VBoxContainer/command" to="." method="_on_command_text_entered"]
|
||||
101
addons/escoria-core/game/scenes/inventory/inventory_ui.gd
Normal file
101
addons/escoria-core/game/scenes/inventory/inventory_ui.gd
Normal file
@@ -0,0 +1,101 @@
|
||||
extends Control
|
||||
class_name ESCInventory
|
||||
|
||||
func get_class():
|
||||
return "ESCInventory"
|
||||
|
||||
# Define the actual container node to add items as children of. Should be a Container.
|
||||
export(NodePath) var items_container
|
||||
onready var all_items = $all_items
|
||||
|
||||
# Methods available for selecting an item
|
||||
enum ITEM_SELECTION_METHODS {
|
||||
VERB_ACTION, # Use a verb action, such as use or give, on inventory item
|
||||
ONE_CLICK, # One click on inventory item selects it (eventually put it on cursor)
|
||||
DRAG_N_DROP # (Useful for mobile) Drag n drop item on another or on background to use/give it
|
||||
}
|
||||
export(ITEM_SELECTION_METHODS) var selection_method
|
||||
|
||||
var items_ids_in_inventory : Dictionary = {} # { item_id : TextureButton}
|
||||
|
||||
func _ready():
|
||||
# # For debugging scene only. These 2 lines should remain commented on normal run.
|
||||
# if !Engine.is_editor_hint():
|
||||
# return
|
||||
|
||||
for item_id in escoria.esc_runner.items_in_inventory():
|
||||
call_deferred("add_new_item_by_id", item_id)
|
||||
|
||||
escoria.register_object(self)
|
||||
|
||||
if items_container == null or items_container.is_empty():
|
||||
escoria.report_errors(self.get_path(), ["Items container is empty."])
|
||||
return
|
||||
for c in get_node(items_container).get_items():
|
||||
items_ids_in_inventory[c.item_id] = c
|
||||
# c.connect("pressed", escoria.inputs_manager, "_on_inventory_item_pressed", [c.item_id])
|
||||
|
||||
escoria.esc_runner.connect("global_changed", self, "_on_escoria_global_changed")
|
||||
|
||||
|
||||
# add item to Inventory UI using its id set in its scene
|
||||
func add_new_item_by_id(item_id : String) -> void:
|
||||
if item_id.begins_with("i/"):
|
||||
item_id = item_id.rsplit("i/", false)[0]
|
||||
if !items_ids_in_inventory.has(item_id):
|
||||
var item_inventory_button = all_items.get_inventory_item(item_id).duplicate()
|
||||
items_ids_in_inventory[item_id] = item_inventory_button
|
||||
get_node(items_container).add_item(item_inventory_button)
|
||||
|
||||
# Add the item to inventory
|
||||
if !escoria.esc_runner.objects.has(item_id):
|
||||
escoria.esc_runner.register_object(item_id, item_inventory_button)
|
||||
item_inventory_button.visible = true
|
||||
|
||||
# connect this new item TextureButton's signals to our inventory UI
|
||||
item_inventory_button.connect("mouse_left_inventory_item",
|
||||
escoria.inputs_manager, "_on_mouse_left_click_inventory_item")
|
||||
item_inventory_button.connect("mouse_double_left_inventory_item",
|
||||
escoria.inputs_manager, "_on_mouse_double_left_click_inventory_item")
|
||||
item_inventory_button.connect("mouse_right_inventory_item",
|
||||
escoria.inputs_manager, "_on_mouse_right_click_inventory_item")
|
||||
|
||||
item_inventory_button.connect("inventory_item_focused",
|
||||
escoria.inputs_manager, "_on_mouse_entered_inventory_item")
|
||||
item_inventory_button.connect("inventory_item_unfocused",
|
||||
escoria.inputs_manager, "_on_mouse_exited_inventory_item")
|
||||
|
||||
# remove item fromInventory UI using its id set in its scene
|
||||
func remove_item_by_id(item_id : String) -> void:
|
||||
if items_ids_in_inventory.has(item_id):
|
||||
var item_inventory_button = items_ids_in_inventory[item_id]
|
||||
|
||||
item_inventory_button.disconnect("mouse_left_inventory_item",
|
||||
escoria.inputs_manager, "_on_mouse_left_click_inventory_item")
|
||||
item_inventory_button.disconnect("mouse_double_left_inventory_item",
|
||||
escoria.inputs_manager, "_on_mouse_double_left_click_inventory_item")
|
||||
item_inventory_button.disconnect("mouse_right_inventory_item",
|
||||
escoria.inputs_manager, "_on_mouse_right_click_inventory_item")
|
||||
item_inventory_button.disconnect("inventory_item_focused",
|
||||
escoria.inputs_manager, "_on_mouse_entered_inventory_item")
|
||||
item_inventory_button.disconnect("inventory_item_unfocused",
|
||||
escoria.inputs_manager, "_on_mouse_exited_inventory_item")
|
||||
|
||||
get_node(items_container).remove_item(item_inventory_button)
|
||||
item_inventory_button.queue_free()
|
||||
items_ids_in_inventory.erase(item_id)
|
||||
|
||||
func _on_escoria_global_changed(global : String) -> void:
|
||||
if !global.begins_with("i/"):
|
||||
return
|
||||
var item = global.rsplit("i/", false)
|
||||
if item.size() == 1:
|
||||
if escoria.esc_runner.globals[global] == "true":
|
||||
add_new_item_by_id(item[0])
|
||||
elif escoria.esc_runner.globals[global] == "false":
|
||||
remove_item_by_id(item[0])
|
||||
else:
|
||||
escoria.report_warnings("inventory_ui.gd:_on_escoria_global_changed()", \
|
||||
["Inventory global " + global + " is neither 'true' nor 'false' (was " + escoria.esc_runner.globals[global] + "). "])
|
||||
else:
|
||||
escoria.report_errors("inventory_ui.gd:_on_escoria_global_changed()", ["Global must contain 1 item name.", "(received: " + global + ")"])
|
||||
Reference in New Issue
Block a user