From e7315308bac0b4eaba8abb0c8c0db02917933d78 Mon Sep 17 00:00:00 2001 From: Marcin Nowak Date: Wed, 23 Oct 2024 21:00:36 +0200 Subject: [PATCH] initial multiplayer --- .../libmaszyna/console/developer_console.gd | 75 +++++++++ addons/libmaszyna/libmaszyna.gd | 8 + .../libmaszyna/player/multiplayer_manager.gd | 17 ++ addons/libmaszyna/player/player_info.gd | 9 ++ addons/libmaszyna/player/player_system.gd | 151 ++++++++++++++++++ addons/libmaszyna/rail_vehicle_3d.gd | 8 +- demo/project.godot | 5 +- src/core/TrainController.cpp | 48 ++++-- src/core/TrainController.hpp | 18 ++- src/core/TrainPart.cpp | 5 +- src/core/TrainPart.hpp | 1 + src/core/TrainSystem.cpp | 1 - src/engines/TrainEngine.cpp | 40 +++-- src/engines/TrainEngine.hpp | 14 +- src/systems/TrainSecuritySystem.cpp | 30 ++-- src/systems/TrainSecuritySystem.hpp | 5 + 16 files changed, 392 insertions(+), 43 deletions(-) create mode 100644 addons/libmaszyna/player/multiplayer_manager.gd create mode 100644 addons/libmaszyna/player/player_info.gd create mode 100644 addons/libmaszyna/player/player_system.gd diff --git a/addons/libmaszyna/console/developer_console.gd b/addons/libmaszyna/console/developer_console.gd index b94e45a8..877cdc39 100644 --- a/addons/libmaszyna/console/developer_console.gd +++ b/addons/libmaszyna/console/developer_console.gd @@ -17,8 +17,80 @@ func _ready() -> void: Console.add_command("get", self.console_get_train_state, ["train", "parameter"], 1, "Get train state / parameter") Console.add_command("prop", self.console_get_config_value, ["train", "property"], 2, "Get train config property") Console.add_command("props", self.console_get_config_properties, ["train"], 1, "List train config properties") + Console.add_command("server", self.console_host_server, ["address"], 0, "Host a game server") + Console.add_command("connect", self.console_connect_server, ["address"], 0, "Connect to a game server") + Console.add_command("nick", self.console_set_nick, ["nick"], 1, "Set your nickname") + Console.add_command("players", self.console_list_players, [], 0, "List all players") + Console.add_command("say", self.console_say, ["message"], 1, "Say message") + #TrainSystem.train_log_updated.connect(self.console_print_train_log) LogSystem.log_updated.connect(self.console_print_log) + PlayerSystem.error_received.connect(func(msg): LogSystem.error(msg)) + + +func _parse_host_ip_from_address(address:String = ""): + var port:int + var host:String + + if address.is_empty(): + host = ProjectSettings.get_setting("maszyna/game/multiplayer/host", "127.0.0.1") + if not host: + console_print_log(LogSystem.LogLevel.WARNING, "Host is not configured in ProjectSetttings") + port = int(ProjectSettings.get_setting("maszyna/game/multiplayer/port", 9797)) + if not port: + console_print_log(LogSystem.LogLevel.WARNING, "Port is not configured in ProjectSetttings") + else: + var _parts = address.split(":") + host = _parts[0].strip_edges() + port = int(_parts[1].strip_edges()) + if host.is_empty(): + host = ProjectSettings.get_setting("maszyna/game/multiplayer/host", "127.0.0.1") + + if not port or not host: + console_print_log(LogSystem.LogLevel.ERROR, "Incorrect address \"%s:%s\"" % [host, port]) + return [null, null] + + return [host, port] + +func console_list_players(): + for player:PlayerInfo in PlayerSystem.get_all_players(): + if player: + if player.train_id: + Console.print_line("%s: %s [in %s]" % [player.peer_id, player.name, player.train_id]) + else: + Console.print_line("%s: %s" % [player.peer_id, player.name]) + +func console_say(message: String): + PlayerSystem.send_message(message) + +func console_set_nick(nick:String): + PlayerSystem.nick = nick + PlayerSystem.set_player_name(PlayerSystem.get_my_peer_id(), nick) + +func console_host_server(address:String = ""): + var host_and_port = _parse_host_ip_from_address(address) + var host = host_and_port[0] + var port = host_and_port[1] + + if host and port: + var err = MultiPlayerManager.host_game(host_and_port[0], host_and_port[1]) + if err == OK: + LogSystem.info("Server started at %s:%s" % [host, port]) + else: + LogSystem.error("Cannot host a game server (%s)" % err) + + +func console_connect_server(address:String = ""): + var host_and_port = _parse_host_ip_from_address(address) + var host = host_and_port[0] + var port = host_and_port[1] + if host and port: + var err = MultiPlayerManager.connect_game(host, port) + if err == OK: + LogSystem.info("Connected to the server %s:%s" % [host, port]) + else: + LogSystem.error("Can't connect to server (%s)" % err) + func console_get_config_value(train, property): Console.print_line("%s" % TrainSystem.get_config_property(train, property)) @@ -45,6 +117,9 @@ func console_list_train_commands(): commands.sort() Console.print_line("%s" % "\n".join(commands)) +func console_print_train_log(train_id, loglevel, line): + console_print_log(loglevel, "%s: %s" % [train_id, line]) + func console_print_log(loglevel, line): if loglevel >= LogSystem.LogLevel.ERROR: Console.print_line("[color=red]%s[/color]" % [line]) diff --git a/addons/libmaszyna/libmaszyna.gd b/addons/libmaszyna/libmaszyna.gd index a5fd722d..10e05c8d 100644 --- a/addons/libmaszyna/libmaszyna.gd +++ b/addons/libmaszyna/libmaszyna.gd @@ -23,6 +23,8 @@ func _enter_tree(): add_custom_project_setting("maszyna/import_model_scale_factor", 1.0, TYPE_FLOAT) + add_autoload_singleton("PlayerSystem", "res://addons/libmaszyna/player/player_system.gd") + add_autoload_singleton("MultiPlayerManager", "res://addons/libmaszyna/player/multiplayer_manager.gd") add_autoload_singleton("SceneryResourceLoader", "res://addons/libmaszyna/scenery/scenery_resource_loader.gd") add_autoload_singleton("MaszynaEnvironment", "res://addons/libmaszyna/environment/maszyna_environment.gd") add_autoload_singleton("Console", "res://addons/libmaszyna/console/console.gd") @@ -52,6 +54,10 @@ func _enter_tree(): user_settings_dock = user_settings_dock_scene.instantiate() add_control_to_dock(DOCK_SLOT_RIGHT_UL, user_settings_dock) + # MOVE TO USER SETTINGS! + add_custom_project_setting("maszyna/game/multiplayer/host", "127.0.0.1", TYPE_STRING) + add_custom_project_setting("maszyna/game/multiplayer/port", 9797, TYPE_INT) + func _exit_tree(): remove_control_from_container(CONTAINER_SPATIAL_EDITOR_MENU, e3d_submodel_toolbar_instance) @@ -64,6 +70,8 @@ func _exit_tree(): remove_custom_type("E3DModelInstance") remove_custom_type("MaszynaEnvironmentNode") + remove_autoload_singleton("PlayerSystem") + remove_autoload_singleton("MultiPlayerManager") remove_autoload_singleton("E3DModelInstanceManager") remove_autoload_singleton("UserSettings") remove_autoload_singleton("E3DNodesInstancer") diff --git a/addons/libmaszyna/player/multiplayer_manager.gd b/addons/libmaszyna/player/multiplayer_manager.gd new file mode 100644 index 00000000..efd4f857 --- /dev/null +++ b/addons/libmaszyna/player/multiplayer_manager.gd @@ -0,0 +1,17 @@ +extends Node + +func host_game(ip, port) -> int: + var peer = ENetMultiplayerPeer.new() + peer.set_bind_ip(ip) + var err = peer.create_server(port, 32) + if err == OK: + multiplayer.multiplayer_peer = peer + return err + + +func connect_game(ip, port) -> int: + var peer = ENetMultiplayerPeer.new() + var err = peer.create_client(ip, port) + if err == OK: + multiplayer.multiplayer_peer = peer + return err diff --git a/addons/libmaszyna/player/player_info.gd b/addons/libmaszyna/player/player_info.gd new file mode 100644 index 00000000..e3b0398f --- /dev/null +++ b/addons/libmaszyna/player/player_info.gd @@ -0,0 +1,9 @@ +extends Resource +class_name PlayerInfo + +enum PlayerType { PLAYER_TYPE_LOCAL, PLAYER_TYPE_REMOTE } + +@export var peer_id:int +@export var name:String +@export var type:PlayerType +@export var train_id:String diff --git a/addons/libmaszyna/player/player_system.gd b/addons/libmaszyna/player/player_system.gd new file mode 100644 index 00000000..9775b8a5 --- /dev/null +++ b/addons/libmaszyna/player/player_system.gd @@ -0,0 +1,151 @@ +extends Node +#class_name PlayerSystem + +var _players = {} +var _trains_occupied = {} # train_id to peer_id map + +var nick:String = "" +var occupied_train: String = "" + + +signal error_received(error: String) +signal kicked_off_train(peer_id: int, train_id: String) + +func _ready(): + multiplayer.peer_connected.connect(_on_peer_connected) + multiplayer.peer_disconnected.connect(_on_peer_disconnected) + multiplayer.connected_to_server.connect(_on_connected_to_server) + multiplayer.server_disconnected.connect(_on_server_disconnected) + +func _on_connected_to_server(): + LogSystem.info("Connected to server successfully") + +func _on_server_disconnected(): + LogSystem.info("Disconnected from server") + +func _error(msg): + error_received.emit(msg) + + +func _on_peer_connected(peer_id: int): + LogSystem.info("Peer connected: %d" % peer_id) + register_player.rpc(peer_id) + set_player_name.rpc(peer_id, nick) + if occupied_train: + enter_train.rpc(peer_id, occupied_train) + + +func _on_peer_disconnected(peer_id: int): + unregister_player.rpc(peer_id) + + +func _require_registered_player(peer_id): + if not _players.has(peer_id): + _error("Player not registered: %s" % peer_id) + false + else: + return true + +func _require_train_occupied_by_player(peer_id, train_id): + if _require_registered_player(peer_id): + if not train_id in _trains_occupied: + _error("Train is not occupied: %s" % train_id) + return false + if not _trains_occupied.has(train_id): + _error("Train is not occupied: %s" % train_id) + return false + return true + else: + return false + + +func recevie_error(message): + error_received.emit(message) + + +@rpc("any_peer", "call_local") +func register_player(peer_id:int, name:String = "", type=PlayerInfo.PlayerType.PLAYER_TYPE_LOCAL): + if _players.has(peer_id): + _error("Player already registered: %s" % peer_id) + return + + var p = PlayerInfo.new() + p.name = name if not name.is_empty() else "Player %d" % peer_id + p.type = type + p.peer_id = peer_id + + _players[peer_id] = p + + +@rpc("any_peer", "call_local") +func unregister_player(peer_id:int): + if _require_registered_player(peer_id): + _players.erase(peer_id) + + +func get_all_players(): + return _players.values() + + +func get_my_peer_id(): + return multiplayer.get_unique_id() + +@rpc("any_peer", "call_local") +func set_player_name(peer_id:int, nick: String): + if not nick.is_empty() and _require_registered_player(peer_id): + receive_player_name.rpc(peer_id, nick) + +@rpc("any_peer", "call_local") +func receive_player_name(peer_id:int, nick: String): + _players[peer_id].name = nick + LogSystem.warning("Player %d changed nick to %s" % [peer_id, nick]) + +func get_player_name(peer_id:int): + if _require_registered_player(peer_id): + return _players[peer_id].name + +@rpc("any_peer", "call_local") +func enter_train(peer_id:int, train_id:String): + if _require_registered_player(peer_id): + if _trains_occupied.has(train_id): + _error("Train is already occupied: %s" % train_id) + if multiplayer.get_remote_sender_id() == 1: + #kicked_off_train.emit(peer_id, train_id) + kick_off_from_train.rpc(peer_id, train_id) + #handle_train_occupy_conflict.rpc_id(peer_id, train_id, _trains_occupied[train_id]) + return + _trains_occupied[train_id] = peer_id + _players[peer_id].train_id = train_id + + +@rpc("any_peer", "call_local") +func exit_train(peer_id:int, train_id:String): + if _require_train_occupied_by_player(peer_id, train_id): + if _trains_occupied[train_id] == peer_id: + _trains_occupied.erase(train_id) + _players[peer_id].train_id = "" + else: + _error("Train is occupied by someone else") + + +@rpc("any_peer", "call_local") +func kick_off_from_train(peer_id:int, train_id:String): + kicked_off_train.emit(peer_id, train_id) + +@rpc("any_peer","call_remote") +func handle_train_occupy_conflict(train_id:String, actual_peer_id:int): + print("HERE") + _trains_occupied[train_id] = actual_peer_id + kicked_off_train.emit(multiplayer.get_remote_sender_id(), train_id) + + +func send_train_command(peer_id:int, train_id:String, command: String, p1 = null, p2 = null): + if _require_train_occupied_by_player(peer_id, train_id): + TrainSystem.send_command(train_id, command, p1, p2) + +func send_message(message: String): + receive_message.rpc(multiplayer.get_unique_id(), message) + +@rpc("any_peer", "call_remote") +func receive_message(peer_id:int, message: String): + LogSystem.info("%s: %s" % [get_player_name(peer_id), message]) diff --git a/addons/libmaszyna/rail_vehicle_3d.gd b/addons/libmaszyna/rail_vehicle_3d.gd index bf775f9c..1d04e1ce 100644 --- a/addons/libmaszyna/rail_vehicle_3d.gd +++ b/addons/libmaszyna/rail_vehicle_3d.gd @@ -51,9 +51,10 @@ func enter_cabin(player:MaszynaPlayer): return if controller_path: - var controller = get_node(controller_path) + var controller:TrainController = get_node(controller_path) if controller: _cabin.controller_path = controller.get_path() + controller.occupied = true # The sequence of adding, removing, hiding, showing nodes is very important # to reduce visual artifacts @@ -105,6 +106,11 @@ func enter_cabin(player:MaszynaPlayer): _cabin.visible = true func leave_cabin(player:Node): + if controller_path: + var controller:TrainController = get_node(controller_path) + if controller: + _cabin.controller_path = controller.get_path() + controller.occupied = false if low_poly_cabin_path: var low_poly_cabin = get_node(low_poly_cabin_path) if low_poly_cabin: diff --git a/demo/project.godot b/demo/project.godot index c2c69672..3c0f1f22 100644 --- a/demo/project.godot +++ b/demo/project.godot @@ -36,6 +36,8 @@ E3DNodesInstancer="*res://addons/libmaszyna/e3d/e3d_nodes_instancer.gd" UserSettings="*res://addons/libmaszyna/settings/user_settings.gd" SceneryResourceLoader="*res://addons/libmaszyna/scenery/scenery_resource_loader.gd" E3DModelInstanceManager="*res://addons/libmaszyna/e3d/e3d_model_instance_manager.gd" +PlayerSystem="*res://addons/libmaszyna/player/player_system.gd" +MultiPlayerManager="*res://addons/libmaszyna/player/multiplayer_manager.gd" [debug] @@ -43,8 +45,7 @@ file_logging/log_path="user://logs/app.log" [display] -window/size/mode=3 -window/size/initial_position_type=3 +window/size/mode.release=3 [editor_plugins] diff --git a/src/core/TrainController.cpp b/src/core/TrainController.cpp index 0187e86c..9676d8d5 100644 --- a/src/core/TrainController.cpp +++ b/src/core/TrainController.cpp @@ -14,6 +14,7 @@ namespace godot { const char *TrainController::MOVER_CONFIG_CHANGED_SIGNAL = "mover_config_changed"; const char *TrainController::MOVER_INITIALIZED_SIGNAL = "mover_initialized"; + const char *TrainController::STATE_CHANGED = "state_changed"; const char *TrainController::POWER_CHANGED_SIGNAL = "power_changed"; const char *TrainController::COMMAND_RECEIVED = "command_received"; const char *TrainController::RADIO_TOGGLED = "radio_toggled"; @@ -22,12 +23,11 @@ namespace godot { void TrainController::_bind_methods() { ClassDB::bind_method(D_METHOD("get_state"), &TrainController::get_state); + ClassDB::bind_method(D_METHOD("set_state", "state"), &TrainController::set_state); + ClassDB::bind_method(D_METHOD("get_occupied"), &TrainController::get_occupied); + ClassDB::bind_method(D_METHOD("set_occupied", "occupied"), &TrainController::set_occupied); ClassDB::bind_method(D_METHOD("get_config"), &TrainController::get_config); - ADD_PROPERTY( - PropertyInfo( - Variant::DICTIONARY, "state", PROPERTY_HINT_NONE, "", - PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_DEFAULT), - "", "get_state"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "state"), "set_state", "get_state"); ADD_PROPERTY( PropertyInfo( Variant::DICTIONARY, "config", PROPERTY_HINT_NONE, "", @@ -59,7 +59,6 @@ namespace godot { ClassDB::bind_method( D_METHOD("radio_channel_decrease", "step"), &TrainController::radio_channel_decrease, DEFVAL(1)); ClassDB::bind_method(D_METHOD("update_mover"), &TrainController::update_mover); - ClassDB::bind_method(D_METHOD("update_state"), &TrainController::update_state); ClassDB::bind_method(D_METHOD("update_config"), &TrainController::update_config); ClassDB::bind_method(D_METHOD("set_train_id"), &TrainController::set_train_id); @@ -82,6 +81,7 @@ namespace godot { ClassDB::bind_method(D_METHOD("get_radio_channel_max"), &TrainController::get_radio_channel_min); ADD_PROPERTY(PropertyInfo(Variant::STRING, "train_id"), "set_train_id", "get_train_id"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "occupied"), "set_occupied", "get_occupied"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "type_name"), "set_type_name", "get_type_name"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dimensions/mass"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "power"), "set_power", "get_power"); @@ -92,6 +92,7 @@ namespace godot { ADD_SIGNAL(MethodInfo(MOVER_CONFIG_CHANGED_SIGNAL)); ADD_SIGNAL(MethodInfo(MOVER_INITIALIZED_SIGNAL)); + ADD_SIGNAL(MethodInfo(STATE_CHANGED)); ADD_SIGNAL(MethodInfo(POWER_CHANGED_SIGNAL, PropertyInfo(Variant::BOOL, "is_powered"))); ADD_SIGNAL(MethodInfo(RADIO_TOGGLED, PropertyInfo(Variant::BOOL, "is_enabled"))); ADD_SIGNAL(MethodInfo(RADIO_CHANNEL_CHANGED, PropertyInfo(Variant::INT, "channel"))); @@ -207,7 +208,6 @@ namespace godot { break; case NOTIFICATION_READY: initialize_mover(); - update_state(); DEBUG("TrainController::_ready() signals connected to train parts"); emit_signal(POWER_CHANGED_SIGNAL, prev_is_powered); @@ -233,7 +233,18 @@ namespace godot { } } + void TrainController::set_occupied(const bool p_occupied) { + occupied = p_occupied; + } + + bool TrainController::get_occupied() const { + return occupied; + } + void TrainController::_process_mover(const double delta) { + if (!occupied) { + return; + } TLocation mock_location; TRotation mock_rotation; mover->ComputeTotalForce(delta); @@ -245,13 +256,12 @@ namespace godot { _handle_mover_update(); } - void TrainController::update_state() { - _handle_mover_update(); - } - void TrainController::_handle_mover_update() { state.merge(get_mover_state(), true); + mark_state_changed(); + } + void TrainController::_on_state_changed() { const bool new_is_powered = (state.get("power24_available", false) || state.get("power110_available", false)); if (prev_is_powered != new_is_powered) { prev_is_powered = new_is_powered; // FIXME: I don't like this @@ -279,6 +289,12 @@ namespace godot { _update_mover_config_if_dirty(); _process_mover(delta); + + if (state_changed) { + state_changed = false; + emit_signal(STATE_CHANGED); + _on_state_changed(); + } } void TrainController::_do_update_internal_mover(TMoverParameters *mover) const { @@ -363,6 +379,7 @@ namespace godot { TMoverParameters *mover = get_mover(); if (mover != nullptr) { _do_fetch_state_from_mover(mover, state); + mark_state_changed(); } else { UtilityFunctions::push_warning("TrainController::get_mover_state() failed: internal mover not initialized"); } @@ -432,6 +449,11 @@ namespace godot { return state; } + void TrainController::set_state(const Dictionary &p_state) { + state = p_state; + mark_state_changed(); + } + void TrainController::emit_command_received_signal(const String &command, const Variant &p1, const Variant &p2) { emit_signal(COMMAND_RECEIVED, command, p1, p2); } @@ -483,4 +505,8 @@ namespace godot { void TrainController::radio(const bool p_enabled) { mover->Radio = p_enabled; } + + void TrainController::mark_state_changed() { + state_changed = true; + } } // namespace godot diff --git a/src/core/TrainController.hpp b/src/core/TrainController.hpp index 6ca21e22..ce63d4d5 100644 --- a/src/core/TrainController.hpp +++ b/src/core/TrainController.hpp @@ -11,7 +11,7 @@ namespace godot { class TrainSystem; - class TrainController: public Node { + class TrainController : public Node { GDCLASS(TrainController, Node) private: TMoverParameters *mover{}; @@ -23,6 +23,7 @@ namespace godot { void initialize_mover(); bool _dirty = false; // Refreshes all elements bool _dirty_prop = false; // Refreshes only TrainController's properties + bool occupied = true; Dictionary state; Dictionary config; Dictionary internal_state; @@ -39,9 +40,14 @@ namespace godot { bool prev_radio_enabled = false; int prev_radio_channel = radio_channel; + bool state_changed = false; + String axle_arrangement = ""; void _collect_train_parts(const Node *node, Vector &train_parts) {}; + void _on_state_changed(); + + private: void _update_mover_config_if_dirty(); void _handle_mover_update(); @@ -56,11 +62,13 @@ namespace godot { void _do_fetch_config_from_mover(TMoverParameters *mover, Dictionary &config) const; void _do_fetch_state_from_mover(TMoverParameters *mover, Dictionary &state); void _process_mover(double delta); + void _notification(int p_what); public: static const char *MOVER_CONFIG_CHANGED_SIGNAL; static const char *MOVER_INITIALIZED_SIGNAL; + static const char *STATE_CHANGED; static const char *POWER_CHANGED_SIGNAL; static const char *COMMAND_RECEIVED; static const char *RADIO_TOGGLED; @@ -70,7 +78,6 @@ namespace godot { Dictionary get_config() const; void update_config(const Dictionary &p_config); void _process(double delta) override; - void _notification(int p_what); void send_command(const StringName &command, const Variant &p1 = Variant(), const Variant &p2 = Variant()); void battery(bool p_enabled); void main_controller_increase(int p_step = 1); @@ -86,8 +93,9 @@ namespace godot { void broadcast_command(const String &command, const Variant &p1 = Variant(), const Variant &p2 = Variant()); void register_command(const String &command, const Callable &callable); void unregister_command(const String &command, const Callable &callable); - void update_state(); void update_mover(); + void mark_state_changed(); + TMoverParameters *get_mover() const; static void _bind_methods(); String get_train_id() const; @@ -108,6 +116,10 @@ namespace godot { int get_radio_channel_max() const; void set_radio_channel_max(int p_value); String get_axle_arrangement() const; + void set_occupied(const bool p_occupied); + bool get_occupied() const; + + void set_state(const Dictionary &p_state); Dictionary get_state(); }; diff --git a/src/core/TrainPart.cpp b/src/core/TrainPart.cpp index 76879d43..0eb11b94 100644 --- a/src/core/TrainPart.cpp +++ b/src/core/TrainPart.cpp @@ -36,6 +36,7 @@ namespace godot { void TrainPart::_register_commands() {}; void TrainPart::_unregister_commands() {}; + void TrainPart::_do_initialize_train_controller(TrainController *train_controller) {}; TMoverParameters *TrainPart::get_mover() { if (train_controller_node != nullptr) { @@ -62,6 +63,7 @@ namespace godot { if (train_controller_node != nullptr) { train_controller_node->connect( TrainController::MOVER_CONFIG_CHANGED_SIGNAL, Callable(this, "update_mover")); + _do_initialize_train_controller(train_controller_node); } if (enabled) { _register_commands(); @@ -127,7 +129,7 @@ namespace godot { _dirty = false; } - if (enabled) { + if (enabled && train_controller_node->get_occupied()) { _process_mover(delta); } @@ -153,6 +155,7 @@ namespace godot { if (mover != nullptr) { _do_process_mover(mover, delta); train_controller_node->get_state().merge(get_mover_state(), true); + train_controller_node->mark_state_changed(); } } } diff --git a/src/core/TrainPart.hpp b/src/core/TrainPart.hpp index 0578b6bf..17c8379d 100644 --- a/src/core/TrainPart.hpp +++ b/src/core/TrainPart.hpp @@ -49,6 +49,7 @@ namespace godot { virtual void _do_fetch_config_from_mover(TMoverParameters *mover, Dictionary &config); virtual void _do_process_mover(TMoverParameters *mover, double delta); + virtual void _do_initialize_train_controller(TrainController *train_controller); virtual void _register_commands(); virtual void _unregister_commands(); diff --git a/src/core/TrainSystem.cpp b/src/core/TrainSystem.cpp index 73207f6e..4819ecc4 100644 --- a/src/core/TrainSystem.cpp +++ b/src/core/TrainSystem.cpp @@ -252,7 +252,6 @@ namespace godot { ERR_PRINT("[" + train_id + "] Unknown command: " + command); } - train->update_state(); train->emit_command_received_signal(command, p1, p2); } diff --git a/src/engines/TrainEngine.cpp b/src/engines/TrainEngine.cpp index cbc00f54..63603424 100644 --- a/src/engines/TrainEngine.cpp +++ b/src/engines/TrainEngine.cpp @@ -1,12 +1,18 @@ -#include "TrainEngine.hpp" +#include #include +#include "TrainEngine.hpp" namespace godot { class TrainController; + + TrainEngine::TrainEngine() = default; + void TrainEngine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_motor_param_table"), &TrainEngine::set_motor_param_table); ClassDB::bind_method(D_METHOD("get_motor_param_table"), &TrainEngine::get_motor_param_table); ClassDB::bind_method(D_METHOD("main_switch", "enabled"), &TrainEngine::main_switch); + ClassDB::bind_method( + D_METHOD("_on_train_controller_state_changed"), &TrainEngine::_on_train_controller_state_changed); ADD_PROPERTY( PropertyInfo( Variant::ARRAY, "motor_param_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, @@ -23,8 +29,8 @@ namespace godot { mover->GroundRelay = true; mover->NoVoltRelay = true; mover->OvervoltageRelay = true; - mover->DamageFlag = 0; - mover->EngDmgFlag = 0; + mover->DamageFlag = false; + mover->EngDmgFlag = false; mover->ConvOvldFlag = false; /* end testing */ @@ -41,7 +47,6 @@ namespace godot { } void TrainEngine::_do_fetch_state_from_mover(TMoverParameters *mover, Dictionary &state) { - bool previous_main_switch = static_cast(state.get("main_switch_enabled", false)); state["main_switch_enabled"] = mover->Mains; state["Mm"] = mover->Mm; state["Mw"] = mover->Mw; @@ -62,12 +67,6 @@ namespace godot { state["line_breaker_delay"] = mover->CtrlDelay; state["line_breaker_initial_delay"] = mover->InitialCtrlDelay; state["line_breaker_closes_at_no_power"] = mover->LineBreakerClosesOnlyAtNoPowerPos; - - if (!previous_main_switch && (static_cast(state["main_switch_enabled"]))) { - emit_signal("engine_start"); - } else if (previous_main_switch && !(static_cast(state["main_switch_enabled"]))) { - emit_signal("engine_stop"); - } } void TrainEngine::_do_fetch_config_from_mover(TMoverParameters *mover, Dictionary &config) { @@ -78,9 +77,9 @@ namespace godot { return motor_param_table; } - void TrainEngine::set_motor_param_table(const TypedArray &p_motor_param_table) { + void TrainEngine::set_motor_param_table(const TypedArray p_value) { motor_param_table.clear(); - motor_param_table.append_array(p_motor_param_table); + motor_param_table.append_array(p_value); } void TrainEngine::main_switch(const bool p_enabled) { @@ -97,4 +96,21 @@ namespace godot { unregister_command("main_switch", Callable(this, "main_switch")); } + void TrainEngine::_do_initialize_train_controller(TrainController *train_controller) { + TrainPart::_do_initialize_train_controller(train_controller); + train_controller->connect(TrainController::STATE_CHANGED, Callable(this, "_on_train_controller_state_changed")); + } + + void TrainEngine::_on_train_controller_state_changed() { + Dictionary state = train_controller_node->get_state(); + + if (!previous_main_switch && (static_cast(state["main_switch_enabled"]))) { + emit_signal("engine_start"); + } else if (previous_main_switch && !(static_cast(state["main_switch_enabled"]))) { + emit_signal("engine_stop"); + } + + previous_main_switch = state["main_switch_enabled"]; + } + } // namespace godot diff --git a/src/engines/TrainEngine.hpp b/src/engines/TrainEngine.hpp index b2f5e5cf..d3c7cb1e 100644 --- a/src/engines/TrainEngine.hpp +++ b/src/engines/TrainEngine.hpp @@ -1,7 +1,7 @@ #pragma once +#include #include "../core/TrainPart.hpp" #include "../maszyna/McZapkie/MOVER.h" -#include namespace godot { @@ -19,10 +19,18 @@ namespace godot { void _do_fetch_config_from_mover(TMoverParameters *mover, Dictionary &config) override; void _register_commands() override; void _unregister_commands() override; + void _do_initialize_train_controller(TrainController *train_controller) override; + + private: + bool previous_main_switch = false; public: TypedArray get_motor_param_table(); - void set_motor_param_table(const TypedArray &p_motor_param_table); - void main_switch(bool p_enabled); + void set_motor_param_table(const TypedArray p_wwlist); + void main_switch(const bool p_enabled); + void _on_train_controller_state_changed(); + + TrainEngine(); + ~TrainEngine() override = default; }; } // namespace godot diff --git a/src/systems/TrainSecuritySystem.cpp b/src/systems/TrainSecuritySystem.cpp index ee343d22..51406e0e 100644 --- a/src/systems/TrainSecuritySystem.cpp +++ b/src/systems/TrainSecuritySystem.cpp @@ -73,6 +73,9 @@ namespace godot { ClassDB::bind_method(D_METHOD("set_ca_max_hold_time"), &TrainSecuritySystem::set_ca_max_hold_time); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ca_max_hold_time"), "set_ca_max_hold_time", "get_ca_max_hold_time"); ClassDB::bind_method(D_METHOD("security_acknowledge", "enabled"), &TrainSecuritySystem::security_acknowledge); + ClassDB::bind_method( + D_METHOD("_on_train_controller_state_changed"), + &TrainSecuritySystem::_on_train_controller_state_changed); ADD_SIGNAL(MethodInfo("blinking_changed", PropertyInfo(Variant::BOOL, "state"))); ADD_SIGNAL(MethodInfo("beeping_changed", PropertyInfo(Variant::BOOL, "state"))); @@ -163,8 +166,6 @@ namespace godot { } void TrainSecuritySystem::_do_fetch_state_from_mover(TMoverParameters *mover, Dictionary &state) { - const bool prev_beeping = state["beeping"]; - const bool prev_blinking = state["blinking"]; state["beeping"] = mover->SecuritySystem.is_beeping(); state["blinking"] = mover->SecuritySystem.is_blinking(); state["radiostop_available"] = mover->SecuritySystem.radiostop_available(); @@ -174,13 +175,6 @@ namespace godot { state["braking"] = mover->SecuritySystem.is_braking(); state["engine_blocked"] = mover->SecuritySystem.is_engine_blocked(); state["separate_acknowledge"] = mover->SecuritySystem.has_separate_acknowledge(); - - if (prev_blinking != static_cast(state["blinking"])) { - emit_signal("blinking_changed", state["blinking"]); - } - if (prev_beeping != static_cast(state["beeping"])) { - emit_signal("beeping_changed", state["beeping"]); - } } void TrainSecuritySystem::_do_update_internal_mover(TMoverParameters *mover) { @@ -231,4 +225,22 @@ namespace godot { mover->SecuritySystem.acknowledge_release(); } } + + void TrainSecuritySystem::_do_initialize_train_controller(TrainController *train_controller) { + train_controller->connect(TrainController::STATE_CHANGED, Callable(this, "_on_train_controller_state_changed")); + } + + void TrainSecuritySystem::_on_train_controller_state_changed() { + Dictionary state = train_controller_node->get_state(); + + if (prev_blinking != static_cast(state["blinking"])) { + emit_signal("blinking_changed", state["blinking"]); + } + if (prev_beeping != static_cast(state["beeping"])) { + emit_signal("beeping_changed", state["beeping"]); + } + + prev_beeping = state["beeping"]; + prev_blinking = state["blinking"]; + } } // namespace godot diff --git a/src/systems/TrainSecuritySystem.hpp b/src/systems/TrainSecuritySystem.hpp index 0544d552..ad8d5c7f 100644 --- a/src/systems/TrainSecuritySystem.hpp +++ b/src/systems/TrainSecuritySystem.hpp @@ -13,6 +13,8 @@ namespace godot { protected: void _do_update_internal_mover(TMoverParameters *mover) override; void _do_fetch_state_from_mover(TMoverParameters *mover, Dictionary &state) override; + void _do_initialize_train_controller(TrainController *train_controller) override; + void _on_train_controller_state_changed(); void _register_commands() override; void _unregister_commands() override; @@ -39,6 +41,9 @@ namespace godot { double shp_magnet_distance = 0.0; // MagnetLocation -> SecuritySystem->MagnetLocation double ca_max_hold_time = 0.0; // MaxHoldTime -> SecuritySystem->MaxHoldTime + bool prev_beeping = false; + bool prev_blinking = false; + public: void security_acknowledge(bool p_enabled);