Initial work on locales, more work on netcode

This commit is contained in:
2020-12-01 13:02:22 -06:00
parent b9c30ade80
commit c7590366a6
20 changed files with 131 additions and 47 deletions

View File

@@ -3,6 +3,7 @@ gem "rake"
gem "opengl-bindings", require: "opengl" gem "opengl-bindings", require: "opengl"
gem "cyberarm_engine", git: "https://github.com/cyberarm/cyberarm_engine" gem "cyberarm_engine", git: "https://github.com/cyberarm/cyberarm_engine"
gem "nokogiri", ">= 1.11.0.rc1" gem "nokogiri", ">= 1.11.0.rc1"
gem "i18n"
group(:packaging) do group(:packaging) do
gem "releasy", github: "gosu/releasy" gem "releasy", github: "gosu/releasy"

View File

@@ -1,9 +1,10 @@
GIT GIT
remote: https://github.com/cyberarm/cyberarm_engine remote: https://github.com/cyberarm/cyberarm_engine
revision: da4188764ce8ac6060068e09f607d1ec904fe165 revision: d02c001989ce967e2b3184d1a0f01cd5b20ac241
specs: specs:
cyberarm_engine (0.14.0) cyberarm_engine (0.14.0)
clipboard (~> 1.3.4) clipboard (~> 1.3.4)
excon (~> 0.76.0)
gosu (~> 0.15.0) gosu (~> 0.15.0)
gosu_more_drawables (~> 0.3) gosu_more_drawables (~> 0.3)
@@ -20,16 +21,19 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
clipboard (1.3.4) clipboard (1.3.5)
concurrent-ruby (1.1.7)
cri (2.1.0) cri (2.1.0)
excon (0.76.0) excon (0.76.0)
gosu (0.15.2) gosu (0.15.2)
gosu (0.15.2-x64-mingw32) gosu (0.15.2-x64-mingw32)
gosu_more_drawables (0.3.1) gosu_more_drawables (0.3.1)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
mini_portile2 (2.5.0) mini_portile2 (2.5.0)
nokogiri (1.11.0.rc2) nokogiri (1.11.0.rc3)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
nokogiri (1.11.0.rc2-x64-mingw32) nokogiri (1.11.0.rc3-x64-mingw32)
ocra (1.3.11) ocra (1.3.11)
opengl-bindings (1.6.10) opengl-bindings (1.6.10)
rake (13.0.1) rake (13.0.1)
@@ -42,6 +46,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
cyberarm_engine! cyberarm_engine!
excon excon
i18n
nokogiri (>= 1.11.0.rc1) nokogiri (>= 1.11.0.rc1)
ocra ocra
opengl-bindings opengl-bindings

View File

@@ -10,6 +10,7 @@ require "securerandom"
require "opengl" require "opengl"
require "glu" require "glu"
require "nokogiri" require "nokogiri"
require "i18n"
begin begin
require_relative "../cyberarm_engine/lib/cyberarm_engine" require_relative "../cyberarm_engine/lib/cyberarm_engine"

View File

