ledmon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/Makefile +5 -0
- data/README.md +5 -0
- data/Rakefile +12 -0
- data/exe/ledmon +7 -0
- data/lib/ledmon/cli/base.rb +7 -0
- data/lib/ledmon/cli/config.rb +21 -0
- data/lib/ledmon/cli/main.rb +15 -0
- data/lib/ledmon/cli/monster.rb +42 -0
- data/lib/ledmon/cli.rb +4 -0
- data/lib/ledmon/configurator.rb +48 -0
- data/lib/ledmon/interceptors/sleep_interceptor.rb +31 -0
- data/lib/ledmon/monster/deployer.rb +58 -0
- data/lib/ledmon/monster/executor.rb +50 -0
- data/lib/ledmon/monster/interaction.rb +80 -0
- data/lib/ledmon/monster/movement.rb +21 -0
- data/lib/ledmon/monster/packer.rb +74 -0
- data/lib/ledmon/monster/runner.rb +44 -0
- data/lib/ledmon/monster/session.rb +70 -0
- data/lib/ledmon/monster/terminal.rb +35 -0
- data/lib/ledmon/version.rb +5 -0
- data/lib/ledmon/world_pb.rb +45 -0
- data/lib/ledmon/world_services_pb.rb +51 -0
- data/lib/ledmon.rb +42 -0
- data/monster.rb.sample +60 -0
- data/protos/world.proto +185 -0
- data/sig/ledmon.rbs +4 -0
- metadata +230 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 040e65bfb0510118bea7a5e33794b12a90210645f5fe58f3530596244fb43055
|
4
|
+
data.tar.gz: 2acd90b6c3322fc5a0fc9c41a34b31e8f96b1a962064a77b4e45375bd8ca29dc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a349f200a4dec63d515d5a185b8d74a7f8e1eafd103c2b1fdaad046a8a13b262cfda863f4ee518d1b4ca7612f62fb95cbd046c46adc3253989111682b212a5fe
|
7
|
+
data.tar.gz: d84ae4b9a22c84728dcd61a4bd6c52429831cbf529c99024c8a39f1742e0be191710f821a8964185f8927dad1696688c159131a9772465eb749c37a92e39b222
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.4.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Ahmed Al Hafoudh
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Makefile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/exe/ledmon
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ledmon::CLI
|
2
|
+
class Config < Base
|
3
|
+
def self.config_value(name, description: nil)
|
4
|
+
desc "#{name} [VALUE]", description || "Read or set #{name} value"
|
5
|
+
define_method(name) do |value = nil|
|
6
|
+
if value
|
7
|
+
puts Ledmon::Config.public_send(:"#{name}=", value)
|
8
|
+
else
|
9
|
+
puts Ledmon::Config.public_send(name.to_sym)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
config_value :host
|
15
|
+
config_value :grpc_port
|
16
|
+
config_value :http_port
|
17
|
+
config_value :auth
|
18
|
+
config_value :mode
|
19
|
+
config_value :log_level
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Ledmon::CLI
|
2
|
+
class Main < Base
|
3
|
+
desc 'version', 'Prints the version'
|
4
|
+
|
5
|
+
def version
|
6
|
+
puts Ledmon::VERSION
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'config', 'Manage config'
|
10
|
+
subcommand 'config', Ledmon::CLI::Config
|
11
|
+
|
12
|
+
desc 'monster', 'Manage monster'
|
13
|
+
subcommand 'monster', Ledmon::CLI::Monster
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Ledmon::CLI
|
2
|
+
class Monster < Base
|
3
|
+
desc 'local', 'Run the monster from local machine'
|
4
|
+
|
5
|
+
def local
|
6
|
+
runner = Ledmon::Monster::Runner.new(path: Dir.pwd)
|
7
|
+
runner.run!
|
8
|
+
rescue Interrupt
|
9
|
+
exit 1
|
10
|
+
rescue Ledmon::Error => e
|
11
|
+
say_error e.message, :yellow
|
12
|
+
if Ledmon::Config.log_level.to_sym == :debug
|
13
|
+
say_error e.backtrace.join("\n"), :white
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'deploy', 'Deploy monster code to remote server'
|
18
|
+
|
19
|
+
def deploy
|
20
|
+
packer = Ledmon::Monster::Packer.new(path: Dir.pwd)
|
21
|
+
packer.pack! do |bundle_path|
|
22
|
+
deployer = Ledmon::Monster::Deployer.new(bundle_path:)
|
23
|
+
say "Uploading packed file ...", :yellow
|
24
|
+
|
25
|
+
deployer.deploy! do |json_data|
|
26
|
+
if json_data['message']
|
27
|
+
say json_data['message'], :cyan
|
28
|
+
elsif json_data['error']
|
29
|
+
say_error json_data['error'], :red
|
30
|
+
else
|
31
|
+
say json_data.to_s, :white
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
say "Successfully deployed monster code!", :green
|
36
|
+
end
|
37
|
+
rescue Ledmon::Error => e
|
38
|
+
say_error e.message, :red
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/ledmon/cli.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Ledmon
|
2
|
+
class Configurator
|
3
|
+
attr_reader :config
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@config = TTY::Config.new
|
7
|
+
config.filename = '.ledmon'
|
8
|
+
config.append_path Dir.pwd
|
9
|
+
config.env_prefix = 'ledmon'
|
10
|
+
|
11
|
+
config.read if config.exist?
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config_value(name, keys: nil, default: nil)
|
15
|
+
keys = Array(keys || name).map(&:to_sym)
|
16
|
+
|
17
|
+
define_method(name) do
|
18
|
+
read_value(keys, default:)
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method("#{name}=") do |value|
|
22
|
+
write_value(keys, value:)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
config_value :host, default: 'localhost'
|
27
|
+
config_value :grpc_port, default: 50051
|
28
|
+
config_value :http_port, default: 4000
|
29
|
+
config_value :auth
|
30
|
+
|
31
|
+
config_value :mode, default: 'client'
|
32
|
+
config_value :log_level, default: 'info'
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def read_value(keys, default: nil)
|
37
|
+
config.set_from_env(*keys)
|
38
|
+
config.fetch(*keys, default:)
|
39
|
+
end
|
40
|
+
|
41
|
+
def write_value(keys, value:)
|
42
|
+
config.set(*keys, value:)
|
43
|
+
config.write(create: true, force: true)
|
44
|
+
|
45
|
+
value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ledmon::Interceptors
|
2
|
+
class SleepInterceptor < ::GRPC::ClientInterceptor
|
3
|
+
def initialize
|
4
|
+
@sleep_until = nil
|
5
|
+
end
|
6
|
+
|
7
|
+
def request_response(request: nil, call: nil, method: nil, metadata: nil, &block)
|
8
|
+
if @sleep_until
|
9
|
+
remaining_sleep_time = @sleep_until - Time.now.to_f
|
10
|
+
if remaining_sleep_time > 0
|
11
|
+
micro_sleep remaining_sleep_time
|
12
|
+
end
|
13
|
+
@sleep_until = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
response = block.call(request:, call:, method:, metadata:)
|
17
|
+
|
18
|
+
if response.respond_to?(:sleep_time)
|
19
|
+
@sleep_until = Time.now.to_f + response.sleep_time
|
20
|
+
end
|
21
|
+
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def micro_sleep(amount)
|
28
|
+
IO.select(nil, nil, nil, amount)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ledmon::Monster
|
4
|
+
class Deployer
|
5
|
+
attr_reader :bundle_path
|
6
|
+
|
7
|
+
def initialize(bundle_path:)
|
8
|
+
@bundle_path = bundle_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def deploy!(&block)
|
12
|
+
require 'http'
|
13
|
+
require 'json'
|
14
|
+
|
15
|
+
url = build_upload_url
|
16
|
+
headers = {
|
17
|
+
'Authorization' => "Bearer #{Ledmon::Config.auth}",
|
18
|
+
}
|
19
|
+
|
20
|
+
response = HTTP.headers(headers).post(url, form: { file: HTTP::FormData::File.new(bundle_path) })
|
21
|
+
|
22
|
+
unless response.status.success?
|
23
|
+
raise Ledmon::Error, "Deploy failed with status: #{response.status}"
|
24
|
+
end
|
25
|
+
|
26
|
+
buffer = ""
|
27
|
+
response.body.each do |chunk|
|
28
|
+
buffer += chunk
|
29
|
+
|
30
|
+
while buffer.include?("\n")
|
31
|
+
line, buffer = buffer.split("\n", 2)
|
32
|
+
process_json_line(line, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Process any remaining data in buffer
|
37
|
+
process_json_line(buffer, &block) unless buffer.strip.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def process_json_line(line, &block)
|
43
|
+
line = line.strip
|
44
|
+
return if line.empty?
|
45
|
+
|
46
|
+
begin
|
47
|
+
json_data = JSON.parse(line)
|
48
|
+
block&.call(json_data)
|
49
|
+
rescue JSON::ParserError
|
50
|
+
# Skip invalid JSON lines
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_upload_url
|
55
|
+
"http://#{Ledmon::Config.host}:#{Ledmon::Config.http_port}/deploys"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Ledmon::Monster
|
2
|
+
class Executor
|
3
|
+
include Session
|
4
|
+
include Terminal
|
5
|
+
include Interaction
|
6
|
+
include Movement
|
7
|
+
|
8
|
+
attr_reader :mode
|
9
|
+
attr_reader :logger
|
10
|
+
|
11
|
+
def initialize(mode: :client)
|
12
|
+
@mode = mode
|
13
|
+
|
14
|
+
@logger = Logger.new(STDERR)
|
15
|
+
logger.level = Ledmon::Config.log_level.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup
|
19
|
+
session_connect!
|
20
|
+
end
|
21
|
+
|
22
|
+
###
|
23
|
+
|
24
|
+
def avatar
|
25
|
+
raise Ledmon::Error, "#{__method__} is not implemented!"
|
26
|
+
end
|
27
|
+
|
28
|
+
def brain
|
29
|
+
raise Ledmon::Error, "#{__method__} is not implemented!"
|
30
|
+
end
|
31
|
+
|
32
|
+
def algo(id, input)
|
33
|
+
raise Ledmon::Error, "#{__method__} is not implemented!"
|
34
|
+
end
|
35
|
+
|
36
|
+
def algo_result(result, id, input, output)
|
37
|
+
raise Ledmon::Error, "#{__method__} is not implemented!"
|
38
|
+
end
|
39
|
+
|
40
|
+
###
|
41
|
+
|
42
|
+
def server?
|
43
|
+
mode == :server
|
44
|
+
end
|
45
|
+
|
46
|
+
def client?
|
47
|
+
mode == :client
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Ledmon::Monster
|
2
|
+
module Interaction
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def interact
|
6
|
+
queue = Queue.new
|
7
|
+
requests = Enumerator.new do |yielder|
|
8
|
+
loop do
|
9
|
+
message = queue.pop
|
10
|
+
yielder << message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
queue << Ledmon::InteractionRequest.new(
|
15
|
+
action: Ledmon::InteractionRequestAction::START,
|
16
|
+
)
|
17
|
+
|
18
|
+
last_challenge_response = nil
|
19
|
+
last_challenge_algo_output = nil
|
20
|
+
|
21
|
+
client.interact(requests).map do |response|
|
22
|
+
logger.debug("Got response: #{response}")
|
23
|
+
|
24
|
+
if response.action == :CHALLENGE
|
25
|
+
logger.debug("Challenge ID: #{response.challenge_id}")
|
26
|
+
logger.debug("Challenge Input: #{response.challenge_input}")
|
27
|
+
|
28
|
+
algo_output = algo(response.challenge_id, response.challenge_input)
|
29
|
+
|
30
|
+
last_challenge_response = response
|
31
|
+
last_challenge_algo_output = algo_output
|
32
|
+
|
33
|
+
queue << Ledmon::InteractionRequest.new(
|
34
|
+
action: Ledmon::InteractionRequestAction::SUBMIT_CHALLENGE,
|
35
|
+
challenge_response: algo_output.to_s,
|
36
|
+
)
|
37
|
+
|
38
|
+
elsif response.action == :SUCCESS
|
39
|
+
logger.debug("Challenge succeeded!")
|
40
|
+
return true, last_challenge_response.challenge_id, last_challenge_response.challenge_input, last_challenge_algo_output
|
41
|
+
elsif response.action == :FAILURE
|
42
|
+
logger.debug("Challenge failed!")
|
43
|
+
return false, last_challenge_response.challenge_id, last_challenge_response.challenge_input, last_challenge_algo_output
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def craft(recipe)
|
49
|
+
client.craft(Ledmon::CraftData.new(recipe:))
|
50
|
+
end
|
51
|
+
|
52
|
+
def research(item)
|
53
|
+
client.research(Ledmon::ItemInfo.new(item:))
|
54
|
+
end
|
55
|
+
|
56
|
+
def equip_item(item, slot)
|
57
|
+
client.equip_item(Ledmon::EquipInfo.new(item:, slot:))
|
58
|
+
end
|
59
|
+
|
60
|
+
def unequip_item(slot)
|
61
|
+
client.unequip_item(Ledmon::UnequipInfo.new(slot:))
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_inventory
|
65
|
+
client.get_inventory(Ledmon::Empty.new)
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_health
|
69
|
+
client.get_health(Ledmon::Empty.new).health
|
70
|
+
end
|
71
|
+
|
72
|
+
def attack
|
73
|
+
client.attack(Ledmon::Empty.new)
|
74
|
+
end
|
75
|
+
|
76
|
+
def look
|
77
|
+
client.look(Ledmon::Empty.new).entities
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ledmon::Monster
|
2
|
+
module Movement
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def move_forward
|
6
|
+
client.move_forward(Ledmon::Empty.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
def move_backward
|
10
|
+
client.move_backward(Ledmon::Empty.new)
|
11
|
+
end
|
12
|
+
|
13
|
+
def turn_left
|
14
|
+
client.turn_left(Ledmon::Empty.new)
|
15
|
+
end
|
16
|
+
|
17
|
+
def turn_right
|
18
|
+
client.turn_right(Ledmon::Empty.new)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'rubygems/package'
|
3
|
+
require 'active_support/core_ext/numeric/bytes'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Ledmon
|
7
|
+
module Monster
|
8
|
+
class Packer
|
9
|
+
MAX_FILE_SIZE = 100.kilobytes
|
10
|
+
MAX_PACK_SIZE = 10.megabytes
|
11
|
+
|
12
|
+
class FileTooLargeError < StandardError; end
|
13
|
+
|
14
|
+
class PackTooLargeError < StandardError; end
|
15
|
+
|
16
|
+
def initialize(path:)
|
17
|
+
@path = path
|
18
|
+
@current_pack_size = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def pack!
|
22
|
+
raise ArgumentError, "Block required" unless block_given?
|
23
|
+
|
24
|
+
Tempfile.create(%w[ledmon_pack .tar.gz]) do |tempfile|
|
25
|
+
tempfile.binmode
|
26
|
+
|
27
|
+
Zlib::GzipWriter.wrap(tempfile) do |gz|
|
28
|
+
Gem::Package::TarWriter.new(gz) do |tar|
|
29
|
+
pack_directory(tar, @path)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
yield tempfile.path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def pack_directory(tar, dir_path, relative_path = '')
|
40
|
+
Dir.foreach(dir_path) do |entry|
|
41
|
+
next if entry == '.' || entry == '..'
|
42
|
+
|
43
|
+
full_path = File.join(dir_path, entry)
|
44
|
+
entry_relative_path = relative_path.empty? ? entry : File.join(relative_path, entry)
|
45
|
+
|
46
|
+
if File.directory?(full_path)
|
47
|
+
pack_directory(tar, full_path, entry_relative_path)
|
48
|
+
else
|
49
|
+
pack_file(tar, full_path, entry_relative_path)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def pack_file(tar, file_path, relative_path)
|
55
|
+
file_size = File.size(file_path)
|
56
|
+
|
57
|
+
raise FileTooLargeError, "File #{relative_path} (#{file_size} bytes) exceeds maximum size of #{MAX_FILE_SIZE} bytes" if file_size > MAX_FILE_SIZE
|
58
|
+
|
59
|
+
if @current_pack_size + file_size > MAX_PACK_SIZE
|
60
|
+
raise PackTooLargeError, "Adding file #{relative_path} would exceed maximum pack size of #{MAX_PACK_SIZE} bytes"
|
61
|
+
end
|
62
|
+
|
63
|
+
file_stat = File.stat(file_path)
|
64
|
+
tar.add_file_simple(relative_path, file_stat.mode, file_stat.size) do |io|
|
65
|
+
File.open(file_path, 'rb') do |file|
|
66
|
+
IO.copy_stream(file, io)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@current_pack_size += file_size
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Ledmon::Monster
|
2
|
+
class Runner
|
3
|
+
class Error < StandardError; end
|
4
|
+
class ScriptNotFoundError < Error; end
|
5
|
+
|
6
|
+
attr_reader :path
|
7
|
+
attr_reader :script_path
|
8
|
+
attr_reader :script
|
9
|
+
attr_reader :executor
|
10
|
+
|
11
|
+
def initialize(path:)
|
12
|
+
@path = path
|
13
|
+
@script_path = File.join(path, 'monster.rb')
|
14
|
+
@executor = Executor.new(mode:)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run!
|
18
|
+
load_script!
|
19
|
+
setup!
|
20
|
+
execute!
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def setup!
|
26
|
+
executor.instance_eval(script, script_path)
|
27
|
+
executor.setup
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute!
|
31
|
+
executor.brain
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_script!
|
35
|
+
raise ScriptNotFoundError.new("Monster script not found at #{script_path}") unless File.exist?(script_path)
|
36
|
+
|
37
|
+
@script = File.read(script_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def mode
|
41
|
+
Ledmon::Config.mode.to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Ledmon::Monster
|
2
|
+
module Session
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attr_reader :client
|
7
|
+
attr_reader :name
|
8
|
+
end
|
9
|
+
|
10
|
+
def session_connect!
|
11
|
+
@client = Ledmon::World::Stub.new(
|
12
|
+
session_grpc_host,
|
13
|
+
:this_channel_is_insecure,
|
14
|
+
interceptors: [
|
15
|
+
Ledmon::Interceptors::SleepInterceptor.new,
|
16
|
+
],
|
17
|
+
)
|
18
|
+
|
19
|
+
session_responses = client.session(Ledmon::SessionRequest.new(token: Ledmon::Config.auth, avatar:,))
|
20
|
+
|
21
|
+
start_response = session_responses.next
|
22
|
+
|
23
|
+
@name = start_response.name
|
24
|
+
|
25
|
+
logger.debug("Got response: #{start_response.inspect}")
|
26
|
+
|
27
|
+
if start_response.status == :STARTED
|
28
|
+
logger.debug("Session started. Welcome, #{name}!")
|
29
|
+
else
|
30
|
+
raise Ledmon::Error, "Failed to start session: #{start_response.status}"
|
31
|
+
end
|
32
|
+
|
33
|
+
Thread.new do
|
34
|
+
loop do
|
35
|
+
session_responses.map do |response|
|
36
|
+
case response.status
|
37
|
+
when :FINISHED
|
38
|
+
logger.debug("Session finished.")
|
39
|
+
throw :finished
|
40
|
+
else
|
41
|
+
logger.debug("Received unknown response: #{response.inspect}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue => ex
|
46
|
+
logger.error("An error occurred: #{ex.message}")
|
47
|
+
logger.error(ex.backtrace.join("\n"))
|
48
|
+
end
|
49
|
+
|
50
|
+
Thread.new do
|
51
|
+
loop do
|
52
|
+
client.keep_alive(Ledmon::Empty.new)
|
53
|
+
sleep 1
|
54
|
+
end
|
55
|
+
rescue GRPC::Unavailable => ex
|
56
|
+
logger.debug("Server is unavailable: #{ex.message}")
|
57
|
+
error "Server disconnected."
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
ok "Connected to the monster server as #{name}"
|
62
|
+
rescue GRPC::Unavailable => ex
|
63
|
+
raise Ledmon::Error, "Server is unavailable: #{ex.message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def session_grpc_host
|
67
|
+
"#{Ledmon::Config.host}:#{Ledmon::Config.grpc_port}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Ledmon::Monster
|
2
|
+
module Terminal
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def prompt
|
7
|
+
@prompt ||= TTY::Prompt.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def safe_prompt
|
11
|
+
if client?
|
12
|
+
prompt
|
13
|
+
else
|
14
|
+
@safe_prompt ||= Class.new do
|
15
|
+
attr_reader :logger
|
16
|
+
|
17
|
+
def initialize(logger)
|
18
|
+
@logger = logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def keypress
|
22
|
+
sleep 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method_name, *args, &block)
|
26
|
+
logger.warn "Attempted to call terminal prompt method '#{method_name}' in a non-client context. This is not supported."
|
27
|
+
end
|
28
|
+
end.new(logger)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def_delegators :prompt, :say, :ok, :error, :warn
|
33
|
+
def_delegators :safe_prompt, :keypress, :ask, :select, :multiselect, :yes?, :no?, :slider
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
3
|
+
# source: world.proto
|
4
|
+
|
5
|
+
require 'google/protobuf'
|
6
|
+
|
7
|
+
|
8
|
+
descriptor_data = "\n\x0bworld.proto\x12\x06ledmon\"\x07\n\x05\x45mpty\"\'\n\tEquipInfo\x12\x0c\n\x04item\x18\x01 \x01(\t\x12\x0c\n\x04slot\x18\x02 \x01(\t\"\x1b\n\x0bUnequipInfo\x12\x0c\n\x04slot\x18\x01 \x01(\t\"\x1c\n\tPingReply\x12\x0f\n\x07message\x18\x01 \x01(\t\"/\n\x0eSessionRequest\x12\r\n\x05token\x18\x01 \x01(\t\x12\x0e\n\x06\x61vatar\x18\x02 \x01(\t\"C\n\x0cSessionReply\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.ledmon.SessionStatus\x12\x0c\n\x04name\x18\x02 \x01(\t\"F\n\nStateReply\x12\x12\n\x05state\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04\x64iff\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_stateB\x07\n\x05_diff\" \n\x08Position\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\"\x84\x01\n\x10MovementResponse\x12&\n\x06result\x18\x01 \x01(\x0e\x32\x16.ledmon.MovementResult\x12\'\n\x08position\x18\x02 \x01(\x0b\x32\x10.ledmon.PositionH\x00\x88\x01\x01\x12\x12\n\nsleep_time\x18\x03 \x01(\x02\x42\x0b\n\t_position\"~\n\x12InteractionRequest\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .ledmon.InteractionRequestAction\x12\x1f\n\x12\x63hallenge_response\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x15\n\x13_challenge_response\"\xa6\x01\n\x13InteractionResponse\x12\x31\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32!.ledmon.InteractionResponseAction\x12\x19\n\x0c\x63hallenge_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0f\x63hallenge_input\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x0f\n\r_challenge_idB\x12\n\x10_challenge_input\"\x89\x01\n\rInventoryData\x12\r\n\x05items\x18\x01 \x03(\t\x12\x37\n\tequipment\x18\x02 \x03(\x0b\x32$.ledmon.InventoryData.EquipmentEntry\x1a\x30\n\x0e\x45quipmentEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x1b\n\tCraftData\x12\x0e\n\x06recipe\x18\x01 \x01(\t\"\x18\n\x08ItemInfo\x12\x0c\n\x04item\x18\x01 \x01(\t\"\x1d\n\x0bHealthCount\x12\x0e\n\x06health\x18\x01 \x01(\x02\"J\n\x0e\x41\x63tionResponse\x12$\n\x06result\x18\x01 \x01(\x0e\x32\x14.ledmon.ActionResult\x12\x12\n\nsleep_time\x18\x02 \x01(\x02\"H\n\x0cLookResponse\x12$\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x12.ledmon.LookEntity\x12\x12\n\nsleep_time\x18\x02 \x01(\x02\"O\n\nLookEntity\x12\n\n\x02id\x18\x01 \x01(\t\x12\"\n\x08position\x18\x02 \x01(\x0b\x32\x10.ledmon.Position\x12\x11\n\tequipment\x18\x03 \x03(\t\"G\n\x13StorageTransferData\x12\x0c\n\x04item\x18\x01 \x01(\t\x12\"\n\x04mode\x18\x02 \x01(\x0e\x32\x14.ledmon.TransferMode\"\x1c\n\x0bStorageData\x12\r\n\x05items\x18\x01 \x03(\t\"}\n\x10ResearchProgress\x12\x38\n\x08progress\x18\x01 \x03(\x0b\x32&.ledmon.ResearchProgress.ProgressEntry\x1a/\n\rProgressEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\"\x94\x01\n\x0eTradeOfferList\x12\x32\n\x06offers\x18\x01 \x03(\x0b\x32\".ledmon.TradeOfferList.OffersEntry\x12\x0b\n\x03own\x18\x02 \x03(\t\x1a\x41\n\x0bOffersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.ledmon.TradeOffer:\x02\x38\x01\"@\n\nTradeOffer\x12\x0e\n\x06inputs\x18\x01 \x03(\t\x12\x0f\n\x07outputs\x18\x02 \x03(\t\x12\x11\n\tremaining\x18\x03 \x01(\x05\"\x1a\n\x0cTradeOfferId\x12\n\n\x02id\x18\x01 \x01(\t\"\"\n\x12\x43onsumableDuration\x12\x0c\n\x04time\x18\x01 \x01(\x02*=\n\rSessionStatus\x12\x0b\n\x07STARTED\x10\x00\x12\x0c\n\x08\x46INISHED\x10\x01\x12\x11\n\rINVALID_TOKEN\x10\x02*%\n\x0eMovementResult\x12\x06\n\x02OK\x10\x00\x12\x0b\n\x07\x42LOCKED\x10\x01*;\n\x18InteractionRequestAction\x12\t\n\x05START\x10\x00\x12\x14\n\x10SUBMIT_CHALLENGE\x10\x01*D\n\x19InteractionResponseAction\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\x12\r\n\tCHALLENGE\x10\x02**\n\x0c\x41\x63tionResult\x12\x0e\n\nSUCCESSFUL\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x01*%\n\x0cTransferMode\x12\x08\n\x04TAKE\x10\x00\x12\x0b\n\x07\x44\x45POSIT\x10\x01\x32\xd9\r\n\x05World\x12*\n\x04Ping\x12\r.ledmon.Empty\x1a\x11.ledmon.PingReply\"\x00\x12\x32\n\nPingStream\x12\r.ledmon.Empty\x1a\x11.ledmon.PingReply\"\x00\x30\x01\x12;\n\x07Session\x12\x16.ledmon.SessionRequest\x1a\x14.ledmon.SessionReply\"\x00\x30\x01\x12+\n\tKeepAlive\x12\r.ledmon.Empty\x1a\r.ledmon.Empty\"\x00\x12/\n\x08GetState\x12\r.ledmon.Empty\x1a\x12.ledmon.StateReply\"\x00\x12\x37\n\x0eGetStateStream\x12\r.ledmon.Empty\x1a\x12.ledmon.StateReply\"\x00\x30\x01\x12\x38\n\x0bMoveForward\x12\r.ledmon.Empty\x1a\x18.ledmon.MovementResponse\"\x00\x12\x39\n\x0cMoveBackward\x12\r.ledmon.Empty\x1a\x18.ledmon.MovementResponse\"\x00\x12\x35\n\x08TurnLeft\x12\r.ledmon.Empty\x1a\x18.ledmon.MovementResponse\"\x00\x12\x36\n\tTurnRight\x12\r.ledmon.Empty\x1a\x18.ledmon.MovementResponse\"\x00\x12\x37\n\nTurnAround\x12\r.ledmon.Empty\x1a\x18.ledmon.MovementResponse\"\x00\x12I\n\x08Interact\x12\x1a.ledmon.InteractionRequest\x1a\x1b.ledmon.InteractionResponse\"\x00(\x01\x30\x01\x12\x34\n\x05\x43raft\x12\x11.ledmon.CraftData\x1a\x16.ledmon.ActionResponse\"\x00\x12H\n\x0fStorageTransfer\x12\x1b.ledmon.StorageTransferData\x1a\x16.ledmon.ActionResponse\"\x00\x12\x33\n\x0bStorageList\x12\r.ledmon.Empty\x1a\x13.ledmon.StorageData\"\x00\x12\x36\n\x08Research\x12\x10.ledmon.ItemInfo\x1a\x16.ledmon.ActionResponse\"\x00\x12;\n\rScrapResearch\x12\x10.ledmon.ItemInfo\x1a\x16.ledmon.ActionResponse\"\x00\x12@\n\x13GetResearchProgress\x12\r.ledmon.Empty\x1a\x18.ledmon.ResearchProgress\"\x00\x12:\n\x0fListTradeOffers\x12\r.ledmon.Empty\x1a\x16.ledmon.TradeOfferList\"\x00\x12>\n\x10\x43reateTradeOffer\x12\x12.ledmon.TradeOffer\x1a\x14.ledmon.TradeOfferId\"\x00\x12\x42\n\x10\x41\x63\x63\x65ptTradeOffer\x12\x14.ledmon.TradeOfferId\x1a\x16.ledmon.ActionResponse\"\x00\x12\x39\n\x10\x43\x61ncelTradeOffer\x12\x14.ledmon.TradeOfferId\x1a\r.ledmon.Empty\"\x00\x12\x36\n\x0cGetInventory\x12\r.ledmon.Empty\x1a\x15.ledmon.InventoryData\"\x00\x12\x38\n\tEquipItem\x12\x11.ledmon.EquipInfo\x1a\x16.ledmon.ActionResponse\"\x00\x12<\n\x0bUnequipItem\x12\x13.ledmon.UnequipInfo\x1a\x16.ledmon.ActionResponse\"\x00\x12\x31\n\tGetHealth\x12\r.ledmon.Empty\x1a\x13.ledmon.HealthCount\"\x00\x12\x31\n\x06\x41ttack\x12\r.ledmon.Empty\x1a\x16.ledmon.ActionResponse\"\x00\x12\x39\n\x0b\x43onsumeItem\x12\x10.ledmon.ItemInfo\x1a\x16.ledmon.ActionResponse\"\x00\x12M\n\x1eGetRemainingConsumableDuration\x12\r.ledmon.Empty\x1a\x1a.ledmon.ConsumableDuration\"\x00\x12-\n\x04Look\x12\r.ledmon.Empty\x1a\x14.ledmon.LookResponse\"\x00\x62\x06proto3"
|
9
|
+
|
10
|
+
pool = Google::Protobuf::DescriptorPool.generated_pool
|
11
|
+
pool.add_serialized_file(descriptor_data)
|
12
|
+
|
13
|
+
module Ledmon
|
14
|
+
Empty = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.Empty").msgclass
|
15
|
+
EquipInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.EquipInfo").msgclass
|
16
|
+
UnequipInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.UnequipInfo").msgclass
|
17
|
+
PingReply = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.PingReply").msgclass
|
18
|
+
SessionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.SessionRequest").msgclass
|
19
|
+
SessionReply = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.SessionReply").msgclass
|
20
|
+
StateReply = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.StateReply").msgclass
|
21
|
+
Position = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.Position").msgclass
|
22
|
+
MovementResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.MovementResponse").msgclass
|
23
|
+
InteractionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.InteractionRequest").msgclass
|
24
|
+
InteractionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.InteractionResponse").msgclass
|
25
|
+
InventoryData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.InventoryData").msgclass
|
26
|
+
CraftData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.CraftData").msgclass
|
27
|
+
ItemInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.ItemInfo").msgclass
|
28
|
+
HealthCount = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.HealthCount").msgclass
|
29
|
+
ActionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.ActionResponse").msgclass
|
30
|
+
LookResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.LookResponse").msgclass
|
31
|
+
LookEntity = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.LookEntity").msgclass
|
32
|
+
StorageTransferData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.StorageTransferData").msgclass
|
33
|
+
StorageData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.StorageData").msgclass
|
34
|
+
ResearchProgress = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.ResearchProgress").msgclass
|
35
|
+
TradeOfferList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.TradeOfferList").msgclass
|
36
|
+
TradeOffer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.TradeOffer").msgclass
|
37
|
+
TradeOfferId = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.TradeOfferId").msgclass
|
38
|
+
ConsumableDuration = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.ConsumableDuration").msgclass
|
39
|
+
SessionStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.SessionStatus").enummodule
|
40
|
+
MovementResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.MovementResult").enummodule
|
41
|
+
InteractionRequestAction = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.InteractionRequestAction").enummodule
|
42
|
+
InteractionResponseAction = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.InteractionResponseAction").enummodule
|
43
|
+
ActionResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.ActionResult").enummodule
|
44
|
+
TransferMode = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ledmon.TransferMode").enummodule
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# Source: world.proto for package 'ledmon'
|
3
|
+
|
4
|
+
require 'grpc'
|
5
|
+
require 'world_pb'
|
6
|
+
|
7
|
+
module Ledmon
|
8
|
+
module World
|
9
|
+
class Service
|
10
|
+
|
11
|
+
include ::GRPC::GenericService
|
12
|
+
|
13
|
+
self.marshal_class_method = :encode
|
14
|
+
self.unmarshal_class_method = :decode
|
15
|
+
self.service_name = 'ledmon.World'
|
16
|
+
|
17
|
+
rpc :Ping, ::Ledmon::Empty, ::Ledmon::PingReply
|
18
|
+
rpc :PingStream, ::Ledmon::Empty, stream(::Ledmon::PingReply)
|
19
|
+
rpc :Session, ::Ledmon::SessionRequest, stream(::Ledmon::SessionReply)
|
20
|
+
rpc :KeepAlive, ::Ledmon::Empty, ::Ledmon::Empty
|
21
|
+
rpc :GetState, ::Ledmon::Empty, ::Ledmon::StateReply
|
22
|
+
rpc :GetStateStream, ::Ledmon::Empty, stream(::Ledmon::StateReply)
|
23
|
+
rpc :MoveForward, ::Ledmon::Empty, ::Ledmon::MovementResponse
|
24
|
+
rpc :MoveBackward, ::Ledmon::Empty, ::Ledmon::MovementResponse
|
25
|
+
rpc :TurnLeft, ::Ledmon::Empty, ::Ledmon::MovementResponse
|
26
|
+
rpc :TurnRight, ::Ledmon::Empty, ::Ledmon::MovementResponse
|
27
|
+
rpc :TurnAround, ::Ledmon::Empty, ::Ledmon::MovementResponse
|
28
|
+
rpc :Interact, stream(::Ledmon::InteractionRequest), stream(::Ledmon::InteractionResponse)
|
29
|
+
rpc :Craft, ::Ledmon::CraftData, ::Ledmon::ActionResponse
|
30
|
+
rpc :StorageTransfer, ::Ledmon::StorageTransferData, ::Ledmon::ActionResponse
|
31
|
+
rpc :StorageList, ::Ledmon::Empty, ::Ledmon::StorageData
|
32
|
+
rpc :Research, ::Ledmon::ItemInfo, ::Ledmon::ActionResponse
|
33
|
+
rpc :ScrapResearch, ::Ledmon::ItemInfo, ::Ledmon::ActionResponse
|
34
|
+
rpc :GetResearchProgress, ::Ledmon::Empty, ::Ledmon::ResearchProgress
|
35
|
+
rpc :ListTradeOffers, ::Ledmon::Empty, ::Ledmon::TradeOfferList
|
36
|
+
rpc :CreateTradeOffer, ::Ledmon::TradeOffer, ::Ledmon::TradeOfferId
|
37
|
+
rpc :AcceptTradeOffer, ::Ledmon::TradeOfferId, ::Ledmon::ActionResponse
|
38
|
+
rpc :CancelTradeOffer, ::Ledmon::TradeOfferId, ::Ledmon::Empty
|
39
|
+
rpc :GetInventory, ::Ledmon::Empty, ::Ledmon::InventoryData
|
40
|
+
rpc :EquipItem, ::Ledmon::EquipInfo, ::Ledmon::ActionResponse
|
41
|
+
rpc :UnequipItem, ::Ledmon::UnequipInfo, ::Ledmon::ActionResponse
|
42
|
+
rpc :GetHealth, ::Ledmon::Empty, ::Ledmon::HealthCount
|
43
|
+
rpc :Attack, ::Ledmon::Empty, ::Ledmon::ActionResponse
|
44
|
+
rpc :ConsumeItem, ::Ledmon::ItemInfo, ::Ledmon::ActionResponse
|
45
|
+
rpc :GetRemainingConsumableDuration, ::Ledmon::Empty, ::Ledmon::ConsumableDuration
|
46
|
+
rpc :Look, ::Ledmon::Empty, ::Ledmon::LookResponse
|
47
|
+
end
|
48
|
+
|
49
|
+
Stub = Service.rpc_stub_class
|
50
|
+
end
|
51
|
+
end
|
data/lib/ledmon.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'http'
|
5
|
+
require 'active_support/concern'
|
6
|
+
require 'active_support/delegation'
|
7
|
+
require 'tty-config'
|
8
|
+
require 'tty-reader'
|
9
|
+
require 'tty-prompt'
|
10
|
+
require 'logger'
|
11
|
+
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'pry'
|
15
|
+
rescue LoadError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
this_dir = File.expand_path(File.dirname(__FILE__))
|
20
|
+
|
21
|
+
require 'zeitwerk'
|
22
|
+
loader = Zeitwerk::Loader.for_gem
|
23
|
+
loader.ignore(File.join(this_dir, '**/*_pb.rb'))
|
24
|
+
loader.inflector.inflect(
|
25
|
+
'cli' => 'CLI',
|
26
|
+
)
|
27
|
+
loader.setup
|
28
|
+
loader.eager_load_namespace(Ledmon::CLI)
|
29
|
+
|
30
|
+
lib_dir = File.join(this_dir, 'ledmon')
|
31
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
32
|
+
|
33
|
+
require 'ledmon/world_pb'
|
34
|
+
require 'ledmon/world_services_pb'
|
35
|
+
|
36
|
+
module Ledmon
|
37
|
+
Config = Configurator.new
|
38
|
+
|
39
|
+
class Error < StandardError; end
|
40
|
+
|
41
|
+
# Your code goes here...
|
42
|
+
end
|
data/monster.rb.sample
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
def name
|
2
|
+
'ahmed'
|
3
|
+
end
|
4
|
+
|
5
|
+
def avatar
|
6
|
+
"@@@@@
|
7
|
+
@)))@
|
8
|
+
@)?)@
|
9
|
+
@)?)@
|
10
|
+
@)<)@"
|
11
|
+
end
|
12
|
+
|
13
|
+
def brain
|
14
|
+
loop do
|
15
|
+
key = keypress
|
16
|
+
|
17
|
+
case key
|
18
|
+
when 'w' then move_forward
|
19
|
+
when 's' then move_backward
|
20
|
+
when 'a' then turn_left
|
21
|
+
when 'd' then turn_right
|
22
|
+
when 'e' then say interact.inspect
|
23
|
+
when 'c' then
|
24
|
+
recipe = ask("recipe: ")
|
25
|
+
craft(recipe)
|
26
|
+
when 'r' then
|
27
|
+
item = ask("research item: ")
|
28
|
+
research(item)
|
29
|
+
when 'o' then
|
30
|
+
item = ask("item: ")
|
31
|
+
slot = ask("slot: ")
|
32
|
+
equip_item(item, slot)
|
33
|
+
when 'p' then
|
34
|
+
slot = ask("slot: ")
|
35
|
+
unequip_item(slot)
|
36
|
+
when 'i' then
|
37
|
+
inventory = get_inventory
|
38
|
+
say "items: #{inventory.items.inspect}"
|
39
|
+
say "equipment: #{inventory.equipment.inspect}"
|
40
|
+
when 'u' then
|
41
|
+
say "health: #{get_health}"
|
42
|
+
when 'j' then
|
43
|
+
attack
|
44
|
+
when 'l' then
|
45
|
+
entities = look
|
46
|
+
say "look: #{entities.inspect}"
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def algo(id, input)
|
54
|
+
case id
|
55
|
+
when 'abc'
|
56
|
+
input.chars.map(&:to_i).sum
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
data/protos/world.proto
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
syntax = "proto3";
|
2
|
+
|
3
|
+
package ledmon;
|
4
|
+
|
5
|
+
service World {
|
6
|
+
rpc Ping (Empty) returns (PingReply) {}
|
7
|
+
rpc PingStream (Empty) returns (stream PingReply) {}
|
8
|
+
|
9
|
+
rpc Session (SessionRequest) returns (stream SessionReply) {}
|
10
|
+
rpc KeepAlive (Empty) returns (Empty) {}
|
11
|
+
|
12
|
+
rpc GetState (Empty) returns (StateReply) {}
|
13
|
+
rpc GetStateStream (Empty) returns (stream StateReply) {}
|
14
|
+
|
15
|
+
rpc MoveForward (Empty) returns (MovementResponse) {}
|
16
|
+
rpc MoveBackward (Empty) returns (MovementResponse) {}
|
17
|
+
rpc TurnLeft (Empty) returns (MovementResponse) {}
|
18
|
+
rpc TurnRight (Empty) returns (MovementResponse) {}
|
19
|
+
rpc TurnAround (Empty) returns (MovementResponse) {}
|
20
|
+
|
21
|
+
rpc Interact (stream InteractionRequest) returns (stream InteractionResponse) {}
|
22
|
+
rpc Craft (CraftData) returns (ActionResponse) {}
|
23
|
+
rpc StorageTransfer (StorageTransferData) returns (ActionResponse) {}
|
24
|
+
rpc StorageList (Empty) returns (StorageData) {}
|
25
|
+
rpc Research (ItemInfo) returns (ActionResponse) {}
|
26
|
+
rpc ScrapResearch (ItemInfo) returns (ActionResponse) {}
|
27
|
+
rpc GetResearchProgress (Empty) returns (ResearchProgress) {}
|
28
|
+
rpc ListTradeOffers (Empty) returns (TradeOfferList) {}
|
29
|
+
rpc CreateTradeOffer (TradeOffer) returns (TradeOfferId) {}
|
30
|
+
rpc AcceptTradeOffer (TradeOfferId) returns (ActionResponse) {}
|
31
|
+
rpc CancelTradeOffer (TradeOfferId) returns (Empty) {}
|
32
|
+
|
33
|
+
rpc GetInventory (Empty) returns (InventoryData) {}
|
34
|
+
rpc EquipItem (EquipInfo) returns (ActionResponse) {}
|
35
|
+
rpc UnequipItem (UnequipInfo) returns (ActionResponse) {}
|
36
|
+
|
37
|
+
rpc GetHealth (Empty) returns (HealthCount) {}
|
38
|
+
rpc Attack (Empty) returns (ActionResponse) {}
|
39
|
+
rpc ConsumeItem (ItemInfo) returns (ActionResponse) {}
|
40
|
+
rpc GetRemainingConsumableDuration (Empty) returns (ConsumableDuration) {}
|
41
|
+
|
42
|
+
rpc Look (Empty) returns (LookResponse) {}
|
43
|
+
}
|
44
|
+
|
45
|
+
message Empty {
|
46
|
+
|
47
|
+
}
|
48
|
+
|
49
|
+
message EquipInfo{
|
50
|
+
string item = 1;
|
51
|
+
string slot = 2;
|
52
|
+
}
|
53
|
+
message UnequipInfo{
|
54
|
+
string slot = 1;
|
55
|
+
}
|
56
|
+
|
57
|
+
message PingReply {
|
58
|
+
string message = 1;
|
59
|
+
}
|
60
|
+
|
61
|
+
message SessionRequest {
|
62
|
+
string token = 1;
|
63
|
+
string avatar = 2;
|
64
|
+
}
|
65
|
+
|
66
|
+
message SessionReply {
|
67
|
+
SessionStatus status = 1;
|
68
|
+
string name = 2;
|
69
|
+
}
|
70
|
+
|
71
|
+
enum SessionStatus {
|
72
|
+
STARTED = 0;
|
73
|
+
FINISHED = 1;
|
74
|
+
INVALID_TOKEN = 2;
|
75
|
+
}
|
76
|
+
|
77
|
+
message StateReply {
|
78
|
+
optional string state = 1;
|
79
|
+
optional string diff = 2;
|
80
|
+
}
|
81
|
+
|
82
|
+
enum MovementResult {
|
83
|
+
OK = 0;
|
84
|
+
BLOCKED = 1;
|
85
|
+
}
|
86
|
+
|
87
|
+
message Position {
|
88
|
+
int32 x = 1;
|
89
|
+
int32 y = 2;
|
90
|
+
}
|
91
|
+
|
92
|
+
message MovementResponse {
|
93
|
+
MovementResult result = 1;
|
94
|
+
optional Position position = 2;
|
95
|
+
float sleep_time = 3;
|
96
|
+
}
|
97
|
+
|
98
|
+
enum InteractionRequestAction {
|
99
|
+
START = 0;
|
100
|
+
SUBMIT_CHALLENGE = 1;
|
101
|
+
}
|
102
|
+
|
103
|
+
enum InteractionResponseAction {
|
104
|
+
SUCCESS = 0;
|
105
|
+
FAILURE = 1;
|
106
|
+
CHALLENGE = 2;
|
107
|
+
}
|
108
|
+
|
109
|
+
message InteractionRequest {
|
110
|
+
InteractionRequestAction action = 1;
|
111
|
+
optional string challenge_response = 2;
|
112
|
+
}
|
113
|
+
|
114
|
+
message InteractionResponse {
|
115
|
+
InteractionResponseAction action = 1;
|
116
|
+
|
117
|
+
optional string challenge_id = 2;
|
118
|
+
optional string challenge_input = 3;
|
119
|
+
}
|
120
|
+
|
121
|
+
message InventoryData{
|
122
|
+
repeated string items = 1;
|
123
|
+
map<string, string> equipment = 2;
|
124
|
+
}
|
125
|
+
|
126
|
+
message CraftData{
|
127
|
+
string recipe = 1;
|
128
|
+
}
|
129
|
+
|
130
|
+
message ItemInfo{
|
131
|
+
string item = 1;
|
132
|
+
}
|
133
|
+
|
134
|
+
message HealthCount{
|
135
|
+
float health = 1;
|
136
|
+
}
|
137
|
+
|
138
|
+
enum ActionResult {
|
139
|
+
SUCCESSFUL = 0;
|
140
|
+
FAILED = 1;
|
141
|
+
}
|
142
|
+
|
143
|
+
message ActionResponse {
|
144
|
+
ActionResult result = 1;
|
145
|
+
float sleep_time = 2;
|
146
|
+
}
|
147
|
+
|
148
|
+
message LookResponse{
|
149
|
+
repeated LookEntity entities = 1;
|
150
|
+
float sleep_time = 2;
|
151
|
+
}
|
152
|
+
message LookEntity{
|
153
|
+
string id = 1;
|
154
|
+
Position position = 2;
|
155
|
+
repeated string equipment = 3;
|
156
|
+
}
|
157
|
+
enum TransferMode{
|
158
|
+
TAKE = 0;
|
159
|
+
DEPOSIT = 1;
|
160
|
+
}
|
161
|
+
message StorageTransferData{
|
162
|
+
string item = 1;
|
163
|
+
TransferMode mode = 2;
|
164
|
+
}
|
165
|
+
message StorageData{
|
166
|
+
repeated string items = 1;
|
167
|
+
}
|
168
|
+
message ResearchProgress{
|
169
|
+
map<string, int32> progress = 1;
|
170
|
+
}
|
171
|
+
message TradeOfferList{
|
172
|
+
map<string, TradeOffer> offers = 1;
|
173
|
+
repeated string own = 2;
|
174
|
+
}
|
175
|
+
message TradeOffer{
|
176
|
+
repeated string inputs = 1;
|
177
|
+
repeated string outputs = 2;
|
178
|
+
int32 remaining = 3;
|
179
|
+
}
|
180
|
+
message TradeOfferId{
|
181
|
+
string id = 1;
|
182
|
+
}
|
183
|
+
message ConsumableDuration{
|
184
|
+
float time = 1;
|
185
|
+
}
|
data/sig/ledmon.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ledmon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ahmed Al Hafoudh
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: zeitwerk
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.7'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.7'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: pry
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.15.2
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.15.2
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: grpc
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.74'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.74'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: thor
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.3'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.3'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: logger
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.7'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.7'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: activesupport
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '8.0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '8.0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rack
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 2.2.3
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.2.3
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: http
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '5.3'
|
117
|
+
type: :runtime
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '5.3'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: tty-config
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 0.6.0
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 0.6.0
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: tty-prompt
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 0.23.1
|
145
|
+
type: :runtime
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 0.23.1
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: grpc-tools
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '1.71'
|
159
|
+
type: :development
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '1.71'
|
166
|
+
description: Ledmon is a Ruby gem that provides tools for managing and deploying monsters
|
167
|
+
in the Ledmon ecosystem. It includes features for session management, deployment,
|
168
|
+
and interaction with Ledmon's monster server.
|
169
|
+
email:
|
170
|
+
- alhafoudh@freevision.sk
|
171
|
+
executables:
|
172
|
+
- ledmon
|
173
|
+
extensions: []
|
174
|
+
extra_rdoc_files: []
|
175
|
+
files:
|
176
|
+
- ".rspec"
|
177
|
+
- ".ruby-version"
|
178
|
+
- LICENSE.txt
|
179
|
+
- Makefile
|
180
|
+
- README.md
|
181
|
+
- Rakefile
|
182
|
+
- exe/ledmon
|
183
|
+
- lib/ledmon.rb
|
184
|
+
- lib/ledmon/cli.rb
|
185
|
+
- lib/ledmon/cli/base.rb
|
186
|
+
- lib/ledmon/cli/config.rb
|
187
|
+
- lib/ledmon/cli/main.rb
|
188
|
+
- lib/ledmon/cli/monster.rb
|
189
|
+
- lib/ledmon/configurator.rb
|
190
|
+
- lib/ledmon/interceptors/sleep_interceptor.rb
|
191
|
+
- lib/ledmon/monster/deployer.rb
|
192
|
+
- lib/ledmon/monster/executor.rb
|
193
|
+
- lib/ledmon/monster/interaction.rb
|
194
|
+
- lib/ledmon/monster/movement.rb
|
195
|
+
- lib/ledmon/monster/packer.rb
|
196
|
+
- lib/ledmon/monster/runner.rb
|
197
|
+
- lib/ledmon/monster/session.rb
|
198
|
+
- lib/ledmon/monster/terminal.rb
|
199
|
+
- lib/ledmon/version.rb
|
200
|
+
- lib/ledmon/world_pb.rb
|
201
|
+
- lib/ledmon/world_services_pb.rb
|
202
|
+
- monster.rb.sample
|
203
|
+
- protos/world.proto
|
204
|
+
- sig/ledmon.rbs
|
205
|
+
homepage: https://www.lstme.sk
|
206
|
+
licenses:
|
207
|
+
- MIT
|
208
|
+
metadata:
|
209
|
+
allowed_push_host: https://rubygems.org
|
210
|
+
homepage_uri: https://www.lstme.sk
|
211
|
+
source_code_uri: https://gitlab.com/lstme/ledmon
|
212
|
+
changelog_uri: https://gitlab.com/lstme/ledmon
|
213
|
+
rdoc_options: []
|
214
|
+
require_paths:
|
215
|
+
- lib
|
216
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - ">="
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: 3.1.0
|
221
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
requirements: []
|
227
|
+
rubygems_version: 3.6.9
|
228
|
+
specification_version: 4
|
229
|
+
summary: ledmon - A Ruby gem for managing and deploying Ledmon monsters.
|
230
|
+
test_files: []
|