aboutsummaryrefslogtreecommitdiff
path: root/client/multiplayer.gd
blob: 20c50efd37325d5df26069d92621ba8dd0d57187 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Hurry Curry! - a game about cooking
# Copyright 2024 nokoe
# Copyright 2024 metamuffin
# Copyright 2024 tpart
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
class_name Multiplayer
extends Node

signal packet(packet: Dictionary)
signal connection_closed()

static var VERSION_MAJOR: int = 7
static var VERSION_MINOR: int = 3

var connected := false
var socket := WebSocketPeer.new()

func _ready():
	print("Multiplayer connect");
	socket.connect_to_url(Global.server_url)
	socket.inbound_buffer_size = 1024 * 1024 * 4
	connected = true

func _notification(what):
	if what == NOTIFICATION_PREDELETE:
		print("Multiplayer disconnect");
		socket.close()
		connected = false

func _process(_delta):
	if connected:
		socket.poll()
		var state = socket.get_ready_state()
		if state == WebSocketPeer.STATE_OPEN:
			while socket.get_available_packet_count():
				handle_packet(socket.get_packet())
		elif state == WebSocketPeer.STATE_CLOSED:
			var code = socket.get_close_code()
			var reason = socket.get_close_reason() if code == socket.STATE_CLOSED else tr("c.error.websocket.unavailable")
			connection_closed.emit(tr("c.error.websocket").format([code, reason, code != -1]))
			self.queue_free()

func fix_packet_types(val):
	match typeof(val):
		TYPE_FLOAT: return val
		TYPE_STRING: return val
		TYPE_BOOL: return val
		TYPE_ARRAY: return val.map(fix_packet_types)
		TYPE_DICTIONARY:
			var newval = {}
			for k in val.keys():
				if typeof(val[k]) == TYPE_ARRAY and val[k].size() == 2 and typeof(val[k][0]) == TYPE_FLOAT and typeof(val[k][1]) == TYPE_FLOAT:
					if k in ["tile"]: newval[k] = Vector2i(val[k][0], val[k][1])
					elif k in ["pos", "position"]: newval[k] = Vector2(val[k][0], val[k][1])
				# TODO reenable when fixed
				# elif k in ["player", "id"] and typeof(val[k]) == TYPE_FLOAT:
				# 	newval[k] = int(val[k])
				else:
					newval[k] = fix_packet_types(val[k])
			return newval

func handle_packet(coded):
	var p = decode_packet(coded)
	if p == null:
		return
	
	p = fix_packet_types(p)

	match p["type"]:
		"version":
			var major = p["major"]
			var minor = p["minor"]
			if major != VERSION_MAJOR or minor > VERSION_MINOR:
				socket.close()
				connected = false
				connection_closed.emit(tr("c.error.version_mismatch").format([major, minor, VERSION_MAJOR, VERSION_MINOR]))
		_: packet.emit(p)

func send_join(player_name: String, character: int):
	send_packet({
		"type": "join",
		"name": player_name,
		"character": character
	})

func send_movement(player, pos: Vector2, direction: Vector2, boost: bool):
	send_packet({
		"type": "movement",
		"player": player,
		"pos": [pos.x, pos.y],
		"dir": [direction.x, direction.y],
		"boost": boost
	})

func send_tile_interact(player, pos: Vector2i, edge: bool):
	@warning_ignore("incompatible_ternary")
	send_packet({
		"type": "interact",
		"player": player,
		"pos": [pos.x, pos.y] if edge else null,
	})

func send_player_interact(_player, _edge: bool):
	push_error("not yet implemented")

func send_chat(player, message: String):
	send_packet({
		"type": "communicate",
		"player": player,
		"persist": false,
		"message": {
			"text": message
		}
	})

func send_replay_tick(dt: float):
	send_packet({
		"type": "replay_tick",
		"dt": dt
	})

func send_leave(player):
	send_packet({
		"type": "leave",
		"player": player,
	})

func send_packet(p):
	var json = JSON.stringify(p)
	socket.send_text(json)

func decode_packet(bytes: PackedByteArray):
	var json = JSON.new()
	var in_str = bytes.get_string_from_utf8()
	var error = json.parse(in_str)
	if error == OK:
		return json.data
	else:
		print("Decode of packet failed: %s in %s" % [json.get_error_message(), in_str])
		return null