@@ -1,6 +1,7 @@
module CyberarmEngine module CyberarmEngine
module Networking module Networking
class Connection class Connection
attr_reader :hostname, :port, :peer
def initialize(hostname:, port:, channels: 3) def initialize(hostname:, port:, channels: 3)
@hostname = hostname @hostname = hostname
@port = port @port = port
@@ -8,14 +9,6 @@ module CyberarmEngine
@channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) } @channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) }
@peer = Peer.new(id: 0, hostname: "", port: "") @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 end
# Callbacks # # Callbacks #
@@ -33,12 +26,13 @@ module CyberarmEngine
# Functions # # Functions #
def send_packet(message:, reliable: false, channel: 0) def send_packet(message:, reliable: false, channel: 0)
@peer.write_queue << PacketHandler.create_raw_packet(peer: @peer, message: message, reliable: reliable, channel: channel)
end end
def connect(timeout: Protocol::TIMEOUT_PERIOD) def connect(timeout: Protocol::TIMEOUT_PERIOD)
@socket = UDPSocket.new @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 end
def disconnect(timeout: Protocol::TIMEOUT_PERIOD) def disconnect(timeout: Protocol::TIMEOUT_PERIOD)
@@ -49,38 +43,36 @@ module CyberarmEngine
end end
@peer.write_queue.reverse.each do |packet| @peer.write_queue.reverse.each do |packet|
write(packet) write(packet: packet)
@peer.write_queue.delete(packet) @peer.write_queue.delete(packet)
end 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) @peer.write_queue << PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_HEARTBEAT)
end end
end end
private
def read def read
data, addr = @socket.recvfrom_nonblock(Protocol::MAX_PACKET_SIZE) data, addr = @socket.recvfrom_nonblock(Protocol::MAX_PACKET_SIZE)
pkt = PacketHandler.handle(host: self, raw: data, peer: @peer) pkt = PacketHandler.handle(host: self, raw: data, peer: @peer)
packet_received(message: pkt.message, channel: -1) if pkt.is_a?(RawPacket) packet_received(message: pkt.message, channel: -1) if pkt.is_a?(RawPacket)
@total_packets_received += 1 @peer.total_packets_received += 1
@total_data_received += data.length @peer.total_data_received += data.length
@last_read_time = Networking.milliseconds @peer.last_read_time = Networking.milliseconds
return true return true
rescue IO::WaitReadable rescue IO::WaitReadable
return false return false
end end
def write(packet) def write(packet:)
raw = packet.encode raw = packet.encode
@socket.send(raw, 0, @hostname, @port) @socket.send(raw, 0, @hostname, @port)
@total_packets_sent += 1 @peer.total_packets_sent += 1
@total_data_sent += raw.length @peer.total_data_sent += raw.length
@last_write_time = Networking.milliseconds @peer.last_write_time = Networking.milliseconds
end end
end end
end end

View File

@@ -11,14 +11,17 @@ module CyberarmEngine
type = packet.message.unpack1("C") type = packet.message.unpack1("C")
puts "#{host.class} received #{type_to_name(type: type)}" puts "#{host.class} received #{type_to_name(type: type)}"
pp raw
case type case type
when Protocol::PACKET_CONTROL when Protocol::PACKET_CONTROL
handle_control_packet(host, packet, peer) handle_control_packet(host, packet, peer)
when Protocol::PACKET_RAW when Protocol::PACKET_RAW
handle_raw_packet(packet) handle_raw_packet(packet)
when Protocol::PACKET_RELIABLE
handle_reliable_packet(host, packet, peer)
else 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
end end
@@ -56,7 +59,16 @@ module CyberarmEngine
when Protocol::CONTROL_HEARTBEAT when Protocol::CONTROL_HEARTBEAT
when Protocol::CONTROL_PING 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 when Protocol::CONTROL_PONG
sent_time = pkt.message.unpack1("Q")
difference = Networking.milliseconds - sent_time
peer.ping = difference
end end
nil nil
@@ -66,12 +78,30 @@ module CyberarmEngine
RawPacket.decode(packet.message) RawPacket.decode(packet.message)
end 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) def self.create_control_packet(peer:, control_type:, message: nil, reliable: false, channel: 0)
message_packet = nil message_packet = nil
if reliable if reliable
warn "Reliable packets are not yet implemented!" 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) message_packet = ReliablePacket.new(sequence_number: peer.next_reliable_sequence_number, message: packet.encode)
else else
message_packet = ControlPacket.new(control_type: control_type, message: message) message_packet = ControlPacket.new(control_type: control_type, message: message)

View File

