carbonmu 0.0.1
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/.gitignore +3 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +161 -0
- data/Guardfile +50 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +10 -0
- data/carbonmu.gemspec +64 -0
- data/config/i18n-tasks.yml +89 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/nl.yml +6 -0
- data/console +9 -0
- data/doc/architecture.png +0 -0
- data/lib/carbonmu.rb +42 -0
- data/lib/commands/locale_command.rb +12 -0
- data/lib/commands/ping_command.rb +9 -0
- data/lib/commands/reboot_command.rb +9 -0
- data/lib/commands/say_command.rb +10 -0
- data/lib/commands/unknown_command.rb +7 -0
- data/lib/core/command.rb +26 -0
- data/lib/core/command_context.rb +12 -0
- data/lib/core/configuration.rb +22 -0
- data/lib/core/connection.rb +19 -0
- data/lib/core/internationalization.rb +16 -0
- data/lib/core/parser.rb +29 -0
- data/lib/core/server.rb +118 -0
- data/lib/core/server_supervision_group.rb +5 -0
- data/lib/core_ext/match_data.rb +7 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/edge_router/edge_connection.rb +39 -0
- data/lib/edge_router/edge_router.rb +104 -0
- data/lib/edge_router/edge_router_supervision_group.rb +5 -0
- data/lib/edge_router/telnet_connection.rb +35 -0
- data/lib/edge_router/telnet_receptor.rb +30 -0
- data/lib/errors.rb +4 -0
- data/lib/game_objects/container.rb +10 -0
- data/lib/game_objects/exit.rb +8 -0
- data/lib/game_objects/game_object.rb +13 -0
- data/lib/game_objects/movable.rb +12 -0
- data/lib/game_objects/player.rb +17 -0
- data/lib/game_objects/room.rb +16 -0
- data/lib/game_objects/thing.rb +7 -0
- data/lib/interactions/notify.rb +11 -0
- data/lib/interactions/reboot.rb +8 -0
- data/lib/ipc/carbon_ipc_socket.rb +21 -0
- data/lib/ipc/ipc_message.rb +54 -0
- data/lib/ipc/read_socket.rb +18 -0
- data/lib/ipc/write_socket.rb +12 -0
- data/lib/version.rb +7 -0
- data/mongoid.yml +12 -0
- data/spec/carbonmu_spec.rb +14 -0
- data/spec/i18n_spec.rb +18 -0
- data/spec/lib/commands/say_command_spec.rb +11 -0
- data/spec/lib/core/command_context_spec.rb +16 -0
- data/spec/lib/core/command_spec.rb +37 -0
- data/spec/lib/core/configuration_spec.rb +37 -0
- data/spec/lib/core/connection_spec.rb +46 -0
- data/spec/lib/core/internationalization_spec.rb +24 -0
- data/spec/lib/core/parser_spec.rb +30 -0
- data/spec/lib/core/server_spec.rb +35 -0
- data/spec/lib/edge_router/edge_connection_spec.rb +9 -0
- data/spec/lib/game_objects/container_spec.rb +9 -0
- data/spec/lib/game_objects/exit_spec.rb +7 -0
- data/spec/lib/game_objects/game_object_spec.rb +12 -0
- data/spec/lib/game_objects/movable_spec.rb +9 -0
- data/spec/lib/game_objects/player_spec.rb +24 -0
- data/spec/lib/game_objects/room_spec.rb +20 -0
- data/spec/lib/game_objects/thing_spec.rb +7 -0
- data/spec/lib/interactions/notify_spec.rb +26 -0
- data/spec/lib/interactions/reboot_spec.rb +9 -0
- data/spec/lib/ipc/carbon_ipc_socket_spec.rb +12 -0
- data/spec/lib/ipc/ipc_message_spec.rb +39 -0
- data/spec/lib/ipc/read_socket_spec.bak +51 -0
- data/spec/lib/ipc/write_socket_spec.bak +25 -0
- data/spec/spec_helper.rb +118 -0
- data/spec/support/helpers.rb +15 -0
- data/spec/support/matchers/be_boolean.rb +5 -0
- data/spec/support/shared_examples/carbon_ipc_socket.rb +3 -0
- data/spec/support/shared_examples/container.rb +3 -0
- data/spec/support/shared_examples/game_object.rb +6 -0
- data/spec/support/shared_examples/movable.rb +4 -0
- data/start +5 -0
- data/start-server-only +8 -0
- metadata +416 -0
data/console
ADDED
Binary file
|
data/lib/carbonmu.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'require_all'
|
5
|
+
require 'colorize' # An exception to our require-where-used rule, since it's likely to be used many places.
|
6
|
+
require_all 'lib'
|
7
|
+
|
8
|
+
Celluloid::ZMQ.init
|
9
|
+
|
10
|
+
module CarbonMU
|
11
|
+
class << self
|
12
|
+
attr_accessor :configuration
|
13
|
+
attr_accessor :edge_router_receive_port
|
14
|
+
attr_accessor :server
|
15
|
+
end
|
16
|
+
self.configuration = Configuration.new
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
yield self.configuration
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.start
|
23
|
+
EdgeRouterSupervisionGroup.run
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.start_in_background
|
27
|
+
EdgeRouterSupervisionGroup.run!
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.start_server
|
31
|
+
ServerSupervisionGroup.run
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.start_server_in_background
|
35
|
+
ServerSupervisionGroup.run!
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.t(str, *opts)
|
39
|
+
CarbonMU::Internationalization.translate(str, opts)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# TODO TEMP
|
2
|
+
module CarbonMU
|
3
|
+
class LocaleCommand < Command
|
4
|
+
syntax /^locale (?<locale>.*)/
|
5
|
+
syntax /^do (?<text>.*)/
|
6
|
+
|
7
|
+
def execute
|
8
|
+
@context.enacting_connection.locale = @params[:locale]
|
9
|
+
Notify.one(@context.enacting_connection, "Locale changed to #{@params[:locale]}.")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/core/command.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module CarbonMU
|
2
|
+
class Command
|
3
|
+
class << self
|
4
|
+
def syntax(value)
|
5
|
+
@syntaxes ||= []
|
6
|
+
@syntaxes << value
|
7
|
+
Parser.register_syntax(value, self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def syntaxes
|
11
|
+
@syntaxes
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :context
|
16
|
+
|
17
|
+
def initialize(context)
|
18
|
+
@context = context
|
19
|
+
@params = context.params
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module CarbonMU
|
2
|
+
class CommandContext
|
3
|
+
attr_reader :enacting_connection, :enactor, :raw_command, :params
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
@enacting_connection = opts[:enacting_connection] || nil
|
7
|
+
@enactor = @enacting_connection.player || nil
|
8
|
+
@raw_command = opts[:raw_command] || ""
|
9
|
+
@params = opts[:params] || {}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module CarbonMU
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :game_name
|
7
|
+
attr_reader :logger
|
8
|
+
attr_accessor :log_ipc_traffic
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@game_name = 'CarbonMU'
|
12
|
+
@logger = Celluloid.logger.tap { |i| i.progname = @game_name }
|
13
|
+
@log_ipc_traffic = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger=(logger)
|
17
|
+
raise ArgumentError.new("Not a logger: #{logger}") unless logger.is_a?(Logger)
|
18
|
+
Celluloid.logger = logger
|
19
|
+
@logger = logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CarbonMU
|
2
|
+
class Connection
|
3
|
+
attr_reader :id
|
4
|
+
attr_accessor :locale
|
5
|
+
attr_accessor :player
|
6
|
+
|
7
|
+
# TODO probably need edge type (:telnet, :ssl, etc), or at least interactive true/false
|
8
|
+
def initialize(id)
|
9
|
+
@id = id
|
10
|
+
@locale = "en" # TODO configurable default locale
|
11
|
+
@player = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_translated(msg, args = {})
|
15
|
+
translation_args = args.merge(locale: @locale)
|
16
|
+
CarbonMU.server.write_to_connection(id, I18n.t(msg, translation_args) + "\n")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "i18n/backend/fallbacks"
|
3
|
+
|
4
|
+
module CarbonMU
|
5
|
+
module Internationalization
|
6
|
+
|
7
|
+
def self.setup
|
8
|
+
I18n.enforce_available_locales = false
|
9
|
+
I18n.load_path = Dir["config/locales/**/*.yml"] # TODO support for plugins too.
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.translate(str, *opts)
|
13
|
+
I18n.t(str, opts)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/core/parser.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module CarbonMU
|
2
|
+
module Parser
|
3
|
+
def self.parse_and_execute(enacting_connection, input)
|
4
|
+
command_class, params = parse(input)
|
5
|
+
|
6
|
+
context = CommandContext.new(enacting_connection: enacting_connection, raw_command: input, params: params)
|
7
|
+
command_class.new(context).execute
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse(input)
|
11
|
+
syntax_match_data = {}
|
12
|
+
syntax_and_class = @@syntaxes.detect { |syntax, klass| syntax_match_data = syntax.match(input) }
|
13
|
+
|
14
|
+
command_class = syntax_and_class.nil? ? UnknownCommand : syntax_and_class[1]
|
15
|
+
syntax_match_data ||= {} # if no syntaxes matched
|
16
|
+
|
17
|
+
[command_class, syntax_match_data.to_hash]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.register_syntax(syntax, klass)
|
21
|
+
@@syntaxes ||= {}
|
22
|
+
@@syntaxes[syntax] = klass
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.syntaxes
|
26
|
+
@@syntaxes
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/core/server.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'celluloid/io'
|
2
|
+
require 'celluloid/zmq'
|
3
|
+
require 'mongoid'
|
4
|
+
|
5
|
+
module CarbonMU
|
6
|
+
class Server
|
7
|
+
include Celluloid::IO
|
8
|
+
include Celluloid::Logger
|
9
|
+
include Celluloid::ZMQ
|
10
|
+
|
11
|
+
finalizer :shutdown
|
12
|
+
|
13
|
+
attr_reader :connections
|
14
|
+
|
15
|
+
def initialize(standalone = false)
|
16
|
+
Internationalization.setup
|
17
|
+
|
18
|
+
info "*** Starting CarbonMU game server to connect to edge router port #{CarbonMU.edge_router_receive_port}."
|
19
|
+
CarbonMU.server = Actor.current
|
20
|
+
|
21
|
+
Server.initialize_database
|
22
|
+
Server.create_starter_objects
|
23
|
+
|
24
|
+
unless standalone
|
25
|
+
@ipc_reader = ReadSocket.new
|
26
|
+
@ipc_writer = WriteSocket.new(CarbonMU.edge_router_receive_port)
|
27
|
+
send_server_started_to_edge_router
|
28
|
+
|
29
|
+
async.run
|
30
|
+
|
31
|
+
retrieve_existing_connections
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
loop do
|
37
|
+
async.handle_edge_router_message(@ipc_reader.read)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def shutdown
|
42
|
+
error "Terminating server!"
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_connection(connection_id)
|
46
|
+
@connections ||= []
|
47
|
+
c = Connection.new(connection_id)
|
48
|
+
c.player = Player.superadmin #TODO real login
|
49
|
+
@connections << c
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove_connection(connection_id)
|
53
|
+
@connections.delete_if { |c| c.id == connection_id }
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_command(input, connection_id)
|
57
|
+
connection = @connections.find { |c| c.id == connection_id }
|
58
|
+
Parser.parse_and_execute(connection, input)
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_to_connection(connection_id, str)
|
62
|
+
send_message_to_edge_router(:write, connection_id: connection_id, output: str)
|
63
|
+
end
|
64
|
+
|
65
|
+
def send_server_started_to_edge_router
|
66
|
+
send_message_to_edge_router(:started, port: @ipc_reader.port_number, pid: Process.pid)
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_edge_router_message(input)
|
70
|
+
message = IPCMessage.unserialize(input)
|
71
|
+
debug "SERVER RECEIVE: #{message}" if CarbonMU.configuration.log_ipc_traffic
|
72
|
+
case message.op
|
73
|
+
when :command
|
74
|
+
handle_command(message.command, message.connection_id)
|
75
|
+
when :connect
|
76
|
+
add_connection(message.connection_id)
|
77
|
+
when :disconnect
|
78
|
+
remove_connection(message.connection_id)
|
79
|
+
else
|
80
|
+
raise ArgumentError, "Unsupported operation '#{message.op}' received from edge router."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def retrieve_existing_connections
|
85
|
+
send_message_to_edge_router(:retrieve_existing_connections)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.trigger_reboot
|
89
|
+
Actor[:server].send_reboot_message_to_edge_router
|
90
|
+
end
|
91
|
+
|
92
|
+
def send_reboot_message_to_edge_router
|
93
|
+
send_message_to_edge_router(:reboot)
|
94
|
+
end
|
95
|
+
|
96
|
+
def send_message_to_edge_router(op, params={})
|
97
|
+
message = IPCMessage.new(op, params)
|
98
|
+
debug "SERVER SEND: #{message}" if CarbonMU.configuration.log_ipc_traffic
|
99
|
+
@ipc_writer.send message.serialize
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.initialize_database
|
103
|
+
Mongoid.logger.level = ::Logger::DEBUG
|
104
|
+
Mongoid.load!("mongoid.yml", ENV["MONGOID_ENV"] || :production)
|
105
|
+
::Mongoid::Tasks::Database.create_indexes
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.create_starter_objects
|
109
|
+
ensure_special_exists(:starting_room, Room, {name: "Starting Room", description: "This is the starting room for newly-created players. Feel free to rename and re-describe it."})
|
110
|
+
ensure_special_exists(:lostandfound_room, Room, {name: "Lost & Found Room", description: "This is the room where objects and players go if the thing that was holding them gets destroyed."})
|
111
|
+
ensure_special_exists(:superadmin_player, Player, {name: "Superadmin", description: "Obviously the most powerful of his race, it could kill us all."})
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.ensure_special_exists(special, klass, attributes)
|
115
|
+
klass.create!(attributes.merge(_special: special)) if klass.where(_special: special).count == 0
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module CarbonMU
|
2
|
+
class EdgeConnection
|
3
|
+
include Celluloid::IO
|
4
|
+
include Celluloid::Logger
|
5
|
+
|
6
|
+
attr_accessor :name
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
finalizer :shutdown
|
10
|
+
|
11
|
+
def initialize(arg = nil)
|
12
|
+
@id = SecureRandom.uuid
|
13
|
+
after_initialize(arg)
|
14
|
+
end
|
15
|
+
|
16
|
+
def after_initialize(arg)
|
17
|
+
nil # to be implemented by subclasses, if desired
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle_input(input)
|
21
|
+
input.chomp!
|
22
|
+
Actor[:edge_router].async.send_command_to_server(input, id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def shutdown
|
30
|
+
before_shutdown
|
31
|
+
Actor[:edge_router].async.remove_connection(Actor.current)
|
32
|
+
end
|
33
|
+
|
34
|
+
def before_shutdown
|
35
|
+
nil # to be implemented by subclasses, if desired
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'celluloid/autostart'
|
2
|
+
require 'celluloid/io'
|
3
|
+
require 'celluloid/zmq'
|
4
|
+
|
5
|
+
module CarbonMU
|
6
|
+
class EdgeRouter
|
7
|
+
include Celluloid::IO
|
8
|
+
include Celluloid::Logger
|
9
|
+
include Celluloid::ZMQ
|
10
|
+
|
11
|
+
finalizer :shutdown
|
12
|
+
|
13
|
+
attr_reader :receptors, :connections
|
14
|
+
|
15
|
+
def initialize(host, port)
|
16
|
+
info "*** Starting CarbonMU edge router."
|
17
|
+
@receptors = TelnetReceptor.new(host,port)
|
18
|
+
@connections = []
|
19
|
+
|
20
|
+
@ipc_reader = ReadSocket.new
|
21
|
+
info "*** Edge router waiting for IPC on port #{@ipc_reader.port_number}"
|
22
|
+
async.run
|
23
|
+
|
24
|
+
start_server
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_server
|
28
|
+
spawn("/usr/bin/env ruby start-server-only #{@ipc_reader.port_number}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def handle_server_started(pid, port)
|
32
|
+
debug "*** Edge router received server IPC start. Pid #{pid}, port #{port}." if CarbonMU.configuration.log_ipc_traffic
|
33
|
+
@current_server_pid = pid
|
34
|
+
Process.detach(pid)
|
35
|
+
@ipc_writer = WriteSocket.new(port)
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_connection(connection)
|
39
|
+
@connections << connection
|
40
|
+
send_connect_to_server(connection)
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_connection(connection)
|
44
|
+
@connections.delete(connection)
|
45
|
+
send_disconnect_to_server(connection)
|
46
|
+
end
|
47
|
+
|
48
|
+
def shutdown
|
49
|
+
# TODO Tell all receptors and connections to quit.
|
50
|
+
Process.kill("TERM", @current_server_pid)
|
51
|
+
end
|
52
|
+
|
53
|
+
def run
|
54
|
+
loop do
|
55
|
+
async.handle_server_message(@ipc_reader.read)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def send_connect_to_server(connection)
|
60
|
+
send_message_to_server(:connect, connection_id: connection.id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def send_disconnect_to_server(connection)
|
64
|
+
send_message_to_server(:disconnect, connection_id: connection.id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def send_command_to_server(input, connection_id)
|
68
|
+
send_message_to_server(:command, command: input, connection_id: connection_id)
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_message_to_server(op, params={})
|
72
|
+
message = IPCMessage.new(op, params)
|
73
|
+
debug "EDGE ROUTER SEND: #{message}" if CarbonMU.configuration.log_ipc_traffic
|
74
|
+
@ipc_writer.send message.serialize
|
75
|
+
end
|
76
|
+
|
77
|
+
def reboot_server
|
78
|
+
warn "Reboot triggered!"
|
79
|
+
Process.kill("TERM", @current_server_pid)
|
80
|
+
start_server
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_server_message(input)
|
84
|
+
message = IPCMessage.unserialize(input)
|
85
|
+
debug "EDGE ROUTER RECEIVE: #{message}" if CarbonMU.configuration.log_ipc_traffic
|
86
|
+
case message.op
|
87
|
+
when :started
|
88
|
+
handle_server_started(message.pid, message.port)
|
89
|
+
when :write
|
90
|
+
conn = @connections.select {|x| x.id == message.connection_id}.first # TODO look for efficiency here
|
91
|
+
conn.write(message.output)
|
92
|
+
when :reboot
|
93
|
+
reboot_server
|
94
|
+
when :retrieve_existing_connections
|
95
|
+
info 'Sending connections to server...'
|
96
|
+
@connections.each do |conn|
|
97
|
+
send_connect_to_server(conn)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
raise ArgumentError, "Unsupported operation '#{message.op}' received from Server."
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|