inform-runtime 1.0.4
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/LICENSE +623 -0
- data/README.md +185 -0
- data/Rakefile +65 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +76 -0
- data/game/example.rb +90 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/game_grammar.inf.rb +11 -0
- data/game/languages/english.rb +2 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +15 -0
- data/game/verbs/metaverbs.rb +2028 -0
- data/lib/runtime/articles.rb +138 -0
- data/lib/runtime/builtins.rb +359 -0
- data/lib/runtime/color.rb +145 -0
- data/lib/runtime/command.rb +470 -0
- data/lib/runtime/config.rb +48 -0
- data/lib/runtime/context.rb +78 -0
- data/lib/runtime/daemon.rb +266 -0
- data/lib/runtime/database.rb +500 -0
- data/lib/runtime/events.rb +771 -0
- data/lib/runtime/experimental/handler_dsl.rb +175 -0
- data/lib/runtime/game.rb +74 -0
- data/lib/runtime/game_loader.rb +132 -0
- data/lib/runtime/grammar_parser.rb +553 -0
- data/lib/runtime/helpers.rb +177 -0
- data/lib/runtime/history.rb +45 -0
- data/lib/runtime/inflector.rb +195 -0
- data/lib/runtime/io.rb +174 -0
- data/lib/runtime/kernel.rb +450 -0
- data/lib/runtime/library.rb +59 -0
- data/lib/runtime/library_loader.rb +135 -0
- data/lib/runtime/link.rb +158 -0
- data/lib/runtime/logging.rb +197 -0
- data/lib/runtime/mixins.rb +570 -0
- data/lib/runtime/module.rb +202 -0
- data/lib/runtime/object.rb +761 -0
- data/lib/runtime/options.rb +104 -0
- data/lib/runtime/persistence.rb +292 -0
- data/lib/runtime/plurals.rb +60 -0
- data/lib/runtime/prototype.rb +307 -0
- data/lib/runtime/publication.rb +92 -0
- data/lib/runtime/runtime.rb +321 -0
- data/lib/runtime/session.rb +202 -0
- data/lib/runtime/stdlib.rb +604 -0
- data/lib/runtime/subscription.rb +47 -0
- data/lib/runtime/tag.rb +287 -0
- data/lib/runtime/tree.rb +204 -0
- data/lib/runtime/version.rb +24 -0
- data/lib/runtime/world_tree.rb +69 -0
- data/lib/runtime.rb +35 -0
- metadata +199 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
# The Inform module
|
|
22
|
+
module Inform
|
|
23
|
+
# TODO: Testme
|
|
24
|
+
# class Object
|
|
25
|
+
# def after_initialize
|
|
26
|
+
# super
|
|
27
|
+
# return if new?
|
|
28
|
+
# Inform::Behavior.attach(self)
|
|
29
|
+
# apply_instance_behavior!
|
|
30
|
+
# end
|
|
31
|
+
|
|
32
|
+
# def after_refresh
|
|
33
|
+
# super
|
|
34
|
+
# apply_instance_behavior!
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
|
|
38
|
+
# The Inform::Behavior module
|
|
39
|
+
module Behavior
|
|
40
|
+
Registry = Class.new(Hash)
|
|
41
|
+
REGISTRY = Registry.new
|
|
42
|
+
|
|
43
|
+
def self.key_for(obj)
|
|
44
|
+
return obj.identity if obj.respond_to?(:identity) &&
|
|
45
|
+
!(obj.identity.nil? || obj.identity.empty?)
|
|
46
|
+
"<##{obj.class}:#{obj.name}>"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.register(obj, mod)
|
|
50
|
+
Inform::Behavior::REGISTRY[key_for(obj)] = mod
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.attach(obj)
|
|
54
|
+
if (mod = Inform::Behavior::REGISTRY[key_for(obj)])
|
|
55
|
+
obj.extend(mod)
|
|
56
|
+
end
|
|
57
|
+
obj
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The BehaviorError class
|
|
62
|
+
class BehaviorError < TypeError
|
|
63
|
+
MESSAGE = 'Behavior registry for %<key>s is not a Module (got %<clazz>s)'.freeze
|
|
64
|
+
def initialize(key, behavior)
|
|
65
|
+
super(format(MESSAGE, key: key, clazz: behavior.class))
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# rubocop: disable Metrics/AbcSize
|
|
70
|
+
# rubocop: disable Metrics/MethodLength
|
|
71
|
+
def with(context = nil, key = Inform::Behavior.key_for(self), &block)
|
|
72
|
+
log.debug "with.self=#{self.inspect} (#{self.identity})"
|
|
73
|
+
behavior = Inform::Behavior::REGISTRY[key] ||= ::Module.new
|
|
74
|
+
ensure_behavior_module!(key, behavior)
|
|
75
|
+
|
|
76
|
+
unless block.nil?
|
|
77
|
+
proxy = context_proxy(context || self, block.binding)
|
|
78
|
+
b = builder(self, behavior, proxy)
|
|
79
|
+
klass = b.class
|
|
80
|
+
before = klass.instance_methods(false)
|
|
81
|
+
|
|
82
|
+
b.instance_exec(proxy, &block)
|
|
83
|
+
|
|
84
|
+
added = klass.instance_methods(false) - before
|
|
85
|
+
added.each do |m|
|
|
86
|
+
behavior.define_method(m, klass.instance_method(m))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
extend_and_persist(behavior)
|
|
91
|
+
end
|
|
92
|
+
# rubocop: enable Metrics/AbcSize
|
|
93
|
+
# rubocop: enable Metrics/MethodLength
|
|
94
|
+
|
|
95
|
+
# rubocop: disable Metrics/MethodLength
|
|
96
|
+
def context_proxy(target, context, proxy = Object.new)
|
|
97
|
+
return target if context.nil?
|
|
98
|
+
|
|
99
|
+
proxy.define_singleton_method(:method_missing) do |method_name, *args, &block|
|
|
100
|
+
if context.local_variable_defined?(method_name)
|
|
101
|
+
context.local_variable_get(method_name)
|
|
102
|
+
elsif target.respond_to?(method_name)
|
|
103
|
+
target.public_send(method_name, *args, &block)
|
|
104
|
+
else
|
|
105
|
+
super(method_name, *args, &block)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
proxy.define_singleton_method(:respond_to_missing?) do |method_name, include_private = false|
|
|
110
|
+
context.local_variable_defined?(method_name) || target.respond_to?(method_name, include_private)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
proxy
|
|
114
|
+
end
|
|
115
|
+
# rubocop: enable Metrics/MethodLength
|
|
116
|
+
|
|
117
|
+
def extend_and_persist(behavior)
|
|
118
|
+
self.extend(behavior)
|
|
119
|
+
self.save_changes if self.respond_to?(:save_changes)
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def ensure_behavior_module!(key, behavior)
|
|
124
|
+
raise BehaviorError.new(key, behavior) unless behavior.is_a?(::Module)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# The BuilderInstanceMethods module
|
|
128
|
+
module BuilderInstanceMethods
|
|
129
|
+
# Unknown method with a block => define it on behavior.
|
|
130
|
+
# def method_missing(method_name, *args, &block)
|
|
131
|
+
# return @target.public_send(method_name, *args) if block.nil?
|
|
132
|
+
# local_context = @context
|
|
133
|
+
# @behavior.send(:define_method, method_name) do |*call_args, &_block|
|
|
134
|
+
# instance_exec(local_context, *call_args, &block)
|
|
135
|
+
# end
|
|
136
|
+
# method_name
|
|
137
|
+
# rescue NoMethodError
|
|
138
|
+
# super
|
|
139
|
+
# end
|
|
140
|
+
|
|
141
|
+
def method_missing(method_name, *args, &block)
|
|
142
|
+
# 1) Try the real receiver first (even when a block is present)
|
|
143
|
+
log.debug "@target: #{@target.inspect}"
|
|
144
|
+
return @target.public_send(method_name, *args) if block.nil?
|
|
145
|
+
return @target.public_send(method_name, *args, &block) if @target.respond_to?(method_name)
|
|
146
|
+
|
|
147
|
+
# 2) Unknown method + block => define DSL method on behavior
|
|
148
|
+
return super if block.nil?
|
|
149
|
+
|
|
150
|
+
local_context = @context
|
|
151
|
+
@behavior.send(:define_method, method_name) do |*call_args, &_block|
|
|
152
|
+
instance_exec(local_context, *call_args, &block)
|
|
153
|
+
end
|
|
154
|
+
method_name
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
158
|
+
true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def define_singleton_method(*args, &blk)
|
|
162
|
+
@target.define_singleton_method(*args, &blk)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def builder(target, behavior, context)
|
|
167
|
+
Class.new do
|
|
168
|
+
def initialize(target, behavior, context)
|
|
169
|
+
@target, @behavior, @context = target, behavior, context
|
|
170
|
+
self.extend(BuilderInstanceMethods)
|
|
171
|
+
end
|
|
172
|
+
end.new(target, behavior, context)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
# module Inform
|
data/lib/runtime/game.rb
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
require 'yaml'
|
|
22
|
+
|
|
23
|
+
require_relative 'game_loader'
|
|
24
|
+
|
|
25
|
+
# The Inform module
|
|
26
|
+
module Inform
|
|
27
|
+
# The Game module
|
|
28
|
+
module Game
|
|
29
|
+
@config = {}
|
|
30
|
+
|
|
31
|
+
def init(options)
|
|
32
|
+
Inform::Game.config[:game_path] = game_dir_path(options[:game_path])
|
|
33
|
+
Inform::Game.config[:game_config_file_path] =
|
|
34
|
+
game_config_file_path(Inform::Game.config[:game_path])
|
|
35
|
+
Inform::Game.config.update(
|
|
36
|
+
YAML.load_file(Inform::Game.config[:game_config_file_path], symbolize_names: true)
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
module_function :init
|
|
40
|
+
|
|
41
|
+
def config
|
|
42
|
+
@config
|
|
43
|
+
end
|
|
44
|
+
module_function :config
|
|
45
|
+
|
|
46
|
+
def environment
|
|
47
|
+
(ENV['GAME_ENVIRONMENT'] || config[:environment] || Inform::Runtime.default_environment).to_sym
|
|
48
|
+
end
|
|
49
|
+
module_function :environment
|
|
50
|
+
|
|
51
|
+
def environment=(env)
|
|
52
|
+
config[:environment] = env.to_sym
|
|
53
|
+
end
|
|
54
|
+
module_function :environment=
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def game_config_file_path(path)
|
|
59
|
+
File.join(path, 'config.yml')
|
|
60
|
+
end
|
|
61
|
+
module_function :game_config_file_path
|
|
62
|
+
|
|
63
|
+
def game_dir_path(path)
|
|
64
|
+
return path if File.directory?(path)
|
|
65
|
+
Inform::Game.config[:game_file_path] = path
|
|
66
|
+
game_dir_path = File.expand_path(File.dirname(path))
|
|
67
|
+
return game_dir_path if File.exist?(game_config_file_path(game_dir_path))
|
|
68
|
+
Inform::Runtime.default_game_dir_path
|
|
69
|
+
end
|
|
70
|
+
module_function :game_dir_path
|
|
71
|
+
end
|
|
72
|
+
# module Game
|
|
73
|
+
end
|
|
74
|
+
# module Inform
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
# The Inform module
|
|
22
|
+
module Inform
|
|
23
|
+
# The Game module
|
|
24
|
+
module Game
|
|
25
|
+
class GameLoadError < StandardError; end
|
|
26
|
+
|
|
27
|
+
# The Game::Loader module loads a game from a given directory.
|
|
28
|
+
module Loader
|
|
29
|
+
def load_game(game_dir_path = Inform::Game.config[:game_path])
|
|
30
|
+
self.load_path = game_dir_path
|
|
31
|
+
require_relative 'world_tree'
|
|
32
|
+
load_game_subcomponents
|
|
33
|
+
load_game_files
|
|
34
|
+
load_grammars
|
|
35
|
+
reset_constants
|
|
36
|
+
load_library
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
handle_load_failure(e)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def first_orphan_object_with_description_that_has_light
|
|
42
|
+
Inform::Object.all.find { |o| !o.description.nil? && o.has?(:light) }&.name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def load_path=(path)
|
|
46
|
+
@game_dir_path = path
|
|
47
|
+
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load_game_subcomponents
|
|
51
|
+
game_components.each { |component| load_game_sub(component) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def reset_constants
|
|
55
|
+
reset_constant(:HDR_GAMESERIAL, defined?(Serial) ? Serial : File.mtime(@game_dir_path).strftime('%y%m%d'))
|
|
56
|
+
reset_constant(:HDR_GAMERELEASE, defined?(Release) ? Release : 0)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def load_game_sub(component, start = Time.now)
|
|
60
|
+
game_component_path = File.join(@game_dir_path, component.to_s)
|
|
61
|
+
Dir.glob(File.join(game_component_path, '*')).sort.each do |file|
|
|
62
|
+
next unless File.file?(file)
|
|
63
|
+
log.debug "Loading file: #{file}"
|
|
64
|
+
require file
|
|
65
|
+
end
|
|
66
|
+
ensure
|
|
67
|
+
elapsed = (Time.now - start) * 1000
|
|
68
|
+
log.debug format("Loaded #{component} in %0.2f milliseconds", elapsed)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def load_game_files(game_path = @game_dir_path)
|
|
72
|
+
log.info "Loading game: #{game_path}"
|
|
73
|
+
Dir.glob(File.join(game_path, '*.rb')).sort.each do |file|
|
|
74
|
+
next unless File.file?(file)
|
|
75
|
+
log.debug "Loading file: #{file}"
|
|
76
|
+
require file
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def handle_load_failure(e)
|
|
81
|
+
case e.message
|
|
82
|
+
when /PG::UndefinedTable/
|
|
83
|
+
log.error "Fatal: Database initialization is required"
|
|
84
|
+
abort
|
|
85
|
+
else
|
|
86
|
+
log.error "Error loading game: #{e.class.name}: #{e.message}"
|
|
87
|
+
e.backtrace.each { |t| log.error t }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Move to the front of the list any element matching the given condition
|
|
92
|
+
def prioritize_if(list, &condition)
|
|
93
|
+
element = list.find(&condition)
|
|
94
|
+
return list if element.nil?
|
|
95
|
+
list.unshift(list.delete(element))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
EnsureFirst = [/emotes.inf/, /grammar.inf/].freeze
|
|
99
|
+
|
|
100
|
+
def grammar_files
|
|
101
|
+
files = Dir.glob(File.join(grammar_module_path, '*'))
|
|
102
|
+
EnsureFirst.each do |pattern|
|
|
103
|
+
prioritize_if(files) { |file_path| file_path.match?(pattern) }
|
|
104
|
+
end
|
|
105
|
+
files
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
GrammarParsedInfoMessage = 'Parsed grammar %<file>s in %0.2<elapsed>f milliseconds'.freeze
|
|
109
|
+
|
|
110
|
+
def load_grammars
|
|
111
|
+
grammar_files.each do |file_path|
|
|
112
|
+
start = Time.now
|
|
113
|
+
Inform.load_grammar_by_path(file_path)
|
|
114
|
+
elapsed = (Time.now - start) * 1000
|
|
115
|
+
log.debug format(GrammarParsedInfoMessage, file: File.basename(file_path), elapsed: elapsed)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def load_game_states
|
|
120
|
+
states = Inform::Game.config.fetch(:additional_promiscuous_states, []).map(&:to_sym)
|
|
121
|
+
Session.add_promiscuous_states(states)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def reload_game
|
|
125
|
+
load_game
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
# module Loader
|
|
129
|
+
end
|
|
130
|
+
# module Game
|
|
131
|
+
end
|
|
132
|
+
# module Inform
|