Proper chat message support

This commit is contained in:
ChillerDragon 2022-11-04 16:26:24 +01:00
parent 316a24852e
commit 0f9c9a0804
7 changed files with 135 additions and 30 deletions

View file

@ -6,11 +6,21 @@ A teeworlds 0.7 client library written in ruby
```ruby
require_relative 'lib/teeworlds-client'
client = TwClient.new(verbose: false)
client = TeeworldsClient.new(verbose: false)
# print all incoming chat messages
# the variable `msg` holds an instance of the class `ChatMessage` which has the following fields
#
# msg.mode
# msg.client_id
# msg.target_id
# msg.message
# msg.author.id
# msg.author.team
# msg.author.name
# msg.author.clan
client.hook_chat do |msg|
puts "chat: #{msg}"
puts "[chat] #{msg}"
end
# properly disconnect on ctrl+c
@ -27,7 +37,7 @@ client.connect('localhost', 8303, detach: false)
```ruby
require_relative 'lib/teeworlds-client'
client = TwClient.new(verbose: true)
client = TeeworldsClient.new(verbose: true)
# connect to localhost and block the current thread
client.connect('localhost', 8303, detach: false)
@ -38,7 +48,7 @@ client.connect('localhost', 8303, detach: false)
```ruby
require_relative 'lib/teeworlds-client'
client = TwClient.new(verbose: true)
client = TeeworldsClient.new(verbose: true)
# connect to localhost and detach a background thread
client.connect('localhost', 8303, detach: true)
@ -55,7 +65,7 @@ end
```ruby
require_relative 'lib/teeworlds-client'
client = TwClient.new(verbose: true)
client = TeeworldsClient.new(verbose: true)
# all keys are optional
# if not provided they will fall back to the default value

30
lib/chat_message.rb Normal file
View file

@ -0,0 +1,30 @@
class ChatMesage
attr_reader :mode, :client_id, :target_id, :message, :author
def initialize(data = {})
# @mode
# Type: Integer
@mode = data[:mode]
# @client_id
# Type: Integer
@client_id = data[:client_id]
# @target_id
# Type: Integer
@target_id = data[:target_id]
# @message
# Type: String
@message = data[:message]
# @author
# Type: Player see player.rb
@author = data[:author]
end
def to_s
"#{@author.name}: #{@message}"
end
end

54
lib/game_client.rb Normal file
View file

@ -0,0 +1,54 @@
require_relative 'player'
require_relative 'packer'
require_relative 'chat_message'
class GameClient
attr_accessor :players
def initialize(client)
@client = client
@players = {}
end
def on_player_join(chunk)
# puts "Got playerinfo flags: #{chunk.flags}"
u = Unpacker.new(chunk.data[1..])
player = Player.new(
id: u.get_int(),
local: u.get_int(),
team: u.get_int(),
name: u.get_string(),
clan: u.get_string(),
country: u.get_int())
# skinparts and the silent flag
# are currently ignored
@players[player.id] = player
puts "'#{player.name}' joined the game"
end
def on_chat(chunk)
# 06 01 00 40 41 00
# msg mode cl_id trgt A nullbyte?
# all -1
# mode = chunk.data[1]
# client_id = chunk.data[2]
# target = chunk.data[3]
# msg = chunk.data[4..]
u = Unpacker.new(chunk.data[1..])
data = {
mode: u.get_int(),
client_id: u.get_int(),
target_id: u.get_int(),
message: u.get_string()
}
data[:author] = @players[data[:client_id]]
msg = ChatMesage.new(data)
if @client.hooks[:chat]
@client.hooks[:chat].call(msg)
end
end
end

View file

@ -69,6 +69,13 @@ end
class Unpacker
def initialize(data)
@data = data
if data.class == String
@data = data.unpack("C*")
elsif data.class == Array
@data = data
else
raise "Error: Unpacker expects array of integers or byte string"
end
end
def get_string()
@ -197,5 +204,5 @@ def todo_also_rspec_unpacker
# p u.get_int()
end
todo_also_rspec_unpacker
# todo_also_rspec_unpacker

18
lib/player.rb Normal file
View file

@ -0,0 +1,18 @@
class Player
attr_reader :id, :local, :team, :name, :clan
attr_reader :country
attr_reader :skin_parts, :skin_custom_colors, :skin_colors
def initialize(data = {})
@id = data[:id] || -1
@local = data[:local] || 0
@team = data[:team] || 0
@name = data[:name] || '(connecting..)'
@clan = data[:clan] || ''
@country = data[:country] || -1
@skin_parts = data[:skin_parts] || Array.new(6, 'standard')
@skin_custom_colors = data[:skin_custom_colors] || Array.new(6, 0)
@skin_colors = data[:skin_colors] || Array.new(6, 0)
end
end

View file

@ -11,9 +11,11 @@ require_relative 'chunk'
require_relative 'server_info'
require_relative 'net_base'
require_relative 'packer'
require_relative 'player'
require_relative 'game_client'
class TwClient
attr_reader :state
class TeeworldsClient
attr_reader :state, :hooks
def initialize(options = {})
@verbose = options[:verbose] || false
@ -24,6 +26,7 @@ class TwClient
@hooks = {}
@thread_running = false
@signal_disconnect = false
@game_client = GameClient.new(self)
@start_info = {
name: "ruby gamer",
clan: "",
@ -64,6 +67,7 @@ class TwClient
disconnect
@signal_disconnect = false
@ticks = 0
@game_client = GameClient.new(self)
# me trying to write cool code
@client_token = (1..4).to_a.map { |_| rand(0..255) }
@client_token = @client_token.map { |b| b.to_s(16) }.join('')
@ -286,34 +290,16 @@ class TwClient
end
end
def on_player_join(chunk)
puts "Got playerinfo flags: #{chunk.flags}"
end
def on_emoticon(chunk)
# puts "Got emoticon flags: #{chunk.flags}"
end
def on_chat(chunk)
# 06 01 00 40 41 00
# msg mode cl_id trgt A nullbyte?
# all -1
mode = chunk.data[1]
client_id = chunk.data[2]
target = chunk.data[3]
msg = chunk.data[4..]
if @hooks[:chat]
@hooks[:chat].call(msg)
end
end
def on_message(chunk)
case chunk.msg
when NETMSGTYPE_SV_READYTOENTER then send_enter_game
when NETMSGTYPE_SV_CLIENTINFO then on_player_join(chunk)
when NETMSGTYPE_SV_CLIENTINFO then @game_client.on_player_join(chunk)
when NETMSGTYPE_SV_EMOTICON then on_emoticon(chunk)
when NETMSGTYPE_SV_CHAT then on_chat(chunk)
when NETMSGTYPE_SV_CHAT then @game_client.on_chat(chunk)
else
if @verbose
puts "todo non sys chunks. skipped msg: #{chunk.msg}"

View file

@ -25,13 +25,13 @@ end
args[:ip] = args[:ip] || '127.0.0.1'
args[:port] = args[:port] || 8303
client = TwClient.new(verbose: args[:verbose])
client = TeeworldsClient.new(verbose: args[:verbose])
client.set_startinfo(
name: "ruby gamer")
client.hook_chat do |msg|
puts "chat: #{msg}"
puts "[chat] #{msg}"
end
Signal.trap('INT') do