carbonmu 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|