2026-03-05 20:13:45 +03:00
|
|
|
extends Node
|
|
|
|
|
|
2026-09-13 21:09:42 +03:00
|
|
|
const SPEED_LIMIT: int = 5
|
2026-03-30 00:48:20 +03:00
|
|
|
|
2026-09-13 22:57:25 +03:00
|
|
|
# Write a readable diagnostic copy next to the encrypted save. Never used for loading.
|
|
|
|
|
var save_plaintext_copy: bool = true
|
|
|
|
|
|
2026-03-05 20:13:45 +03:00
|
|
|
var Settings: Dictionary = {
|
|
|
|
|
"filename": "save-0001",
|
|
|
|
|
"sound_volume": 1.0,
|
|
|
|
|
"language": "en",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# TODO getter setter for locale
|
|
|
|
|
|
2026-03-30 00:48:20 +03:00
|
|
|
var Locales: Dictionary = {}
|
|
|
|
|
var Locale = "en"
|
|
|
|
|
|
2026-04-08 00:23:22 +03:00
|
|
|
var OwniverseEntities: Dictionary = {}
|
|
|
|
|
var OwniverseItems: Dictionary = {}
|
2026-04-05 23:26:43 +03:00
|
|
|
var Templates: Dictionary = {}
|
2026-09-15 23:05:05 +03:00
|
|
|
var LifeRules: Dictionary = {}
|
|
|
|
|
var CivilizationRules: Dictionary = {}
|
2026-04-05 23:26:43 +03:00
|
|
|
|
2026-08-02 22:56:54 +03:00
|
|
|
var batch_count: float = 40.0 # must be even not odd
|
2026-07-21 23:54:17 +03:00
|
|
|
|
2026-03-30 00:48:20 +03:00
|
|
|
|
2026-04-08 00:23:22 +03:00
|
|
|
func get_localized_item_description(item_id: String, item: String) -> String:
|
|
|
|
|
if Locales[Locale].has(item_id):
|
|
|
|
|
return Locales[Locale][item_id][item]
|
2026-03-30 00:48:20 +03:00
|
|
|
return ""
|
|
|
|
|
|
2026-04-05 23:26:43 +03:00
|
|
|
|
2026-09-15 22:01:45 +03:00
|
|
|
# Russian templates include the class; legacy English keys omit it.
|
|
|
|
|
func get_template(id: String, category: String, field: String) -> Dictionary:
|
|
|
|
|
var rows: Dictionary = Templates.get(Locale, {})
|
|
|
|
|
for key in [id + "." + category + "." + field, id + "." + field]:
|
|
|
|
|
if rows.has(key):
|
|
|
|
|
return rows[key]
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func render_template(row: Dictionary, variables: Dictionary) -> String:
|
|
|
|
|
var result: String = str(row.get("text", ""))
|
|
|
|
|
for i in range(5):
|
|
|
|
|
var variable: String = str(row.get("value" + str(i), "")).strip_edges()
|
|
|
|
|
var value: String = str(variables.get(variable, "—")) if variable != "" else ""
|
|
|
|
|
result = result.replace("%value" + str(i) + "%", value)
|
|
|
|
|
return result.replace("<br>", "\n").replace("[p]", "\n")
|
|
|
|
|
|
|
|
|
|
|
2026-04-08 00:23:22 +03:00
|
|
|
func get_description(item_id: String, items: Array = []) -> String:
|
2026-09-15 22:01:45 +03:00
|
|
|
var row: Dictionary = Templates.get(Locale, {}).get(item_id, {})
|
|
|
|
|
var variables: Dictionary = {}
|
|
|
|
|
for i in mini(items.size(), 5):
|
|
|
|
|
variables[str(row.get("value" + str(i), ""))] = items[i]
|
|
|
|
|
return render_template(row, variables)
|
2026-04-05 23:26:43 +03:00
|
|
|
|
|
|
|
|
|
2026-06-21 17:27:31 +03:00
|
|
|
func format_number(value: float, digits: int = 0) -> String:
|
|
|
|
|
if value == 0: return ""
|
2026-09-20 20:56:15 +03:00
|
|
|
# Keep ordinary quantities readable, grouping thousands with spaces.
|
|
|
|
|
# Scientific notation is reserved for values whose fixed form would be
|
|
|
|
|
# unwieldy in the UI.
|
|
|
|
|
if absf(value) < 1_000_000_000.0:
|
|
|
|
|
var fixed: String = "%.*f" % [digits, value]
|
|
|
|
|
var parts: PackedStringArray = fixed.split(".", true)
|
|
|
|
|
var integer: String = parts[0]
|
|
|
|
|
var sign: String = ""
|
|
|
|
|
if integer.begins_with("-") or integer.begins_with("+"):
|
|
|
|
|
sign = integer.left(1)
|
|
|
|
|
integer = integer.substr(1)
|
|
|
|
|
var grouped: String = ""
|
|
|
|
|
while integer.length() > 3:
|
|
|
|
|
grouped = " " + integer.right(3) + grouped
|
|
|
|
|
integer = integer.left(integer.length() - 3)
|
|
|
|
|
grouped = sign + integer + grouped
|
|
|
|
|
return grouped + ("." + parts[1] if parts.size() > 1 else "")
|
2026-06-03 23:26:44 +03:00
|
|
|
|
2026-06-21 17:27:31 +03:00
|
|
|
var exp_value = int(floor(log(abs(value)) / log(10.0)))
|
|
|
|
|
var mantissa = value / pow(10.0, exp_value)
|
2026-06-03 23:26:44 +03:00
|
|
|
|
2026-06-21 17:27:31 +03:00
|
|
|
return "%.*f" % [digits, mantissa] + "E%+d" % exp_value
|
2026-06-03 23:26:44 +03:00
|
|
|
|
|
|
|
|
|
2026-03-05 20:13:45 +03:00
|
|
|
func _ready() -> void:
|
2026-09-16 19:54:47 +03:00
|
|
|
CivilizationRules.clear()
|
2026-09-15 23:05:05 +03:00
|
|
|
for row: Dictionary in _read_from_csv("res://data/civilization_rules.txt").values():
|
|
|
|
|
if not CivilizationRules.has(row.stage):
|
2026-09-16 19:54:47 +03:00
|
|
|
CivilizationRules[row.stage] = {"home": row.home, "span": float(row.span), "stars": float(row.stars), "ethics": {},
|
|
|
|
|
"colonizes": row.colonizes == "1", "fights": row.fights == "1", "diplomacy": row.diplomacy == "1", "worships": row.worships == "1"}
|
2026-09-15 23:05:05 +03:00
|
|
|
CivilizationRules[row.stage].ethics[row.ethic] = row
|
2026-09-16 19:54:47 +03:00
|
|
|
var stages: Array = CivilizationRules.keys()
|
|
|
|
|
stages.sort()
|
|
|
|
|
for i in stages.size():
|
|
|
|
|
CivilizationRules[stages[i]].next = stages[i + 1] if i + 1 < stages.size() else "Ct"
|
|
|
|
|
CivilizationRules[stages[i]].previous = stages[i - 1] if i > 0 else "C0.1"
|
|
|
|
|
CivilizationRules[stages[i]].colony = stages[maxi(0, i - 2)]
|
|
|
|
|
CivilizationRules.Ct = {"home": "", "span": 0.0, "stars": 0.0, "ethics": {}, "next": "Ct", "previous": "C4.4", "colonizes": false, "fights": false, "diplomacy": false, "worships": false}
|
2026-09-15 23:05:05 +03:00
|
|
|
LifeRules = _read_from_csv("res://data/life_rules.txt")
|
2026-09-15 07:30:01 +03:00
|
|
|
OwniverseEntities = _read_from_csv("res://data/owniverse_entities.txt")
|
2026-05-26 23:21:44 +03:00
|
|
|
OwniverseItems = _read_from_csv("res://data/owniverse_items.txt")
|
2026-03-30 00:48:20 +03:00
|
|
|
|
2026-05-26 23:21:44 +03:00
|
|
|
Locales["en"] = _read_from_csv("res://data/locale.en.txt")
|
|
|
|
|
Locales["ru"] = _read_from_csv("res://data/locale.ru.txt")
|
2026-04-05 23:26:43 +03:00
|
|
|
|
2026-05-26 23:21:44 +03:00
|
|
|
Templates["en"] = _read_from_csv("res://data/templates.en.txt")
|
2026-09-15 22:01:45 +03:00
|
|
|
Templates["ru"] = _read_from_csv("res://data/templates.ru.txt")
|
2026-04-08 00:23:22 +03:00
|
|
|
|
2026-03-05 20:13:45 +03:00
|
|
|
|
|
|
|
|
func _read_from_csv(file_path: String) -> Dictionary:
|
|
|
|
|
if not FileAccess.file_exists(file_path):
|
|
|
|
|
printerr("File not found: " + file_path)
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
var file = FileAccess.open(file_path, FileAccess.READ)
|
|
|
|
|
if file == null:
|
|
|
|
|
printerr("File can not be opened:" + file_path)
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
var headers: PackedStringArray = []
|
|
|
|
|
var data: Array = []
|
|
|
|
|
|
|
|
|
|
if not file.eof_reached():
|
|
|
|
|
headers = PackedStringArray()
|
|
|
|
|
for val in file.get_csv_line("|"):
|
|
|
|
|
headers.append(val.strip_edges())
|
|
|
|
|
|
|
|
|
|
while not file.eof_reached():
|
|
|
|
|
var raw_row = file.get_csv_line("|")
|
2026-05-26 23:21:44 +03:00
|
|
|
if raw_row.is_empty(): # пропускаем пустые строки
|
2026-03-05 20:13:45 +03:00
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
var trimmed_row: PackedStringArray = []
|
|
|
|
|
for val in raw_row:
|
|
|
|
|
trimmed_row.append(val.strip_edges())
|
|
|
|
|
|
|
|
|
|
data.append(trimmed_row)
|
|
|
|
|
|
|
|
|
|
file.close()
|
|
|
|
|
|
|
|
|
|
var dict_data: Dictionary = {}
|
|
|
|
|
for row in data:
|
|
|
|
|
if row.size() < 2: continue
|
|
|
|
|
|
|
|
|
|
if row.size() == headers.size():
|
|
|
|
|
var dict: Dictionary = {}
|
|
|
|
|
for i in headers.size():
|
|
|
|
|
dict[headers[i]] = row[i]
|
|
|
|
|
dict_data[row[0]] = dict
|
|
|
|
|
|
|
|
|
|
return dict_data
|
|
|
|
|
|
2026-05-05 23:49:12 +03:00
|
|
|
|
|
|
|
|
func _read_folder_list(folder_name: String) -> void:
|
|
|
|
|
var dir := DirAccess.open(folder_name)
|
|
|
|
|
if dir == null: printerr("Could not open folder: ", folder_name); return
|
|
|
|
|
dir.list_dir_begin()
|
|
|
|
|
for file: String in dir.get_files():
|
|
|
|
|
print(" → ", file)
|
|
|
|
|
|
|
|
|
|
|
2026-03-05 20:13:45 +03:00
|
|
|
func _print_dict(dict_to_print: Dictionary):
|
|
|
|
|
for d in dict_to_print:
|
|
|
|
|
print(d, " → ", dict_to_print[d])
|