diff --git a/game/autoload/CursorManager.gd b/game/autoload/CursorManager.gd new file mode 100644 index 00000000..dd1a2a3b --- /dev/null +++ b/game/autoload/CursorManager.gd @@ -0,0 +1,28 @@ +extends Node + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +export(Texture) var default_cursor : Texture +export(Texture) var loot_cursor : Texture +export(Texture) var attack_cursor : Texture +export(Texture) var speak_cursor : Texture +export(Texture) var drag_drop_cursor : Texture +export(Texture) var forbidden_cursor : Texture +export(Texture) var text_cursor : Texture +export(Vector2) var text_cursor_hotspot : Vector2 + +func _ready(): + # Changes only the arrow shape of the cursor. + # This is similar to changing it in the project settings. + Input.set_custom_mouse_cursor(default_cursor, Input.CURSOR_ARROW) + Input.set_custom_mouse_cursor(attack_cursor, Input.CURSOR_MOVE) + Input.set_custom_mouse_cursor(loot_cursor, Input.CURSOR_CROSS) + Input.set_custom_mouse_cursor(speak_cursor, Input.CURSOR_HELP) + Input.set_custom_mouse_cursor(drag_drop_cursor, Input.CURSOR_CAN_DROP) + Input.set_custom_mouse_cursor(forbidden_cursor, Input.CURSOR_FORBIDDEN) + Input.set_custom_mouse_cursor(text_cursor, Input.CURSOR_IBEAM, text_cursor_hotspot) + + # Changes a specific shape of the cursor (here, the I-beam shape). +# Input.set_custom_mouse_cursor(beam, Input.CURSOR_IBEAM) diff --git a/game/autoload/CursorManager.tscn b/game/autoload/CursorManager.tscn new file mode 100644 index 00000000..ae875936 --- /dev/null +++ b/game/autoload/CursorManager.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=9 format=2] + +[ext_resource path="res://autoload/CursorManager.gd" type="Script" id=1] +[ext_resource path="res://data/cursors/arrow16.png" type="Texture" id=2] +[ext_resource path="res://data/cursors/loot16.png" type="Texture" id=3] +[ext_resource path="res://data/cursors/attack16.png" type="Texture" id=4] +[ext_resource path="res://data/cursors/speak.png" type="Texture" id=5] +[ext_resource path="res://data/cursors/drag_drop.png" type="Texture" id=6] +[ext_resource path="res://data/cursors/forbidden.png" type="Texture" id=7] +[ext_resource path="res://data/cursors/ibeam.png" type="Texture" id=8] + + +[node name="CursorManager" type="Node"] +script = ExtResource( 1 ) +default_cursor = ExtResource( 2 ) +loot_cursor = ExtResource( 3 ) +attack_cursor = ExtResource( 4 ) +speak_cursor = ExtResource( 5 ) +drag_drop_cursor = ExtResource( 6 ) +forbidden_cursor = ExtResource( 7 ) +text_cursor = ExtResource( 8 ) +text_cursor_hotspot = Vector2( 2, 11 ) diff --git a/game/autoload/EntityDataManager.gd b/game/autoload/EntityDataManager.gd new file mode 100644 index 00000000..cdfb1ef1 --- /dev/null +++ b/game/autoload/EntityDataManager.gd @@ -0,0 +1,331 @@ +extends Node + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +export (PackedScene) var player_scene : PackedScene +export (PackedScene) var networked_player_scene : PackedScene +export (PackedScene) var mob_scene : PackedScene +export (PackedScene) var player_display_scene : PackedScene +export (String) var spawn_parent_path : String = "/root/Main" + +var _spawn_parent : Node = null + +var _next_entity_guid : int = 0 + +var _players : Array +var _mobs : Array + +func _ready(): + _spawn_parent = get_node(spawn_parent_path) + +# get_tree().connect("network_peer_connected", self, "_player_connected") +# get_tree().connect("network_peer_disconnected", self, "_player_disconnected") +# get_tree().connect("connected_to_server", self, "_connected_ok") +# get_tree().connect("connection_failed", self, "_connected_fail") +# get_tree().connect("server_disconnected", self, "_server_disconnected") + pass + +func spawn_for(player : Entity, target: Entity) -> void: +# print("spawnfor " + target.name) + rpc_id(player.get_network_master(), "creceive_spawn_for", to_json(target.to_dict()), target.name, target.translation) + +func despawn_for(player : Entity, target: Entity) -> void: +# print("despawnfor " + target.name) + rpc_id(player.get_network_master(), "creceive_despawn_for", target.get_path()) + +remote func creceive_spawn_for(data: String, global_name : String, position: Vector3) -> void: +# print("recspawnfor " + global_name) + var entity : Entity = networked_player_scene.instance() + + var spawn_parent = _spawn_parent.current_scene + + spawn_parent.add_child(entity) + entity.owner = spawn_parent + entity.name = str(global_name) + entity.from_dict(parse_json(data)) + + entity.translation = position + + Logger.info("Player spawned ") + + _players.append(entity) + +remote func creceive_despawn_for(path : NodePath) -> void: +# print("recdespawnfor " + path) + var ent = get_node_or_null(path) + + if ent: + ent.queue_free() + +func spawn_networked_player_from_data(data : String, position : Vector3, network_owner : int) -> Entity: + var entity : Entity = networked_player_scene.instance() + + _next_entity_guid += 1 + + var spawn_parent = _spawn_parent.current_scene + + spawn_parent.add_child(entity) + entity.owner = spawn_parent + entity.name = str(network_owner) + entity.from_dict(parse_json(data)) + + entity.set_network_master(network_owner) + entity.translation = position + + Logger.info("Player spawned ") + + _players.append(entity) + + rpc_id(network_owner, "spawn_owned_player", data, position) + + return entity + +puppet func spawn_owned_player(data : String, position : Vector3) -> void: + var entity : Entity = player_scene.instance() + + var spawn_parent = _spawn_parent.current_scene + + spawn_parent.add_child(entity) + entity.owner = spawn_parent + + entity.from_dict(parse_json(data)) + entity.name = str(multiplayer.get_network_unique_id()) + entity.translation = position + entity.set_network_master(multiplayer.get_network_unique_id()) + + Logger.info("Player spawned ") + + +func load_player(file_name : String, position : Vector3, network_owner : int) -> Entity: +# var createinfo : EntityCreateInfo = EntityCreateInfo.new() +# +# var cls : EntityData = Entities.get_player_character_data(class_id) +# +# var class_profile : ClassProfile = Profiles.get_class_profile(class_id) +# +# createinfo.entity_data = cls +# createinfo.player_name = name +# createinfo.level = class_profile.level +# createinfo.xp = class_profile.xp +# createinfo.entity_controller = EntityEnums.ENITIY_CONTROLLER_PLAYER + + var entity : Entity = player_scene.instance() + + _next_entity_guid += 1 + + var spawn_parent = _spawn_parent.current_scene + + spawn_parent.add_child(entity) + entity.owner = spawn_parent + + entity.from_dict(load_file(file_name)) + + entity.translation = position +# entity.initialize(createinfo) + entity.set_network_master(network_owner) + + Logger.info("Player spawned ") + + _players.append(entity) + + return entity + +func spawn_display_player(name : String) -> Entity: + var entity : Entity = player_display_scene.instance() as Entity + + entity.name = name + + Logger.info("Player Display spawned") + + return entity + +func spawn_player_for_menu(class_id : int, name : String, parent : Node) -> Entity: + var createinfo : EntityCreateInfo = EntityCreateInfo.new() + var cls : EntityData = Entities.get_player_character_data(class_id) + var class_profile : ClassProfile = Profiles.get_class_profile(class_id) + + createinfo.entity_data = cls + createinfo.player_name = name + createinfo.level = class_profile.level + createinfo.xp = class_profile.xp + createinfo.entity_controller = EntityEnums.ENITIY_CONTROLLER_PLAYER + + var entity : Entity = player_display_scene.instance() as Entity + + parent.add_child(entity) + entity.owner = parent + + entity.initialize(createinfo) + + return entity + +func spawn_networked_player(class_id : int, position : Vector3, name : String, node_name : String, sid : int) -> Entity: + var createinfo : EntityCreateInfo = EntityCreateInfo.new() + + var cls : EntityData = Entities.get_entity_data(class_id) + + var class_profile : ClassProfile = Profiles.get_class_profile(class_id) + + createinfo.entity_data = cls + createinfo.player_name = name + createinfo.level = class_profile.level + createinfo.xp = class_profile.xp + createinfo.entity_controller = EntityEnums.ENITIY_CONTROLLER_PLAYER + + var entity : Entity = spawn(createinfo, true, position, node_name) + + if get_tree().is_network_server(): + entity.set_network_master(sid) + + Logger.info("Player spawned " + str(createinfo)) + + _players.append(entity) + + return entity + +func spawn_player(class_id : int, position : Vector3, name : String, node_name : String, network_owner : int) -> Entity: + var createinfo : EntityCreateInfo = EntityCreateInfo.new() + + var cls : EntityData = Entities.get_player_character_data(class_id) + + var class_profile : ClassProfile = Profiles.get_class_profile(class_id) + + createinfo.entity_data = cls + createinfo.player_name = name + createinfo.level = class_profile.level + createinfo.xp = class_profile.xp + createinfo.entity_controller = EntityEnums.ENITIY_CONTROLLER_PLAYER + + var entity : Entity = spawn(createinfo, false, position, node_name) + + entity.set_network_master(network_owner) + + Logger.info("Player spawned " + str(createinfo)) + + _players.append(entity) + + return entity + +func spawn_mob(class_id : int, level : int, position : Vector3) -> Entity: + var createinfo : EntityCreateInfo = EntityCreateInfo.new() + + var cls : EntityData = Entities.get_entity_data(class_id) + + if cls == null: + print("clsnull") + + createinfo.entity_data = cls + createinfo.player_name = "Mob" + createinfo.level = level +# createinfo.level = level + createinfo.entity_controller = EntityEnums.ENITIY_CONTROLLER_MOB + + var entity : Entity = spawn(createinfo, false, position) + entity.slevelup(level - 1) + + Logger.info("Mob spawned " + str(createinfo)) + + _mobs.append(entity) + + return entity + +func spawn(createinfo : EntityCreateInfo, networked : bool, position : Vector3, node_name : String = "") -> Entity: + var entity_node : Entity = null + + if not networked: + if createinfo.entity_controller == EntityEnums.ENITIY_CONTROLLER_PLAYER: + entity_node = player_scene.instance() + else: + entity_node = mob_scene.instance() + else: + entity_node = networked_player_scene.instance() + + if entity_node == null: + print("EntityManager: entity node is null") + return null + + if node_name == "": + entity_node.name += "_" + str(_next_entity_guid) + else: + entity_node.name = node_name + + _next_entity_guid += 1 + + var spawn_parent = _spawn_parent.current_scene + + spawn_parent.add_child(entity_node) + entity_node.owner = spawn_parent + + entity_node.translation = position + + entity_node.initialize(createinfo) + + return entity_node + + +func _player_connected(id): + pass # Will go unused; not useful here. + +func _player_disconnected(id): + #player_info.erase(id) # Erase player from info. + pass + +func _connected_ok(): + # Only called on clients, not server. Send my ID and info to all the other peers. + #rpc("register_player", get_tree().get_network_unique_id(), my_info) + pass + +func _server_disconnected(): + pass # Server kicked us; show error and abort. + +func _connected_fail(): + pass # Could not even connect to server; abort. + +remote func register_player(id, info): + # Store the info +# player_info[id] = info + # If I'm the server, let the new guy know about existing players. +# if get_tree().is_network_server(): +# # Send my info to new player +# rpc_id(id, "register_player", 1, my_info) +# # Send the info of existing players +# for peer_id in player_info: +# rpc_id(id, "register_player", peer_id, player_info[peer_id]) + + # Call function to update lobby UI here + pass + +func load_file(file_name : String) -> Dictionary: + + var f : File = File.new() + + if f.open("user://characters/" + file_name, File.READ) == OK: + var st : String = f.get_as_text() + f.close() + + var json_err : String = validate_json(st) + + if json_err != "": + Logger.error("Save corrupted! " + file_name) + Logger.error(json_err) + return Dictionary() + + var p = parse_json(st) + + if typeof(p) != TYPE_DICTIONARY: + Logger.error("Save corrupted! Not Dict! " + file_name) + return Dictionary() + + if p is Dictionary: + return p as Dictionary + + return Dictionary() + +func save_player(player: Entity, file_name : String) -> void: + var f : File = File.new() + + if f.open("user://characters/" + file_name, File.WRITE) == OK: + f.store_string(to_json(player.to_dict())) + f.close() diff --git a/game/autoload/EntityDataManager.tscn b/game/autoload/EntityDataManager.tscn new file mode 100644 index 00000000..7fde4cfe --- /dev/null +++ b/game/autoload/EntityDataManager.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://autoload/EntityDataManager.gd" type="Script" id=1] +[ext_resource path="res://player/NetworkedPlayer.tscn" type="PackedScene" id=2] +[ext_resource path="res://player/Player.tscn" type="PackedScene" id=3] +[ext_resource path="res://player/Mob.tscn" type="PackedScene" id=4] +[ext_resource path="res://player/DisplayPlayer.tscn" type="PackedScene" id=5] + +[node name="EntityDataManager" type="EntityDataManager"] +xp_data_path = "res://data/xp/xp_data.tres" +entity_datas_folder = "res://data/entities" +spells_folder = "res://data/spells" +auras_folder = "res://data/auras" +craft_data_folder = "res://data/crafting" +item_template_folder = "res://data/item_templates" +mob_data_folder = "res://data/mob_data" +player_character_data_folder = "res://data/player_character_data" +script = ExtResource( 1 ) +player_scene = ExtResource( 3 ) +networked_player_scene = ExtResource( 2 ) +mob_scene = ExtResource( 4 ) +player_display_scene = ExtResource( 5 ) diff --git a/game/autoload/Logger.gd b/game/autoload/Logger.gd new file mode 100644 index 00000000..47a8cd26 --- /dev/null +++ b/game/autoload/Logger.gd @@ -0,0 +1,637 @@ +# Copyright (c) 2016 KOBUGE Games +# Distributed under the terms of the MIT license. +# https://github.com/KOBUGE-Games/godot-logger/blob/master/LICENSE.md +# +# Upstream repo: https://github.com/KOBUGE-Games/godot-logger + +extends Node # Needed to work as a singleton + +##================## +## Inner classes ## +##================## + +class Logfile: + # TODO: Godot doesn't support docstrings for inner classes, GoDoIt (GH-1320) + # """Class for log files that can be shared between various modules.""" + var file = null + var path = "" + var queue_mode = QUEUE_NONE + var buffer = PoolStringArray() + var buffer_idx = 0 + + func _init(_path, _queue_mode = QUEUE_NONE): + file = File.new() + if validate_path(_path): + path = _path + queue_mode = _queue_mode + buffer.resize(FILE_BUFFER_SIZE) + + func get_path(): + return path + + func set_queue_mode(new_mode): + queue_mode = new_mode + + func get_queue_mode(): + return queue_mode + + func get_write_mode(): + if not file.file_exists(path): + return File.WRITE # create + else: + return File.READ_WRITE # append + + func validate_path(ppath): + """Validate the path given as argument, making it possible to write to + the designated file or folder. Returns whether the path is valid.""" + if !(ppath.is_abs_path() or ppath.is_rel_path()): + print("[ERROR] [logger] The given path '%s' is not valid." % ppath) + return false + var dir = Directory.new() + var base_dir = ppath.get_base_dir() + if not dir.dir_exists(base_dir): + # TODO: Move directory creation to the function that will actually *write* + var err = dir.make_dir_recursive(base_dir) + if err: + print("[ERROR] [logger] Could not create the '%s' directory; exited with error %d." \ + % [base_dir, err]) + return false + else: + print("[INFO] [logger] Successfully created the '%s' directory." % base_dir) + return true + + func flush_buffer(): + """Flush the buffer, i.e. write its contents to the target file.""" + if buffer_idx == 0: + return # Nothing to write + var err = file.open(path, get_write_mode()) + if err: + print("[ERROR] [logger] Could not open the '%s' log file; exited with error %d." \ + % [path, err]) + return + file.seek_end() + for i in range(buffer_idx): + file.store_line(buffer[i]) + file.close() + buffer_idx = 0 # We don't clear the memory, we'll just overwrite it + + func write(output, level): + """Write the string at the end of the file (append mode), following + the queue mode.""" + var queue_action = queue_mode + if queue_action == QUEUE_SMART: + if level >= WARN: # Don't queue warnings and errors + queue_action = QUEUE_NONE + flush_buffer() + else: # Queue the log, not important enough for "smart" + queue_action = QUEUE_ALL + + if queue_action == QUEUE_NONE: + var err = file.open(path, get_write_mode()) + if err: + print("[ERROR] [logger] Could not open the '%s' log file; exited with error %d." \ + % [path, err]) + return + file.seek_end() + file.store_line(output) + file.close() + + if queue_action == QUEUE_ALL: + buffer[buffer_idx] = output + buffer_idx += 1 + if buffer_idx >= FILE_BUFFER_SIZE: + flush_buffer() + + func get_config(): + return { + "path": get_path(), + "queue_mode": get_queue_mode() + } + + +class Module: + # """Class for customizable logging modules.""" + var name = "" + var output_level = 0 + var output_strategies = [] + var logfile = null + + func _init(_name, _output_level, _output_strategies, _logfile): + name = _name + set_output_level(_output_level) + + if typeof(_output_strategies) == TYPE_INT: # Only one strategy, use it for all + #warning-ignore:unused_variable + for i in range(0, LEVELS.size()): + output_strategies.append(_output_strategies) + else: + for strategy in _output_strategies: # Need to force deep copy + output_strategies.append(strategy) + + set_logfile(_logfile) + + func get_name(): + return name + + func set_output_level(level): + """Set the custom minimal level for the output of the module. + All levels greater or equal to the given once will be output based + on their respective strategies, while levels lower than the given one + will be discarded.""" + if not level in range(0, LEVELS.size()): + print("[ERROR] [%s] The level must be comprised between 0 and %d." \ + % [PLUGIN_NAME, LEVELS.size() - 1]) + return + output_level = level + + func get_output_level(): + return output_level + + func set_common_output_strategy(output_strategy_mask): + """Set the common output strategy mask for all levels of the module.""" + if not output_strategy_mask in range(0, MAX_STRATEGY + 1): + print("[ERROR] [%s] The output strategy mask must be comprised between 0 and %d." \ + % [PLUGIN_NAME, MAX_STRATEGY]) + return + for i in range(0, LEVELS.size()): + output_strategies[i] = output_strategy_mask + + func set_output_strategy(output_strategy_mask, level = -1): + """Set the output strategy for the given level or (by default) all + levels of the module.""" + if not output_strategy_mask in range(0, MAX_STRATEGY + 1): + print("[ERROR] [%s] The output strategy mask must be comprised between 0 and %d." \ + % [PLUGIN_NAME, MAX_STRATEGY]) + return + if level == -1: # Set for all levels + for i in range(0, LEVELS.size()): + output_strategies[i] = output_strategy_mask + else: + if not level in range(0, LEVELS.size()): + print("[ERROR] [%s] The level must be comprised between 0 and %d." \ + % [PLUGIN_NAME, LEVELS.size() - 1]) + return + output_strategies[level] = output_strategy_mask + + func get_output_strategy(level = -1): + if level == -1: + return output_strategies + else: + return output_strategies[level] + + func set_logfile(new_logfile): + """Set the Logfile instance for the module.""" + logfile = new_logfile + + func get_logfile(): + return logfile + + func get_config(): + return { + "name": get_name(), + "output_level": get_output_level(), + "output_strategies": get_output_strategy(), + "logfile_path": get_logfile().get_path() + } + + +##=============## +## Constants ## +##=============## + +const PLUGIN_NAME = "logger" + +# Logging levels - the array and the integers should be matching +const LEVELS = ["VERBOSE", "DEBUG", "INFO", "WARN", "ERROR"] +const VERBOSE = 0 +const DEBUG = 1 +const INFO = 2 +const WARN = 3 +const ERROR = 4 + +# Output strategies +const STRATEGY_MUTE = 0 +const STRATEGY_PRINT = 1 +const STRATEGY_FILE = 2 +const STRATEGY_MEMORY = 4 +const MAX_STRATEGY = STRATEGY_MEMORY*2 - 1 + +# Output format identifiers +const FORMAT_IDS = { + "level": "{LVL}", + "module": "{MOD}", + "message": "{MSG}" +} + +# Queue modes +const QUEUE_NONE = 0 +const QUEUE_ALL = 1 +const QUEUE_SMART = 2 + +const FILE_BUFFER_SIZE = 30 + + +##=============## +## Variables ## +##=============## + +# Configuration +var default_output_level = WARN +# TODO: Find (or implement in Godot) a more clever way to achieve that +var default_output_strategies = [STRATEGY_PRINT, STRATEGY_PRINT, STRATEGY_PRINT, STRATEGY_PRINT, STRATEGY_PRINT] +var default_logfile_path = "user://%s.log" % ProjectSettings.get_setting("application/config/name") +var default_configfile_path = "user://%s.cfg" % PLUGIN_NAME + +# e.g. "[INFO] [main] The young alpaca started growing a goatie." +var output_format = "[{LVL}] [{MOD}] {MSG}" + +# Specific to STRATEGY_MEMORY +var max_memory_size = 30 +var memory_buffer = [] +var memory_idx = 0 +var memory_first_loop = true +var memory_cache = [] +var invalid_memory_cache = false + +# Holds default and custom modules and logfiles defined by the user +# Default modules are initialized in _init via add_module +var logfiles = {} +var modules = {} + + +##=============## +## Functions ## +##=============## + +func put(level, message, module = "main"): + """Log a message in the given module with the given logging level.""" + var module_ref = get_module(module) + var output_strategy = module_ref.get_output_strategy(level) + if output_strategy == STRATEGY_MUTE or module_ref.get_output_level() > level: + return # Out of scope + + var output = format(output_format, level, module, message) + + if output_strategy & STRATEGY_PRINT: + print(output) + + if output_strategy & STRATEGY_FILE: + module_ref.get_logfile().write(output, level) + + if output_strategy & STRATEGY_MEMORY: + memory_buffer[memory_idx] = output + memory_idx += 1 + invalid_memory_cache = true + if memory_idx >= max_memory_size: + memory_idx = 0 + memory_first_loop = false + +# Helper functions for each level +# ------------------------------- + +func verbose(message, module = "main"): + """Log a message in the given module with level VERBOSE.""" + put(VERBOSE, message, module) + +func debug(message, module = "main"): + """Log a message in the given module with level DEBUG.""" + put(DEBUG, message, module) + +func info(message, module = "main"): + """Log a message in the given module with level INFO.""" + put(INFO, message, module) + +func warn(message, module = "main"): + """Log a message in the given module with level WARN.""" + put(WARN, message, module) + +func error(message, module = "main"): + """Log a message in the given module with level ERROR.""" + put(ERROR, message, module) + +# Module management +# ----------------- + +func add_module(name, output_level = default_output_level, \ + output_strategies = default_output_strategies, logfile = null): + """Add a new module with the given parameter or (by default) the + default ones. + Returns a reference to the instanced module.""" + if modules.has(name): + info("The module '%s' already exists; discarding the call to add it anew." \ + % name, PLUGIN_NAME) + else: + if logfile == null: + logfile = get_logfile(default_logfile_path) + modules[name] = Module.new(name, output_level, output_strategies, logfile) + return modules[name] + +func get_module(module = "main"): + """Retrieve the given module if it exists; if not, it will be created.""" + if not modules.has(module): + info("The requested module '%s' does not exist. It will be created with default values." \ + % module, PLUGIN_NAME) + add_module(module) + return modules[module] + +func get_modules(): + """Retrieve the dictionary containing all modules.""" + return modules + +# Logfiles management +# ------------------- + +func set_default_logfile_path(new_logfile_path, keep_old = false): + """Sets the new default logfile path. Unless configured otherwise with + the optional keep_old argument, it will replace the logfile for all + modules which were configured for the previous logfile path.""" + if new_logfile_path == default_logfile_path: + return # Nothing to do + + var old_logfile = get_logfile(default_logfile_path) + var new_logfile = null + if logfiles.has(new_logfile_path): # Already exists + new_logfile = logfiles[new_logfile_path] + else: # Create a new logfile + new_logfile = add_logfile(new_logfile_path) + logfiles[new_logfile_path] = new_logfile + + if not keep_old: # Replace the old defaut logfile in all modules that used it + for module in modules.values(): + if module.get_logfile() == old_logfile: + module.set_logfile(new_logfile) + logfiles.erase(default_logfile_path) + default_logfile_path = new_logfile_path + +func get_default_logfile_path(): + """Return the default logfile path.""" + return default_logfile_path + +func add_logfile(logfile_path = default_logfile_path): + """Add a new logfile that can then be attached to one or more modules. + Returns a reference to the instanced logfile.""" + if logfiles.has(logfile_path): + info("A logfile pointing to '%s' already exists; discarding the call to add it anew." \ + % logfile_path, PLUGIN_NAME) + else: + logfiles[logfile_path] = Logfile.new(logfile_path) + return logfiles[logfile_path] + +func get_logfile(logfile_path): + """Retrieve the given logfile if it exists, otherwise returns null.""" + if not logfiles.has(logfile_path): + warn("The requested logfile pointing to '%s' does not exist." % logfile_path, PLUGIN_NAME) + return null + else: + return logfiles[logfile_path] + +func get_logfiles(): + """Retrieve the dictionary containing all logfiles.""" + return logfiles + +# Default output configuration +# ---------------------------- + +func set_default_output_strategy(output_strategy_mask, level = -1): + """Set the default output strategy mask of the given level or (by + default) all levels for all modules without a custom strategy.""" + if not output_strategy_mask in range(0, MAX_STRATEGY + 1): + error("The output strategy mask must be comprised between 0 and %d." \ + % MAX_STRATEGY, PLUGIN_NAME) + return + if level == -1: # Set for all levels + for i in range(0, LEVELS.size()): + default_output_strategies[i] = output_strategy_mask + info("The default output strategy mask was set to '%d' for all levels." \ + % [output_strategy_mask], PLUGIN_NAME) + else: + if not level in range(0, LEVELS.size()): + error("The level must be comprised between 0 and %d." % (int(LEVELS.size()) - 1), PLUGIN_NAME) + return + default_output_strategies[level] = output_strategy_mask + info("The default output strategy mask was set to '%d' for the '%s' level." \ + % [output_strategy_mask, LEVELS[level]], PLUGIN_NAME) + +func get_default_output_strategy(level): + """Get the default output strategy mask of the given level or (by + default) all levels for all modules without a custom strategy.""" + return default_output_strategies[level] + +func set_default_output_level(level): + """Set the default minimal level for the output of all modules without + a custom output level. + All levels greater or equal to the given once will be output based on + their respective strategies, while levels lower than the given one will + be discarded. + """ + if not level in range(0, LEVELS.size()): + error("The level must be comprised between 0 and %d." % (int(LEVELS.size()) - 1), PLUGIN_NAME) + return + default_output_level = level + info("The default output level was set to '%s'." % LEVELS[level], PLUGIN_NAME) + +func get_default_output_level(): + """Get the default minimal level for the output of all modules without + a custom output level.""" + return default_output_level + +# Output formatting +# ----------------- + +static func format(template, level, module, message): + var output = template + output = output.replace(FORMAT_IDS.level, LEVELS[level]) + output = output.replace(FORMAT_IDS.module, module) + output = output.replace(FORMAT_IDS.message, message) + return output + +func set_output_format(new_format): + """Set the output string format using the following identifiers: + {LVL} for the level, {MOD} for the module, {MSG} for the message. + The three identifiers should be contained in the output format string. + """ + for key in FORMAT_IDS: + if new_format.find(FORMAT_IDS[key]) == -1: + error("Invalid output string format. It lacks the '%s' identifier." \ + % FORMAT_IDS[key], PLUGIN_NAME) + return + output_format = new_format + info("Successfully changed the output format to '%s'." % output_format, PLUGIN_NAME) + +func get_output_format(): + """Get the output string format.""" + return output_format + +# Strategy "memory" +# ----------------- + +func set_max_memory_size(new_size): + """Set the maximum amount of messages to be remembered when + using the STRATEGY_MEMORY output strategy.""" + if new_size <= 0: + error("The maximum amount of remembered messages must be a positive non-null integer. Received %d." \ + % new_size, PLUGIN_NAME) + return + + var new_buffer = [] + var new_idx = 0 + new_buffer.resize(new_size) + + # Better algorithm welcome :D + if memory_first_loop: + var offset = 0 + if memory_idx > new_size: + offset = memory_idx - new_size + memory_first_loop = false + else: + new_idx = memory_idx + for i in range(0, min(memory_idx, new_size)): + new_buffer[i] = memory_buffer[i + offset] + else: + var delta = 0 + if max_memory_size > new_size: + delta = max_memory_size - new_size + else: + new_idx = max_memory_size + memory_first_loop = true + for i in range(0, min(max_memory_size, new_size)): + new_buffer[i] = memory_buffer[(memory_idx + delta + i) % max_memory_size] + + memory_buffer = new_buffer + memory_idx = new_idx + invalid_memory_cache = true + max_memory_size = new_size + info("Successfully set the maximum amount of remembered messages to %d." % max_memory_size, PLUGIN_NAME) + +func get_max_memory_size(): + """Get the maximum amount of messages to be remembered when + using the STRATEGY_MEMORY output strategy.""" + return max_memory_size + +func get_memory(): + """Get an array of the messages remembered following STRATEGY_MEMORY. + The messages are sorted from the oldest to the newest.""" + if invalid_memory_cache: # Need to recreate the cached ordered array + memory_cache = [] + if not memory_first_loop: # else those would be uninitialized + for i in range(memory_idx, max_memory_size): + memory_cache.append(memory_buffer[i]) + for i in range(0, memory_idx): + memory_cache.append(memory_buffer[i]) + invalid_memory_cache = false + return memory_cache + +func clear_memory(): + """Clear the buffer or remembered messages.""" + memory_buffer.clear() + memory_idx = 0 + memory_first_loop = true + invalid_memory_cache = true + + +# Configuration loading/saving +# ---------------------------- + +func save_config(configfile = default_configfile_path): + """Save the default configuration as well as the set of modules and + their respective configurations. + The ConfigFile API is used to generate the config file passed as argument. + A unique section is used, so that it can be merged in a project's engine.cfg. + Returns an error code (OK or some ERR_*).""" + var config = ConfigFile.new() + + # Store default config + config.set_value(PLUGIN_NAME, "default_output_level", default_output_level) + config.set_value(PLUGIN_NAME, "default_output_strategies", default_output_strategies) + config.set_value(PLUGIN_NAME, "default_logfile_path", default_logfile_path) + config.set_value(PLUGIN_NAME, "max_memory_size", max_memory_size) + + # Logfiles config + var logfiles_arr = [] + var sorted_keys = logfiles.keys() + sorted_keys.sort() # Sadly doesn't return the array, so we need to split it + for logfile in sorted_keys: + logfiles_arr.append(logfiles[logfile].get_config()) + config.set_value(PLUGIN_NAME, "logfiles", logfiles_arr) + + # Modules config + var modules_arr = [] + sorted_keys = modules.keys() + sorted_keys.sort() + for module in sorted_keys: + modules_arr.append(modules[module].get_config()) + config.set_value(PLUGIN_NAME, "modules", modules_arr) + + # Save and return the corresponding error code + var err = config.save(configfile) + if err: + error("Could not save the config in '%s'; exited with error %d." \ + % [configfile, err], PLUGIN_NAME) + return err + info("Successfully saved the config to '%s'." % configfile, PLUGIN_NAME) + return OK + +func load_config(configfile = default_configfile_path): + """Load the configuration as well as the set of defined modules and + their respective configurations. The expect file contents must be those + produced by the ConfigFile API. + Returns an error code (OK or some ERR_*).""" + # Look for the file + var dir = Directory.new() + if not dir.file_exists(configfile): + warn("Could not load the config in '%s', the file does not exist." % configfile, PLUGIN_NAME) + return ERR_FILE_NOT_FOUND + + # Load its contents + var config = ConfigFile.new() + var err = config.load(configfile) + if err: + warn("Could not load the config in '%s'; exited with error %d." \ + % [configfile, err], PLUGIN_NAME) + return err + + # Load default config + default_output_level = config.get_value(PLUGIN_NAME, "default_output_level", default_output_level) + default_output_strategies = config.get_value(PLUGIN_NAME, "default_output_strategies", default_output_strategies) + default_logfile_path = config.get_value(PLUGIN_NAME, "default_logfile_path", default_logfile_path) + max_memory_size = config.get_value(PLUGIN_NAME, "max_memory_size", max_memory_size) + + # Load logfiles config and initialize them + logfiles = {} + for logfile_cfg in config.get_value(PLUGIN_NAME, "logfiles"): + var logfile = Logfile.new(logfile_cfg["path"], logfile_cfg["queue_mode"]) + logfiles[logfile_cfg["path"]] = logfile + + # Load modules config and initialize them + modules = {} + for module_cfg in config.get_value(PLUGIN_NAME, "modules"): + var module = Module.new(module_cfg["name"], module_cfg["output_level"], \ + module_cfg["output_strategies"], get_logfile(module_cfg["logfile_path"])) + modules[module_cfg["name"]] = module + + info("Successfully loaded the config from '%s'." % configfile, PLUGIN_NAME) + return OK + + +##=============## +## Callbacks ## +##=============## + +func _init(): + # Default logfile + add_logfile(default_logfile_path) + # Default modules + add_module(PLUGIN_NAME) # needs to be instanced first + add_module("main") + memory_buffer.resize(max_memory_size) + +func _exit_tree(): + # Flush non-empty buffers + var processed_logfiles = [] + var logfile = null + for module in modules: + logfile = modules[module].get_logfile() + if logfile in processed_logfiles: + continue + logfile.flush_buffer() + processed_logfiles.append(logfile) diff --git a/game/autoload/Logger.tscn b/game/autoload/Logger.tscn new file mode 100644 index 00000000..f504f331 --- /dev/null +++ b/game/autoload/Logger.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://autoload/Logger.gd" type="Script" id=1] + +[node name="Logger" type="Node"] +script = ExtResource( 1 ) diff --git a/game/autoload/PhysicsQuery.gd b/game/autoload/PhysicsQuery.gd new file mode 100644 index 00000000..3c8d7bf7 --- /dev/null +++ b/game/autoload/PhysicsQuery.gd @@ -0,0 +1,14 @@ +extends Spatial + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +func _ready(): + set_physics_process(true) + +func _physics_process(delta): + pass + +#func queue_raycast(): +# var space_state : PhysicsDirectSpaceState = get_world().direct_space_state diff --git a/game/autoload/PhysicsQuery.tscn b/game/autoload/PhysicsQuery.tscn new file mode 100644 index 00000000..0dd35824 --- /dev/null +++ b/game/autoload/PhysicsQuery.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://autoload/PhysicsQuery.gd" type="Script" id=1] + +[node name="PhysicsQuery" type="Spatial"] +script = ExtResource( 1 ) diff --git a/game/autoload/ProfileManager.gd b/game/autoload/ProfileManager.gd new file mode 100644 index 00000000..7dae0454 --- /dev/null +++ b/game/autoload/ProfileManager.gd @@ -0,0 +1,66 @@ +extends ProfileManager + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#export (float) var save_interval : float = 20 +#var last_save_time : float = 0 + +func _ready(): + var save_game : File = File.new() + + if save_game.file_exists("user://profile.save"): + load_full() + else: + load_defaults() + + var actions : Array = InputMap.get_actions() + + for action in actions: + var acts : Array = InputMap.get_action_list(action) + + for i in range(len(acts)): + var a = acts[i] + if a is InputEventKey: + var nie : BSInputEventKey = BSInputEventKey.new() + nie.from_input_event_key(a as InputEventKey) + acts[i] = nie + + InputMap.action_erase_event(action, a) + InputMap.action_add_event(action, nie) + + +func _save() -> void: + save_full() + +func _load() -> void: + load_full() + +func save_full() -> void: + var save_game = File.new() + + save_game.open("user://profile.save", File.WRITE) + + save_game.store_line(to_json(to_dict())) + + save_game.close() + +func load_full() -> void: + clear_class_profiles() + + var save_game : File = File.new() + + if save_game.file_exists("user://profile.save"): + if save_game.open("user://profile.save", File.READ) == OK: + + var text : String = save_game.get_as_text() + + if text == "": + load_defaults() + return + + var save_json : Dictionary = parse_json(text) + + from_dict(save_json) + diff --git a/game/autoload/ProfileManager.tscn b/game/autoload/ProfileManager.tscn new file mode 100644 index 00000000..c95eede7 --- /dev/null +++ b/game/autoload/ProfileManager.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://autoload/ProfileManager.gd" type="Script" id=1] + +[node name="ProfileManager" type="ProfileManager"] +script = ExtResource( 1 ) +save_interval = 1.0 diff --git a/game/autoload/Server.gd b/game/autoload/Server.gd new file mode 100644 index 00000000..9e511091 --- /dev/null +++ b/game/autoload/Server.gd @@ -0,0 +1,203 @@ +extends Node + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +export (int) var port : int = 23223 + +signal cplayer_master_created(player_master) +signal cplayer_master_destroyed(player_master) + +signal splayer_master_created(player_master) +signal splayer_master_destroyed(player_master) + +var splayers_dict : Dictionary = {} +var splayers_array : Array = [] + +var cplayers_dict : Dictionary = {} +var cplayers_array : Array = [] + +var _sseed : int +var _cseed : int + +var local_player_master : PlayerMaster = PlayerMaster.new() + +func _ready() -> void: + #Temporary! REMOVE! + get_multiplayer().allow_object_decoding = true + + get_tree().connect("network_peer_connected", self, "_network_peer_connected") + get_tree().connect("network_peer_disconnected", self, "_network_peer_disconnected") + get_tree().connect("connected_to_server", self, "_connected_to_server") + get_tree().connect("connection_failed", self, "_connection_failed") + get_tree().connect("server_disconnected", self, "_server_disconnected") + + +func start_hosting(p_port : int = 0) -> int: + if p_port == 0: + p_port = port + + var peer : NetworkedMultiplayerENet = NetworkedMultiplayerENet.new() + var err : int = peer.create_server(p_port, 32) + get_tree().set_network_peer(peer) + + _connected_to_server() + + return err + +func start_hosting_websocket(p_port : int = 0) -> int: + if p_port == 0: + p_port = port + + var peer : WebSocketServer = WebSocketServer.new() + var err : int = peer.listen(p_port, [], true) + get_tree().set_network_peer(peer) + + _connected_to_server() + + return err + +func connect_to_server(address : String = "127.0.0.1", p_port : int = 0) -> int: + if p_port == 0: + p_port = port + + var peer = NetworkedMultiplayerENet.new() + var err : int = peer.create_client(address, p_port) + get_tree().set_network_peer(peer) + + return err + +func connect_to_server_websocket(address : String = "127.0.0.1", p_port : int = 0) -> int: + if p_port == 0: + p_port = port + + var peer = WebSocketClient.new() + var err : int = peer.connect_to_url(address + ":" + str(p_port), [], true) + get_tree().set_network_peer(peer) + + return err + +func _network_peer_connected(id : int) -> void: + Logger.verbose("NetworkManager peer connected " + str(id)) + +# for p in splayers_array: +# rpc_id(id, "cspawn_player", p.my_info, p.sid, p.player.translation) + + var pm : PlayerMaster = PlayerMaster.new() + pm.sid = id + + splayers_array.append(pm) + splayers_dict[id] = pm + + emit_signal("splayer_master_created", pm) + + rpc_id(id, "cset_seed", _sseed) + +func _network_peer_disconnected(id : int) -> void: + Logger.verbose("NetworkManager peer disconnected " + str(id)) + + var player : PlayerMaster = splayers_dict[id] + splayers_dict.erase(id) + + for pi in range(len(splayers_array)): + if (splayers_array[pi] as PlayerMaster) == player: + splayers_array.remove(pi) + break + + if player: + emit_signal("splayer_master_destroyed", player) + +func _connected_to_server() -> void: + Logger.verbose("NetworkManager _connected_to_server") + + var pm : PlayerMaster = PlayerMaster.new() + pm.sid = get_tree().get_network_unique_id() + + local_player_master = pm + + emit_signal("cplayer_master_created", pm) + +func _server_disconnected() -> void: + Logger.verbose("_server_disconnected") + + # Server kicked us; show error and abort. + + for player in get_children(): + emit_signal("NetworkManager cplayer_master_destroyed", player) + player.queue_free() + +func _connection_failed() -> void: + Logger.verbose("NetworkManager _connection_failed") + + pass # Could not even connect to server; abort. + +func sset_seed(pseed): + _sseed = pseed + + if multiplayer.has_network_peer() and multiplayer.is_network_server(): + rpc("cset_seed", _sseed) + +remote func cset_seed(pseed): + + _cseed = pseed + + print("clientseed set") + + +func set_class(): + Logger.verbose("set_class") + + if not get_tree().is_network_server(): + rpc_id(1, "crequest_select_class", local_player_master.my_info) + else: + crequest_select_class(local_player_master.my_info) + +remote func crequest_select_class(info : Dictionary) -> void: + Logger.verbose("NetworkManager crequest_select_class") + + if get_tree().is_network_server(): + var sid : int = get_tree().multiplayer.get_rpc_sender_id() + + if sid == 0: + sid = 1 + + rpc("cspawn_player", info, sid, Vector3(10, 10, 10)) + + +remotesync func cspawn_player(info : Dictionary, sid : int, pos : Vector3): + Logger.verbose("NetworkManager cspawn_player") + + if sid == get_tree().get_network_unique_id(): + local_player_master.player = Entities.spawn_player(info["selected_class"] as int, pos, info["name"] as String, str(sid), sid) + call_deferred("set_terrarin_player") + + if get_tree().is_network_server() and not splayers_dict.has(sid): + splayers_dict[sid] = local_player_master + splayers_array.append(local_player_master) + else: + var pm : PlayerMaster = PlayerMaster.new() + pm.sid = sid + + pm.player = Entities.spawn_networked_player(info["selected_class"] as int, pos, info["name"] as String, str(sid), sid) + + if get_tree().is_network_server() and not splayers_dict.has(sid): + splayers_dict[sid] = pm + splayers_array.append(pm) + + cplayers_dict[sid] = pm + cplayers_array.append(pm) + + +func upload_character(data : String) -> void: + rpc_id(1, "sreceive_upload_character", data) + +master func sreceive_upload_character(data: String) -> void: + Entities.spawn_networked_player_from_data(data, Vector3(0, 10, 0), multiplayer.get_rpc_sender_id()) + +func set_terrarin_player(): + Logger.verbose("NetworkManager cspawn_player") + + var terrarin : VoxelWorld = get_node("/root/GameScene/VoxelWorld") + + terrarin.set_player(local_player_master.player as Spatial) diff --git a/game/autoload/Server.tscn b/game/autoload/Server.tscn new file mode 100644 index 00000000..2ba69753 --- /dev/null +++ b/game/autoload/Server.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://autoload/Server.gd" type="Script" id=1] + +[node name="Server" type="Node"] +script = ExtResource( 1 ) diff --git a/game/autoload/SettingsManager.gd b/game/autoload/SettingsManager.gd new file mode 100644 index 00000000..10f99d49 --- /dev/null +++ b/game/autoload/SettingsManager.gd @@ -0,0 +1,63 @@ +extends Node + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +signal setting_changed(section, key, value) + +const SAVE_PATH : String = "user://settings.cfg" + +var _config_file : ConfigFile = ConfigFile.new() +var _settings : Dictionary = { + "rendering" : { + "thread_model" : ProjectSettings.get("rendering/threads/thread_model") + }, + "debug" : { + "debug_info" : false + } +} + +func _ready(): + load_settings() + +func set_value(section, key, value) -> void: + _settings[section][key] = value + + if has_method("set_" + section + "_" + key): + call("set_" + section + "_" + key, value) + + save_settings() + + emit_signal("setting_changed", section, key, value) + +func get_value(section, key): + return _settings[section][key] + +func _set_value(section, key, value) -> void: + _settings[section][key] = value + + if has_method("set_" + section + "_" + key): + call("set_" + section + "_" + key, value) + +func save_settings() -> void: + for section in _settings.keys(): + for key in _settings[section]: + _config_file.set_value(section, key, _settings[section][key]) + + _config_file.save(SAVE_PATH) + +func load_settings() -> void: + var error : int = _config_file.load(SAVE_PATH) + + if error != OK: +# print("Failed to load the settings file! Error code %s" % error) + return + + for section in _settings.keys(): + for key in _settings[section]: + _set_value(section, key, _config_file.get_value(section, key, _settings[section][key])) + + +func set_rendering_thread_model(value : int) -> void: + ProjectSettings.set("rendering/threads/thread_model", value) diff --git a/game/autoload/SettingsManager.tscn b/game/autoload/SettingsManager.tscn new file mode 100644 index 00000000..8e95a7d7 --- /dev/null +++ b/game/autoload/SettingsManager.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://autoload/SettingsManager.gd" type="Script" id=1] + +[node name="SettingsManager" type="Node"] +script = ExtResource( 1 ) diff --git a/game/autoload/ThemeAtlas.gd b/game/autoload/ThemeAtlas.gd new file mode 100644 index 00000000..9ff5cf0e --- /dev/null +++ b/game/autoload/ThemeAtlas.gd @@ -0,0 +1,14 @@ +extends TextureMerger + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#func _texture_added(texture): +# print("added") +# +#func _texture_removed(): +# print("removed") + +func _texture_merged(): + $Sprite.texture = get_generated_texture(0) diff --git a/game/autoload/ThemeAtlas.tscn b/game/autoload/ThemeAtlas.tscn new file mode 100644 index 00000000..1f171c96 --- /dev/null +++ b/game/autoload/ThemeAtlas.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=18 format=2] + +[ext_resource path="res://ui/theme/scrollbar_grabber_atlas.tres" type="Texture" id=2] +[ext_resource path="res://ui/theme/h_scroll_bar_texture.tres" type="Texture" id=3] +[ext_resource path="res://ui/theme/panel_bg_atlas.tres" type="Texture" id=4] +[ext_resource path="res://ui/theme/scrollbar_bg_atlas.tres" type="Texture" id=5] +[ext_resource path="res://ui/theme/separator_texture.tres" type="Texture" id=6] +[ext_resource path="res://ui/theme/button_bg_atlas.tres" type="Texture" id=7] +[ext_resource path="res://ui/theme/bag_icon.tres" type="Texture" id=8] +[ext_resource path="res://ui/theme/locked_icon.tres" type="Texture" id=9] +[ext_resource path="res://ui/theme/unlocked_icon.tres" type="Texture" id=10] +[ext_resource path="res://ui/theme/spellbook_icon.tres" type="Texture" id=11] +[ext_resource path="res://ui/theme/radio_texture.tres" type="Texture" id=12] +[ext_resource path="res://ui/theme/radio_checked_texture.tres" type="Texture" id=13] +[ext_resource path="res://ui/theme/checkbox_texture.tres" type="Texture" id=14] +[ext_resource path="res://ui/theme/dropdown_icon.tres" type="Texture" id=15] +[ext_resource path="res://ui/theme/checkbox_checked_texture.tres" type="Texture" id=16] +[ext_resource path="res://ui/theme/window_bg_atlas.tres" type="Texture" id=17] +[ext_resource path="res://ui/theme/window_bg_bg.tres" type="Texture" id=18] + +[node name="ThemeAtlas" type="TextureMerger"] +texture_flags = 0 +keep_original_atlases = true +automatic_merge = true +textures = [ ExtResource( 8 ), ExtResource( 7 ), ExtResource( 16 ), ExtResource( 14 ), ExtResource( 15 ), ExtResource( 3 ), ExtResource( 9 ), ExtResource( 4 ), ExtResource( 13 ), ExtResource( 12 ), ExtResource( 5 ), ExtResource( 2 ), ExtResource( 6 ), ExtResource( 11 ), ExtResource( 10 ), ExtResource( 17 ), ExtResource( 18 ) ] diff --git a/game/autoload/WorldNumbers.gd b/game/autoload/WorldNumbers.gd new file mode 100644 index 00000000..a2276749 --- /dev/null +++ b/game/autoload/WorldNumbers.gd @@ -0,0 +1,38 @@ +extends Node + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +export(PackedScene) var number_scene + +func damage(entity_position : Vector3, entity_height : float, value : int, crit : bool) -> void: + var scene : Node = number_scene.instance() + + add_child(scene) + scene.owner = self + + entity_position.y += entity_height + (0.2 * randf()) + entity_position.x += entity_height * 0.4 - entity_height * 0.8 * randf() + entity_position.z += entity_height * 0.4 - entity_height * 0.8 * randf() + + scene.damage(entity_position, value, crit) + +func heal(entity_position : Vector3, entity_height : float, value : int, crit : bool) -> void: + var scene : Node = number_scene.instance() + + add_child(scene) + scene.owner = self + + randomize() + + entity_position.y += entity_height + (0.3 * randf()) + entity_position.x += entity_height * 0.4 - entity_height * 0.8 * randf() + entity_position.z += entity_height * 0.4 - entity_height * 0.8 * randf() + + + scene.heal(entity_position, value, crit) + +func clear() -> void: + for child in get_children(): + child.queue_free() diff --git a/game/autoload/WorldNumbers.tscn b/game/autoload/WorldNumbers.tscn new file mode 100644 index 00000000..7037d13a --- /dev/null +++ b/game/autoload/WorldNumbers.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://autoload/WorldNumbers.gd" type="Script" id=1] +[ext_resource path="res://ui/numbers/Number.tscn" type="PackedScene" id=2] + +[node name="WorldNumbers" type="Node"] +script = ExtResource( 1 ) +number_scene = ExtResource( 2 )