@@ -1,7 +1,7 @@
module CyberarmEngine module CyberarmEngine
module Networking module Networking
class ReliablePacket class ReliablePacket
attr_reader :message, :type, :control_type attr_reader :message, :type, :sequence_number
HEADER_PACKER = "Cn" HEADER_PACKER = "Cn"
HEADER_LENGTH = 1 + 2 # bytes HEADER_LENGTH = 1 + 2 # bytes
@@ -10,7 +10,7 @@ module CyberarmEngine
header = raw_message.unpack(HEADER_PACKER) header = raw_message.unpack(HEADER_PACKER)
message = raw_message[HEADER_LENGTH..raw_message.length - 1] 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 end
def initialize(sequence_number:, message:, type: Protocol::PACKET_RELIABLE) def initialize(sequence_number:, message:, type: Protocol::PACKET_RELIABLE)
@@ -22,7 +22,7 @@ module CyberarmEngine
def encode def encode
header = [ header = [
@type, @type,
@control_type @sequence_number
].pack(HEADER_PACKER) ].pack(HEADER_PACKER)
"#{header}#{@message}" "#{header}#{@message}"

View File

@@ -4,7 +4,8 @@ module CyberarmEngine
attr_reader :id, :hostname, :port, :data, :read_queue, :write_queue attr_reader :id, :hostname, :port, :data, :read_queue, :write_queue
attr_accessor :total_packets_sent, :total_packets_received, attr_accessor :total_packets_sent, :total_packets_received,
:total_data_sent, :total_data_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:) def initialize(id:, hostname:, port:)
@id = id @id = id
@@ -22,6 +23,10 @@ module CyberarmEngine
@total_packets_received = 0 @total_packets_received = 0
@total_data_sent = 0 @total_data_sent = 0
@total_data_received = 0 @total_data_received = 0
@ping = 0
@reliable_sequence_number = 65_500
end end
def id=(n) def id=(n)
@@ -29,6 +34,10 @@ module CyberarmEngine
@id = n @id = n
end end
def next_reliable_sequence_number
@reliable_sequence_number = (@reliable_sequence_number + 1) % 65_535
end
end end
end end
end end

View File

@@ -70,7 +70,7 @@ module CyberarmEngine
# Send packet to specified peer # Send packet to specified peer
def send_packet(peer:, message:, reliable: false, channel: 0) 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) packet = PacketHandler.create_raw_packet(message, reliable, channel)
peer.write_queue << packet peer.write_queue << packet
else else
@@ -80,12 +80,12 @@ module CyberarmEngine
# Send packet to all connected peer # Send packet to all connected peer
def broadcast_packet(message:, reliable: false, channel: 0) 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 end
# Disconnect peer # Disconnect peer
def disconnect_client(peer:, reason: "") def disconnect_client(peer:, reason: "")
if (peer = @peers.get(peer)) if (peer = @peers[peer])
packet = PacketHandler.create_disconnect_packet(peer.id, reason) packet = PacketHandler.create_disconnect_packet(peer.id, reason)
peer.write_now!(packet) peer.write_now!(packet)
@peers.delete(peer) @peers.delete(peer)
@@ -115,6 +115,16 @@ module CyberarmEngine
next next
end 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) while(packet = peer.write_queue.shift)
write(peer: peer, packet: packet) write(peer: peer, packet: packet)
end end

View File

@@ -12,7 +12,7 @@ class IMICFPS
push_state(IMICFPS::MapEditorTool::MainMenu) push_state(IMICFPS::MapEditorTool::MainMenu)
end end
link "Back" do link I18n.t("menus.back") do
pop_state pop_state
end end
end end

View File

@@ -9,11 +9,11 @@ class IMICFPS
pop_state pop_state
end end
link "Settings" do link I18n.t("menus.settings") do
push_state(SettingsMenu) push_state(SettingsMenu)
end end
link "Leave" do link I18n.t("menus.leave") do
push_state(MainMenu) push_state(MainMenu)
end end
end end

View File

@@ -12,7 +12,7 @@ class IMICFPS
end end
end end
link "Back" do link I18n.t("menus.back") do
pop_state pop_state
end end
end end

View File

@@ -3,23 +3,23 @@ class IMICFPS
def setup def setup
title IMICFPS::NAME title IMICFPS::NAME
link "Single Player" do link I18n.t("menus.singleplayer") do
push_state(LevelSelectMenu) push_state(LevelSelectMenu)
end end
link "Multiplayer" do link I18n.t("menus.multiplayer") do
push_state(MultiplayerMenu) push_state(MultiplayerMenu)
end end
link "Settings" do link I18n.t("menus.settings") do
push_state(SettingsMenu) push_state(SettingsMenu)
end end
link "Extras" do link I18n.t("menus.extras") do
push_state(ExtrasMenu) push_state(ExtrasMenu)
end end
link "Quit" do link I18n.t("menus.quit") do
window.close window.close
end end

View File

@@ -11,7 +11,7 @@ class IMICFPS
link "Profile" do link "Profile" do
push_state(MultiplayerProfileMenu) push_state(MultiplayerProfileMenu)
end end
link "Back" do link I18n.t("menus.back") do
pop_state pop_state
end end
end end

View File

@@ -7,7 +7,7 @@ class IMICFPS
stack(width: 0.25, height: 1.0) do stack(width: 0.25, height: 1.0) do
button "Edit Profile", width: 1.0 button "Edit Profile", width: 1.0
button "Log Out", 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 pop_state
end end
end end

View File

@@ -34,7 +34,7 @@ class IMICFPS
button "Host Game", width: 1.0 button "Host Game", width: 1.0
button "Direct Connect", 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 pop_state
end end
end end

View File

@@ -29,7 +29,7 @@ class IMICFPS
end end
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 pop_state
end end
end end

View File

@@ -12,6 +12,11 @@ class IMICFPS
super(width: window_width, height: window_height, fullscreen: fullscreen, resizable: true, update_interval: 1000.0/fps_target) super(width: window_width, height: window_height, fullscreen: fullscreen, resizable: true, update_interval: 1000.0/fps_target)
end end
$window = self $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 @needs_cursor = false
@cursor = Gosu::Image.new(IMICFPS::GAME_ROOT_PATH + "/static/cursors/pointer.png") @cursor = Gosu::Image.new(IMICFPS::GAME_ROOT_PATH + "/static/cursors/pointer.png")
@number_of_vertices = 0 @number_of_vertices = 0

13
locales/de.yml Normal file
View File

@@ -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

13
locales/en.yml Normal file
View File

@@ -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

View File

@@ -19,13 +19,15 @@ def require_all(directory)
else else
files = failed files = failed
end end
end until( failed.empty? ) end until(failed.empty?)
end end
require "socket" require "socket"
require_relative "lib/networking" require_relative "lib/networking"
require_all "lib/networking/backend" require_all "lib/networking/backend"
Thread.abort_on_exception = true
server = CyberarmEngine::Networking::Server.new server = CyberarmEngine::Networking::Server.new
def server.client_connected(peer:) def server.client_connected(peer:)
puts "Client connected as peer: #{peer.id}" puts "Client connected as peer: #{peer.id}"
@@ -33,6 +35,7 @@ end
def server.packet_received(peer:, message:, channel:) def server.packet_received(peer:, message:, channel:)
pp "Server received: #{message} [on channel: #{channel} from peer: #{peer&.id}]" pp "Server received: #{message} [on channel: #{channel} from peer: #{peer&.id}]"
broadcast_packet(message: "Broadcasting...")
end end
Thread.new do Thread.new do
@@ -47,6 +50,7 @@ end
connection = CyberarmEngine::Networking::Connection.new(hostname: "localhost", port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT, channels: 3) connection = CyberarmEngine::Networking::Connection.new(hostname: "localhost", port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT, channels: 3)
def connection.connected def connection.connected
puts "Connection: Connected!" puts "Connection: Connected!"
send_packet(message: "I be connected!")
end end
def connection.disconnected(reason:) def connection.disconnected(reason:)
@@ -55,6 +59,7 @@ end
def connection.packet_received(message:, channel:) def connection.packet_received(message:, channel:)
pp "Connection received: #{message} [on channel: #{channel} from peer: SERVER]" pp "Connection received: #{message} [on channel: #{channel} from peer: SERVER]"
send_packet(message: "ECHO: #{message}")
end end
connection.connect(timeout: 1_000) connection.connect(timeout: 1_000)