Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions addons/libmaszyna/console/developer_console.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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])
Expand Down
8 changes: 8 additions & 0 deletions addons/libmaszyna/libmaszyna.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down
17 changes: 17 additions & 0 deletions addons/libmaszyna/player/multiplayer_manager.gd
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions addons/libmaszyna/player/player_info.gd
Original file line number Diff line number Diff line change
@@ -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
151 changes: 151 additions & 0 deletions addons/libmaszyna/player/player_system.gd
Original file line number Diff line number Diff line change
@@ -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])
8 changes: 7 additions & 1 deletion addons/libmaszyna/rail_vehicle_3d.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions demo/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ 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]

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]

Expand Down
Loading