Add UDP/TCP proxying and NAT Traversal

main
milesthenerd 9 months ago
parent 781d9f201d
commit bb9d411096

@ -5687,6 +5687,29 @@
</backgrounds> </backgrounds>
<paths name="paths"/> <paths name="paths"/>
<scripts name="scripts"> <scripts name="scripts">
<script>scripts\popup_text_ext.gml</script>
<script>scripts\send_seed.gml</script>
<script>scripts\popup_seed.gml</script>
<script>scripts\json_encode_nat_message.gml</script>
<script>scripts\json_parse_nat_message.gml</script>
<script>scripts\udp_send_reliable.gml</script>
<script>scripts\udp_send_unreliable.gml</script>
<script>scripts\udp_send.gml</script>
<script>scripts\udp_client_cleanup.gml</script>
<script>scripts\udp_client_cleanup_old_fragments.gml</script>
<script>scripts\shoot_missile_receive.gml</script>
<scripts name="NAT Traversal">
<script>scripts\nat_constants.gml</script>
<script>scripts\nat_init_client.gml</script>
<script>scripts\nat_connect_mediation.gml</script>
<script>scripts\nat_send_tcp_message.gml</script>
<script>scripts\nat_handle_tcp_message.gml</script>
<script>scripts\nat_send_hole_punch.gml</script>
<script>scripts\nat_step_hole_punching.gml</script>
<script>scripts\nat_handle_udp_message.gml</script>
<script>scripts\nat_request_connection.gml</script>
<script>scripts\nat_cleanup.gml</script>
</scripts>
<scripts name="Player"> <scripts name="Player">
<script>scripts\characterCreateEvent.gml</script> <script>scripts\characterCreateEvent.gml</script>
<script>scripts\characterStepEvent.gml</script> <script>scripts\characterStepEvent.gml</script>
@ -5741,6 +5764,7 @@
<script>scripts\damage_player_push.gml</script> <script>scripts\damage_player_push.gml</script>
<script>scripts\damage_player_knockdown.gml</script> <script>scripts\damage_player_knockdown.gml</script>
<script>scripts\shoot_beam.gml</script> <script>scripts\shoot_beam.gml</script>
<script>scripts\shoot_beam_receive.gml</script>
<script>scripts\shoot_missile.gml</script> <script>scripts\shoot_missile.gml</script>
<script>scripts\link_tile.gml</script> <script>scripts\link_tile.gml</script>
<script>scripts\link_surface_tile.gml</script> <script>scripts\link_surface_tile.gml</script>
@ -5927,6 +5951,7 @@
<script>scripts\Mute_Loops.gml</script> <script>scripts\Mute_Loops.gml</script>
<script>scripts\Unmute_Loops.gml</script> <script>scripts\Unmute_Loops.gml</script>
<script>scripts\PlayFootstep.gml</script> <script>scripts\PlayFootstep.gml</script>
<script>scripts\PlayFootstepSAX.gml</script>
<script>scripts\PlayLandingSound.gml</script> <script>scripts\PlayLandingSound.gml</script>
<script>scripts\SetSoundAmb.gml</script> <script>scripts\SetSoundAmb.gml</script>
<script>scripts\SetAmb_Player.gml</script> <script>scripts\SetAmb_Player.gml</script>
@ -6223,15 +6248,8 @@
<scripts name="Miles"> <scripts name="Miles">
<script>scripts\update_variables.gml</script> <script>scripts\update_variables.gml</script>
<script>scripts\create_buffer.gml</script> <script>scripts\create_buffer.gml</script>
<script>scripts\send_seed.gml</script>
<script>scripts\popup_text_ext.gml</script>
<script>scripts\draw_character_ext.gml</script> <script>scripts\draw_character_ext.gml</script>
<script>scripts\reset_dmap.gml</script> <script>scripts\reset_dmap.gml</script>
<script>scripts\popup_seed.gml</script>
<script>scripts\shoot_beam_receive.gml</script>
<script>scripts\shoot_missile_receive.gml</script>
<script>scripts\receive_randmod_buffer.gml</script>
<script>scripts\PlayFootstepSAX.gml</script>
</scripts> </scripts>
<scripts name="Misc"> <scripts name="Misc">
<script>scripts\pal_swap_get_color_count.gml</script> <script>scripts\pal_swap_get_color_count.gml</script>

@ -51,7 +51,7 @@ if(instance_exists(oClient)){
buffer_write(bombBuffer, buffer_s16, x); buffer_write(bombBuffer, buffer_s16, x);
buffer_write(bombBuffer, buffer_s16, y); buffer_write(bombBuffer, buffer_s16, y);
buffer_write(bombBuffer, buffer_u8, global.sax); buffer_write(bombBuffer, buffer_u8, global.sax);
var result = network_send_packet(oClient.socket, bombBuffer, buffer_tell(bombBuffer)); var result = udp_send_unreliable(oClient.socket, oClient.server_ip, oClient.server_port, bombBuffer, buffer_tell(bombBuffer));
buffer_delete(bombBuffer); buffer_delete(bombBuffer);
} }
} }

