diff --git a/Gemfile b/Gemfile index 933ba47..3534d64 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ gem "rake" gem "opengl-bindings", require: "opengl" gem "cyberarm_engine", git: "https://github.com/cyberarm/cyberarm_engine" gem "nokogiri", ">= 1.11.0.rc1" +gem "i18n" group(:packaging) do gem "releasy", github: "gosu/releasy" diff --git a/Gemfile.lock b/Gemfile.lock index 94044a7..d7e3a88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,10 @@ GIT remote: https://github.com/cyberarm/cyberarm_engine - revision: da4188764ce8ac6060068e09f607d1ec904fe165 + revision: d02c001989ce967e2b3184d1a0f01cd5b20ac241 specs: cyberarm_engine (0.14.0) clipboard (~> 1.3.4) + excon (~> 0.76.0) gosu (~> 0.15.0) gosu_more_drawables (~> 0.3) @@ -20,16 +21,19 @@ GIT GEM remote: https://rubygems.org/ specs: - clipboard (1.3.4) + clipboard (1.3.5) + concurrent-ruby (1.1.7) cri (2.1.0) excon (0.76.0) gosu (0.15.2) gosu (0.15.2-x64-mingw32) gosu_more_drawables (0.3.1) + i18n (1.8.5) + concurrent-ruby (~> 1.0) mini_portile2 (2.5.0) - nokogiri (1.11.0.rc2) + nokogiri (1.11.0.rc3) mini_portile2 (~> 2.5.0) - nokogiri (1.11.0.rc2-x64-mingw32) + nokogiri (1.11.0.rc3-x64-mingw32) ocra (1.3.11) opengl-bindings (1.6.10) rake (13.0.1) @@ -42,6 +46,7 @@ PLATFORMS DEPENDENCIES cyberarm_engine! excon + i18n nokogiri (>= 1.11.0.rc1) ocra opengl-bindings diff --git a/i-mic-fps.rb b/i-mic-fps.rb index 643b0fb..9542e4b 100644 --- a/i-mic-fps.rb +++ b/i-mic-fps.rb @@ -10,6 +10,7 @@ require "securerandom" require "opengl" require "glu" require "nokogiri" +require "i18n" begin require_relative "../cyberarm_engine/lib/cyberarm_engine" diff --git a/lib/networking/backend/connection.rb b/lib/networking/backend/connection.rb index 56742e7..31c97b2 100644 --- a/lib/networking/backend/connection.rb +++ b/lib/networking/backend/connection.rb @@ -1,6 +1,7 @@ module CyberarmEngine module Networking class Connection + attr_reader :hostname, :port, :peer def initialize(hostname:, port:, channels: 3) @hostname = hostname @port = port @@ -8,14 +9,6 @@ module CyberarmEngine @channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) } @peer = Peer.new(id: 0, hostname: "", port: "") - - @last_read_time = Networking.milliseconds - @last_write_time = Networking.milliseconds - - @total_packets_sent = 0 - @total_packets_received = 0 - @total_data_sent = 0 - @total_data_received = 0 end # Callbacks # @@ -33,12 +26,13 @@ module CyberarmEngine # Functions # def send_packet(message:, reliable: false, channel: 0) + @peer.write_queue << PacketHandler.create_raw_packet(peer: @peer, message: message, reliable: reliable, channel: channel) end def connect(timeout: Protocol::TIMEOUT_PERIOD) @socket = UDPSocket.new - write(PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_CONNECT)) + write(packet: PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_CONNECT)) end def disconnect(timeout: Protocol::TIMEOUT_PERIOD) @@ -49,38 +43,36 @@ module CyberarmEngine end @peer.write_queue.reverse.each do |packet| - write(packet) + write(packet: packet) @peer.write_queue.delete(packet) end - if Networking.milliseconds - @last_write_time > Protocol::HEARTBEAT_INTERVAL + if Networking.milliseconds - @peer.last_write_time > Protocol::HEARTBEAT_INTERVAL @peer.write_queue << PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_HEARTBEAT) end end - private - def read data, addr = @socket.recvfrom_nonblock(Protocol::MAX_PACKET_SIZE) pkt = PacketHandler.handle(host: self, raw: data, peer: @peer) packet_received(message: pkt.message, channel: -1) if pkt.is_a?(RawPacket) - @total_packets_received += 1 - @total_data_received += data.length - @last_read_time = Networking.milliseconds + @peer.total_packets_received += 1 + @peer.total_data_received += data.length + @peer.last_read_time = Networking.milliseconds return true rescue IO::WaitReadable return false end - def write(packet) + def write(packet:) raw = packet.encode @socket.send(raw, 0, @hostname, @port) - @total_packets_sent += 1 - @total_data_sent += raw.length - @last_write_time = Networking.milliseconds + @peer.total_packets_sent += 1 + @peer.total_data_sent += raw.length + @peer.last_write_time = Networking.milliseconds end end end diff --git a/lib/networking/backend/packet_handler.rb b/lib/networking/backend/packet_handler.rb index b5fe709..efa92b2 100644 --- a/lib/networking/backend/packet_handler.rb +++ b/lib/networking/backend/packet_handler.rb @@ -11,14 +11,17 @@ module CyberarmEngine type = packet.message.unpack1("C") puts "#{host.class} received #{type_to_name(type: type)}" + pp raw case type when Protocol::PACKET_CONTROL handle_control_packet(host, packet, peer) when Protocol::PACKET_RAW handle_raw_packet(packet) + when Protocol::PACKET_RELIABLE + handle_reliable_packet(host, packet, peer) else - raise NotImplementedError, "A Packet handler for #{type} is not implmented!" + raise NotImplementedError, "A Packet handler for #{type_to_name(type: type)}[#{type}] is not implemented!" end end @@ -56,7 +59,16 @@ module CyberarmEngine when Protocol::CONTROL_HEARTBEAT when Protocol::CONTROL_PING + peer.write_queue << PacketHandler.create_control_packet( + peer: peer, control_type: Protocol::CONTROL_PONG, + reliable: true, + message: [Networking.milliseconds].pack("Q") # Uint64, native endian + ) when Protocol::CONTROL_PONG + sent_time = pkt.message.unpack1("Q") + difference = Networking.milliseconds - sent_time + + peer.ping = difference end nil @@ -66,12 +78,30 @@ module CyberarmEngine RawPacket.decode(packet.message) end + def self.handle_reliable_packet(host, packet, peer) + # TODO: Preserve delivery order of reliable packets + + pkt = ReliablePacket.decode(packet.message) + peer.write_queue << create_control_packet( + peer: peer, + control_type: Protocol::CONTROL_ACKNOWLEDGE, + message: [pkt.sequence_number].pack("n") + ) + + handle(host: host, raw: pkt.message, peer: peer) + end + def self.create_control_packet(peer:, control_type:, message: nil, reliable: false, channel: 0) message_packet = nil if reliable warn "Reliable packets are not yet implemented!" - packet = ControlPacket.new(control_type: control_type, message: message) + packet = Packet.new( + protocol_version: Protocol::PROTOCOL_VERSION, + peer_id: peer.id, + channel: channel, + message: ControlPacket.new(control_type: control_type, message: message).encode + ) message_packet = ReliablePacket.new(sequence_number: peer.next_reliable_sequence_number, message: packet.encode) else message_packet = ControlPacket.new(control_type: control_type, message: message) diff --git a/lib/networking/backend/packets/reliable_packet.rb b/lib/networking/backend/packets/reliable_packet.rb index e8ffae7..f5a7dad 100644 --- a/lib/networking/backend/packets/reliable_packet.rb +++ b/lib/networking/backend/packets/reliable_packet.rb @@ -1,7 +1,7 @@ module CyberarmEngine module Networking class ReliablePacket - attr_reader :message, :type, :control_type + attr_reader :message, :type, :sequence_number HEADER_PACKER = "Cn" HEADER_LENGTH = 1 + 2 # bytes @@ -10,7 +10,7 @@ module CyberarmEngine header = raw_message.unpack(HEADER_PACKER) message = raw_message[HEADER_LENGTH..raw_message.length - 1] - ReliablePacket.new(type: header[0], control_type: header[1], message: message) + ReliablePacket.new(type: header[0], sequence_number: header[1], message: message) end def initialize(sequence_number:, message:, type: Protocol::PACKET_RELIABLE) @@ -22,7 +22,7 @@ module CyberarmEngine def encode header = [ @type, - @control_type + @sequence_number ].pack(HEADER_PACKER) "#{header}#{@message}" diff --git a/lib/networking/backend/peer.rb b/lib/networking/backend/peer.rb index c671f8e..f49a3b1 100644 --- a/lib/networking/backend/peer.rb +++ b/lib/networking/backend/peer.rb @@ -4,7 +4,8 @@ module CyberarmEngine attr_reader :id, :hostname, :port, :data, :read_queue, :write_queue attr_accessor :total_packets_sent, :total_packets_received, :total_data_sent, :total_data_received, - :last_read_time, :last_write_time + :last_read_time, :last_write_time, + :ping def initialize(id:, hostname:, port:) @id = id @@ -22,6 +23,10 @@ module CyberarmEngine @total_packets_received = 0 @total_data_sent = 0 @total_data_received = 0 + + @ping = 0 + + @reliable_sequence_number = 65_500 end def id=(n) @@ -29,6 +34,10 @@ module CyberarmEngine @id = n end + + def next_reliable_sequence_number + @reliable_sequence_number = (@reliable_sequence_number + 1) % 65_535 + end end end end diff --git a/lib/networking/backend/server.rb b/lib/networking/backend/server.rb index 2ec2511..e6d2c50 100644 --- a/lib/networking/backend/server.rb +++ b/lib/networking/backend/server.rb @@ -70,7 +70,7 @@ module CyberarmEngine # Send packet to specified peer def send_packet(peer:, message:, reliable: false, channel: 0) - if (peer = @peers.get(peer)) + if (peer = @peers[peer]) packet = PacketHandler.create_raw_packet(message, reliable, channel) peer.write_queue << packet else @@ -80,12 +80,12 @@ module CyberarmEngine # Send packet to all connected peer def broadcast_packet(message:, reliable: false, channel: 0) - @peers.each { |peer| send_packet(peer.id, message, reliable, channel) } + @peers.each { |peer| send_packet(peer: peer.id, message: message, reliable: reliable, channel: channel) } end # Disconnect peer def disconnect_client(peer:, reason: "") - if (peer = @peers.get(peer)) + if (peer = @peers[peer]) packet = PacketHandler.create_disconnect_packet(peer.id, reason) peer.write_now!(packet) @peers.delete(peer) @@ -115,6 +115,16 @@ module CyberarmEngine next end + if Networking.milliseconds - peer.last_write_time > Protocol::HEARTBEAT_INTERVAL + write( + peer: peer, + packet: PacketHandler.create_control_packet( + peer: peer, + control_type: Protocol::CONTROL_PING + ) + ) + end + while(packet = peer.write_queue.shift) write(peer: peer, packet: packet) end diff --git a/lib/ui/menus/extras_menu.rb b/lib/ui/menus/extras_menu.rb index 6b12856..1be77a0 100644 --- a/lib/ui/menus/extras_menu.rb +++ b/lib/ui/menus/extras_menu.rb @@ -12,7 +12,7 @@ class IMICFPS push_state(IMICFPS::MapEditorTool::MainMenu) end - link "Back" do + link I18n.t("menus.back") do pop_state end end diff --git a/lib/ui/menus/game_pause_menu.rb b/lib/ui/menus/game_pause_menu.rb index de3bc7a..c0f9748 100644 --- a/lib/ui/menus/game_pause_menu.rb +++ b/lib/ui/menus/game_pause_menu.rb @@ -9,11 +9,11 @@ class IMICFPS pop_state end - link "Settings" do + link I18n.t("menus.settings") do push_state(SettingsMenu) end - link "Leave" do + link I18n.t("menus.leave") do push_state(MainMenu) end end diff --git a/lib/ui/menus/level_select_menu.rb b/lib/ui/menus/level_select_menu.rb index c614a16..5e7cf53 100644 --- a/lib/ui/menus/level_select_menu.rb +++ b/lib/ui/menus/level_select_menu.rb @@ -12,7 +12,7 @@ class IMICFPS end end - link "Back" do + link I18n.t("menus.back") do pop_state end end diff --git a/lib/ui/menus/main_menu.rb b/lib/ui/menus/main_menu.rb index dca0e23..f107424 100644 --- a/lib/ui/menus/main_menu.rb +++ b/lib/ui/menus/main_menu.rb @@ -3,23 +3,23 @@ class IMICFPS def setup title IMICFPS::NAME - link "Single Player" do + link I18n.t("menus.singleplayer") do push_state(LevelSelectMenu) end - link "Multiplayer" do + link I18n.t("menus.multiplayer") do push_state(MultiplayerMenu) end - link "Settings" do + link I18n.t("menus.settings") do push_state(SettingsMenu) end - link "Extras" do + link I18n.t("menus.extras") do push_state(ExtrasMenu) end - link "Quit" do + link I18n.t("menus.quit") do window.close end diff --git a/lib/ui/menus/multiplayer_menu.rb b/lib/ui/menus/multiplayer_menu.rb index bbfd6b8..8e9ea51 100644 --- a/lib/ui/menus/multiplayer_menu.rb +++ b/lib/ui/menus/multiplayer_menu.rb @@ -11,7 +11,7 @@ class IMICFPS link "Profile" do push_state(MultiplayerProfileMenu) end - link "Back" do + link I18n.t("menus.back") do pop_state end end diff --git a/lib/ui/menus/multiplayer_profile_menu.rb b/lib/ui/menus/multiplayer_profile_menu.rb index 4338d1d..d8669f4 100644 --- a/lib/ui/menus/multiplayer_profile_menu.rb +++ b/lib/ui/menus/multiplayer_profile_menu.rb @@ -7,7 +7,7 @@ class IMICFPS stack(width: 0.25, height: 1.0) do button "Edit Profile", width: 1.0 button "Log Out", width: 1.0 - button "Back", width: 1.0, margin_top: 64 do + button I18n.t("menus.back"), width: 1.0, margin_top: 64 do pop_state end end diff --git a/lib/ui/menus/multiplayer_server_browser_menu.rb b/lib/ui/menus/multiplayer_server_browser_menu.rb index 5489d1f..3989326 100644 --- a/lib/ui/menus/multiplayer_server_browser_menu.rb +++ b/lib/ui/menus/multiplayer_server_browser_menu.rb @@ -34,7 +34,7 @@ class IMICFPS button "Host Game", width: 1.0 button "Direct Connect", width: 1.0 - button "Back", width: 1.0, margin_top: 64 do + button I18n.t("menus.back"), width: 1.0, margin_top: 64 do pop_state end end diff --git a/lib/ui/menus/settings_menu.rb b/lib/ui/menus/settings_menu.rb index ef1bf9b..33892c2 100644 --- a/lib/ui/menus/settings_menu.rb +++ b/lib/ui/menus/settings_menu.rb @@ -29,7 +29,7 @@ class IMICFPS end end - button "Back", width: 1.0, margin_top: 64 do + button I18n.t("menus.back"), width: 1.0, margin_top: 64 do pop_state end end diff --git a/lib/window.rb b/lib/window.rb index 11239ec..2dec4b6 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -12,6 +12,11 @@ class IMICFPS super(width: window_width, height: window_height, fullscreen: fullscreen, resizable: true, update_interval: 1000.0/fps_target) end $window = self + I18n.load_path << Dir["#{GAME_ROOT_PATH}/locales/*.yml"] + I18n.default_locale = :en + language = Gosu.language.split("_").first.to_sym + I18n.locale = language if I18n.available_locales.include?(language) + @needs_cursor = false @cursor = Gosu::Image.new(IMICFPS::GAME_ROOT_PATH + "/static/cursors/pointer.png") @number_of_vertices = 0 diff --git a/locales/de.yml b/locales/de.yml new file mode 100644 index 0000000..6ab75b1 --- /dev/null +++ b/locales/de.yml @@ -0,0 +1,13 @@ +de: + game: + name: I-MIC FPS + menus: + singleplayer: Einzelspieler + multiplayer: Multiplayer + settings: Optionen + extras: Mehr + quit: Fortgehen + back: Zurückbringen + leave: Verlassen + paused: Pausieren + resume: Fortsetzen \ No newline at end of file diff --git a/locales/en.yml b/locales/en.yml new file mode 100644 index 0000000..317ed67 --- /dev/null +++ b/locales/en.yml @@ -0,0 +1,13 @@ +en: + game: + name: I-MIC FPS + menus: + singleplayer: Single Player + multiplayer: Multiplayer + settings: Settings + extras: Extras + quit: Quit + back: Back + leave: Leave + paused: Paused + resume: Resume \ No newline at end of file diff --git a/new_server_test.rb b/new_server_test.rb index 0e0c6c8..4c24835 100644 --- a/new_server_test.rb +++ b/new_server_test.rb @@ -19,13 +19,15 @@ def require_all(directory) else files = failed end - end until( failed.empty? ) + end until(failed.empty?) end require "socket" require_relative "lib/networking" require_all "lib/networking/backend" +Thread.abort_on_exception = true + server = CyberarmEngine::Networking::Server.new def server.client_connected(peer:) puts "Client connected as peer: #{peer.id}" @@ -33,6 +35,7 @@ end def server.packet_received(peer:, message:, channel:) pp "Server received: #{message} [on channel: #{channel} from peer: #{peer&.id}]" + broadcast_packet(message: "Broadcasting...") end Thread.new do @@ -47,6 +50,7 @@ end connection = CyberarmEngine::Networking::Connection.new(hostname: "localhost", port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT, channels: 3) def connection.connected puts "Connection: Connected!" + send_packet(message: "I be connected!") end def connection.disconnected(reason:) @@ -55,6 +59,7 @@ end def connection.packet_received(message:, channel:) pp "Connection received: #{message} [on channel: #{channel} from peer: SERVER]" + send_packet(message: "ECHO: #{message}") end connection.connect(timeout: 1_000)