Strong control over lib using Context objects
This commit is contained in:
parent
1d3076e34b
commit
7689d2725a
70
docs/01.md
70
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)
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue