Skip to content

Commit ede97cb

Browse files
committed
initial multiplayer
1 parent 1d6e969 commit ede97cb

15 files changed

Lines changed: 368 additions & 42 deletions

addons/libmaszyna/console/developer_console.gd

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,80 @@ func _ready() -> void:
1717
Console.add_command("get", self.console_get_train_state, ["train", "parameter"], 1, "Get train state / parameter")
1818
Console.add_command("prop", self.console_get_config_value, ["train", "property"], 2, "Get train config property")
1919
Console.add_command("props", self.console_get_config_properties, ["train"], 1, "List train config properties")
20+
Console.add_command("server", self.console_host_server, ["address"], 0, "Host a game server")
21+
Console.add_command("connect", self.console_connect_server, ["address"], 0, "Connect to a game server")
22+
Console.add_command("nick", self.console_set_nick, ["nick"], 1, "Set your nickname")
23+
Console.add_command("players", self.console_list_players, [], 0, "List all players")
24+
Console.add_command("say", self.console_say, ["message"], 1, "Say message")
2025

26+
#TrainSystem.train_log_updated.connect(self.console_print_train_log)
2127
LogSystem.log_updated.connect(self.console_print_log)
28+
PlayerSystem.error_received.connect(func(msg): LogSystem.error(msg))
29+
30+
31+
func _parse_host_ip_from_address(address:String = ""):
32+
var port:int
33+
var host:String
34+
35+
if address.is_empty():
36+
host = ProjectSettings.get_setting("maszyna/game/multiplayer/host", "127.0.0.1")
37+
if not host:
38+
console_print_log(LogSystem.LogLevel.WARNING, "Host is not configured in ProjectSetttings")
39+
port = int(ProjectSettings.get_setting("maszyna/game/multiplayer/port", 9797))
40+
if not port:
41+
console_print_log(LogSystem.LogLevel.WARNING, "Port is not configured in ProjectSetttings")
42+
else:
43+
var _parts = address.split(":")
44+
host = _parts[0].strip_edges()
45+
port = int(_parts[1].strip_edges())
46+
if host.is_empty():
47+
host = ProjectSettings.get_setting("maszyna/game/multiplayer/host", "127.0.0.1")
48+
49+
if not port or not host:
50+
console_print_log(LogSystem.LogLevel.ERROR, "Incorrect address \"%s:%s\"" % [host, port])
51+
return [null, null]
52+
53+
return [host, port]
54+
55+
func console_list_players():
56+
for player:PlayerInfo in PlayerSystem.get_all_players():
57+
if player:
58+
if player.train_id:
59+
Console.print_line("%s: %s [in %s]" % [player.peer_id, player.name, player.train_id])
60+
else:
61+
Console.print_line("%s: %s" % [player.peer_id, player.name])
62+
63+
func console_say(message: String):
64+
PlayerSystem.send_message(message)
65+
66+
func console_set_nick(nick:String):
67+
PlayerSystem.nick = nick
68+
PlayerSystem.set_player_name(PlayerSystem.get_my_peer_id(), nick)
69+
70+
func console_host_server(address:String = ""):
71+
var host_and_port = _parse_host_ip_from_address(address)
72+
var host = host_and_port[0]
73+
var port = host_and_port[1]
74+
75+
if host and port:
76+
var err = MultiPlayerManager.host_game(host_and_port[0], host_and_port[1])
77+
if err == OK:
78+
LogSystem.info("Server started at %s:%s" % [host, port])
79+
else:
80+
LogSystem.error("Cannot host a game server (%s)" % err)
81+
82+
83+
func console_connect_server(address:String = ""):
84+
var host_and_port = _parse_host_ip_from_address(address)
85+
var host = host_and_port[0]
86+
var port = host_and_port[1]
87+
if host and port:
88+
var err = MultiPlayerManager.connect_game(host, port)
89+
if err == OK:
90+
LogSystem.info("Connected to the server %s:%s" % [host, port])
91+
else:
92+
LogSystem.error("Can't connect to server (%s)" % err)
93+
2294

2395
func console_get_config_value(train, property):
2496
Console.print_line("%s" % TrainSystem.get_config_property(train, property))
@@ -45,6 +117,9 @@ func console_list_train_commands():
45117
commands.sort()
46118
Console.print_line("%s" % "\n".join(commands))
47119

