Advertisement
Guest User

Untitled

a guest
Feb 29th, 2024
42
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.19 KB | None | 0 0
  1. extends Node
  2.  
  3. # For developers to set from the outside, for example:
  4. # OnlineMatch.max_players = 8
  5. # OnlineMatch.client_version = 'v1.2'
  6. var min_players := 2
  7. var max_players := 4
  8. var client_version := 'dev'
  9.  
  10. # Nakama variables:
  11. var nakama_socket : NakamaSocket
  12. var my_session_id : String : get = get_my_session_id
  13. var match_id : String : get = get_match_id
  14. var matchmaker_ticket : String : get = get_matchmaker_ticket
  15.  
  16. # RPC variables
  17. var my_peer_id: int : set = _set_readonly_variable
  18.  
  19. var players: Dictionary
  20. var _next_peer_id: int
  21.  
  22. enum MatchState {
  23. LOBBY = 0,
  24. MATCHING = 1,
  25. CONNECTING = 2,
  26. WAITING_FOR_ENOUGH_PLAYERS = 3,
  27. READY = 4,
  28. PLAYING = 5,
  29. }
  30.  
  31. var match_state: int = MatchState.LOBBY : set = _set_readonly_variable , get = get_match_state
  32.  
  33. enum MatchMode {
  34. NONE = 0,
  35. CREATE = 1,
  36. JOIN = 2,
  37. MATCHMAKER = 3,
  38. }
  39.  
  40. var match_mode: int = MatchMode.NONE
  41.  
  42. enum PlayerStatus {
  43. CONNECTING = 0,
  44. CONNECTED = 1,
  45. }
  46.  
  47. enum MatchOpCode {
  48. CUSTOM_RPC = 9001,
  49. JOIN_SUCCESS = 9002,
  50. JOIN_ERROR = 9003,
  51. }
  52.  
  53. signal error(message)
  54. signal disconnected()
  55.  
  56. signal match_created(match_id)
  57. signal match_joined(match_id)
  58. signal matchmaker_matched(players)
  59.  
  60. signal player_joined(player)
  61. signal player_left(player)
  62. signal player_status_changed(player, status)
  63.  
  64. signal match_ready(players)
  65. signal match_not_ready()
  66.  
  67. class Player:
  68. var session_id: String
  69. var peer_id: int
  70. var username: String
  71.  
  72. func _init(_session_id: String, _username: String, _peer_id: int) -> void:
  73. session_id = _session_id
  74. username = _username
  75. peer_id = _peer_id
  76.  
  77. static func from_presence(presence: NakamaRTAPI.UserPresence, _peer_id: int) -> Player:
  78. return Player.new(presence.session_id, presence.username, _peer_id)
  79.  
  80. static func from_dict(data: Dictionary) -> Player:
  81. return Player.new(data['session_id'], data['username'], int(data['peer_id']))
  82.  
  83. func to_dict() -> Dictionary:
  84. return {
  85. session_id = session_id,
  86. username = username,
  87. peer_id = peer_id,
  88. }
  89.  
  90. static func serialize_players(_players: Dictionary) -> Dictionary:
  91. var result := {}
  92. for key in _players:
  93. result[key] = _players[key].to_dict()
  94. return result
  95.  
  96. static func unserialize_players(_players: Dictionary) -> Dictionary:
  97. var result := {}
  98. for key in _players:
  99. result[key] = Player.from_dict(_players[key])
  100. return result
  101.  
  102. func _set_readonly_variable(_value) -> void:
  103. pass
  104.  
  105. func _set_nakama_socket(_nakama_socket: NakamaSocket) -> void:
  106. if nakama_socket == _nakama_socket:
  107. return
  108.  
  109. if nakama_socket:
  110. nakama_socket.closed.disconnect(_on_nakama_closed)
  111. nakama_socket.received_error.disconnect(_on_nakama_error)
  112. nakama_socket.received_match_state.disconnect(_on_nakama_match_state)
  113. nakama_socket.received_match_presence.disconnect(_on_nakama_match_presence)
  114. nakama_socket.received_matchmaker_matched.disconnect(_on_nakama_matchmaker_matched)
  115.  
  116. nakama_socket = _nakama_socket
  117.  
  118. if nakama_socket:
  119. nakama_socket.closed.connect(_on_nakama_closed)
  120. nakama_socket.received_error.connect(_on_nakama_error)
  121. nakama_socket.received_match_state.connect(_on_nakama_match_state)
  122. nakama_socket.received_match_presence.connect(_on_nakama_match_presence)
  123. nakama_socket.received_matchmaker_matched.connect(_on_nakama_matchmaker_matched)
  124.  
  125.  
  126. func create_match(_nakama_socket: NakamaSocket) -> void:
  127. leave()
  128. _set_nakama_socket(_nakama_socket)
  129. match_mode = MatchMode.CREATE
  130.  
  131. var data = await nakama_socket.create_match_async()
  132. if data.is_exception():
  133. leave()
  134. error.emit("Failed to create match: " + str(data.get_exception().message))
  135. else:
  136. _on_nakama_match_created(data)
  137.  
  138. func join_match(_nakama_socket: NakamaSocket, _match_id: String) -> void:
  139. leave()
  140. _set_nakama_socket(_nakama_socket)
  141. match_mode = MatchMode.JOIN
  142.  
  143. var data = await nakama_socket.join_match_async(_match_id)
  144. if data.is_exception():
  145. leave()
  146. error.emit("Unable to join match: " + str(data.get_exception().message))
  147. else:
  148. _on_nakama_match_join(data)
  149.  
  150. func start_matchmaking(_nakama_socket: NakamaSocket, data: Dictionary = {}) -> void:
  151. leave()
  152. _set_nakama_socket(_nakama_socket)
  153. match_mode = MatchMode.MATCHMAKER
  154.  
  155. if data.has('min_count'):
  156. data['min_count'] = max(min_players, data['min_count'])
  157. else:
  158. data['min_count'] = min_players
  159.  
  160. if data.has('max_count'):
  161. data['max_count'] = min(max_players, data['max_count'])
  162. else:
  163. data['max_count'] = max_players
  164.  
  165. if client_version != '':
  166. if not data.has('string_properties'):
  167. data['string_properties'] = {}
  168. data['string_properties']['client_version'] = client_version
  169.  
  170. var query = '+properties.client_version:' + client_version
  171. if data.has('query'):
  172. data['query'] += ' ' + query
  173. else:
  174. data['query'] = query
  175.  
  176. match_state = MatchState.MATCHING
  177. var result = await nakama_socket.add_matchmaker_async(data.get('query', '*'), data['min_count'], data['max_count'], data.get('string_properties', {}), data.get('numeric_properties', {}))
  178. if result.is_exception():
  179. leave()
  180. error.emit("Unable to join match making pool: " + str(result.get_exception().message))
  181. else:
  182. matchmaker_ticket = result.ticket
  183.  
  184. func start_playing() -> void:
  185. assert(match_state == MatchState.READY)
  186. match_state = MatchState.PLAYING
  187.  
  188. func leave(close_socket: bool = false) -> void:
  189. # Nakama disconnect.
  190. if nakama_socket:
  191. if match_id:
  192. await nakama_socket.leave_match_async(match_id)
  193. elif matchmaker_ticket:
  194. await nakama_socket.remove_matchmaker_async(matchmaker_ticket)
  195. if close_socket:
  196. nakama_socket.close()
  197. _set_nakama_socket(null)
  198.  
  199. # Initialize all the variables to their default state.
  200. my_session_id = ''
  201. match_id = ''
  202. matchmaker_ticket = ''
  203. players = {}
  204. my_peer_id = 0
  205. _next_peer_id = 1
  206. match_state = MatchState.LOBBY
  207. match_mode = MatchMode.NONE
  208.  
  209. func get_my_session_id() -> String:
  210. return my_session_id
  211.  
  212. func get_match_id() -> String:
  213. return match_id
  214.  
  215. func get_matchmaker_ticket() -> String:
  216. return matchmaker_ticket
  217.  
  218. func get_match_mode() -> int:
  219. return match_mode
  220.  
  221. func get_match_state() -> int:
  222. return match_state
  223.  
  224. func get_session_id(peer_id: int):
  225. for session_id in players:
  226. if players[session_id]['peer_id'] == peer_id:
  227. return session_id
  228. return null
  229.  
  230. func get_player_names_by_peer_id() -> Dictionary:
  231. var result = {}
  232. for session_id in players:
  233. result[players[session_id]['peer_id']] = players[session_id]['username']
  234. return result
  235.  
  236. func get_network_unique_id() -> int:
  237. return my_peer_id
  238.  
  239. func is_network_server() -> bool:
  240. return my_peer_id == 1
  241.  
  242. func is_network_master_for_node(node: Node) -> bool:
  243. return node.get_multiplayer_authority() == my_peer_id
  244.  
  245. func custom_rpc(node: Node, method: String, args: Array = []) -> void:
  246. custom_rpc_id(node, 0, method, args)
  247.  
  248. func custom_rpc_id(node: Node, id: int, method: String, args: Array = []) -> void:
  249. assert(match_state == MatchState.READY or match_state == MatchState.PLAYING)
  250. assert(match_id != '')
  251. assert(nakama_socket != null)
  252.  
  253. if nakama_socket:
  254. nakama_socket.send_match_state_async(match_id, MatchOpCode.CUSTOM_RPC, JSON.stringify({
  255. peer_id = id,
  256. node_path = str(node.get_path()),
  257. method = method,
  258. args = JSON.stringify(args),
  259. }))
  260.  
  261. func custom_rpc_sync(node: Node, method: String, args: Array = []) -> void:
  262. print(method)
  263. node.callv(method , args)
  264. custom_rpc(node, method, args)
  265.  
  266. func custom_rpc_id_sync(node: Node, id: int, method: String, args: Array = []) -> void:
  267. if my_peer_id == id:
  268. node.callv(method , args)
  269. else:
  270. custom_rpc_id(node, id, method, args)
  271.  
  272. func _on_nakama_error(data) -> void:
  273. print ("ERROR:")
  274. print(data)
  275. leave()
  276. error.emit("Websocket connection error")
  277.  
  278. func _on_nakama_closed() -> void:
  279. leave()
  280. disconnected.emit()
  281.  
  282. func _on_nakama_match_created(data: NakamaRTAPI.Match) -> void:
  283. match_id = data.match_id
  284. my_session_id = data.self_user.session_id
  285. var my_player = Player.from_presence(data.self_user, 1)
  286. players[my_session_id] = my_player
  287. my_peer_id = 1
  288. _next_peer_id = 2
  289.  
  290. match_created.emit(match_id)
  291. player_joined.emit(my_player)
  292. player_status_changed.emit(my_player , PlayerStatus.CONNECTED)
  293.  
  294.  
  295. func _check_enough_players() -> void:
  296. if players.size() >= min_players:
  297. match_state = MatchState.READY
  298. match_ready.emit(players)
  299. else:
  300. match_state = MatchState.WAITING_FOR_ENOUGH_PLAYERS
  301.  
  302. func _on_nakama_match_presence(data: NakamaRTAPI.MatchPresenceEvent) -> void:
  303. for u in data.joins:
  304. if u.session_id == my_session_id:
  305. continue
  306.  
  307. if match_mode == MatchMode.CREATE:
  308. if match_state == MatchState.PLAYING:
  309. # Tell this player that we've already started
  310. nakama_socket.send_match_state_async(match_id, MatchOpCode.JOIN_ERROR, JSON.stringify({
  311. target = u['session_id'],
  312. reason = 'Sorry! The match has already begun.',
  313. }))
  314.  
  315. if players.size() < max_players:
  316. var new_player = Player.from_presence(u, _next_peer_id)
  317. _next_peer_id += 1
  318. players[u.session_id] = new_player
  319. player_joined.emit(new_player)
  320. player_status_changed.emit(new_player , PlayerStatus.CONNECTED)
  321.  
  322. # Tell this player (and the others) about all the players peer ids.
  323. nakama_socket.send_match_state_async(match_id, MatchOpCode.JOIN_SUCCESS, JSON.stringify({
  324. players = serialize_players(players),
  325. client_version = client_version,
  326. }))
  327.  
  328. _check_enough_players()
  329. else:
  330. # Tell this player that we're full up!
  331. nakama_socket.send_match_state_async(match_id, MatchOpCode.JOIN_ERROR, JSON.stringify({
  332. target = u['session_id'],
  333. reason = 'Sorry! The match is full.,',
  334. }))
  335. elif match_mode == MatchMode.MATCHMAKER:
  336. emit_signal("player_joined", players[u.session_id])
  337.  
  338. for u in data.leaves:
  339. if u.session_id == my_session_id:
  340. continue
  341. if not players.has(u.session_id):
  342. continue
  343.  
  344. var player = players[u.session_id]
  345.  
  346. # if the host disconnected, this is the end!
  347. if player.peer_id == 1:
  348. error.emit("Host has disconnnected")
  349. else:
  350. players.erase(u.session_id)
  351. player_left.emit(player)
  352.  
  353. if players.size() < min_players:
  354. # If state was previously ready, but this brings us below the minimum players,
  355. # then we aren't ready anymore.
  356. if match_state == MatchState.READY || match_state == MatchState.PLAYING:
  357. match_not_ready.emit()
  358.  
  359. func _on_nakama_match_join(data: NakamaRTAPI.Match) -> void:
  360. match_id = data.match_id
  361. my_session_id = data.self_user.session_id
  362.  
  363. if match_mode == MatchMode.JOIN:
  364. emit_signal("match_joined", match_id)
  365. elif match_mode == MatchMode.MATCHMAKER:
  366. _check_enough_players()
  367.  
  368. func _on_nakama_matchmaker_matched(data: NakamaRTAPI.MatchmakerMatched) -> void:
  369. if data.is_exception():
  370. leave()
  371. emit_signal("error", "Matchmaker error")
  372. return
  373.  
  374. my_session_id = data.self_user.presence.session_id
  375.  
  376. # Use the list of users to assign peer ids.
  377. for u in data.users:
  378. players[u.presence.session_id] = Player.from_presence(u.presence, 0)
  379. var session_ids = players.keys()
  380. session_ids.sort()
  381.  
  382. for session_id in session_ids:
  383. players[session_id].peer_id = _next_peer_id
  384. _next_peer_id += 1
  385.  
  386. my_peer_id = players[my_session_id].peer_id
  387.  
  388. matchmaker_matched.emit(players)
  389.  
  390. for session_id in players:
  391. player_status_changed.emit(players[session_id] , PlayerStatus.CONNECTED)
  392.  
  393. # Join the match.
  394. var result = await nakama_socket.join_matched_async(data)
  395. if result.is_exception():
  396. leave()
  397. error.emit("Unable to join match: " + str(result.get_exception().message))
  398. else:
  399. _on_nakama_match_join(result)
  400.  
  401. func _on_nakama_match_state(data: NakamaRTAPI.MatchData):
  402. var json = JSON.new()
  403. var _error = json.parse(data.data)
  404. if _error != OK:
  405. return
  406.  
  407. var json_result = json.data
  408. if json_result.error != OK:
  409. return
  410.  
  411. var content = json_result.result
  412. if data.op_code == MatchOpCode.CUSTOM_RPC:
  413. if content['peer_id'] == 0 or content['peer_id'] == my_peer_id:
  414. var node = get_node(content['node_path'])
  415. if not node or not is_instance_valid(node) or node.is_queued_for_deletion():
  416. push_warning("Custom RPC: Cannot find node at path: %s" % [content['node_path']])
  417. return
  418.  
  419. if not node.has_method('_get_custom_rpc_methods') or not node._get_custom_rpc_methods().has(content['method']):
  420. push_error("Custom RPC: Method %s is not returned by %s._get_custom_rpc_methods()" % [content['method'], content['node_path']])
  421. return
  422.  
  423. node.callv(content['method'], JSON.parse_string(content['args']))
  424. if data.op_code == MatchOpCode.JOIN_SUCCESS && match_mode == MatchMode.JOIN:
  425. var host_client_version = content.get('client_version', '')
  426. if client_version != host_client_version:
  427. leave()
  428. error.emit("Client version doesn't match host")
  429. return
  430.  
  431. var content_players = unserialize_players(content['players'])
  432. my_peer_id = content_players[my_session_id].peer_id
  433. for session_id in content_players:
  434. if not players.has(session_id):
  435. players[session_id] = content_players[session_id]
  436. player_joined.emit(players[session_id])
  437. player_status_changed.emit(players[session_id] , PlayerStatus.CONNECTED)
  438. _check_enough_players()
  439. if data.op_code == MatchOpCode.JOIN_ERROR:
  440. if content['target'] == my_session_id:
  441. leave()
  442. error.emit(content['reason'])
  443.  
  444.  
  445.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement