1 | /**************************************************************************/ |
2 | /* enet_multiplayer_peer.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "enet_multiplayer_peer.h" |
32 | |
33 | #include "core/io/ip.h" |
34 | #include "core/io/marshalls.h" |
35 | #include "core/os/os.h" |
36 | |
37 | void ENetMultiplayerPeer::set_target_peer(int p_peer) { |
38 | target_peer = p_peer; |
39 | } |
40 | |
41 | int ENetMultiplayerPeer::get_packet_peer() const { |
42 | ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active." ); |
43 | ERR_FAIL_COND_V(incoming_packets.size() == 0, 1); |
44 | |
45 | return incoming_packets.front()->get().from; |
46 | } |
47 | |
48 | MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_packet_mode() const { |
49 | ERR_FAIL_COND_V_MSG(!_is_active(), TRANSFER_MODE_RELIABLE, "The multiplayer instance isn't currently active." ); |
50 | ERR_FAIL_COND_V(incoming_packets.size() == 0, TRANSFER_MODE_RELIABLE); |
51 | return incoming_packets.front()->get().transfer_mode; |
52 | } |
53 | |
54 | int ENetMultiplayerPeer::get_packet_channel() const { |
55 | ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active." ); |
56 | ERR_FAIL_COND_V(incoming_packets.size() == 0, 1); |
57 | int ch = incoming_packets.front()->get().channel; |
58 | if (ch >= SYSCH_MAX) { // First 2 channels are reserved. |
59 | return ch - SYSCH_MAX + 1; |
60 | } |
61 | return 0; |
62 | } |
63 | |
64 | Error ENetMultiplayerPeer::create_server(int p_port, int p_max_clients, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) { |
65 | ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active." ); |
66 | set_refuse_new_connections(false); |
67 | Ref<ENetConnection> host; |
68 | host.instantiate(); |
69 | Error err = host->create_host_bound(bind_ip, p_port, p_max_clients, 0, p_max_channels > 0 ? p_max_channels + SYSCH_MAX : 0, p_out_bandwidth); |
70 | if (err != OK) { |
71 | return err; |
72 | } |
73 | |
74 | active_mode = MODE_SERVER; |
75 | unique_id = 1; |
76 | connection_status = CONNECTION_CONNECTED; |
77 | hosts[0] = host; |
78 | return OK; |
79 | } |
80 | |
81 | Error ENetMultiplayerPeer::create_client(const String &p_address, int p_port, int p_channel_count, int p_in_bandwidth, int p_out_bandwidth, int p_local_port) { |
82 | ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active." ); |
83 | set_refuse_new_connections(false); |
84 | Ref<ENetConnection> host; |
85 | host.instantiate(); |
86 | Error err; |
87 | if (p_local_port) { |
88 | err = host->create_host_bound(bind_ip, p_local_port, 1, 0, p_in_bandwidth, p_out_bandwidth); |
89 | } else { |
90 | err = host->create_host(1, 0, p_in_bandwidth, p_out_bandwidth); |
91 | } |
92 | if (err != OK) { |
93 | return err; |
94 | } |
95 | |
96 | unique_id = generate_unique_id(); |
97 | |
98 | Ref<ENetPacketPeer> peer = host->connect_to_host(p_address, p_port, p_channel_count > 0 ? p_channel_count + SYSCH_MAX : 0, unique_id); |
99 | if (peer.is_null()) { |
100 | host->destroy(); |
101 | ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Couldn't connect to the ENet multiplayer server." ); |
102 | } |
103 | |
104 | // Need to wait for CONNECT event. |
105 | connection_status = CONNECTION_CONNECTING; |
106 | active_mode = MODE_CLIENT; |
107 | peers[1] = peer; |
108 | hosts[0] = host; |
109 | |
110 | return OK; |
111 | } |
112 | |
113 | Error ENetMultiplayerPeer::create_mesh(int p_id) { |
114 | ERR_FAIL_COND_V_MSG(p_id <= 0, ERR_INVALID_PARAMETER, "The unique ID must be greater then 0" ); |
115 | ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active." ); |
116 | active_mode = MODE_MESH; |
117 | unique_id = p_id; |
118 | connection_status = CONNECTION_CONNECTED; |
119 | return OK; |
120 | } |
121 | |
122 | Error ENetMultiplayerPeer::add_mesh_peer(int p_id, Ref<ENetConnection> p_host) { |
123 | ERR_FAIL_COND_V(p_host.is_null(), ERR_INVALID_PARAMETER); |
124 | ERR_FAIL_COND_V_MSG(active_mode != MODE_MESH, ERR_UNCONFIGURED, "The multiplayer instance is not configured as a mesh. Call 'create_mesh' first." ); |
125 | List<Ref<ENetPacketPeer>> host_peers; |
126 | p_host->get_peers(host_peers); |
127 | ERR_FAIL_COND_V_MSG(host_peers.size() != 1 || host_peers[0]->get_state() != ENetPacketPeer::STATE_CONNECTED, ERR_INVALID_PARAMETER, "The provided host must have exactly one peer in the connected state." ); |
128 | hosts[p_id] = p_host; |
129 | peers[p_id] = host_peers[0]; |
130 | emit_signal(SNAME("peer_connected" ), p_id); |
131 | return OK; |
132 | } |
133 | |
134 | void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event &p_event) { |
135 | Packet packet; |
136 | packet.packet = p_event.packet; |
137 | packet.channel = p_event.channel_id; |
138 | packet.from = p_source; |
139 | if (p_event.packet->flags & ENET_PACKET_FLAG_RELIABLE) { |
140 | packet.transfer_mode = TRANSFER_MODE_RELIABLE; |
141 | } else if (p_event.packet->flags & ENET_PACKET_FLAG_UNSEQUENCED) { |
142 | packet.transfer_mode = TRANSFER_MODE_UNRELIABLE; |
143 | } else { |
144 | packet.transfer_mode = TRANSFER_MODE_UNRELIABLE_ORDERED; |
145 | } |
146 | packet.packet->referenceCount++; |
147 | incoming_packets.push_back(packet); |
148 | } |
149 | |
150 | void ENetMultiplayerPeer::_disconnect_inactive_peers() { |
151 | HashSet<int> to_drop; |
152 | for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { |
153 | if (E.value->is_active()) { |
154 | continue; |
155 | } |
156 | to_drop.insert(E.key); |
157 | } |
158 | for (const int &P : to_drop) { |
159 | peers.erase(P); |
160 | if (hosts.has(P)) { |
161 | hosts.erase(P); |
162 | } |
163 | ERR_CONTINUE(active_mode == MODE_CLIENT && P != TARGET_PEER_SERVER); |
164 | emit_signal(SNAME("peer_disconnected" ), P); |
165 | } |
166 | } |
167 | |
168 | void ENetMultiplayerPeer::poll() { |
169 | ERR_FAIL_COND_MSG(!_is_active(), "The multiplayer instance isn't currently active." ); |
170 | |
171 | _pop_current_packet(); |
172 | |
173 | _disconnect_inactive_peers(); |
174 | |
175 | switch (active_mode) { |
176 | case MODE_CLIENT: { |
177 | if (!peers.has(1)) { |
178 | close(); |
179 | return; |
180 | } |
181 | ENetConnection::Event event; |
182 | ENetConnection::EventType ret = hosts[0]->service(0, event); |
183 | do { |
184 | if (ret == ENetConnection::EVENT_CONNECT) { |
185 | connection_status = CONNECTION_CONNECTED; |
186 | emit_signal(SNAME("peer_connected" ), 1); |
187 | } else if (ret == ENetConnection::EVENT_DISCONNECT) { |
188 | if (connection_status == CONNECTION_CONNECTED) { |
189 | // Client just disconnected from server. |
190 | emit_signal(SNAME("peer_disconnected" ), 1); |
191 | } |
192 | close(); |
193 | } else if (ret == ENetConnection::EVENT_RECEIVE) { |
194 | _store_packet(1, event); |
195 | } else if (ret != ENetConnection::EVENT_NONE) { |
196 | close(); // Error. |
197 | } |
198 | } while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0); |
199 | } break; |
200 | case MODE_SERVER: { |
201 | ENetConnection::Event event; |
202 | ENetConnection::EventType ret = hosts[0]->service(0, event); |
203 | do { |
204 | if (ret == ENetConnection::EVENT_CONNECT) { |
205 | if (is_refusing_new_connections()) { |
206 | event.peer->reset(); |
207 | continue; |
208 | } |
209 | // Client joined with invalid ID, probably trying to exploit us. |
210 | if (event.data < 2 || peers.has((int)event.data)) { |
211 | event.peer->reset(); |
212 | continue; |
213 | } |
214 | int id = event.data; |
215 | event.peer->set_meta(SNAME("_net_id" ), id); |
216 | peers[id] = event.peer; |
217 | emit_signal(SNAME("peer_connected" ), id); |
218 | } else if (ret == ENetConnection::EVENT_DISCONNECT) { |
219 | int id = event.peer->get_meta(SNAME("_net_id" )); |
220 | if (!peers.has(id)) { |
221 | // Never fully connected. |
222 | continue; |
223 | } |
224 | emit_signal(SNAME("peer_disconnected" ), id); |
225 | peers.erase(id); |
226 | } else if (ret == ENetConnection::EVENT_RECEIVE) { |
227 | int32_t source = event.peer->get_meta(SNAME("_net_id" )); |
228 | _store_packet(source, event); |
229 | } else if (ret != ENetConnection::EVENT_NONE) { |
230 | close(); // Error |
231 | } |
232 | } while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0); |
233 | } break; |
234 | case MODE_MESH: { |
235 | HashSet<int> to_drop; |
236 | for (KeyValue<int, Ref<ENetConnection>> &E : hosts) { |
237 | ENetConnection::Event event; |
238 | ENetConnection::EventType ret = E.value->service(0, event); |
239 | do { |
240 | if (ret == ENetConnection::EVENT_CONNECT) { |
241 | event.peer->reset(); |
242 | } else if (ret == ENetConnection::EVENT_RECEIVE) { |
243 | _store_packet(E.key, event); |
244 | } else if (ret == ENetConnection::EVENT_NONE) { |
245 | break; // Keep polling the others. |
246 | } else { |
247 | to_drop.insert(E.key); // Error or disconnect. |
248 | break; // Keep polling the others. |
249 | } |
250 | } while (E.value->check_events(ret, event) > 0); |
251 | } |
252 | for (const int &P : to_drop) { |
253 | if (peers.has(P)) { |
254 | emit_signal(SNAME("peer_disconnected" ), P); |
255 | peers.erase(P); |
256 | } |
257 | hosts.erase(P); |
258 | } |
259 | } break; |
260 | default: |
261 | return; |
262 | } |
263 | } |
264 | |
265 | bool ENetMultiplayerPeer::is_server() const { |
266 | return active_mode == MODE_SERVER; |
267 | } |
268 | |
269 | bool ENetMultiplayerPeer::is_server_relay_supported() const { |
270 | return active_mode == MODE_SERVER || active_mode == MODE_CLIENT; |
271 | } |
272 | |
273 | void ENetMultiplayerPeer::disconnect_peer(int p_peer, bool p_force) { |
274 | ERR_FAIL_COND(!_is_active() || !peers.has(p_peer)); |
275 | peers[p_peer]->peer_disconnect(0); // Will be removed during next poll. |
276 | if (active_mode == MODE_CLIENT || active_mode == MODE_SERVER) { |
277 | hosts[0]->flush(); |
278 | } else { |
279 | ERR_FAIL_COND(!hosts.has(p_peer)); |
280 | hosts[p_peer]->flush(); |
281 | } |
282 | if (p_force) { |
283 | peers.erase(p_peer); |
284 | if (hosts.has(p_peer)) { |
285 | hosts.erase(p_peer); |
286 | } |
287 | if (active_mode == MODE_CLIENT) { |
288 | hosts.clear(); // Avoid flushing again. |
289 | close(); |
290 | } |
291 | } |
292 | } |
293 | |
294 | void ENetMultiplayerPeer::close() { |
295 | if (!_is_active()) { |
296 | return; |
297 | } |
298 | |
299 | _pop_current_packet(); |
300 | |
301 | for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { |
302 | if (E.value.is_valid() && E.value->get_state() == ENetPacketPeer::STATE_CONNECTED) { |
303 | E.value->peer_disconnect_now(0); |
304 | } |
305 | } |
306 | for (KeyValue<int, Ref<ENetConnection>> &E : hosts) { |
307 | E.value->flush(); |
308 | } |
309 | |
310 | active_mode = MODE_NONE; |
311 | incoming_packets.clear(); |
312 | peers.clear(); |
313 | hosts.clear(); |
314 | unique_id = 0; |
315 | connection_status = CONNECTION_DISCONNECTED; |
316 | set_refuse_new_connections(false); |
317 | } |
318 | |
319 | int ENetMultiplayerPeer::get_available_packet_count() const { |
320 | return incoming_packets.size(); |
321 | } |
322 | |
323 | Error ENetMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { |
324 | ERR_FAIL_COND_V_MSG(incoming_packets.size() == 0, ERR_UNAVAILABLE, "No incoming packets available." ); |
325 | |
326 | _pop_current_packet(); |
327 | |
328 | current_packet = incoming_packets.front()->get(); |
329 | incoming_packets.pop_front(); |
330 | |
331 | *r_buffer = (const uint8_t *)(current_packet.packet->data); |
332 | r_buffer_size = current_packet.packet->dataLength; |
333 | |
334 | return OK; |
335 | } |
336 | |
337 | Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { |
338 | ERR_FAIL_COND_V_MSG(!_is_active(), ERR_UNCONFIGURED, "The multiplayer instance isn't currently active." ); |
339 | ERR_FAIL_COND_V_MSG(connection_status != CONNECTION_CONNECTED, ERR_UNCONFIGURED, "The multiplayer instance isn't currently connected to any server or client." ); |
340 | ERR_FAIL_COND_V_MSG(target_peer != 0 && !peers.has(ABS(target_peer)), ERR_INVALID_PARAMETER, vformat("Invalid target peer: %d" , target_peer)); |
341 | ERR_FAIL_COND_V(active_mode == MODE_CLIENT && !peers.has(1), ERR_BUG); |
342 | |
343 | int packet_flags = 0; |
344 | int channel = SYSCH_RELIABLE; |
345 | int tr_channel = get_transfer_channel(); |
346 | switch (get_transfer_mode()) { |
347 | case TRANSFER_MODE_UNRELIABLE: { |
348 | packet_flags = ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT; |
349 | channel = SYSCH_UNRELIABLE; |
350 | } break; |
351 | case TRANSFER_MODE_UNRELIABLE_ORDERED: { |
352 | packet_flags = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT; |
353 | channel = SYSCH_UNRELIABLE; |
354 | } break; |
355 | case TRANSFER_MODE_RELIABLE: { |
356 | packet_flags = ENET_PACKET_FLAG_RELIABLE; |
357 | channel = SYSCH_RELIABLE; |
358 | } break; |
359 | } |
360 | if (tr_channel > 0) { |
361 | channel = SYSCH_MAX + tr_channel - 1; |
362 | } |
363 | |
364 | #ifdef DEBUG_ENABLED |
365 | if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size > ENET_HOST_DEFAULT_MTU) { |
366 | WARN_PRINT_ONCE(vformat("Sending %d bytes unreliably which is above the MTU (%d), this will result in higher packet loss" , p_buffer_size, ENET_HOST_DEFAULT_MTU)); |
367 | } |
368 | #endif |
369 | |
370 | ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size, packet_flags); |
371 | memcpy(&packet->data[0], p_buffer, p_buffer_size); |
372 | |
373 | if (is_server()) { |
374 | if (target_peer == 0) { |
375 | hosts[0]->broadcast(channel, packet); |
376 | |
377 | } else if (target_peer < 0) { |
378 | // Send to all but one and make copies for sending. |
379 | int exclude = -target_peer; |
380 | for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { |
381 | if (E.key == exclude) { |
382 | continue; |
383 | } |
384 | E.value->send(channel, packet); |
385 | } |
386 | _destroy_unused(packet); |
387 | } else { |
388 | peers[target_peer]->send(channel, packet); |
389 | } |
390 | ERR_FAIL_COND_V(!hosts.has(0), ERR_BUG); |
391 | hosts[0]->flush(); |
392 | |
393 | } else if (active_mode == MODE_CLIENT) { |
394 | peers[1]->send(channel, packet); // Send to server for broadcast. |
395 | ERR_FAIL_COND_V(!hosts.has(0), ERR_BUG); |
396 | hosts[0]->flush(); |
397 | |
398 | } else { |
399 | if (target_peer <= 0) { |
400 | int exclude = ABS(target_peer); |
401 | for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) { |
402 | if (E.key == exclude) { |
403 | continue; |
404 | } |
405 | E.value->send(channel, packet); |
406 | ERR_CONTINUE(!hosts.has(E.key)); |
407 | hosts[E.key]->flush(); |
408 | } |
409 | _destroy_unused(packet); |
410 | } else { |
411 | peers[target_peer]->send(channel, packet); |
412 | ERR_FAIL_COND_V(!hosts.has(target_peer), ERR_BUG); |
413 | hosts[target_peer]->flush(); |
414 | } |
415 | } |
416 | |
417 | return OK; |
418 | } |
419 | |
420 | int ENetMultiplayerPeer::get_max_packet_size() const { |
421 | return 1 << 24; // Anything is good |
422 | } |
423 | |
424 | void ENetMultiplayerPeer::_pop_current_packet() { |
425 | if (current_packet.packet) { |
426 | current_packet.packet->referenceCount--; |
427 | _destroy_unused(current_packet.packet); |
428 | current_packet.packet = nullptr; |
429 | current_packet.from = 0; |
430 | current_packet.channel = -1; |
431 | } |
432 | } |
433 | |
434 | MultiplayerPeer::ConnectionStatus ENetMultiplayerPeer::get_connection_status() const { |
435 | return connection_status; |
436 | } |
437 | |
438 | int ENetMultiplayerPeer::get_unique_id() const { |
439 | ERR_FAIL_COND_V_MSG(!_is_active(), 0, "The multiplayer instance isn't currently active." ); |
440 | return unique_id; |
441 | } |
442 | |
443 | void ENetMultiplayerPeer::set_refuse_new_connections(bool p_enabled) { |
444 | #ifdef GODOT_ENET |
445 | if (_is_active()) { |
446 | for (KeyValue<int, Ref<ENetConnection>> &E : hosts) { |
447 | E.value->refuse_new_connections(p_enabled); |
448 | } |
449 | } |
450 | #endif |
451 | MultiplayerPeer::set_refuse_new_connections(p_enabled); |
452 | } |
453 | |
454 | Ref<ENetConnection> ENetMultiplayerPeer::get_host() const { |
455 | ERR_FAIL_COND_V(!_is_active(), nullptr); |
456 | ERR_FAIL_COND_V(active_mode == MODE_MESH, nullptr); |
457 | return hosts[0]; |
458 | } |
459 | |
460 | Ref<ENetPacketPeer> ENetMultiplayerPeer::get_peer(int p_id) const { |
461 | ERR_FAIL_COND_V(!_is_active(), nullptr); |
462 | ERR_FAIL_COND_V(!peers.has(p_id), nullptr); |
463 | ERR_FAIL_COND_V(active_mode == MODE_CLIENT && p_id != 1, nullptr); |
464 | return peers[p_id]; |
465 | } |
466 | |
467 | void ENetMultiplayerPeer::_destroy_unused(ENetPacket *p_packet) { |
468 | if (p_packet->referenceCount == 0) { |
469 | enet_packet_destroy(p_packet); |
470 | } |
471 | } |
472 | |
473 | void ENetMultiplayerPeer::_bind_methods() { |
474 | ClassDB::bind_method(D_METHOD("create_server" , "port" , "max_clients" , "max_channels" , "in_bandwidth" , "out_bandwidth" ), &ENetMultiplayerPeer::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0), DEFVAL(0)); |
475 | ClassDB::bind_method(D_METHOD("create_client" , "address" , "port" , "channel_count" , "in_bandwidth" , "out_bandwidth" , "local_port" ), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0)); |
476 | ClassDB::bind_method(D_METHOD("create_mesh" , "unique_id" ), &ENetMultiplayerPeer::create_mesh); |
477 | ClassDB::bind_method(D_METHOD("add_mesh_peer" , "peer_id" , "host" ), &ENetMultiplayerPeer::add_mesh_peer); |
478 | ClassDB::bind_method(D_METHOD("set_bind_ip" , "ip" ), &ENetMultiplayerPeer::set_bind_ip); |
479 | |
480 | ClassDB::bind_method(D_METHOD("get_host" ), &ENetMultiplayerPeer::get_host); |
481 | ClassDB::bind_method(D_METHOD("get_peer" , "id" ), &ENetMultiplayerPeer::get_peer); |
482 | |
483 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "host" , PROPERTY_HINT_RESOURCE_TYPE, "ENetConnection" , PROPERTY_USAGE_NONE), "" , "get_host" ); |
484 | } |
485 | |
486 | ENetMultiplayerPeer::ENetMultiplayerPeer() { |
487 | bind_ip = IPAddress("*" ); |
488 | } |
489 | |
490 | ENetMultiplayerPeer::~ENetMultiplayerPeer() { |
491 | if (_is_active()) { |
492 | close(); |
493 | } |
494 | } |
495 | |
496 | // Sets IP for ENet to bind when using create_server or create_client |
497 | // if no IP is set, then ENet bind to ENET_HOST_ANY |
498 | void ENetMultiplayerPeer::set_bind_ip(const IPAddress &p_ip) { |
499 | ERR_FAIL_COND_MSG(!p_ip.is_valid() && !p_ip.is_wildcard(), vformat("Invalid bind IP address: %s" , String(p_ip))); |
500 | |
501 | bind_ip = p_ip; |
502 | } |
503 | |