@ -74,8 +74,6 @@ syncedDifficulty = 1;
elm = 0; elm = 0;
state = undefined; state = undefined;
seed = undefined; seed = undefined;
var type;
type = network_socket_tcp;
syncedDifficulty = global.difficulty; syncedDifficulty = global.difficulty;
elm = oControl.mod_monstersextremecheck; elm = oControl.mod_monstersextremecheck;
@ -87,8 +85,16 @@ port = ini_read_real("ModSettings", "port", 64198);
reactorsequence = string_lower(ini_read_string("ModSettings", "reactorsequence", "synced")); reactorsequence = string_lower(ini_read_string("ModSettings", "reactorsequence", "synced"));
preferredcolor = string_lower(ini_read_string("ModSettings", "preferredcolor", "random")); preferredcolor = string_lower(ini_read_string("ModSettings", "preferredcolor", "random"));
showName = string_lower(ini_read_string("ModSettings", "showname", "enabled")); showName = string_lower(ini_read_string("ModSettings", "showname", "enabled"));
// NAT traversal settings
var nat_enabled_str = ini_read_string("ModSettings", "use_nat_traversal", "disabled");
use_nat_traversal = (nat_enabled_str == "enabled");
mediation_server_ip = ini_read_string("ModSettings", "mediation_server_ip", "127.0.0.1");
mediation_server_port = ini_read_real("ModSettings", "mediation_server_port", 6510);
ini_close(); ini_close();
// Initialize NAT constants
nat_constants();
var nameLength = string_length(name); var nameLength = string_length(name);
if(nameLength &gt; 15){ if(nameLength &gt; 15){
name = string_copy(name, 1, 15); name = string_copy(name, 1, 15);
@ -103,8 +109,46 @@ if(string_length(name) == 0){
instance_destroy(); instance_destroy();
} }
socket = network_create_socket(type); // Use UDP socket instead of TCP - proxy handles TCP connection
isConnected = network_connect(socket, ipaddress, port); // Use a specific local port so we can report it to the mediation server
var local_udp_port = irandom_range(49152, 65535);
socket = network_create_socket_ext(network_socket_udp, local_udp_port);
server_ip = ipaddress;
server_port = port;
// Initialize NAT traversal if enabled
if (use_nat_traversal) {
nat_init_client();
nat_local_port = local_udp_port; // Store our actual local port for NAT detection
nat_connect_mediation();
isConnected = -1; // Will be set to 1 when NAT connection completes
nat_keepalive_timer = 0;
NAT_KEEPALIVE_INTERVAL = 150; // Send keepalive every 5 seconds (150 steps at 30fps)
} else {
// UDP doesn't have a "connect" - we just start sending
isConnected = 1; // Assume connected, will be confirmed by server response
}
// Fragment reassembly data structures
fragment_buffers = ds_map_create(); // fragment_id -&gt; ds_map of fragments
fragment_timestamps = ds_map_create(); // fragment_id -&gt; creation time
FRAGMENT_TIMEOUT = 5000; // 5 seconds - discard incomplete fragments after this
UDP_MAX_FRAGMENT_SIZE = 1400;
// Reliable UDP with ACK
received_sequences = ds_map_create(); // Track received sequence numbers to detect duplicates
SEQUENCE_CACHE_SIZE = 1000; // How many sequence numbers to remember
next_sequence_number = 0; // For client-side reliable sends
pending_acks = ds_map_create(); // Track our sent reliable packets waiting for ACK
// Adaptive RTT tracking for retransmit timeout
smoothed_rtt = 100; // Initial estimate: 100ms
rtt_variance = 50; // Initial variance
BASE_RETRANSMIT_TIMEOUT = 200; // Minimum timeout
MAX_RETRANSMIT_TIMEOUT = 3000; // Maximum timeout (3 seconds)
MAX_RETRANSMITS = 5;
show_debug_message("Client: Using UDP to connect to proxy at " + server_ip + ":" + string(server_port));
switch(preferredcolor){ switch(preferredcolor){
case "green": case "green":
@ -290,7 +334,9 @@ beginReceiveTimer = 300;
buffer_write(buffer, buffer_u8, 254); buffer_write(buffer, buffer_u8, 254);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
buffer_write(buffer, buffer_u8, global.sax); buffer_write(buffer, buffer_u8, global.sax);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} else {
popup_text("Failed to connect");
} }
if(!noName){ if(!noName){
@ -319,6 +365,43 @@ oControl.alarm[10] = 90;
if(banned) popup_text("Banned from server"); if(banned) popup_text("Banned from server");
if(kicked) popup_text("Kicked from server"); if(kicked) popup_text("Kicked from server");
// Clean up NAT traversal resources
if (use_nat_traversal) {
nat_cleanup();
}
// Clean up fragment reassembly buffers
var frag_id = ds_map_find_first(fragment_buffers);
while (!is_undefined(frag_id)) {
var frag_map = ds_map_find_value(fragment_buffers, frag_id);
var frag_idx = ds_map_find_first(frag_map);
while (!is_undefined(frag_idx)) {
var frag_buffer = ds_map_find_value(frag_map, frag_idx);
if (is_real(frag_buffer) &amp;&amp; buffer_exists(frag_buffer)) {
buffer_delete(frag_buffer);
}
frag_idx = ds_map_find_next(frag_map, frag_idx);
}
ds_map_destroy(frag_map);
frag_id = ds_map_find_next(fragment_buffers, frag_id);
}
ds_map_destroy(fragment_buffers);
ds_map_destroy(fragment_timestamps);
ds_map_destroy(received_sequences);
// Clean up pending ACKs
if (!is_undefined(pending_acks)) {
var seq_num = ds_map_find_first(pending_acks);
while (!is_undefined(seq_num)) {
var ack_data = ds_map_find_value(pending_acks, seq_num);
var pkt_buffer = ds_map_find_value(ack_data, "buffer");
buffer_delete(pkt_buffer);
ds_map_destroy(ack_data);
seq_num = ds_map_find_next(pending_acks, seq_num);
}
ds_map_destroy(pending_acks);
}
if(global.sax &amp;&amp; global.saxmode){ if(global.sax &amp;&amp; global.saxmode){
global.sax = 0; global.sax = 0;
load_character_vars(); load_character_vars();
@ -375,7 +458,7 @@ buffer_write(buffer, buffer_s16, posX);
buffer_write(buffer, buffer_s16, posY); buffer_write(buffer, buffer_s16, posY);
buffer_write(buffer, buffer_u8, global.sax); buffer_write(buffer, buffer_u8, global.sax);
buffer_write(buffer, buffer_u8, global.spectator); buffer_write(buffer, buffer_u8, global.spectator);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
alarm[11] = 20; alarm[11] = 20;
</string> </string>
@ -497,7 +580,7 @@ if(sameRoom){
buffer_write(buffer, buffer_u8, global.mosaic); buffer_write(buffer, buffer_u8, global.mosaic);
buffer_write(buffer, buffer_u8, global.reform); buffer_write(buffer, buffer_u8, global.reform);
buffer_write(buffer, buffer_u8, oCharacter.visible); buffer_write(buffer, buffer_u8, oCharacter.visible);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
if(global.roomPrev != room){ if(global.roomPrev != room){
global.roomPrev = room; global.roomPrev = room;
} }
@ -547,7 +630,7 @@ if(mosaicEffect){
buffer_write(buffer, buffer_u8, 12); buffer_write(buffer, buffer_u8, 12);
buffer_write(buffer, buffer_u8, global.maxsmissiles); buffer_write(buffer, buffer_u8, global.maxsmissiles);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
maxsmissilesChanged = true; maxsmissilesChanged = true;
} }
@ -598,7 +681,7 @@ if(maxsmissilesChanged){
buffer_write(buffer, buffer_u8, 11); buffer_write(buffer, buffer_u8, 11);
buffer_write(buffer, buffer_u16, global.maxmissiles); buffer_write(buffer, buffer_u16, global.maxmissiles);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
maxmissilesChanged = true; maxmissilesChanged = true;
} }
@ -649,7 +732,7 @@ if(maxmissilesChanged){
buffer_write(buffer, buffer_u8, 10); buffer_write(buffer, buffer_u8, 10);
buffer_write(buffer, buffer_u8, global.itemstaken); buffer_write(buffer, buffer_u8, global.itemstaken);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
itemstakenChanged = true; itemstakenChanged = true;
} }
@ -738,7 +821,7 @@ if(mapChanged){
} }
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(!mapChanged){ if(!mapChanged){
@ -805,7 +888,7 @@ if(eventChanged){
buffer_write(buffer, buffer_u8, 8); buffer_write(buffer, buffer_u8, 8);
buffer_write(buffer, buffer_string, strict_compress(ds_list_write(global.onlineEvent))); buffer_write(buffer, buffer_string, strict_compress(ds_list_write(global.onlineEvent)));
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(!eventChanged){ if(!eventChanged){
@ -866,7 +949,7 @@ if(metdeadChanged){
buffer_write(buffer, buffer_u8, 7); buffer_write(buffer, buffer_u8, 7);
buffer_write(buffer, buffer_string, strict_compress(ds_list_write(global.onlineMetdead))); buffer_write(buffer, buffer_string, strict_compress(ds_list_write(global.onlineMetdead)));
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(!metdeadChanged){ if(!metdeadChanged){
@ -927,7 +1010,7 @@ if(itemChanged){
buffer_write(buffer, buffer_u8, 6); buffer_write(buffer, buffer_u8, 6);
buffer_write(buffer, buffer_string, strict_compress(ds_list_write(global.onlineItem))); buffer_write(buffer, buffer_string, strict_compress(ds_list_write(global.onlineItem)));
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(!itemChanged){ if(!itemChanged){
@ -977,7 +1060,7 @@ if(itemChanged){
buffer_write(buffer, buffer_u8, 4); buffer_write(buffer, buffer_u8, 4);
buffer_write(buffer, buffer_s8, global.monstersleft); buffer_write(buffer, buffer_s8, global.monstersleft);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
metroidCountChanged = true; metroidCountChanged = true;
} }
@ -998,7 +1081,7 @@ if(global.monstersareaPrev != global.monstersarea){
buffer_write(buffer, buffer_u8, 5); buffer_write(buffer, buffer_u8, 5);
buffer_write(buffer, buffer_s8, global.monstersarea); buffer_write(buffer, buffer_s8, global.monstersarea);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
metroidCountChanged = true; metroidCountChanged = true;
} }
@ -1171,7 +1254,7 @@ if(canSend){
buffer_write(buffer, buffer_string, strict_compress(ds_grid_write(global.onlineSend))); buffer_write(buffer, buffer_string, strict_compress(ds_grid_write(global.onlineSend)));
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
buffer_write(buffer, buffer_u32, global.packetID); buffer_write(buffer, buffer_u32, global.packetID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
show_debug_message("packetID = " + string(global.packetID)); show_debug_message("packetID = " + string(global.packetID));
global.packetID++; global.packetID++;
ds_grid_clear(global.onlineSend, -1); ds_grid_clear(global.onlineSend, -1);
@ -1220,7 +1303,7 @@ if(instance_exists(oQueen) &amp;&amp; instance_exists(oQueenHead) &amp;&amp; !is
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
buffer_write(buffer, buffer_s8, oQueen.phase); buffer_write(buffer, buffer_s8, oQueen.phase);
buffer_write(buffer, buffer_s8, oQueen.state); buffer_write(buffer, buffer_s8, oQueen.state);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
} }
@ -1257,7 +1340,91 @@ if(itemCount != global.itemstaken){
<arguments> <arguments>
<argument> <argument>
<kind>1</kind> <kind>1</kind>
<string>if(!connected){ <string>// Handle NAT traversal hole punching and keepalive
if (use_nat_traversal) {
nat_step_hole_punching();
// Send keepalive to mediation server to prevent timeout
nat_keepalive_timer++;
if (nat_keepalive_timer &gt;= NAT_KEEPALIVE_INTERVAL) {
nat_keepalive_timer = 0;
if (mediation_connected &amp;&amp; mediation_socket != -1) {
var params = ds_map_create();
nat_send_tcp_message(mediation_socket, MSG_KEEP_ALIVE, params);
ds_map_destroy(params);
}
}
}
// Handle retransmissions for client-side reliable sends
var current = current_time;
// Calculate adaptive retransmit timeout: RTO = smoothed_rtt + (4 * rtt_variance)
var retransmit_timeout = clamp(smoothed_rtt + (4 * rtt_variance), BASE_RETRANSMIT_TIMEOUT, MAX_RETRANSMIT_TIMEOUT);
if (!is_undefined(pending_acks)) {
var seq_num = ds_map_find_first(pending_acks);
while (!is_undefined(seq_num)) {
var next_seq = ds_map_find_next(pending_acks, seq_num);
var ack_data = ds_map_find_value(pending_acks, seq_num);
var send_time = ds_map_find_value(ack_data, "send_time");
var retransmits = ds_map_find_value(ack_data, "retransmits");
if (current - send_time &gt; retransmit_timeout) {
if (retransmits &lt; MAX_RETRANSMITS) {
var pkt_buffer = ds_map_find_value(ack_data, "buffer");
var pkt_size = ds_map_find_value(ack_data, "size");
var pkt_ip = ds_map_find_value(ack_data, "ip");
var pkt_port = ds_map_find_value(ack_data, "port");
network_send_udp(socket, pkt_ip, pkt_port, pkt_buffer, pkt_size);
ds_map_replace(ack_data, "send_time", current);
ds_map_replace(ack_data, "retransmits", retransmits + 1);
} else {
var pkt_buffer = ds_map_find_value(ack_data, "buffer");
buffer_delete(pkt_buffer);
ds_map_destroy(ack_data);
ds_map_delete(pending_acks, seq_num);
}
}
seq_num = next_seq;
}
}
// Clean up old incomplete fragments to prevent memory leaks
var frag_id = ds_map_find_first(fragment_timestamps);
while (!is_undefined(frag_id)) {
var next_id = ds_map_find_next(fragment_timestamps, frag_id);
var timestamp = ds_map_find_value(fragment_timestamps, frag_id);
// If fragment is older than timeout, clean it up
if (current - timestamp &gt; FRAGMENT_TIMEOUT) {
show_debug_message("Client: Fragment timeout for ID " + string(frag_id) + " - cleaning up incomplete fragments");
// Clean up the fragment buffers
if (ds_map_exists(fragment_buffers, frag_id)) {
var frag_map = ds_map_find_value(fragment_buffers, frag_id);
var total = ds_map_find_value(frag_map, "total");
// Delete any stored fragment buffers
for (var i = 0; i &lt; total; i++) {
if (ds_map_exists(frag_map, i)) {
var frag_buf = ds_map_find_value(frag_map, i);
buffer_delete(frag_buf);
}
}
ds_map_destroy(frag_map);
ds_map_delete(fragment_buffers, frag_id);
}
ds_map_delete(fragment_timestamps, frag_id);
}
frag_id = next_id;
}
if(!connected){
if(isConnected &gt;= 0){ if(isConnected &gt;= 0){
connected = true; connected = true;
popup_text("Connected"); popup_text("Connected");
@ -1277,9 +1444,11 @@ if(itemCount != global.itemstaken){
buffer_write(buffer, buffer_u8, 1); buffer_write(buffer, buffer_u8, 1);
buffer_write(buffer, buffer_string, name + "," + global.multitroid_version); buffer_write(buffer, buffer_string, name + "," + global.multitroid_version);
buffer_write(buffer, buffer_u8, global.sax); buffer_write(buffer, buffer_u8, global.sax);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
instance_create(x, y, oNametag); instance_create(x, y, oNametag);
} else { } else if (!use_nat_traversal) {
// Only fail immediately if not using NAT traversal
// With NAT, isConnected starts at -1 and will be set to 1 when connection completes
instance_destroy(); instance_destroy();
popup_text("Failed to connect"); popup_text("Failed to connect");
} }
@ -1468,7 +1637,7 @@ if(maxpbombsTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 13); buffer_write(buffer, buffer_u8, 13);
buffer_write(buffer, buffer_u8, global.maxpbombs); buffer_write(buffer, buffer_u8, global.maxpbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
maxpbombsChanged = true; maxpbombsChanged = true;
} }
@ -1500,7 +1669,7 @@ if(maxhealthTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 14); buffer_write(buffer, buffer_u8, 14);
buffer_write(buffer, buffer_u16, global.maxhealth); buffer_write(buffer, buffer_u16, global.maxhealth);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
maxhealthChanged = true; maxhealthChanged = true;
} }
@ -1532,7 +1701,7 @@ if(etanksTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 15); buffer_write(buffer, buffer_u8, 15);
buffer_write(buffer, buffer_u8, global.etanks); buffer_write(buffer, buffer_u8, global.etanks);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
etanksChanged = true; etanksChanged = true;
} }
@ -1564,7 +1733,7 @@ if(mtanksTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 16); buffer_write(buffer, buffer_u8, 16);
buffer_write(buffer, buffer_u8, global.mtanks); buffer_write(buffer, buffer_u8, global.mtanks);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
mtanksChanged = true; mtanksChanged = true;
} }
@ -1596,7 +1765,7 @@ if(stanksTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 17); buffer_write(buffer, buffer_u8, 17);
buffer_write(buffer, buffer_u8, global.stanks); buffer_write(buffer, buffer_u8, global.stanks);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
stanksChanged = true; stanksChanged = true;
} }
@ -1628,7 +1797,7 @@ if(ptanksTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 18); buffer_write(buffer, buffer_u8, 18);
buffer_write(buffer, buffer_u8, global.ptanks); buffer_write(buffer, buffer_u8, global.ptanks);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
ptanksChanged = true; ptanksChanged = true;
} }
@ -1660,7 +1829,7 @@ if(healthTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 25); buffer_write(buffer, buffer_u8, 25);
buffer_write(buffer, buffer_s16, global.playerhealth); buffer_write(buffer, buffer_s16, global.playerhealth);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
healthTimer = true; healthTimer = true;
} }
@ -1692,7 +1861,7 @@ if(missilesTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 26); buffer_write(buffer, buffer_u8, 26);
buffer_write(buffer, buffer_s16, global.missiles); buffer_write(buffer, buffer_s16, global.missiles);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
missilesTimer = true; missilesTimer = true;
} }
@ -1724,7 +1893,7 @@ if(smissilesTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 27); buffer_write(buffer, buffer_u8, 27);
buffer_write(buffer, buffer_s16, global.smissiles); buffer_write(buffer, buffer_s16, global.smissiles);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
smissilesTimer = true; smissilesTimer = true;
} }
@ -1756,7 +1925,7 @@ if(pbombsTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, 28); buffer_write(buffer, buffer_u8, 28);
buffer_write(buffer, buffer_s16, global.pbombs); buffer_write(buffer, buffer_s16, global.pbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
pbombsTimer = true; pbombsTimer = true;
} }
@ -1800,7 +1969,7 @@ if(pingTimer &lt;= 0){
buffer_write(buffer, buffer_u8, 103); buffer_write(buffer, buffer_u8, 103);
buffer_write(buffer, buffer_u32, current_time); buffer_write(buffer, buffer_u32, current_time);
buffer_write(buffer, buffer_u16, global.ping); buffer_write(buffer, buffer_u16, global.ping);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(resetTimer == 0){ if(resetTimer == 0){
@ -1818,7 +1987,7 @@ if(resetTimer == 0){
buffer_write(buffer, buffer_s32, bufferSize); buffer_write(buffer, buffer_s32, bufferSize);
buffer_write(buffer, buffer_u8, 20); buffer_write(buffer, buffer_u8, 20);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(currentItemsTimer == 0 &amp;&amp; connected){ if(currentItemsTimer == 0 &amp;&amp; connected){
@ -1857,10 +2026,11 @@ if(currentItemsTimer == 0 &amp;&amp; connected){
buffer_write(buffer, buffer_u8, global.pbeam); buffer_write(buffer, buffer_u8, global.pbeam);
buffer_write(buffer, buffer_u8, global.sbeam); buffer_write(buffer, buffer_u8, global.sbeam);
buffer_write(buffer, buffer_u8, global.cbeam); buffer_write(buffer, buffer_u8, global.cbeam);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(beginReceiveTimer == 0 &amp;&amp; connected){ if(beginReceiveTimer == 0 &amp;&amp; connected){
show_debug_message("Starting all alarms - position sync will now begin");
alarm[0] = 5; alarm[0] = 5;
alarm[2] = 5; alarm[2] = 5;
alarm[3] = 5; alarm[3] = 5;
@ -2099,12 +2269,224 @@ if(connected &amp;&amp; room == rm_a1h01 &amp;&amp; global.event[51] == 0 &amp;&
if(!global.acceptPacket) exit; if(!global.acceptPacket) exit;
var type_event = ds_map_find_value(async_load, "type"); var type_event = ds_map_find_value(async_load, "type");
var event_socket = ds_map_find_value(async_load, "id");
// Handle NAT mediation server TCP messages
if (use_nat_traversal &amp;&amp; event_socket == mediation_socket &amp;&amp; type_event == network_type_data) {
var _buffer = ds_map_find_value(async_load, "buffer");
buffer_seek(_buffer, buffer_seek_start, 0);
var data = buffer_read(_buffer, buffer_text);
// Append to TCP buffer (messages may be fragmented)
nat_tcp_buffer += data;
// Try to parse complete JSON messages
var brace_count = 0;
var start_index = 0;
var i = 1;
while (i &lt;= string_length(nat_tcp_buffer)) {
var ch = string_char_at(nat_tcp_buffer, i);
if (ch == "{") {
if (brace_count == 0) start_index = i;
brace_count++;
} else if (ch == "}") {
brace_count--;
if (brace_count == 0 &amp;&amp; start_index &gt; 0) {
var json_str = string_copy(nat_tcp_buffer, start_index, i - start_index + 1);
var msg = json_parse_nat_message(json_str);
if (msg != noone) {
nat_handle_tcp_message(msg);
ds_map_destroy(msg);
}
nat_tcp_buffer = string_delete(nat_tcp_buffer, 1, i);
i = 0;
}
}
i++;
}
exit;
}
// Handle NAT connection established event
if (use_nat_traversal &amp;&amp; event_socket == mediation_socket &amp;&amp; type_event == network_type_connect) {
show_debug_message("NAT: TCP connected to mediation server");
exit;
}
switch(type_event){ switch(type_event){
case network_type_data: case network_type_data:
var _buffer = ds_map_find_value(async_load, "buffer"); var _buffer = ds_map_find_value(async_load, "buffer");
var bufferSize = buffer_get_size(_buffer); var bufferSize = buffer_get_size(_buffer);
buffer_seek(_buffer, buffer_seek_start, 0); buffer_seek(_buffer, buffer_seek_start, 0);
var needs_cleanup = false;
// Debug: Log ALL incoming UDP packets
var recv_socket = ds_map_find_value(async_load, "id");
var recv_ip = ds_map_find_value(async_load, "ip");
var recv_port = ds_map_find_value(async_load, "port");
show_debug_message("Client: Received UDP on socket " + string(recv_socket) + " from " + recv_ip + ":" + string(recv_port) + ", size=" + string(bufferSize));
// Check for NAT hole punch packets during hole punching phase
if (use_nat_traversal) {
var sender_ip = ds_map_find_value(async_load, "ip");
var sender_port = ds_map_find_value(async_load, "port");
var receiving_socket = ds_map_find_value(async_load, "id");
// Debug: Log all incoming UDP when using NAT
if (nat_state == NAT_STATE_CONNECTED) {
show_debug_message("NAT: Received UDP after connection: from " + sender_ip + ":" + string(sender_port) + " on socket " + string(receiving_socket));
}
if (nat_state == NAT_STATE_PUNCHING) {
if (nat_handle_udp_message(_buffer, sender_ip, sender_port, receiving_socket)) {
exit; // Was a NAT message, don't process further
}
}
buffer_seek(_buffer, buffer_seek_start, 0); // Reset for normal processing
}
// Check if this is an ACK packet from server (starts with 255 marker)
if (bufferSize &gt;= 5) {
if (buffer_peek(_buffer, 0, buffer_u8) == 255) {
buffer_seek(_buffer, buffer_seek_start, 0);
buffer_read(_buffer, buffer_u8);
var acked_seq = buffer_read(_buffer, buffer_u32);
if (ds_map_exists(pending_acks, acked_seq)) {
var ack_data = ds_map_find_value(pending_acks, acked_seq);
// Measure RTT and update smoothed estimates (RFC 6298)
var send_time = ds_map_find_value(ack_data, "send_time");
var measured_rtt = current_time - send_time;
// First measurement
if (smoothed_rtt == 100) {
smoothed_rtt = measured_rtt;
rtt_variance = measured_rtt / 2;
} else {
// Update using exponential weighted moving average
var alpha = 0.125; // Weight for new sample
var beta = 0.25; // Weight for variance
rtt_variance = (1 - beta) * rtt_variance + beta * abs(smoothed_rtt - measured_rtt);
smoothed_rtt = (1 - alpha) * smoothed_rtt + alpha * measured_rtt;
}
var ack_buffer = ds_map_find_value(ack_data, "buffer");
buffer_delete(ack_buffer);
ds_map_destroy(ack_data);
ds_map_delete(pending_acks, acked_seq);
}
exit;
}
}
// Check if this is a reliable packet (starts with 254 marker)
if (bufferSize &gt;= 5 &amp;&amp; buffer_peek(_buffer, 0, buffer_u8) == 254) {
// Reliable packet format: [0xFE][seq_num (u32)][original_packet]
buffer_seek(_buffer, buffer_seek_start, 0);
buffer_read(_buffer, buffer_u8); // Skip marker
var seq_num = buffer_read(_buffer, buffer_u32);
// Send ACK back to server
var ack_buffer = buffer_create(5, buffer_fixed, 1);
buffer_write(ack_buffer, buffer_u8, 255); // ACK marker
buffer_write(ack_buffer, buffer_u32, seq_num);
network_send_udp(socket, server_ip, server_port, ack_buffer, 5);
buffer_delete(ack_buffer);
// Check if we've already processed this sequence number (duplicate)
if (ds_map_exists(received_sequences, seq_num)) {
exit; // Duplicate packet, ignore
}
// Mark as received and trim cache if needed
ds_map_add(received_sequences, seq_num, current_time);
if (ds_map_size(received_sequences) &gt; SEQUENCE_CACHE_SIZE) {
// Remove oldest entry
var oldest_seq = ds_map_find_first(received_sequences);
ds_map_delete(received_sequences, oldest_seq);
}
// Extract original packet (skip the 5-byte reliable header)
var original_size = bufferSize - 5;
var original_buffer = buffer_create(original_size, buffer_fixed, 1);
buffer_copy(_buffer, 5, original_size, original_buffer, 0);
_buffer = original_buffer;
bufferSize = original_size;
buffer_seek(_buffer, buffer_seek_start, 0);
needs_cleanup = true;
}
// Check if this is a fragmented packet (no size prefix, starts with fragment header)
var first_bytes = buffer_peek(_buffer, 0, buffer_u16);
var has_size_prefix = (bufferSize &gt; 4 &amp;&amp; buffer_peek(_buffer, 0, buffer_s32) + 4 == bufferSize);
var UDP_MAX_FRAGMENT_SIZE = 1200; // Must match server setting
if (!has_size_prefix &amp;&amp; bufferSize &gt;= 4) {
// This might be a fragment - check fragment header
var fragment_id = buffer_read(_buffer, buffer_u16);
var fragment_index = buffer_read(_buffer, buffer_u8);
var total_fragments = buffer_read(_buffer, buffer_u8);
// If total_fragments looks reasonable (1-255), treat as fragment
if (total_fragments &gt; 0 &amp;&amp; total_fragments &lt; 256 &amp;&amp; fragment_index &lt; total_fragments) {
// Get or create fragment map for this packet
var frag_map;
if (ds_map_exists(fragment_buffers, fragment_id)) {
frag_map = ds_map_find_value(fragment_buffers, fragment_id);
} else {
frag_map = ds_map_create();
ds_map_add(fragment_buffers, fragment_id, frag_map);
ds_map_add(frag_map, "total", total_fragments);
ds_map_add(frag_map, "received", 0);
ds_map_add(fragment_timestamps, fragment_id, current_time);
}
// Store this fragment
var fragment_data_size = bufferSize - 4;
var fragment_buffer = buffer_create(fragment_data_size, buffer_fixed, 1);
buffer_copy(_buffer, 4, fragment_data_size, fragment_buffer, 0);
ds_map_add(frag_map, fragment_index, fragment_buffer);
ds_map_replace(frag_map, "received", ds_map_find_value(frag_map, "received") + 1);
// Check if we have all fragments
if (ds_map_find_value(frag_map, "received") == total_fragments) {
// Reassemble the complete packet
var max_payload = UDP_MAX_FRAGMENT_SIZE - 4;
var total_size = 0;
for (var i = 0; i &lt; total_fragments; i++) {
var frag_buf = ds_map_find_value(frag_map, i);
total_size += buffer_get_size(frag_buf);
}
// Reassemble without adding size prefix - it's already in the fragment data
var reassembled = buffer_create(total_size, buffer_fixed, 1);
var write_pos = 0;
for (var i = 0; i &lt; total_fragments; i++) {
var frag_buf = ds_map_find_value(frag_map, i);
var frag_size = buffer_get_size(frag_buf);
buffer_copy(frag_buf, 0, frag_size, reassembled, write_pos);
write_pos += frag_size;
buffer_delete(frag_buf);
}
ds_map_destroy(frag_map);
ds_map_delete(fragment_buffers, fragment_id);
ds_map_delete(fragment_timestamps, fragment_id);
// Now process the reassembled packet as normal
_buffer = reassembled;
bufferSize = total_size;
buffer_seek(_buffer, buffer_seek_start, 0);
} else {
// Waiting for more fragments
exit;
}
}
}
// Normal packet processing (original code)
var bufferSizePacket = buffer_read(_buffer, buffer_s32); var bufferSizePacket = buffer_read(_buffer, buffer_s32);
//show_debug_message(string(bufferSize) + " " + string(bufferSizePacket)); //show_debug_message(string(bufferSize) + " " + string(bufferSizePacket));
if(!is_real(bufferSizePacket)) exit; if(!is_real(bufferSizePacket)) exit;
@ -2563,6 +2945,7 @@ switch(type_event){
case 0: case 0:
global.clientID = buffer_read(_buffer, buffer_u8); global.clientID = buffer_read(_buffer, buffer_u8);
show_debug_message(string(msgid) + " " + string(global.clientID)); show_debug_message(string(msgid) + " " + string(global.clientID));
popup_text("Received ID " + string(global.clientID));
global.receiving = true; global.receiving = true;
buffer_delete(buffer); buffer_delete(buffer);
var size, type, alignment; var size, type, alignment;
@ -2582,7 +2965,7 @@ switch(type_event){
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
buffer_write(buffer, buffer_u8, preferredcolor); buffer_write(buffer, buffer_u8, preferredcolor);
buffer_write(buffer, buffer_u8, global.sax); buffer_write(buffer, buffer_u8, global.sax);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
break; break;
case 1: case 1:
ds_grid_read(global.onlineReceived, strict_decompress(buffer_read(_buffer, buffer_string))); ds_grid_read(global.onlineReceived, strict_decompress(buffer_read(_buffer, buffer_string)));
@ -3364,7 +3747,7 @@ switch(type_event){
buffer_write(buffer, buffer_u8, global.smissiles); buffer_write(buffer, buffer_u8, global.smissiles);
buffer_write(buffer, buffer_u8, global.pbombs); buffer_write(buffer, buffer_u8, global.pbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
} else { } else {
global.playerhealthPrev = playerhealth; global.playerhealthPrev = playerhealth;
@ -3418,7 +3801,7 @@ switch(type_event){
buffer_write(buffer, buffer_u8, global.smissiles); buffer_write(buffer, buffer_u8, global.smissiles);
buffer_write(buffer, buffer_u8, global.pbombs); buffer_write(buffer, buffer_u8, global.pbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
} else { } else {
global.missilesPrev = missiles; global.missilesPrev = missiles;
@ -3472,7 +3855,7 @@ switch(type_event){
buffer_write(buffer, buffer_u8, global.smissiles); buffer_write(buffer, buffer_u8, global.smissiles);
buffer_write(buffer, buffer_u8, global.pbombs); buffer_write(buffer, buffer_u8, global.pbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
} else { } else {
global.smissilesPrev = smissiles; global.smissilesPrev = smissiles;
@ -3526,7 +3909,7 @@ switch(type_event){
buffer_write(buffer, buffer_u8, global.smissiles); buffer_write(buffer, buffer_u8, global.smissiles);
buffer_write(buffer, buffer_u8, global.pbombs); buffer_write(buffer, buffer_u8, global.pbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
} else { } else {
global.pbombsPrev = pbombs; global.pbombsPrev = pbombs;
@ -3844,7 +4227,7 @@ switch(type_event){
for(var i=0; i&lt;array_length_1d(global.item); i++){ for(var i=0; i&lt;array_length_1d(global.item); i++){
buffer_write(itemBuffer, buffer_u8, global.item[i]); buffer_write(itemBuffer, buffer_u8, global.item[i]);
} }
network_send_packet(socket, itemBuffer, buffer_tell(itemBuffer)); network_send_udp(socket, server_ip, server_port, itemBuffer, buffer_tell(itemBuffer));
buffer_delete(itemBuffer); buffer_delete(itemBuffer);
} }
break; break;
@ -3949,7 +4332,7 @@ switch(type_event){
for(var i=0; i&lt;array_length_1d(global.event); i++){ for(var i=0; i&lt;array_length_1d(global.event); i++){
buffer_write(eventBuffer, buffer_u8, global.event[i]); buffer_write(eventBuffer, buffer_u8, global.event[i]);
} }
network_send_packet(socket, eventBuffer, buffer_tell(eventBuffer)); network_send_udp(socket, server_ip, server_port, eventBuffer, buffer_tell(eventBuffer));
buffer_delete(eventBuffer); buffer_delete(eventBuffer);
} }
break; break;
@ -4101,7 +4484,7 @@ switch(type_event){
for(var i=0; i&lt;array_length_1d(global.metdead); i++){ for(var i=0; i&lt;array_length_1d(global.metdead); i++){
buffer_write(metdeadBuffer, buffer_u8, global.metdead[i]); buffer_write(metdeadBuffer, buffer_u8, global.metdead[i]);
} }
network_send_packet(socket, metdeadBuffer, buffer_tell(metdeadBuffer)); network_send_udp(socket, server_ip, server_port, metdeadBuffer, buffer_tell(metdeadBuffer));
buffer_delete(metdeadBuffer); buffer_delete(metdeadBuffer);
} }
break; break;
@ -4144,7 +4527,7 @@ switch(type_event){
buffer_write(dmapBuffer, buffer_u8, global.dmap[i, j]); buffer_write(dmapBuffer, buffer_u8, global.dmap[i, j]);
} }
} }
network_send_packet(socket, dmapBuffer, buffer_tell(dmapBuffer)); network_send_udp(socket, server_ip, server_port, dmapBuffer, buffer_tell(dmapBuffer));
buffer_delete(dmapBuffer); buffer_delete(dmapBuffer);
} }
@ -4201,7 +4584,7 @@ buffer_write(buffer11, buffer_s16, room);
buffer_write(buffer11, buffer_s16, global.currX); buffer_write(buffer11, buffer_s16, global.currX);
buffer_write(buffer11, buffer_s16, global.currY); buffer_write(buffer11, buffer_s16, global.currY);
buffer_write(buffer11, buffer_s8, global.sax); buffer_write(buffer11, buffer_s8, global.sax);
var result = network_send_packet(socket, buffer11, buffer_tell(buffer11)); var result = network_send_udp(socket, server_ip, server_port, buffer11, buffer_tell(buffer11));
buffer_delete(buffer11); buffer_delete(buffer11);
</string> </string>
</argument> </argument>
@ -4241,7 +4624,7 @@ buffer_write(buffer10, buffer_s32, bufferSize);
buffer_write(buffer10, buffer_u8, 35); buffer_write(buffer10, buffer_u8, 35);
buffer_write(buffer10, buffer_s16, global.mapposx); buffer_write(buffer10, buffer_s16, global.mapposx);
buffer_write(buffer10, buffer_s16, global.mapposy); buffer_write(buffer10, buffer_s16, global.mapposy);
var result = network_send_packet(socket, buffer10, buffer_tell(buffer10)); var result = network_send_udp(socket, server_ip, server_port, buffer10, buffer_tell(buffer10));
buffer_delete(buffer10); buffer_delete(buffer10);
</string> </string>
</argument> </argument>
@ -4278,7 +4661,7 @@ buffer_seek(buffer9, buffer_seek_start, 0);
buffer_write(buffer9, buffer_s32, bufferSize); buffer_write(buffer9, buffer_s32, bufferSize);
buffer_write(buffer9, buffer_u8, 34); buffer_write(buffer9, buffer_u8, 34);
buffer_write(buffer9, buffer_u8, global.saxmode); buffer_write(buffer9, buffer_u8, global.saxmode);
var result = network_send_packet(socket, buffer9, buffer_tell(buffer9)); var result = network_send_udp(socket, server_ip, server_port, buffer9, buffer_tell(buffer9));
buffer_delete(buffer9); buffer_delete(buffer9);
</string> </string>
</argument> </argument>
@ -4315,7 +4698,7 @@ buffer_seek(buffer8, buffer_seek_start, 0);
buffer_write(buffer8, buffer_s32, bufferSize); buffer_write(buffer8, buffer_s32, bufferSize);
buffer_write(buffer8, buffer_u8, 33); buffer_write(buffer8, buffer_u8, 33);
buffer_write(buffer8, buffer_u8, global.lobbyLocked); buffer_write(buffer8, buffer_u8, global.lobbyLocked);
var result = network_send_packet(socket, buffer8, buffer_tell(buffer8)); var result = network_send_udp(socket, server_ip, server_port, buffer8, buffer_tell(buffer8));
buffer_delete(buffer8); buffer_delete(buffer8);
</string> </string>
</argument> </argument>
@ -4351,7 +4734,7 @@ var bufferSize = buffer_tell(buffer7);
buffer_seek(buffer7, buffer_seek_start, 0); buffer_seek(buffer7, buffer_seek_start, 0);
buffer_write(buffer7, buffer_s32, bufferSize); buffer_write(buffer7, buffer_s32, bufferSize);
buffer_write(buffer7, buffer_u8, 32); buffer_write(buffer7, buffer_u8, 32);
var result = network_send_packet(socket, buffer7, buffer_tell(buffer7)); var result = network_send_udp(socket, server_ip, server_port, buffer7, buffer_tell(buffer7));
buffer_delete(buffer7); buffer_delete(buffer7);
</string> </string>
</argument> </argument>
@ -4394,7 +4777,7 @@ buffer_write(buffer6, buffer_u8, global.otherAbsorbID);
buffer_write(buffer6, buffer_s16, global.otherAbsorbRelativeX); buffer_write(buffer6, buffer_s16, global.otherAbsorbRelativeX);
buffer_write(buffer6, buffer_s16, global.otherAbsorbRelativeY); buffer_write(buffer6, buffer_s16, global.otherAbsorbRelativeY);
buffer_write(buffer6, buffer_u8, global.otherAbsorbSpriteHeight); buffer_write(buffer6, buffer_u8, global.otherAbsorbSpriteHeight);
var result = network_send_packet(socket, buffer6, buffer_tell(buffer6)); var result = network_send_udp(socket, server_ip, server_port, buffer6, buffer_tell(buffer6));
buffer_delete(buffer6); buffer_delete(buffer6);
</string> </string>
</argument> </argument>
@ -4443,7 +4826,7 @@ buffer_write(buffer5, buffer_u8, global.checkBeam);
buffer_write(buffer5, buffer_u8, global.checkMissile); buffer_write(buffer5, buffer_u8, global.checkMissile);
buffer_write(buffer5, buffer_u8, global.checkDamage); buffer_write(buffer5, buffer_u8, global.checkDamage);
buffer_write(buffer5, buffer_u8, global.checkFreeze); buffer_write(buffer5, buffer_u8, global.checkFreeze);
var result = network_send_packet(socket, buffer5, buffer_tell(buffer5)); var result = network_send_udp(socket, server_ip, server_port, buffer5, buffer_tell(buffer5));
buffer_delete(buffer5); buffer_delete(buffer5);
</string> </string>
</argument> </argument>
@ -4481,7 +4864,7 @@ buffer_seek(buffer4, buffer_seek_start, 0);
buffer_write(buffer4, buffer_s32, bufferSize); buffer_write(buffer4, buffer_s32, bufferSize);
buffer_write(buffer4, buffer_u8, 105); buffer_write(buffer4, buffer_u8, 105);
buffer_write(buffer4, buffer_u8, global.otherID); buffer_write(buffer4, buffer_u8, global.otherID);
var result = network_send_packet(socket, buffer4, buffer_tell(buffer4)); var result = network_send_udp(socket, server_ip, server_port, buffer4, buffer_tell(buffer4));
buffer_delete(buffer4); buffer_delete(buffer4);
</string> </string>
</argument> </argument>
@ -4518,7 +4901,7 @@ buffer_seek(buffer3, buffer_seek_start, 0);
buffer_write(buffer3, buffer_s32, bufferSize); buffer_write(buffer3, buffer_s32, bufferSize);
buffer_write(buffer3, buffer_u8, 30); buffer_write(buffer3, buffer_u8, 30);
buffer_write(buffer3, buffer_u8, elm); buffer_write(buffer3, buffer_u8, elm);
var result = network_send_packet(socket, buffer3, buffer_tell(buffer3)); var result = network_send_udp(socket, server_ip, server_port, buffer3, buffer_tell(buffer3));
buffer_delete(buffer3); buffer_delete(buffer3);
</string> </string>
</argument> </argument>
@ -4555,7 +4938,7 @@ buffer_seek(buffer2, buffer_seek_start, 0);
buffer_write(buffer2, buffer_s32, bufferSize); buffer_write(buffer2, buffer_s32, bufferSize);
buffer_write(buffer2, buffer_u8, 29); buffer_write(buffer2, buffer_u8, 29);
buffer_write(buffer2, buffer_u8, syncedDifficulty); buffer_write(buffer2, buffer_u8, syncedDifficulty);
var result = network_send_packet(socket, buffer2, buffer_tell(buffer2)); var result = network_send_udp(socket, server_ip, server_port, buffer2, buffer_tell(buffer2));
buffer_delete(buffer2); buffer_delete(buffer2);
</string> </string>
</argument> </argument>
@ -4596,7 +4979,7 @@ buffer_delete(buffer2);
buffer_write(buffer, buffer_u8, 19); buffer_write(buffer, buffer_u8, 19);
buffer_write(buffer, buffer_s32, global.gametime); buffer_write(buffer, buffer_s32, global.gametime);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(room == rm_score){ if(room == rm_score){
@ -4642,7 +5025,7 @@ if(room == rm_score){
buffer_write(buffer, buffer_u8, 254); buffer_write(buffer, buffer_u8, 254);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
buffer_write(buffer, buffer_u8, global.sax); buffer_write(buffer, buffer_u8, global.sax);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
} }
if(!noName){ if(!noName){

@ -407,6 +407,9 @@ if(!(file_exists(working_directory + "\multitroid\mod_settings.ini"))){
ini_write_real("ModSettings", "censorip", 0); ini_write_real("ModSettings", "censorip", 0);
ini_write_real("ModSettings", "selfpalette", 0); ini_write_real("ModSettings", "selfpalette", 0);
ini_write_string("ModSettings", "moderatorpass", "123456789"); ini_write_string("ModSettings", "moderatorpass", "123456789");
ini_write_string("ModSettings", "use_nat_traversal", "enabled");
ini_write_string("ModSettings", "mediation_server_ip", "150.136.166.80");
ini_write_real("ModSettings", "mediation_server_port", 7000);
ini_close(); ini_close();
} }
ini_open(working_directory + "\multitroid\mod_settings.ini"); ini_open(working_directory + "\multitroid\mod_settings.ini");
@ -420,6 +423,9 @@ palette = ini_read_real("ModSettings", "palette", 0);
censorip = ini_read_real("ModSettings", "censorip", 0); censorip = ini_read_real("ModSettings", "censorip", 0);
useselfpalette = ini_read_real("ModSettings", "selfpalette", 0); useselfpalette = ini_read_real("ModSettings", "selfpalette", 0);
password = ini_read_string("ModSettings", "moderatorpass", "123456789"); password = ini_read_string("ModSettings", "moderatorpass", "123456789");
use_nat_server = ini_read_string("ModSettings", "use_nat_traversal", "disabled");
nat_server = ini_read_string("ModSettings", "mediation_server_ip", "150.136.166.80");
nat_server_port = ini_read_real("ModSettings", "mediation_server_port", 7000);
ini_close(); ini_close();
var nameLength = string_length(name); var nameLength = string_length(name);

@ -49,6 +49,9 @@ num_customseed = tempnumcounter++;
num_reset = tempnumcounter++; num_reset = tempnumcounter++;
num_locklobby = tempnumcounter++; num_locklobby = tempnumcounter++;
num_saxmode = tempnumcounter++; num_saxmode = tempnumcounter++;
num_natenabled = tempnumcounter++;
num_natserver = tempnumcounter++;
num_natport = tempnumcounter++;
num_connection = tempnumcounter++; num_connection = tempnumcounter++;
lastitem = tempnumcounter; // This value will be one number higher than the last number in the list above. lastitem = tempnumcounter; // This value will be one number higher than the last number in the list above.
@ -58,7 +61,10 @@ name = oControl.name;
port = string(oControl.port); port = string(oControl.port);
showname = 1; showname = 1;
sequence = 1; sequence = 1;
use_nat_server = 1;
password = oControl.password; password = oControl.password;
nat_server = oControl.nat_server;
nat_server_port = string(oControl.nat_server_port);
menuDifficulty = global.difficulty; menuDifficulty = global.difficulty;
@ -92,6 +98,15 @@ switch(oControl.showname){
break; break;
} }
switch(oControl.use_nat_server){
case "enabled":
use_nat_server = 1;
break;
case "disabled":
use_nat_server = 0;
break;
}
vTargetY = 18 * lastitem + 30; vTargetY = 18 * lastitem + 30;
editing = 0; editing = 0;
@ -128,9 +143,11 @@ tip[num_customseed] = "Enter a custom seed";
tip[num_reset] = "Reset the server"; tip[num_reset] = "Reset the server";
tip[num_locklobby] = "Lock the lobby"; tip[num_locklobby] = "Lock the lobby";
tip[num_saxmode] = "Toggle SA-X Mode"; tip[num_saxmode] = "Toggle SA-X Mode";
tip[num_natenabled] = "Enable/disable nat traversal";
tip[num_natserver] = "Set the IP address of the NAT server";
tip[num_natport] = "Set the port of the NAT server";
tip[num_connection] = "Connect/disconnect to the server"; tip[num_connection] = "Connect/disconnect to the server";
tip[lastitem] = get_text("GlobalOptions", "ExitMain_Tip"); tip[lastitem] = get_text("GlobalOptions", "ExitMain_Tip");
global.tiptext = tip[global.curropt]; global.tiptext = tip[global.curropt];
alarm[1] = 1; alarm[1] = 1;
@ -334,6 +351,7 @@ if (active) {
if (global.curropt == num_censorip) oControl.censorip = !oControl.censorip; if (global.curropt == num_censorip) oControl.censorip = !oControl.censorip;
if (global.curropt == num_showname) showname = !showname; if (global.curropt == num_showname) showname = !showname;
if (global.curropt == num_selfpalette) oControl.useselfpalette = !oControl.useselfpalette; if (global.curropt == num_selfpalette) oControl.useselfpalette = !oControl.useselfpalette;
if (global.curropt == num_natenabled) use_nat_server = !use_nat_server;
//if (global.shaders_compiled) { //if (global.shaders_compiled) {
//if (global.curropt == num_MSRfusion) oControl.msr_fusionsuit = !oControl.msr_fusionsuit; //if (global.curropt == num_MSRfusion) oControl.msr_fusionsuit = !oControl.msr_fusionsuit;
//} //}
@ -383,6 +401,21 @@ if (active) {
editing = !editing; editing = !editing;
op[num_ip].editing = editing; op[num_ip].editing = editing;
} }
if (global.curropt == num_natserver &amp;&amp; os_type != os_android) {
editing = !editing;
op[num_natserver].editing = editing;
if(editing){
keyboard_string = nat_server;
}
sfx_play(sndMenuSel);
event_user(2);
} else if(global.curropt == num_natserver &amp;&amp; os_type == os_android){
if(!editing){
msg = get_string_async("Type in the NAT Server IP to connect to.", nat_server);
}
editing = !editing;
op[num_natserver].editing = editing;
}
if (global.curropt == num_port &amp;&amp; os_type != os_android) { if (global.curropt == num_port &amp;&amp; os_type != os_android) {
editing = !editing; editing = !editing;
op[num_port].editing = editing; op[num_port].editing = editing;
@ -398,6 +431,21 @@ if (active) {
editing = !editing; editing = !editing;
op[num_port].editing = editing; op[num_port].editing = editing;
} }
if (global.curropt == num_natport &amp;&amp; os_type != os_android) {
editing = !editing;
op[num_natport].editing = editing;
if(editing){
keyboard_string = nat_server_port;
}
sfx_play(sndMenuSel);
event_user(2);
} else if(global.curropt = num_natport &amp;&amp; os_type == os_android){
if(!editing){
msg = get_integer_async("Type in the NAT server port.", real(nat_server_port));
}
editing = !editing;
op[num_natport].editing = editing;
}
if (global.curropt == num_name &amp;&amp; os_type != os_android) { if (global.curropt == num_name &amp;&amp; os_type != os_android) {
editing = !editing; editing = !editing;
op[num_name].editing = editing; op[num_name].editing = editing;
@ -502,11 +550,21 @@ if (active) {
op[num_ip].optext = ip; op[num_ip].optext = ip;
} }
if(editing &amp;&amp; global.curropt == num_natserver &amp;&amp; os_type != os_android){
nat_server = keyboard_string;
op[num_natserver].optext = nat_server;
}
if(editing &amp;&amp; global.curropt == num_port &amp;&amp; os_type != os_android){ if(editing &amp;&amp; global.curropt == num_port &amp;&amp; os_type != os_android){
port = keyboard_string; port = keyboard_string;
op[num_port].optext = port; op[num_port].optext = port;
} }
if(editing &amp;&amp; global.curropt == num_natport &amp;&amp; os_type != os_android){
nat_server_port = keyboard_string;
op[num_natport].optext = nat_server_port;
}
if(editing &amp;&amp; global.curropt == num_name &amp;&amp; os_type != os_android){ if(editing &amp;&amp; global.curropt == num_name &amp;&amp; os_type != os_android){
var keyboardLength = string_length(keyboard_string); var keyboardLength = string_length(keyboard_string);
if(keyboardLength &gt; 15){ if(keyboardLength &gt; 15){
@ -681,6 +739,15 @@ if(!done){
break; break;
} }
switch(use_nat_server){
case 0:
op[num_natenabled].optext = "Disabled";
break;
case 1:
op[num_natenabled].optext = "Enabled";
break;
}
if(oControl.censorip &amp;&amp; !op[num_ip].editing){ if(oControl.censorip &amp;&amp; !op[num_ip].editing){
op[num_ip].optext = ""; op[num_ip].optext = "";
init = 1; init = 1;
@ -691,6 +758,12 @@ if(!done){
keyboard_string = clipboard_get_text(); keyboard_string = clipboard_get_text();
} }
} }
if(op[num_natserver].editing){
if(keyboard_check(vk_control) &amp;&amp; keyboard_check_pressed(ord('V'))){
keyboard_string = clipboard_get_text();
}
}
} }
</string> </string>
</argument> </argument>
@ -821,6 +894,8 @@ if(oControl.censorip){
} else op[num_ip].optext = ip; } else op[num_ip].optext = ip;
op[num_port].optext = port; op[num_port].optext = port;
op[num_natserver].optext = nat_server;
op[num_natport].optext = nat_server_port;
op[num_name].optext = name; op[num_name].optext = name;
op[num_moderator].optext = password; op[num_moderator].optext = password;
op[num_customseed].optext = string(global.multiCustomSeed); op[num_customseed].optext = string(global.multiCustomSeed);
@ -891,6 +966,17 @@ switch(showname){
break; break;
} }
switch(use_nat_server){
case 0:
op[num_natenabled].optext = "enabled";
oControl.use_nat_server = "disabled";
break;
case 1:
op[num_natenabled].optext = "enabled";
oControl.use_nat_server = "enabled";
break;
}
switch(oControl.preferredcolor){ switch(oControl.preferredcolor){
case 1: case 1:
color = "green"; color = "green";
@ -1026,12 +1112,17 @@ ini_write_real("ModSettings", "palette", oControl.palette);
ini_write_real("ModSettings", "censorip", oControl.censorip); ini_write_real("ModSettings", "censorip", oControl.censorip);
ini_write_real("ModSettings", "selfpalette", oControl.useselfpalette); ini_write_real("ModSettings", "selfpalette", oControl.useselfpalette);
ini_write_string("ModSettings", "moderatorpass", password); ini_write_string("ModSettings", "moderatorpass", password);
ini_write_string("ModSettings", "use_nat_traversal", oControl.use_nat_server);
ini_write_string("ModSettings", "mediation_server_ip", nat_server);
ini_write_real("ModSettings", "mediation_server_port", real(nat_server_port));
ini_close(); ini_close();
oControl.ipaddress = ip; oControl.ipaddress = ip;
oControl.name = name; oControl.name = name;
oControl.port = real(port); oControl.port = real(port);
oControl.password = password; oControl.password = password;
oControl.nat_server = nat_server;
oControl.nat_server_port = real(nat_server_port);
</string> </string>
</argument> </argument>
</arguments> </arguments>
@ -1188,6 +1279,18 @@ op[num_saxmode].label = "SA-X Mode";
op[num_saxmode].enabled = 0; op[num_saxmode].enabled = 0;
canedit[num_saxmode] = 0; canedit[num_saxmode] = 0;
op[num_natenabled] = instance_create(x, y + sep * tempcount++, oOptionLR);
op[num_natenabled].optionid = num_natenabled;
op[num_natenabled].label = "NAT Traversal";
op[num_natserver] = instance_create(x, y + sep * tempcount++, oOptionLRMod);
op[num_natserver].optionid = num_natserver;
op[num_natserver].label = "NAT Server";
op[num_natport] = instance_create(x, y + sep * tempcount++, oOptionLRMod);
op[num_natport].optionid = num_natport;
op[num_natport].label = "NAT Server Port";
op[num_connection] = instance_create(x, y + sep * tempcount++, oPauseOption); op[num_connection] = instance_create(x, y + sep * tempcount++, oPauseOption);
op[num_connection].optionid = num_connection; op[num_connection].optionid = num_connection;
op[num_connection].label = connectiontxt; op[num_connection].label = connectiontxt;

@ -52,7 +52,7 @@ if(instance_exists(oClient)){
buffer_write(pbombBuffer, buffer_s16, x); buffer_write(pbombBuffer, buffer_s16, x);
buffer_write(pbombBuffer, buffer_s16, y); buffer_write(pbombBuffer, buffer_s16, y);
buffer_write(pbombBuffer, buffer_u8, global.sax); buffer_write(pbombBuffer, buffer_u8, global.sax);
var result = network_send_packet(oClient.socket, pbombBuffer, buffer_tell(pbombBuffer)); var result = udp_send_unreliable(oClient.socket, oClient.server_ip, oClient.server_port, pbombBuffer, buffer_tell(pbombBuffer));
buffer_delete(pbombBuffer); buffer_delete(pbombBuffer);
} }
} }

@ -0,0 +1,35 @@
/// json_encode_nat_message(msg_type, params_map)
// Creates a JSON string from message type and parameters
// msg_type: MSG_* constant
// params_map: ds_map of key-value pairs (or noone for no params)
// Returns: JSON string ready to send
var msg_type = argument0;
var params = argument1;
var json = '{"ID":' + string(msg_type);
if (params != noone && ds_exists(params, ds_type_map)) {
var key = ds_map_find_first(params);
while (!is_undefined(key)) {
var value = ds_map_find_value(params, key);
// Add comma before each additional field
json += ',';
// Add the key
json += '"' + string(key) + '":';
// Add the value (handle strings vs numbers)
if (is_string(value)) {
json += '"' + string(value) + '"';
} else {
json += string(value);
}
key = ds_map_find_next(params, key);
}
}
json += '}';
return json;

@ -0,0 +1,82 @@
/// json_parse_nat_message(json_string)
// Parses a JSON message into a ds_map
// json_string: JSON string received from server
// Returns: ds_map with parsed values (caller must destroy!)
// Returns noone if parsing fails
var json_str = argument0;
// Create result map
var result = ds_map_create();
// Basic JSON parser - extracts key-value pairs from {"key":value,"key2":value2}
// This is simplified for our specific use case
// Remove surrounding braces and whitespace
json_str = string_replace_all(json_str, " ", "");
json_str = string_replace_all(json_str, chr(9), ""); // tabs
json_str = string_replace_all(json_str, chr(10), ""); // newlines
json_str = string_replace_all(json_str, chr(13), ""); // carriage returns
if (string_char_at(json_str, 1) == "{") {
json_str = string_copy(json_str, 2, string_length(json_str) - 2);
}
// Split by commas (but not commas inside quotes)
var in_quotes = false;
var current_pair = "";
var pos = 1;
while (pos <= string_length(json_str)) {
var ch = string_char_at(json_str, pos);
if (ch == '"') {
in_quotes = !in_quotes;
current_pair += ch;
} else if (ch == "," && !in_quotes) {
// Process this key-value pair
var colon_pos = string_pos(":", current_pair);
if (colon_pos > 0) {
var key = string_copy(current_pair, 1, colon_pos - 1);
var value = string_copy(current_pair, colon_pos + 1, string_length(current_pair) - colon_pos);
// Remove quotes from key
key = string_replace_all(key, '"', "");
// Check if value is a string (has quotes) or number
if (string_char_at(value, 1) == '"') {
// String value - remove quotes
value = string_replace_all(value, '"', "");
ds_map_add(result, key, value);
} else {
// Numeric value
ds_map_add(result, key, real(value));
}
}
current_pair = "";
} else {
current_pair += ch;
}
pos++;
}
// Process last pair
if (current_pair != "") {
var colon_pos = string_pos(":", current_pair);
if (colon_pos > 0) {
var key = string_copy(current_pair, 1, colon_pos - 1);
var value = string_copy(current_pair, colon_pos + 1, string_length(current_pair) - colon_pos);
key = string_replace_all(key, '"', "");
if (string_char_at(value, 1) == '"') {
value = string_replace_all(value, '"', "");
ds_map_add(result, key, value);
} else {
ds_map_add(result, key, real(value));
}
}
}
return result;

@ -0,0 +1,23 @@
/// nat_cleanup()
// Cleans up NAT traversal resources
// Call this in oClient Destroy event
if (mediation_socket != -1) {
network_destroy(mediation_socket);
mediation_socket = -1;
}
// Clean up random sockets for symmetric NAT
if (ds_exists(nat_random_sockets, ds_type_list)) {
for (var i = ds_list_size(nat_random_sockets) - 1; i >= 0; i--) {
var sock = ds_list_find_value(nat_random_sockets, i);
network_destroy(sock);
}
ds_list_destroy(nat_random_sockets);
}
mediation_connected = false;
nat_state = NAT_STATE_DISCONNECTED;
nat_type = NAT_TYPE_UNKNOWN;
show_debug_message("NAT client cleaned up");

@ -0,0 +1,29 @@
/// nat_connect_mediation()
// Connects to mediation server and starts NAT detection
// Call this when client wants to use NAT traversal
if (!use_nat_traversal) {
show_debug_message("NAT traversal not enabled");
exit;
}
if (mediation_socket != -1) {
show_debug_message("Already connected to mediation server");
exit;
}
// Create TCP connection to mediation server
mediation_socket = network_create_socket(network_socket_tcp);
var result = network_connect_raw(mediation_socket, mediation_server_ip, mediation_server_port);
if (result >= 0) {
nat_state = NAT_STATE_CONNECTING;
show_debug_message("NAT: Connecting to mediation server at " + mediation_server_ip + ":" + string(mediation_server_port));
popup_text("Starting NAT traversal...");
} else {
show_debug_message("NAT: Failed to connect to mediation server");
network_destroy(mediation_socket);
mediation_socket = -1;
nat_state = NAT_STATE_ERROR;
popup_text("NAT connection failed");
}

@ -0,0 +1,41 @@
/// nat_constants()
// NAT traversal constants - call once at game start to define globals
// These must match the Node.js mediation server
// NAT States
globalvar NAT_STATE_DISCONNECTED; NAT_STATE_DISCONNECTED = 0;
globalvar NAT_STATE_CONNECTING; NAT_STATE_CONNECTING = 1;
globalvar NAT_STATE_NAT_DETECTING; NAT_STATE_NAT_DETECTING = 2;
globalvar NAT_STATE_WAITING; NAT_STATE_WAITING = 3;
globalvar NAT_STATE_PUNCHING; NAT_STATE_PUNCHING = 4;
globalvar NAT_STATE_CONNECTED; NAT_STATE_CONNECTED = 5;
globalvar NAT_STATE_ERROR; NAT_STATE_ERROR = 6;
// NAT Types
globalvar NAT_TYPE_UNKNOWN; NAT_TYPE_UNKNOWN = -1;
globalvar NAT_TYPE_DIRECT_MAPPING; NAT_TYPE_DIRECT_MAPPING = 0;
globalvar NAT_TYPE_RESTRICTED; NAT_TYPE_RESTRICTED = 1;
globalvar NAT_TYPE_SYMMETRIC; NAT_TYPE_SYMMETRIC = 2;
// Message Types (must match mediation server constants.js)
globalvar MSG_CONNECTED; MSG_CONNECTED = 0;
globalvar MSG_NAT_TYPE_REQUEST; MSG_NAT_TYPE_REQUEST = 1;
globalvar MSG_NAT_TEST_BEGIN; MSG_NAT_TEST_BEGIN = 2;
globalvar MSG_NAT_TEST; MSG_NAT_TEST = 3;
globalvar MSG_NAT_TYPE_RESPONSE; MSG_NAT_TYPE_RESPONSE = 4;
globalvar MSG_KEEP_ALIVE; MSG_KEEP_ALIVE = 5;
globalvar MSG_CONNECTION_REQUEST; MSG_CONNECTION_REQUEST = 6;
globalvar MSG_CONNECTION_BEGIN; MSG_CONNECTION_BEGIN = 7;
globalvar MSG_SERVER_NOT_AVAILABLE; MSG_SERVER_NOT_AVAILABLE = 8;
globalvar MSG_HOLE_PUNCH_ATTEMPT; MSG_HOLE_PUNCH_ATTEMPT = 9;
globalvar MSG_NAT_TUNNEL_DATA; MSG_NAT_TUNNEL_DATA = 10;
globalvar MSG_SYMMETRIC_HOLE_PUNCH; MSG_SYMMETRIC_HOLE_PUNCH = 11;
globalvar MSG_CONNECTION_COMPLETE; MSG_CONNECTION_COMPLETE = 12;
globalvar MSG_RECEIVED_PEER; MSG_RECEIVED_PEER = 13;
globalvar MSG_CONNECTION_TIMEOUT; MSG_CONNECTION_TIMEOUT = 14;
globalvar MSG_SERVER_REGISTER; MSG_SERVER_REGISTER = 22;
// Mediation server ports
globalvar NAT_MEDIATION_PORT; NAT_MEDIATION_PORT = 6510;
globalvar NAT_TEST_PORT_ONE; NAT_TEST_PORT_ONE = 6511;
globalvar NAT_TEST_PORT_TWO; NAT_TEST_PORT_TWO = 6512;

@ -0,0 +1,155 @@
/// nat_handle_tcp_message(message_map)
// Handles incoming TCP messages from mediation server
// message_map: ds_map returned from json_parse_nat_message()
// Call this from Async Networking event
var msg = argument0;
if (!ds_exists(msg, ds_type_map)) {
exit;
}
var msg_id = ds_map_find_value(msg, "ID");
if (is_undefined(msg_id)) {
exit;
}
switch (msg_id) {
case MSG_CONNECTED:
// Connected to mediation server - start NAT detection
mediation_connected = true;
nat_state = NAT_STATE_NAT_DETECTING;
// Send NAT type request
var params = ds_map_create();
ds_map_add(params, "LocalPort", nat_local_port);
ds_map_add(params, "ClientID", nat_client_id);
nat_send_tcp_message(mediation_socket, MSG_NAT_TYPE_REQUEST, params);
ds_map_destroy(params);
break;
case MSG_NAT_TEST_BEGIN:
// Mediation server wants us to send UDP to test ports
var test_port_one = ds_map_find_value(msg, "NATTestPortOne");
var test_port_two = ds_map_find_value(msg, "NATTestPortTwo");
if (!is_undefined(test_port_one) && !is_undefined(test_port_two)) {
// Send NAT test packets to both ports
var test_params = ds_map_create();
ds_map_add(test_params, "ClientID", nat_client_id);
var test_json = json_encode_nat_message(MSG_NAT_TEST, test_params);
ds_map_destroy(test_params);
var test_buf = buffer_create(string_length(test_json) + 1, buffer_fixed, 1);
buffer_seek(test_buf, buffer_seek_start, 0);
buffer_write(test_buf, buffer_text, test_json);
network_send_udp_raw(socket, mediation_server_ip, test_port_one, test_buf, buffer_tell(test_buf));
network_send_udp_raw(socket, mediation_server_ip, test_port_two, test_buf, buffer_tell(test_buf));
buffer_delete(test_buf);
}
break;
case MSG_NAT_TYPE_RESPONSE:
// Received NAT type from server
nat_type = ds_map_find_value(msg, "NATType");
if (!is_undefined(nat_type)) {
nat_state = NAT_STATE_WAITING;
var type_name = "Unknown";
if (nat_type == NAT_TYPE_DIRECT_MAPPING) type_name = "Direct Mapping (Full Cone)";
else if (nat_type == NAT_TYPE_RESTRICTED) type_name = "Restricted";
else if (nat_type == NAT_TYPE_SYMMETRIC) type_name = "Symmetric";
// Now request connection to the game server
nat_request_connection();
}
break;
case MSG_CONNECTION_BEGIN:
// Mediation server is coordinating connection - start hole punching
var endpoint_str = ds_map_find_value(msg, "EndpointString");
var peer_nat = ds_map_find_value(msg, "NATType");
var conn_id = ds_map_find_value(msg, "ConnectionID");
if (!is_undefined(endpoint_str) && !is_undefined(peer_nat)) {
// Parse endpoint "ip:port"
var colon_pos = string_pos(":", endpoint_str);
if (colon_pos > 0) {
nat_peer_ip = string_copy(endpoint_str, 1, colon_pos - 1);
nat_peer_port = real(string_copy(endpoint_str, colon_pos + 1, string_length(endpoint_str) - colon_pos));
nat_peer_nat_type = peer_nat;
nat_connection_id = conn_id;
// Check for unsupported Symmetric-to-Symmetric
if (nat_type == NAT_TYPE_SYMMETRIC && nat_peer_nat_type == NAT_TYPE_SYMMETRIC) {
show_message("Connection not possible - both sides have Symmetric NAT");
nat_state = NAT_STATE_ERROR;
exit;
}
// Start hole punching
nat_state = NAT_STATE_PUNCHING;
nat_hole_punch_timer = 0;
nat_hole_punch_count = 0;
nat_hole_punch_received = 0;
}
}
break;
case MSG_CONNECTION_COMPLETE:
// Connection established!
nat_state = NAT_STATE_CONNECTED;
// Update oClient to use the discovered endpoint
server_ip = nat_peer_ip;
server_port = nat_peer_port;
// Mark as connected so game protocol can start
isConnected = 1;
connected = true; // Mark as connected to prevent duplicate connection flow in Step event
popup_text("Connected to server!");
// Send initial connection packet to server
buffer_delete(buffer);
var size, type, alignment;
size = 1024;
type = buffer_grow;
alignment = 1;
buffer = buffer_create(size, type, alignment);
buffer_seek(buffer, buffer_seek_start, 0);
buffer_write(buffer, buffer_u8, 1);
buffer_write(buffer, buffer_string, name + "," + global.multitroid_version);
buffer_write(buffer, buffer_u8, global.sax);
var bufferSize = buffer_tell(buffer);
buffer_seek(buffer, buffer_seek_start, 0);
buffer_write(buffer, buffer_s32, bufferSize);
buffer_write(buffer, buffer_u8, 1);
buffer_write(buffer, buffer_string, name + "," + global.multitroid_version);
buffer_write(buffer, buffer_u8, global.sax);
network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
// Create nametag instance
instance_create(x, y, oNametag);
// Disconnect from mediation server - we don't need it anymore
if (mediation_socket != -1) {
network_destroy(mediation_socket);
mediation_socket = -1;
mediation_connected = false;
}
break;
case MSG_SERVER_NOT_AVAILABLE:
show_message("Server not available");
nat_state = NAT_STATE_ERROR;
break;
case MSG_CONNECTION_TIMEOUT:
show_message("Connection timeout");
nat_state = NAT_STATE_ERROR;
break;
}

@ -0,0 +1,46 @@
/// nat_handle_udp_message(buffer, sender_ip, sender_port, receiving_socket)
// Handles incoming UDP messages that might be hole punch packets
// Call this from Async Networking event for UDP messages
// receiving_socket: The socket that received this packet (to detect random socket success)
// Returns true if it was a NAT message, false otherwise
var buf = argument0;
var sender_ip = argument1;
var sender_port = argument2;
var receiving_socket = argument3;
// Only process if we're in hole punching state
if (nat_state != NAT_STATE_PUNCHING) {
return false;
}
// During hole punching, count ANY packet from the expected peer IP as successful hole punch
// This is because the server may transition to sending game protocol before we've received enough packets
if (sender_ip == nat_peer_ip) {
nat_hole_punch_received++;
// Update peer port if different
if (sender_port != nat_peer_port) {
nat_peer_port = sender_port;
}
// If we're symmetric NAT and received on one of our random sockets, note it but DON'T replace yet
// We'll switch after hole punching completes to avoid losing async events
if (nat_type == NAT_TYPE_SYMMETRIC && receiving_socket != socket) {
// Check if this is one of our random sockets
var socket_index = ds_list_find_index(nat_random_sockets, receiving_socket);
if (socket_index >= 0 && nat_successful_socket == noone) {
// Remember which socket succeeded, but don't switch yet
nat_successful_socket = receiving_socket;
// Remove from random sockets list so we don't destroy it
ds_list_delete(nat_random_sockets, socket_index);
}
}
// Return true to indicate we handled this packet during hole punching
return true;
}
// Not from expected peer during hole punching - ignore
return false;

@ -0,0 +1,41 @@
/// nat_init_client()
// Initializes NAT traversal variables for client
// Call this in oClient Create event AFTER config is loaded
// Mediation server connection (config variables loaded separately)
mediation_socket = -1;
mediation_connected = false;
// NAT state machine
nat_state = NAT_STATE_DISCONNECTED;
// NAT detection
nat_type = NAT_TYPE_UNKNOWN;
nat_local_port = 0;
nat_external_port_one = 0;
nat_external_port_two = 0;
// Client ID (random string for mediation server)
nat_client_id = string(floor(random(999999)));
// Connection coordination
nat_connection_id = -1;
nat_peer_ip = "";
nat_peer_port = 0;
nat_peer_nat_type = NAT_TYPE_UNKNOWN;
// Hole punching
nat_hole_punch_timer = 0;
nat_hole_punch_count = 0;
nat_hole_punch_received = 0;
nat_hole_punch_confirmed = false;
NAT_HOLE_PUNCH_THRESHOLD = 5;
NAT_HOLE_PUNCH_INTERVAL = 10; // Send every 10 steps (~100ms)
// Birthday paradox sockets for symmetric NAT
nat_random_sockets = ds_list_create(); // List of random UDP sockets
nat_random_sockets_created = false;
nat_successful_socket = noone; // Track which random socket succeeded
// TCP message buffer for partial messages
nat_tcp_buffer = "";

@ -0,0 +1,17 @@
/// nat_request_connection()
// Requests connection to game server through mediation server
// Call this after NAT type is detected
if (nat_state != NAT_STATE_WAITING) {
show_debug_message("NAT: Cannot request connection - not in WAITING state");
exit;
}
// Send connection request to mediation server
var params = ds_map_create();
ds_map_add(params, "EndpointString", server_ip); // The game server IP we want to connect to
ds_map_add(params, "NATType", nat_type);
nat_send_tcp_message(mediation_socket, MSG_CONNECTION_REQUEST, params);
ds_map_destroy(params);
show_debug_message("NAT: Sent connection request for server " + server_ip);

@ -0,0 +1,23 @@
/// nat_send_hole_punch(udp_socket, target_ip, target_port)
// Sends a UDP hole punch packet to peer
// udp_socket: UDP socket ID
// target_ip: Peer's IP address
// target_port: Peer's UDP port
var udp_socket = argument0;
var target_ip = argument1;
var target_port = argument2;
// Create simple hole punch message
var params = ds_map_create();
var json_str = json_encode_nat_message(MSG_HOLE_PUNCH_ATTEMPT, params);
ds_map_destroy(params);
// Send as UDP
var buf = buffer_create(string_length(json_str) + 1, buffer_fixed, 1);
buffer_seek(buf, buffer_seek_start, 0);
buffer_write(buf, buffer_text, json_str);
network_send_udp_raw(udp_socket, target_ip, target_port, buf, buffer_tell(buf));
buffer_delete(buf);

@ -0,0 +1,23 @@
/// nat_send_tcp_message(tcp_socket, msg_type, params_map)
// Sends a JSON message over TCP to mediation server
// tcp_socket: TCP socket ID
// msg_type: MSG_* constant
// params_map: ds_map of parameters (or noone)
var tcp_socket = argument0;
var msg_type = argument1;
var params = argument2;
// Encode to JSON
var json_str = json_encode_nat_message(msg_type, params);
// Create buffer with JSON string
var buf = buffer_create(string_length(json_str) + 1, buffer_fixed, 1);
buffer_seek(buf, buffer_seek_start, 0);
buffer_write(buf, buffer_text, json_str);
// Send over TCP
network_send_packet(tcp_socket, buf, buffer_tell(buf));
// Clean up
buffer_delete(buf);

@ -0,0 +1,119 @@
/// nat_step_hole_punching()
// Handles hole punching in Step event
// Call this from oClient Step event when using NAT traversal
if (!use_nat_traversal) exit;
// Only run hole punching if in the PUNCHING state
if (nat_state != NAT_STATE_PUNCHING) exit;
nat_hole_punch_timer++;
// If WE are symmetric, create 256 random UDP sockets (birthday paradox)
if (nat_type == NAT_TYPE_SYMMETRIC && !nat_random_sockets_created) {
for (var i = 0; i < 256; i++) {
var random_socket = network_create_socket_ext(network_socket_udp, 0);
if (random_socket >= 0) {
ds_list_add(nat_random_sockets, random_socket);
}
}
nat_random_sockets_created = true;
}
// Send hole punch packet every NAT_HOLE_PUNCH_INTERVAL steps
if (nat_hole_punch_timer >= NAT_HOLE_PUNCH_INTERVAL) {
nat_hole_punch_timer = 0;
nat_hole_punch_count++;
// Determine punch strategy based on OUR NAT type and PEER's NAT type
if (nat_type == NAT_TYPE_SYMMETRIC) {
// WE are symmetric - send from all 256 random sockets to peer's known port
// This creates 256 different source ports, leveraging birthday paradox
var socket_count = ds_list_size(nat_random_sockets);
for (var i = 0; i < socket_count; i++) {
var random_socket = ds_list_find_value(nat_random_sockets, i);
nat_send_hole_punch(random_socket, nat_peer_ip, nat_peer_port);
}
} else if (nat_peer_nat_type == NAT_TYPE_SYMMETRIC) {
// PEER is symmetric - we scan random ports to find their dynamic port
for (var i = 0; i < 100; i++) {
var random_port = irandom_range(1024, 65535);
nat_send_hole_punch(socket, nat_peer_ip, random_port);
}
} else {
// Neither is symmetric - simple bidirectional punch to known port
nat_send_hole_punch(socket, nat_peer_ip, nat_peer_port);
}
// After receiving enough packets, confirm connection (only once)
if (nat_hole_punch_received >= NAT_HOLE_PUNCH_THRESHOLD && !nat_hole_punch_confirmed) {
// Send confirmation to mediation server
var params = ds_map_create();
ds_map_add(params, "ConnectionID", nat_connection_id);
ds_map_add(params, "IsServer", false);
nat_send_tcp_message(mediation_socket, MSG_RECEIVED_PEER, params);
ds_map_destroy(params);
nat_hole_punch_confirmed = true;
// Connection is established - update state
nat_state = NAT_STATE_CONNECTED;
server_ip = nat_peer_ip;
server_port = nat_peer_port;
isConnected = 1;
connected = true; // Mark as connected to prevent duplicate connection flow in Step event
popup_text("Connected!");
// If symmetric NAT and we found a successful socket, switch to it NOW before sending initial packet
if (nat_successful_socket != noone) {
// Destroy old main socket
network_destroy(socket);
// Switch to successful socket
socket = nat_successful_socket;
// Clean up remaining random sockets
for (var i = ds_list_size(nat_random_sockets) - 1; i >= 0; i--) {
var sock = ds_list_find_value(nat_random_sockets, i);
network_destroy(sock);
}
ds_list_clear(nat_random_sockets);
}
// Send initial connection packet to server
buffer_delete(buffer);
var size, type, alignment;
size = 1024;
type = buffer_grow;
alignment = 1;
buffer = buffer_create(size, type, alignment);
buffer_seek(buffer, buffer_seek_start, 0);
buffer_write(buffer, buffer_u8, 1);
buffer_write(buffer, buffer_string, name + "," + global.multitroid_version);
buffer_write(buffer, buffer_u8, global.sax);
var bufferSize = buffer_tell(buffer);
buffer_seek(buffer, buffer_seek_start, 0);
buffer_write(buffer, buffer_s32, bufferSize);
buffer_write(buffer, buffer_u8, 1);
buffer_write(buffer, buffer_string, name + "," + global.multitroid_version);
buffer_write(buffer, buffer_u8, global.sax);
network_send_udp(socket, server_ip, server_port, buffer, buffer_tell(buffer));
// Create nametag instance
instance_create(x, y, oNametag);
// Disconnect from mediation server
if (mediation_socket != -1) {
network_destroy(mediation_socket);
mediation_socket = -1;
mediation_connected = false;
}
}
// Timeout after too many attempts
if (nat_hole_punch_count > 300) { // 30 seconds
popup_text("Connection timeout");
nat_state = NAT_STATE_ERROR;
}
}

@ -22,5 +22,5 @@ buffer_write(seedBuffer, buffer_s32, bufferSize);
buffer_write(seedBuffer, buffer_u8, 3); buffer_write(seedBuffer, buffer_u8, 3);
buffer_write(seedBuffer, buffer_f64, currSeed); buffer_write(seedBuffer, buffer_f64, currSeed);
buffer_write(seedBuffer, buffer_u8, global.clientID); buffer_write(seedBuffer, buffer_u8, global.clientID);
var result = network_send_packet(oClient.socket, seedBuffer, buffer_tell(seedBuffer)); var result = udp_send_reliable(oClient.socket, oClient.server_ip, oClient.server_port, seedBuffer, buffer_tell(seedBuffer));
popup_text(str); popup_text(str);

@ -272,7 +272,7 @@ if(instance_exists(oClient)){
buffer_write(beamBuffer, buffer_s16, beamY); buffer_write(beamBuffer, buffer_s16, beamY);
buffer_write(beamBuffer, buffer_u8, chargebeam); buffer_write(beamBuffer, buffer_u8, chargebeam);
buffer_write(beamBuffer, buffer_u8, global.sax); buffer_write(beamBuffer, buffer_u8, global.sax);
var result = network_send_packet(oClient.socket, beamBuffer, buffer_tell(beamBuffer)); var result = udp_send_unreliable(oClient.socket, oClient.server_ip, oClient.server_port, beamBuffer, buffer_tell(beamBuffer));
buffer_delete(beamBuffer); buffer_delete(beamBuffer);
} }
} }

@ -89,7 +89,7 @@ if(instance_exists(oClient) && instance_exists(oCharacter) && missileX != 0 && m
buffer_write(missileBuffer, buffer_s8, oCharacter.xVel); buffer_write(missileBuffer, buffer_s8, oCharacter.xVel);
buffer_write(missileBuffer, buffer_s8, oCharacter.yVel); buffer_write(missileBuffer, buffer_s8, oCharacter.yVel);
buffer_write(missileBuffer, buffer_u8, global.icemissiles); buffer_write(missileBuffer, buffer_u8, global.icemissiles);
var result = network_send_packet(oClient.socket, missileBuffer, buffer_tell(missileBuffer)); var result = udp_send_unreliable(oClient.socket, oClient.server_ip, oClient.server_port, missileBuffer, buffer_tell(missileBuffer));
buffer_delete(missileBuffer); buffer_delete(missileBuffer);
} }
} }

@ -0,0 +1,22 @@
/// udp_client_cleanup()
// Clean up client UDP resources
// Clean up fragment buffers
var frag_id = ds_map_find_first(global.fragment_buffers);
while (!is_undefined(frag_id)) {
var frag_map = ds_map_find_value(global.fragment_buffers, frag_id);
var frag_idx = ds_map_find_first(frag_map);
while (!is_undefined(frag_idx)) {
var frag_buffer = ds_map_find_value(frag_map, frag_idx);
if (is_real(frag_buffer) && buffer_exists(frag_buffer)) {
buffer_delete(frag_buffer);
}
frag_idx = ds_map_find_next(frag_map, frag_idx);
}
ds_map_destroy(frag_map);
frag_id = ds_map_find_next(global.fragment_buffers, frag_id);
}
ds_map_destroy(global.fragment_buffers);
ds_map_destroy(global.fragment_timestamps);
ds_map_destroy(global.received_sequences);

@ -0,0 +1,35 @@
/// udp_client_cleanup_old_fragments()
// Clean up incomplete fragment packets that have timed out
// Call this in Step event
var current = current_time;
var frag_id = ds_map_find_first(global.fragment_timestamps);
while (!is_undefined(frag_id)) {
var next_id = ds_map_find_next(global.fragment_timestamps, frag_id);
var timestamp = ds_map_find_value(global.fragment_timestamps, frag_id);
// If fragment is older than timeout, clean it up
if (current - timestamp > global.FRAGMENT_TIMEOUT) {
// Clean up the fragment buffers
if (ds_map_exists(global.fragment_buffers, frag_id)) {
var frag_map = ds_map_find_value(global.fragment_buffers, frag_id);
var total = ds_map_find_value(frag_map, "total");
// Delete any stored fragment buffers
for (var i = 0; i < total; i++) {
if (ds_map_exists(frag_map, i)) {
var frag_buf = ds_map_find_value(frag_map, i);
buffer_delete(frag_buf);
}
}
ds_map_destroy(frag_map);
ds_map_delete(global.fragment_buffers, frag_id);
}
ds_map_delete(global.fragment_timestamps, frag_id);
}
frag_id = next_id;
}

@ -0,0 +1,26 @@
/// udp_client_init(server_ip, server_port)
// Initialize UDP client for connecting to proxy server
// Returns socket ID
var ip = argument0;
var port = argument1;
// Create UDP socket
var sock = network_create_socket_ext(network_socket_udp, port);
// Initialize client data structures
global.udp_client_socket = sock;
global.udp_client_server_ip = ip;
global.udp_client_server_port = port;
// Fragment reassembly
global.fragment_buffers = ds_map_create();
global.fragment_timestamps = ds_map_create();
global.FRAGMENT_TIMEOUT = 5000;
global.UDP_MAX_FRAGMENT_SIZE = 1400;
// Reliable UDP with ACK
global.received_sequences = ds_map_create();
global.SEQUENCE_CACHE_SIZE = 1000;
return sock;

@ -0,0 +1,11 @@
/// udp_send(socket, ip, port, buffer, size)
// Wrapper for network_send_udp - drop-in replacement for network_send_packet
// Use this instead of network_send_packet for UDP client connections
var sock = argument0;
var ip = argument1;
var port = argument2;
var buff = argument3;
var sz = argument4;
return network_send_udp(sock, ip, port, buff, sz);

@ -0,0 +1,41 @@
/// udp_send_reliable(socket, ip, port, buffer, size)
// Send a UDP packet with ACK/retransmission reliability
// Use this for critical packets that must arrive (items, events, etc.)
var sock = argument0;
var ip = argument1;
var port = argument2;
var buff = argument3;
var sz = argument4;
// If oClient doesn't exist, fall back to unreliable send
if (!instance_exists(oClient)) {
return network_send_udp(sock, ip, port, buff, sz);
}
// Wrap with sequence number for ACK: [254][seq_num (u32)][original_packet]
var seq_num = oClient.next_sequence_number;
oClient.next_sequence_number = (oClient.next_sequence_number + 1) mod 4294967296;
var reliable_buffer = buffer_create(sz + 5, buffer_fixed, 1);
buffer_write(reliable_buffer, buffer_u8, 254); // Reliable packet marker
buffer_write(reliable_buffer, buffer_u32, seq_num);
buffer_copy(buff, 0, sz, reliable_buffer, 5);
var result = network_send_udp(sock, ip, port, reliable_buffer, sz + 5);
// Track for retransmission on client side
if (is_undefined(oClient.pending_acks)) {
oClient.pending_acks = ds_map_create();
}
var ack_data = ds_map_create();
ds_map_add(ack_data, "buffer", reliable_buffer);
ds_map_add(ack_data, "size", sz + 5);
ds_map_add(ack_data, "send_time", current_time);
ds_map_add(ack_data, "retransmits", 0);
ds_map_add(ack_data, "ip", ip);
ds_map_add(ack_data, "port", port);
ds_map_add(oClient.pending_acks, seq_num, ack_data);
return result;

@ -0,0 +1,11 @@
/// udp_send_unreliable(socket, ip, port, buffer, size)
// Send a UDP packet without reliability (fast, no ACK needed)
// Use this for frequent updates where latest data is most important (positions, etc.)
var sock = argument0;
var ip = argument1;
var port = argument2;
var buff = argument3;
var sz = argument4;
return network_send_udp(sock, ip, port, buff, sz);

@ -603,7 +603,7 @@ switch(argument0){
buffer_write(buffer, buffer_u8, global.smissiles); buffer_write(buffer, buffer_u8, global.smissiles);
buffer_write(buffer, buffer_u8, global.pbombs); buffer_write(buffer, buffer_u8, global.pbombs);
buffer_write(buffer, buffer_u8, global.clientID); buffer_write(buffer, buffer_u8, global.clientID);
var result = network_send_packet(socket, buffer, buffer_tell(buffer)); var result = udp_send_reliable(socket, oClient.server_ip, oClient.server_port, buffer, buffer_tell(buffer));
} }
} else { } else {
global.onlineList[# 1, i] = global.onlineReceived[# 1, i]; global.onlineList[# 1, i] = global.onlineReceived[# 1, i];

Loading…
Cancel
Save