120+
func console_print_train_log(train_id, loglevel, line):
121+
console_print_log(loglevel, "%s: %s" % [train_id, line])
122+
48123
func console_print_log(loglevel, line):
49124
if loglevel >= LogSystem.LogLevel.ERROR:
50125
Console.print_line("[color=red]%s[/color]" % [line])

addons/libmaszyna/libmaszyna.gd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ func _enter_tree():
2323

2424
add_custom_project_setting("maszyna/import_model_scale_factor", 1.0, TYPE_FLOAT)
2525

26+
add_autoload_singleton("PlayerSystem", "res://addons/libmaszyna/player/player_system.gd")
27+
add_autoload_singleton("MultiPlayerManager", "res://addons/libmaszyna/player/multiplayer_manager.gd")
2628
add_autoload_singleton("SceneryResourceLoader", "res://addons/libmaszyna/scenery/scenery_resource_loader.gd")
2729
add_autoload_singleton("MaszynaEnvironment", "res://addons/libmaszyna/environment/maszyna_environment.gd")
2830
add_autoload_singleton("Console", "res://addons/libmaszyna/console/console.gd")
@@ -52,6 +54,10 @@ func _enter_tree():
5254
user_settings_dock = user_settings_dock_scene.instantiate()
5355
add_control_to_dock(DOCK_SLOT_RIGHT_UL, user_settings_dock)
5456

57+
# MOVE TO USER SETTINGS!
58+
add_custom_project_setting("maszyna/game/multiplayer/host", "127.0.0.1", TYPE_STRING)
59+
add_custom_project_setting("maszyna/game/multiplayer/port", 9797, TYPE_INT)
60+
5561

5662
func _exit_tree():
5763
remove_control_from_container(CONTAINER_SPATIAL_EDITOR_MENU, e3d_submodel_toolbar_instance)
@@ -64,6 +70,8 @@ func _exit_tree():
6470
remove_custom_type("E3DModelInstance")
6571
remove_custom_type("MaszynaEnvironmentNode")
6672

