From 7689d2725ae61fd436521ff1142c4987bd76d1e9 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Sat, 5 Nov 2022 10:35:40 +0100 Subject: [PATCH] Strong control over lib using Context objects --- docs/01.md | 70 ++++++++++++++++++++++++++++++++++++- lib/game_client.rb | 65 +++++++++++++++++++++++++++++++++- lib/player.rb | 6 ++-- lib/teeworlds-client.rb | 77 ++++++++++++++--------------------------- sample.rb | 9 +++++ 5 files changed, 171 insertions(+), 56 deletions(-) diff --git a/docs/01.md b/docs/01.md index 28a19d1..74bb329 100644 --- a/docs/01.md +++ b/docs/01.md @@ -20,11 +20,57 @@ ### @message [Integer] ### @author [[Player](#player)] +## Context + +This class is the callback context. +When you hook into methods using a ``on_*`` method you can access its context. +This gives you the ability to read and modify the data before the default behavior processes it. +Or skip the default behavior and implement your own logic. + +### #cancle + +Call the ``cancle()`` on the context object to not run any default code for that event. + +```ruby +client.on_map_change do |context| + # do nothing when a map change packet comes in + # skips the send ready packet code + context.cancle +end +``` + +### @data [Hash] + +This hash holds all the current data. They keys might vary depending on the current context. +You can read and write those values. If you set an unused key the program will panic. + +Here an example to see what keys you are given for a client info event. + +```ruby +client = TeeworldsClient.new + +client.on_client_info do |context| + p context.keys + # [:player, :chunk] +end +``` + +Here an example to modify all incoming player info to rename all player objects to yee. +Which is a bit weird but shows the power of the modding api. + +```ruby +client = TeeworldsClient.new + +client.on_client_info do |context| + context.data[:player].name = 'yee' +end +``` + ## TeeworldsClient ### #on_chat(&block) -**Parameter: block [Block]** +**Parameter: block [Block |[ChatMessage](#chatmessage)|]** Takes a block that will be called when the client receives a chat message. The block takes one parameter of type [ChatMessage](#chatmessage). @@ -40,6 +86,28 @@ end client.connect('localhost', 8303, detach: true) ``` +### #on_map_change(&block) + +**Parameter: block [Block |[context](#context)|]** + +Takes a block that will be called when the client receives a map change packet. + +**Example:** + +```ruby +client = TeeworldsClient.new + +client.on_map_change do |context| + puts "Got new map!" + + # skip default behavior + # in this case do not send the ready packet + context.cancle +end + +client.connect('localhost', 8303, detach: true) +``` + ### connect(ip, port, options) diff --git a/lib/game_client.rb b/lib/game_client.rb index ecd7141..b63168b 100644 --- a/lib/game_client.rb +++ b/lib/game_client.rb @@ -2,6 +2,33 @@ require_relative 'player' require_relative 'packer' require_relative 'chat_message' +class Context + attr_reader :old_data + attr_accessor :data + + def initialize(keys = {}) + @cancle = false + @old_data = keys + @data = keys + end + + def verify + @data.each do |key, value| + next if @old_data.key? key + + raise "Error: invalid data key '#{key}'\n valid keys: #{@old_data.keys}" + end + end + + def cancled? + @cancle + end + + def cancle + @cancle = true + end +end + class GameClient attr_accessor :players @@ -10,7 +37,12 @@ class GameClient @players = {} end - def on_player_join(chunk) + def on_client_enter(chunk) + puts "ON CLIENT ENTER" + puts chunk + end + + def on_client_info(chunk) # puts "Got playerinfo flags: #{chunk.flags}" u = Unpacker.new(chunk.data[1..]) player = Player.new( @@ -23,10 +55,41 @@ class GameClient # skinparts and the silent flag # are currently ignored + context = Context.new( + player: player, + chunk: chunk + ) + if @client.hooks[:client_info] + @client.hooks[:client_info].call(context) + context.verify + return if context.cancled? + end + + player = context.data[:player] @players[player.id] = player puts "'#{player.name}' joined the game" end + def on_ready_to_enter(chunk) + @client.send_enter_game + end + + def on_connected + @client.send_msg_startinfo + end + + def on_map_change(chunk) + context = Context.new(chunk: chunk) + if @client.hooks[:map_change] + @client.hooks[:map_change].call(context) + context.verify + return if context.cancled? + end + # ignore mapdownload at all times + # and claim to have the map + @client.send_msg_ready + end + def on_chat(chunk) # 06 01 00 40 41 00 # msg mode cl_id trgt A nullbyte? diff --git a/lib/player.rb b/lib/player.rb index 733183f..e0ba12c 100644 --- a/lib/player.rb +++ b/lib/player.rb @@ -1,7 +1,7 @@ class Player - attr_reader :id, :local, :team, :name, :clan - attr_reader :country - attr_reader :skin_parts, :skin_custom_colors, :skin_colors + attr_accessor :id, :local, :team, :name, :clan + attr_accessor :country + attr_accessor :skin_parts, :skin_custom_colors, :skin_colors def initialize(data = {}) @id = data[:id] || -1 diff --git a/lib/teeworlds-client.rb b/lib/teeworlds-client.rb index 1a19ba8..56136aa 100644 --- a/lib/teeworlds-client.rb +++ b/lib/teeworlds-client.rb @@ -56,6 +56,14 @@ class TeeworldsClient @hooks[:chat] = block end + def on_map_change(&block) + @hooks[:map_change] = block + end + + def on_client_info(&block) + @hooks[:client_info] = block + end + def send_chat(str) @netbase.send_packet( NetChunk.create_vital_header({vital: true}, 4 + str.length) + @@ -128,18 +136,6 @@ class TeeworldsClient end end - private - - def connection_loop - until @signal_disconnect - tick - # todo: proper tick speed sleep - sleep 0.001 - end - @thread_running = false - @signal_disconnect = false - end - def send_msg(data) @netbase.send_packet(data) end @@ -246,36 +242,7 @@ class TeeworldsClient puts "got NET_CTRLMSG_CLOSE" end - def get_strings(data) - strings = [] - str = "" - data.chars.each do |b| - # use a bunch of control characters as delimiters - # https://en.wikipedia.org/wiki/Control_character - if (0x00..0x0F).to_a.include?(b.unpack('C*').first) - strings.push(str) unless str.length.zero? - str = "" - next - end - - str += b - end - strings - end - - def on_msg_map_change(data) - mapname = get_strings(data).first - puts "map: #{mapname}" - send_msg_ready() - end - - def on_motd(data) - puts "motd: #{get_strings(data)}" - end - - def on_playerinfo(data) - puts "playerinfo: #{get_strings(data).join(', ')}" - end + private # CClient::ProcessConnlessPacket def on_ctrl_message(msg, data) @@ -290,15 +257,12 @@ class TeeworldsClient end end - def on_emoticon(chunk) - # puts "Got emoticon flags: #{chunk.flags}" - end - def on_message(chunk) case chunk.msg - when NETMSGTYPE_SV_READYTOENTER then send_enter_game - when NETMSGTYPE_SV_CLIENTINFO then @game_client.on_player_join(chunk) - when NETMSGTYPE_SV_EMOTICON then on_emoticon(chunk) + when NETMSGTYPE_SV_READYTOENTER then @game_client.on_ready_to_enter(chunk) + when NETMSGTYPE_SV_CLIENTINFO then @game_client.on_client_info(chunk) + when NETMSGTYPE_DE_CLIENTENTER then @game_client.on_client_enter(chunk) + when NETMSGTYPE_SV_EMOTICON then @game_client.on_emoticon(chunk) when NETMSGTYPE_SV_CHAT then @game_client.on_chat(chunk) else if @verbose @@ -315,11 +279,11 @@ class TeeworldsClient puts "proccess chunk with msg: #{chunk.msg}" case chunk.msg when NETMSG_MAP_CHANGE - send_msg_ready + @game_client.on_map_change(chunk) when NETMSG_SERVERINFO puts "ignore server info for now" when NETMSG_CON_READY - send_msg_startinfo + @game_client.on_connected when NETMSG_NULL # should we be in alert here? else @@ -388,5 +352,16 @@ class TeeworldsClient send_chat("hello world") end end + + def connection_loop + until @signal_disconnect + tick + # todo: proper tick speed sleep + sleep 0.001 + end + @thread_running = false + @signal_disconnect = false + end + end diff --git a/sample.rb b/sample.rb index 4f15e53..02a4f5b 100755 --- a/sample.rb +++ b/sample.rb @@ -34,6 +34,15 @@ client.on_chat do |msg| puts "[chat] #{msg}" end +client.on_map_change do |ctx| + puts "GOT MAP CHANGOS" +end + +client.on_client_info do |ctx| + puts "player info: #{ctx.data[:player]}" + ctx.data[:player].name = "yee" +end + Signal.trap('INT') do client.disconnect end