ChocolateBird/scripts/managers/Common.gd

253 lines
8.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends Node
const CONFIG_FILENAME := 'user://config.ini'
var base_resolution: Vector2 = Vector2(ProjectSettings.get_setting('display/window/size/width'), ProjectSettings.get_setting('display/window/size/height')) # Vector2(640, 360)
var shrink_timer := Timer.new()
var last_resolution_pre_shrink := Vector2.ZERO
var config := ConfigFile.new()
var SNES_PSX_addresses := load_tsv('res://data/5/addresses_SNES_PSX.tsv')
# Canonicalize english stat names within the codebase
# This is based off of FF8, and is more or less RPGe but with Speed instead of Agility
# I don't like Vigor or Stamina, Agility could go either way but I side with FF8 and FF9 rather than FF10
enum {
STAT_STRENGTH = 0,
STAT_STR = 0,
STAT_SPEED = 1,
STAT_SPD = 1,
STAT_VITALITY = 2,
STAT_VIT = 2,
STAT_MAGIC = 3,
STAT_MAG = 3,
}
static func eval(expression: String):
var ex = Expression.new()
match ex.parse(expression):
OK:
return ex.execute()
var error:
print_debug(ex.get_error_text(), error)
static func load_json(filename: String): # Valid JSON will return Array or Dictionary. int error code for anything else.
var file := File.new()
match file.open(filename, File.READ):
OK:
var result = JSON.parse(file.get_as_text())
match result.error:
OK:
return result.result
var error:
print_debug(result.error_string)
print_debug(result.error_line)
return error
var error:
return error
static func _to_json(data, current_indent: int = 0) -> String:
match typeof(data):
TYPE_RAW_ARRAY: # XX: add more as required
return str(data)
TYPE_ARRAY:
var contents := PoolStringArray()
for value in data:
contents.append(_to_json(value, current_indent + 1))
return '[' + ', '.join(contents) + ']\n'
TYPE_DICTIONARY:
var contents := PoolStringArray()
for k in data:
contents.append('"%s": %s' % [k, _to_json(data[k], current_indent + 1)])
return '{' + ', '.join(contents) + '}\n'
_:
return JSON.print(data)
static func save_json(filename: String, data) -> int: # Returns error code but Error is not usable as return signature
# Oh cool, 3.6.beta2's json serialization is borked on PoolByteArray and PoolVector2Array
# print(JSON.print([PoolByteArray([0,1]), PoolIntArray([0,1]), PoolRealArray([0,1]), PoolVector2Array([Vector2.ONE, Vector2.LEFT]), PoolStringArray(['hello', 'world'])]))
# ["[0, 1]",[0,1],[0,1],"[(1, 1), (-1, 0)]",["hello","world"]]
var file := File.new()
var error := file.open(filename, File.WRITE)
if error != OK:
return error
# file.store_line(to_json(data))
# file.store_line(JSON.print(data))
file.store_line(_to_json(data, 0))
return OK
static func load_glyph_table(filename: String) -> PoolStringArray:
var output = PoolStringArray()
var file = File.new()
match file.open(filename, File.READ):
OK:
var l: int = file.get_len()
while file.get_position() < l:
output.append(file.get_line().replace('\\n', '\n').replace('', "'").replace('', '"')) # TODO: Fix font to accept the special quote marks
var error:
print_debug('Failed to open glyph table "%s" - %d' % [filename, error])
return output
static func genericize_type(type: int) -> int:
match type:
TYPE_RAW_ARRAY, TYPE_INT_ARRAY, TYPE_REAL_ARRAY, TYPE_STRING_ARRAY:
return TYPE_ARRAY
TYPE_INT:
return TYPE_REAL
_:
return type
static func are_values_equal(v1, v2, fuzzy_types: bool = true) -> bool:
var t1 := typeof(v1)
var t2 := typeof(v2)
if fuzzy_types:
t1 = genericize_type(t1)
t2 = genericize_type(t2)
if t1 != t2:
print_debug('Type mismatch: %s vs %s' % [t1, t2])
return false
match t1:
TYPE_ARRAY:
return are_arrays_equal(v1, v2, fuzzy_types)
TYPE_DICTIONARY:
return are_dictionaries_equal(v1, v2, fuzzy_types)
_:
return v1 == v2
static func are_dictionaries_equal(d1: Dictionary, d2: Dictionary, fuzzy_types: bool = true) -> bool:
if len(d1) != len(d2):
print_debug('Array lengths not equal: %s vs %s' % [len(d1), len(d2)])
return false
for k in d1:
if not (k in d2):
print_debug('d1 has key "%s" that d2 does not' % k)
return false
if not are_values_equal(d1[k], d2[k], fuzzy_types):
print_debug('d1["%s"] != d2["%s"]' % [k, k])
print_debug(d1[k])
print_debug(d2[k])
return false
return true
static func are_arrays_equal(a1: Array, a2: Array, fuzzy_types: bool = true) -> bool:
var l1 := len(a1)
if l1 != len(a2):
print_debug('Array lengths not equal: %s vs %s' % [l1, len(a2)])
return false
for i in l1:
if not are_values_equal(a1[i], a2[i], fuzzy_types):
print_debug('a1[%d] != a2[%d]' % [i, i])
print_debug(a1[i])
print_debug(a2[i])
return false
return true
static func limited_eval(token: String):
# Try int literal
if token.is_valid_integer():
return int(token)
# Try float literal
if token.is_valid_float():
return float(token)
# Try hexadecimal literal
if token.is_valid_hex_number(true):
return token.hex_to_int()
# Try bool literal
match token.to_lower():
'true':
return true
'false':
return false
# It's just a string
return token
static func load_tsv(filename: String, delimiter: String = '\t') -> Dictionary:
var file := File.new()
var error := file.open(filename, File.READ)
if error == OK:
var headers := file.get_csv_line(delimiter)
var n := headers.size()
var output = {}
while file.get_position() < file.get_len():
var line := file.get_csv_line(delimiter)
var entry := {}
for i in range(1, n):
if line.size() > i:
entry[headers[i]] = limited_eval(line[i])
else:
entry[headers[i]] = limited_eval('')
output[line[0]] = entry
return output
print_debug(error)
return {}
func shrink_to_integer():
var size := OS.get_window_size()
var scale_vec := size / base_resolution
var scale_f := max(min(scale_vec.x, scale_vec.y), 1)
var scale := int(scale_f)
var new_window_size := base_resolution*scale
if OS.get_window_size() != new_window_size: # avoid retriggering this event forever
OS.set_window_size(new_window_size)
print('resized to scale %d %s' % [scale, new_window_size])
get_tree().set_screen_stretch(SceneTree.STRETCH_MODE_VIEWPORT, SceneTree.STRETCH_ASPECT_KEEP, base_resolution*scale, scale)
func update_window_scale():
if OS.window_size < base_resolution:
OS.window_size = base_resolution
var size := OS.get_window_size()
var scale_vec := size / base_resolution
var scale_f := max(min(scale_vec.x, scale_vec.y), 1)
var scale := int(scale_f)
if ProjectSettings.get_setting('display/window/size/snap_to_integer'):
var target_size := base_resolution * scale
# OS.window_size = base_resolution*scale
if size == target_size:
print_debug('Config is to shrink down to integer scale, but %s is already a perfect %dx scale' % [size, scale])
elif size == self.last_resolution_pre_shrink:
# Don't fight the WM if it sets the window size back again
print_debug("Config is to shrink down to integer scale, but %s was the last external resize event, so we won't fight the window manager" % size)
else:
var delay = 1.0 - min((size - self.last_resolution_pre_shrink).length_squared()/1000, 0.95)
print_debug('Setting lastres to %s, will resize to %dx == %s in %f seconds' % [self.last_resolution_pre_shrink, scale, target_size, delay])
self.last_resolution_pre_shrink = size
shrink_timer.paused = false
shrink_timer.start(delay)
get_tree().set_screen_stretch(SceneTree.STRETCH_MODE_VIEWPORT, SceneTree.STRETCH_ASPECT_KEEP, base_resolution*scale, scale)
static func game_time_frames_to_hhmm(game_time_frames: int) -> String:
var game_seconds = game_time_frames / 60
var game_minutes = game_seconds / 60
var game_hours = game_minutes / 60
return '%d:%02d' % [game_hours, game_minutes % 60]
func load_config():
match self.config.load(CONFIG_FILENAME):
OK:
print('loaded config.ini')
var err:
print('failed to load config.ini: %d'%err)
func save_config():
match self.config.save(CONFIG_FILENAME):
OK:
print('saved config.ini')
var err:
print('failed to save config.ini: %d'%err)
func _ready():
load_config()
match shrink_timer.connect('timeout', self, 'shrink_to_integer'):
OK:
pass
_:
assert(false)
shrink_timer.one_shot = true
add_child(shrink_timer)
match get_tree().connect('screen_resized', self, 'update_window_scale'):
OK:
pass
_:
assert(false)
OS.set_min_window_size(base_resolution*2)