73+
remove_autoload_singleton("PlayerSystem")
74+
remove_autoload_singleton("MultiPlayerManager")
6775
remove_autoload_singleton("E3DModelInstanceManager")
6876
remove_autoload_singleton("UserSettings")
6977
remove_autoload_singleton("E3DNodesInstancer")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extends Node
2+
3+
func host_game(ip, port) -> int:
4+
var peer = ENetMultiplayerPeer.new()
5+
peer.set_bind_ip(ip)
6+
var err = peer.create_server(port, 32)
7+
if err == OK:
8+
multiplayer.multiplayer_peer = peer
9+
return err
10+
11+
12+
func connect_game(ip, port) -> int:
13+
var peer = ENetMultiplayerPeer.new()
14+
var err = peer.create_client(ip, port)
15+
if err == OK:
16+
multiplayer.multiplayer_peer = peer
17+
return err
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
extends Resource
2+
class_name PlayerInfo
3+
4+
enum PlayerType { PLAYER_TYPE_LOCAL, PLAYER_TYPE_REMOTE }
5+
6+
@export var peer_id:int
7+
@export var name:String
8+
@export var type:PlayerType
9+
@export var train_id:String
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
extends Node
2+
#class_name PlayerSystem
3+
4+
var _players = {}
5+
var _trains_occupied = {} # train_id to peer_id map
6+
7+
var nick:String = ""
8+
var occupied_train: String = ""
9+
10+
11+
signal error_received(error: String)
12+
signal kicked_off_train(peer_id: int, train_id: String)
13+
14+
func _ready():
15+
multiplayer.peer_connected.connect(_on_peer_connected)
16+
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
17+
18+
19+
func _error(msg):
20+
error_received.emit(msg)
21+
22+
23+
func _on_peer_connected(peer_id: int):
24+
print("Peer connected: %d" % peer_id)
25+
register_player.rpc(peer_id)
26+
set_player_name.rpc(peer_id, nick)
27+
if occupied_train:
28+
enter_train.rpc(peer_id, occupied_train)
29+
30+
31+
func _on_peer_disconnected(peer_id: int):
32+
unregister_player.rpc(peer_id)
33+
34+
35+
func _require_registered_player(peer_id):
36+
if not _players.has(peer_id):
37+
_error("Player not registered: %s" % peer_id)
38+
false
39+
else:
40+
return true
41+
42+
func _require_train_occupied_by_player(peer_id, train_id):
43+
if _require_registered_player(peer_id):
44+
if not train_id in _trains_occupied:
45+
_error("Train is not occupied: %s" % train_id)
46+
return false
47+
if not _trains_occupied.has(train_id):
48+
_error("Train is not occupied: %s" % train_id)
49+
return false
50+
return true
51+
else:
52+
return false
53+
54+
55+
func recevie_error(message):
56+
error_received.emit(message)
57+
58+
59+
@rpc("any_peer", "call_local")
60+
func register_player(peer_id:int, name:String = "", type=PlayerInfo.PlayerType.PLAYER_TYPE_LOCAL):
61+
if _players.has(peer_id):
62+
_error("Player already registered: %s" % peer_id)
63+
return
64+
65+
var p = PlayerInfo.new()
66+
p.name = name if not name.is_empty() else "Player %d" % peer_id
67+
p.type = type
68+
p.peer_id = peer_id
69+
70+
_players[peer_id] = p
71+
72+
73+
@rpc("any_peer", "call_local")
74+
func unregister_player(peer_id:int):
75+
if _require_registered_player(peer_id):
76+
_players.erase(peer_id)
77+
78+
79+
func get_all_players():
80+
return _players.values()
81+
82+
83+
func get_my_peer_id():
84+
return multiplayer.get_unique_id()
85+
86+
87+
func set_player_name(peer_id:int, nick: String):
88+
if not nick.is_empty() and _require_registered_player(peer_id):
89+
receive_player_name.rpc(peer_id, nick)
90+
91+
@rpc("any_peer", "call_local")
92+
func receive_player_name(peer_id:int, nick: String):
93+
_players[peer_id].name = nick
94+
LogSystem.warning("Player %d changed nick to %s" % [peer_id, nick])
95+
96+
97+
@rpc("any_peer", "call_local")
98+
func enter_train(peer_id:int, train_id:String):
99+
if _require_registered_player(peer_id):
100+
if _trains_occupied.has(train_id):
101+
_error("Train is already occupied: %s" % train_id)
102+
if multiplayer.get_remote_sender_id() == 1:
103+
#kicked_off_train.emit(peer_id, train_id)
104+
kick_off_from_train.rpc(peer_id, train_id)
105+
#handle_train_occupy_conflict.rpc_id(peer_id, train_id, _trains_occupied[train_id])
106+
return
107+
_trains_occupied[train_id] = peer_id
108+
_players[peer_id].train_id = train_id
109+
110+
111+
@rpc("any_peer", "call_local")
112+
func exit_train(peer_id:int, train_id:String):
113+
if _require_train_occupied_by_player(peer_id, train_id):
114+
if _trains_occupied[train_id] == peer_id:
115+
_trains_occupied.erase(train_id)
116+
_players[peer_id].train_id = ""
117+
else:
118+
_error("Train is occupied by someone else")
119+
120+
121+
@rpc("any_peer", "call_local")
122+
func kick_off_from_train(peer_id:int, train_id:String):
123+
kicked_off_train.emit(peer_id, train_id)
124+
125+
@rpc("any_peer","call_remote")
126+
func handle_train_occupy_conflict(train_id:String, actual_peer_id:int):
127+
print("HERE")
128+
_trains_occupied[train_id] = actual_peer_id
129+
kicked_off_train.emit(multiplayer.get_remote_sender_id(), train_id)
130+
131+
132+
func send_train_command(peer_id:int, train_id:String, command: String, p1 = null, p2 = null):
133+
if _require_train_occupied_by_player(peer_id, train_id):
134+
TrainSystem.send_command(train_id, command, p1, p2)

demo/project.godot

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,16 @@ E3DNodesInstancer="*res://addons/libmaszyna/e3d/e3d_nodes_instancer.gd"
3636
UserSettings="*res://addons/libmaszyna/settings/user_settings.gd"
3737
SceneryResourceLoader="*res://addons/libmaszyna/scenery/scenery_resource_loader.gd"
3838
E3DModelInstanceManager="*res://addons/libmaszyna/e3d/e3d_model_instance_manager.gd"
39+
PlayerSystem="*res://addons/libmaszyna/player/player_system.gd"
40+
MultiPlayerManager="*res://addons/libmaszyna/player/multiplayer_manager.gd"
3941

4042
[debug]
4143

4244
file_logging/log_path="user://logs/app.log"
4345

4446
[display]
4547

46-
window/size/mode=3
47-
window/size/initial_position_type=3
48+
window/size/mode.release=3
4849

4950
[editor_plugins]
5051

0 commit comments

Comments
 (0)