gamefic 1.6.0 → 2.0.3
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 +5 -5
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +16 -0
- data/.solargraph.yml +5 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/gamefic.gemspec +27 -0
- data/lib/gamefic.rb +11 -8
- data/lib/gamefic/action.rb +68 -58
- data/lib/gamefic/active.rb +331 -0
- data/lib/gamefic/actor.rb +8 -0
- data/lib/gamefic/command.rb +9 -7
- data/lib/gamefic/core_ext/array.rb +27 -49
- data/lib/gamefic/core_ext/string.rb +25 -16
- data/lib/gamefic/describable.rb +37 -22
- data/lib/gamefic/element.rb +47 -0
- data/lib/gamefic/entity.rb +24 -48
- data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
- data/lib/gamefic/messaging.rb +43 -45
- data/lib/gamefic/node.rb +14 -5
- data/lib/gamefic/plot.rb +73 -85
- data/lib/gamefic/plot/darkroom.rb +80 -0
- data/lib/gamefic/plot/host.rb +42 -46
- data/lib/gamefic/plot/snapshot.rb +14 -214
- data/lib/gamefic/query.rb +15 -17
- data/lib/gamefic/query/base.rb +51 -42
- data/lib/gamefic/query/children.rb +0 -0
- data/lib/gamefic/query/descendants.rb +2 -2
- data/lib/gamefic/query/external.rb +18 -0
- data/lib/gamefic/query/family.rb +3 -7
- data/lib/gamefic/query/matches.rb +75 -67
- data/lib/gamefic/query/parent.rb +0 -0
- data/lib/gamefic/query/siblings.rb +0 -0
- data/lib/gamefic/query/text.rb +12 -12
- data/lib/gamefic/query/tree.rb +17 -0
- data/lib/gamefic/scene.rb +1 -5
- data/lib/gamefic/scene/{active.rb → activity.rb} +4 -6
- data/lib/gamefic/scene/base.rb +77 -13
- data/lib/gamefic/scene/conclusion.rb +0 -2
- data/lib/gamefic/scene/custom.rb +0 -2
- data/lib/gamefic/scene/multiple_choice.rb +18 -16
- data/lib/gamefic/scene/multiple_scene.rb +29 -20
- data/lib/gamefic/scene/pause.rb +7 -2
- data/lib/gamefic/scene/yes_or_no.rb +21 -9
- data/lib/gamefic/scriptable.rb +88 -0
- data/lib/gamefic/serialize.rb +223 -0
- data/lib/gamefic/subplot.rb +47 -51
- data/lib/gamefic/syntax.rb +15 -13
- data/lib/gamefic/version.rb +3 -3
- data/lib/gamefic/world.rb +18 -0
- data/lib/gamefic/world/callbacks.rb +135 -0
- data/lib/gamefic/world/commands.rb +184 -0
- data/lib/gamefic/world/entities.rb +98 -0
- data/lib/gamefic/{plot → world}/playbook.rb +245 -236
- data/lib/gamefic/world/players.rb +37 -0
- data/lib/gamefic/world/scenes.rb +226 -0
- metadata +40 -108
- data/bin/gamefic +0 -9
- data/lib/gamefic/character.rb +0 -232
- data/lib/gamefic/character/state.rb +0 -12
- data/lib/gamefic/engine.rb +0 -7
- data/lib/gamefic/engine/base.rb +0 -66
- data/lib/gamefic/engine/tty.rb +0 -24
- data/lib/gamefic/grammar.rb +0 -13
- data/lib/gamefic/grammar/conjugator.rb +0 -20
- data/lib/gamefic/grammar/gender.rb +0 -11
- data/lib/gamefic/grammar/person.rb +0 -10
- data/lib/gamefic/grammar/plural.rb +0 -13
- data/lib/gamefic/grammar/pronouns.rb +0 -105
- data/lib/gamefic/grammar/tense.rb +0 -6
- data/lib/gamefic/grammar/verb_set.rb +0 -43
- data/lib/gamefic/grammar/verbs.rb +0 -26
- data/lib/gamefic/grammar/word_adapter.rb +0 -49
- data/lib/gamefic/plot/articles.rb +0 -22
- data/lib/gamefic/plot/callbacks.rb +0 -127
- data/lib/gamefic/plot/commands.rb +0 -121
- data/lib/gamefic/plot/entities.rb +0 -88
- data/lib/gamefic/plot/players.rb +0 -15
- data/lib/gamefic/plot/scenes.rb +0 -149
- data/lib/gamefic/plot/theater.rb +0 -73
- data/lib/gamefic/plot/you_mount.rb +0 -22
- data/lib/gamefic/script.rb +0 -13
- data/lib/gamefic/script/base.rb +0 -42
- data/lib/gamefic/script/file.rb +0 -14
- data/lib/gamefic/script/text.rb +0 -14
- data/lib/gamefic/shell.rb +0 -76
- data/lib/gamefic/source.rb +0 -14
- data/lib/gamefic/source/base.rb +0 -12
- data/lib/gamefic/source/file.rb +0 -23
- data/lib/gamefic/source/text.rb +0 -16
- data/lib/gamefic/tester.rb +0 -19
- data/lib/gamefic/text.rb +0 -8
- data/lib/gamefic/text/ansi.rb +0 -53
- data/lib/gamefic/text/html.rb +0 -68
- data/lib/gamefic/text/html/conversions.rb +0 -250
- data/lib/gamefic/text/html/entities.rb +0 -9
- data/lib/gamefic/tty.rb +0 -10
- data/lib/gamefic/user.rb +0 -8
- data/lib/gamefic/user/base.rb +0 -15
- data/lib/gamefic/user/buffer.rb +0 -32
- data/lib/gamefic/user/tty.rb +0 -54
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1ac7da649347939f4c5baadc676151b8a8c73714552c48a290de75bd072bb801
|
|
4
|
+
data.tar.gz: 6d83150ed62df24a4056e3e54ff5b499bc5f7a69ebe8d5cfaddb261f64573af4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e068317caa9fbfb1f8a73bde4725f5df3f3bcad95ba87d44c8f973759cdc991e0fbbcea874fd451eacbd6ffd2ea2ade9144e61f2d358f31fec6a7f1e97ca3a10
|
|
7
|
+
data.tar.gz: 1ab813a5cb14307bc844c42424c9503bae6f73a48d12979a5369812619e5a2e80dfab2ac6181401045511286743fc20f35968bd46e5071cf24f296e3c1c20e36
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metrics/LineLength:
|
|
2
|
+
Description: 'Limit lines to 80 characters.'
|
|
3
|
+
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
|
|
4
|
+
Enabled: false
|
|
5
|
+
|
|
6
|
+
Style/StringLiterals:
|
|
7
|
+
Description: 'Checks if uses of quotes match the configured preference.'
|
|
8
|
+
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
|
|
9
|
+
Enabled: false
|
|
10
|
+
|
|
11
|
+
Style/Documentation:
|
|
12
|
+
Description: 'Document classes and non-namespace modules.'
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
15
|
+
Style/MethodDefParentheses:
|
|
16
|
+
Enabled: false
|
data/.solargraph.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Gamefic
|
|
2
|
+
Copyright (c) 2013 by Fred Snyder for Castwide Technologies
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Gamefic
|
|
2
|
+
|
|
3
|
+
**A Ruby Interactive Fiction Framework**
|
|
4
|
+
|
|
5
|
+
Gamefic is a system for developing and playing adventure games and interactive
|
|
6
|
+
fiction. This gem provides the core library for running game narratives.
|
|
7
|
+
|
|
8
|
+
Developers should refer to the [Gamefic SDK](https://github.com/castwide/gamefic-sdk)
|
|
9
|
+
for information about writing, building, and distributing Gamefic apps.
|
|
10
|
+
|
|
11
|
+
## Development
|
|
12
|
+
|
|
13
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
14
|
+
|
|
15
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
16
|
+
|
|
17
|
+
## Contributing
|
|
18
|
+
|
|
19
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/castwide/gamefic-sdk.
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
24
|
+
|
|
25
|
+
## More Information
|
|
26
|
+
|
|
27
|
+
Go to [the official Gamefic website](http://gamefic.com) for games, news, and
|
|
28
|
+
more documentation.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rspec/core/rake_task'
|
|
3
|
+
|
|
4
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
5
|
+
t.pattern = Dir.glob('spec/**/*_spec.rb')
|
|
6
|
+
t.rspec_opts = '--format documentation'
|
|
7
|
+
# t.rspec_opts << ' more options'
|
|
8
|
+
#t.rcov = true
|
|
9
|
+
end
|
|
10
|
+
task :default => :spec
|
data/gamefic.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'gamefic/version'
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = 'gamefic'
|
|
8
|
+
s.version = Gamefic::VERSION
|
|
9
|
+
s.date = Date.today.strftime("%Y-%m-%d")
|
|
10
|
+
s.summary = "Gamefic"
|
|
11
|
+
s.description = "An adventure game and interactive fiction framework"
|
|
12
|
+
s.authors = ["Fred Snyder"]
|
|
13
|
+
s.email = 'fsnyder@gamefic.com'
|
|
14
|
+
s.homepage = 'http://gamefic.com'
|
|
15
|
+
s.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
|
+
end
|
|
20
|
+
s.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
s.required_ruby_version = '>= 2.1.0'
|
|
23
|
+
|
|
24
|
+
s.add_development_dependency 'rake', '~> 12.3', '>= 12.3'
|
|
25
|
+
s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
|
|
26
|
+
s.add_development_dependency 'simplecov', '~> 0.14'
|
|
27
|
+
end
|
data/lib/gamefic.rb
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
require 'gamefic/
|
|
1
|
+
require 'gamefic/version'
|
|
2
|
+
|
|
3
|
+
require 'gamefic/keywords'
|
|
2
4
|
require 'gamefic/core_ext/array'
|
|
3
5
|
require 'gamefic/core_ext/string'
|
|
4
6
|
|
|
5
|
-
require 'gamefic/
|
|
7
|
+
require 'gamefic/describable'
|
|
8
|
+
require 'gamefic/serialize'
|
|
9
|
+
require 'gamefic/element'
|
|
6
10
|
require 'gamefic/entity'
|
|
7
|
-
require 'gamefic/
|
|
11
|
+
require 'gamefic/active'
|
|
12
|
+
require 'gamefic/actor'
|
|
8
13
|
require "gamefic/scene"
|
|
9
14
|
require "gamefic/query"
|
|
10
15
|
require "gamefic/action"
|
|
11
16
|
require "gamefic/syntax"
|
|
12
|
-
require
|
|
17
|
+
require 'gamefic/world'
|
|
18
|
+
require 'gamefic/scriptable'
|
|
19
|
+
require 'gamefic/plot'
|
|
13
20
|
require 'gamefic/subplot'
|
|
14
|
-
require "gamefic/engine"
|
|
15
|
-
require "gamefic/user"
|
|
16
|
-
|
|
17
|
-
require 'gamefic/version'
|
data/lib/gamefic/action.rb
CHANGED
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
module Gamefic
|
|
2
|
-
# Exception raised when the Action's proc arity is not compatible with the
|
|
3
|
-
# number of queries
|
|
4
|
-
class ActionArgumentError < ArgumentError
|
|
5
|
-
end
|
|
6
|
-
|
|
7
2
|
class Action
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
# An array of objects on which the action will operate, e.g., an entity
|
|
4
|
+
# that is a direct object of a command.
|
|
5
|
+
#
|
|
6
|
+
# @return [Array<Object>]
|
|
7
|
+
attr_reader :arguments
|
|
8
|
+
alias parameters arguments
|
|
9
|
+
|
|
10
|
+
def initialize actor, arguments
|
|
11
11
|
@actor = actor
|
|
12
|
-
@
|
|
12
|
+
@arguments = arguments
|
|
13
13
|
@executed = false
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
parameters
|
|
19
|
-
end
|
|
20
|
-
|
|
16
|
+
# Perform the action.
|
|
17
|
+
#
|
|
21
18
|
def execute
|
|
22
19
|
@executed = true
|
|
23
|
-
self.class.executor.call(@actor,
|
|
20
|
+
self.class.executor.call(@actor, *arguments) unless self.class.executor.nil?
|
|
24
21
|
end
|
|
25
22
|
|
|
23
|
+
# True if the #execute method has been called for this action.
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean]
|
|
26
26
|
def executed?
|
|
27
27
|
@executed
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# The verb associated with this action.
|
|
31
|
+
#
|
|
32
|
+
# @return [Symbol] The symbol representing the verb
|
|
30
33
|
def verb
|
|
31
34
|
self.class.verb
|
|
32
35
|
end
|
|
@@ -39,34 +42,39 @@ module Gamefic
|
|
|
39
42
|
self.class.rank
|
|
40
43
|
end
|
|
41
44
|
|
|
45
|
+
# True if the action is flagged as meta.
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean]
|
|
42
48
|
def meta?
|
|
43
49
|
self.class.meta?
|
|
44
50
|
end
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def self.subclass verb, *
|
|
52
|
+
# @param verb [Symbol]
|
|
53
|
+
# @param queries [Array<Gamefic::Query::Base>]
|
|
54
|
+
# @param meta [Boolean]
|
|
55
|
+
# @return [Class<Action>]
|
|
56
|
+
def self.subclass verb, *queries, meta: false, &block
|
|
51
57
|
act = Class.new(self) do
|
|
52
58
|
self.verb = verb
|
|
53
59
|
self.meta = meta
|
|
54
|
-
|
|
55
|
-
q.each { |q|
|
|
60
|
+
queries.each do |q|
|
|
56
61
|
add_query q
|
|
57
|
-
|
|
62
|
+
end
|
|
58
63
|
on_execute &block
|
|
59
64
|
end
|
|
60
|
-
if !block.nil?
|
|
61
|
-
raise
|
|
65
|
+
if !block.nil? && act.queries.length + 1 != block.arity && block.arity > 0
|
|
66
|
+
raise ArgumentError.new("Number of parameters is not compatible with proc arguments")
|
|
62
67
|
end
|
|
63
68
|
act
|
|
64
69
|
end
|
|
65
70
|
|
|
66
71
|
class << self
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
attr_reader :verb
|
|
73
|
+
|
|
74
|
+
# The proc to call when the action is executed
|
|
75
|
+
#
|
|
76
|
+
# @return [Proc]
|
|
77
|
+
attr_reader :executor
|
|
70
78
|
|
|
71
79
|
def meta?
|
|
72
80
|
@meta ||= false
|
|
@@ -87,23 +95,27 @@ module Gamefic
|
|
|
87
95
|
|
|
88
96
|
def signature
|
|
89
97
|
# @todo This is clearly unfinished
|
|
90
|
-
"#{verb} #{queries.map{|m| m.signature}.join(',')}"
|
|
98
|
+
"#{verb} #{queries.map {|m| m.signature}.join(', ')}"
|
|
91
99
|
end
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
# True if this action is not intended to be performed directly by a
|
|
102
|
+
# character.
|
|
103
|
+
# If the action is hidden, users should not be able to perform it with a
|
|
104
|
+
# direct command. By default, any action whose verb starts with an
|
|
105
|
+
# underscore is hidden.
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
def hidden?
|
|
109
|
+
verb.to_s.start_with?('_')
|
|
99
110
|
end
|
|
100
111
|
|
|
112
|
+
# @return [Integer]
|
|
101
113
|
def rank
|
|
102
114
|
if @rank.nil?
|
|
103
115
|
@rank = 0
|
|
104
|
-
queries.each
|
|
116
|
+
queries.each do |q|
|
|
105
117
|
@rank += (q.rank + 1)
|
|
106
|
-
|
|
118
|
+
end
|
|
107
119
|
@rank -= 1000 if verb.nil?
|
|
108
120
|
end
|
|
109
121
|
@rank
|
|
@@ -112,45 +124,43 @@ module Gamefic
|
|
|
112
124
|
def valid? actor, objects
|
|
113
125
|
return false if objects.length != queries.length
|
|
114
126
|
i = 0
|
|
115
|
-
queries.each
|
|
127
|
+
queries.each do |p|
|
|
116
128
|
return false unless p.include?(actor, objects[i])
|
|
117
129
|
i += 1
|
|
118
|
-
|
|
130
|
+
end
|
|
119
131
|
true
|
|
120
132
|
end
|
|
121
133
|
|
|
134
|
+
# Return an instance of this Action if the actor can execute it with the
|
|
135
|
+
# provided tokens, or nil if the tokens are invalid.
|
|
136
|
+
#
|
|
137
|
+
# @param action [Gamefic::Entity]
|
|
138
|
+
# @param tokens [Array<String>]
|
|
139
|
+
# @return [self, nil]
|
|
122
140
|
def attempt actor, tokens
|
|
123
|
-
i = 0
|
|
124
141
|
result = []
|
|
125
142
|
matches = Gamefic::Query::Matches.new([], '', '')
|
|
126
|
-
queries.
|
|
127
|
-
return nil if tokens[i].nil?
|
|
143
|
+
queries.each_with_index do |p, i|
|
|
144
|
+
return nil if tokens[i].nil? && matches.remaining == ''
|
|
128
145
|
matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1))
|
|
129
146
|
return nil if matches.objects.empty?
|
|
147
|
+
accepted = matches.objects.select { |o| p.accept?(o) }
|
|
148
|
+
return nil if accepted.empty?
|
|
130
149
|
if p.ambiguous?
|
|
131
|
-
result.push
|
|
150
|
+
result.push accepted
|
|
132
151
|
else
|
|
133
|
-
return nil if
|
|
134
|
-
result.push
|
|
152
|
+
return nil if accepted.length != 1
|
|
153
|
+
result.push accepted.first
|
|
135
154
|
end
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self.new(actor, result)
|
|
155
|
+
end
|
|
156
|
+
new(actor, result)
|
|
139
157
|
end
|
|
140
158
|
|
|
141
159
|
protected
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
@verb = sym
|
|
145
|
-
end
|
|
161
|
+
attr_writer :verb
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
@meta = bool
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def order_key= num
|
|
152
|
-
@order_key = num
|
|
153
|
-
end
|
|
163
|
+
attr_writer :meta
|
|
154
164
|
end
|
|
155
165
|
end
|
|
156
166
|
end
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
module Gamefic
|
|
2
|
+
class NotConclusionError < RuntimeError; end
|
|
3
|
+
|
|
4
|
+
# The Active module gives entities the ability to perform actions and
|
|
5
|
+
# participate in scenes. The Actor class, for example, is an Entity
|
|
6
|
+
# subclass that includes this module.
|
|
7
|
+
#
|
|
8
|
+
module Active
|
|
9
|
+
# The last action executed by the entity, as reported by the
|
|
10
|
+
# Active#performed method.
|
|
11
|
+
#
|
|
12
|
+
# @return [Gamefic::Action]
|
|
13
|
+
attr_reader :last_action
|
|
14
|
+
|
|
15
|
+
# The scene in which the entity is currently participating.
|
|
16
|
+
#
|
|
17
|
+
# @return [Gamefic::Scene::Base]
|
|
18
|
+
attr_reader :scene
|
|
19
|
+
|
|
20
|
+
# The scene class that will be cued for this entity on the next turn.
|
|
21
|
+
# Usually set with the #prepare method.
|
|
22
|
+
#
|
|
23
|
+
# @return [Class<Gamefic::Scene::Base>]
|
|
24
|
+
attr_reader :next_scene
|
|
25
|
+
|
|
26
|
+
attr_reader :next_options
|
|
27
|
+
|
|
28
|
+
# The prompt for the previous scene.
|
|
29
|
+
#
|
|
30
|
+
# @return [String]
|
|
31
|
+
attr_accessor :last_prompt
|
|
32
|
+
|
|
33
|
+
# The input for the previous scene.
|
|
34
|
+
#
|
|
35
|
+
# @return [String]
|
|
36
|
+
attr_accessor :last_input
|
|
37
|
+
|
|
38
|
+
# The playbooks that will be used to perform commands.
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Gamefic::World::Playbook>]
|
|
41
|
+
def playbooks
|
|
42
|
+
@playbooks ||= []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def syntaxes
|
|
46
|
+
playbooks.map(&:syntaxes).flatten
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# An array of actions waiting to be performed.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<String>]
|
|
52
|
+
def queue
|
|
53
|
+
@queue ||= []
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# A hash of values representing the state of a performing entity.
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash{Symbol => Object}]
|
|
59
|
+
def state
|
|
60
|
+
@state ||= {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def output
|
|
64
|
+
@output ||= {}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Send a message to the entity.
|
|
68
|
+
# This method will automatically wrap the message in HTML paragraphs.
|
|
69
|
+
# To send a message without paragraph formatting, use #stream instead.
|
|
70
|
+
#
|
|
71
|
+
# @param message [String]
|
|
72
|
+
def tell(message)
|
|
73
|
+
if buffer_stack > 0
|
|
74
|
+
append_buffer format(message)
|
|
75
|
+
else
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Send a message to the Character as raw text.
|
|
81
|
+
# Unlike #tell, this method will not wrap the message in HTML paragraphs.
|
|
82
|
+
#
|
|
83
|
+
# @param message [String]
|
|
84
|
+
def stream(message)
|
|
85
|
+
if buffer_stack > 0
|
|
86
|
+
append_buffer message
|
|
87
|
+
else
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Perform a command.
|
|
93
|
+
# The command can be specified as a String or a verb with a list of
|
|
94
|
+
# parameters. Either form should yield the same result, but the
|
|
95
|
+
# verb/parameter form can yield better performance since it bypasses the
|
|
96
|
+
# parser.
|
|
97
|
+
#
|
|
98
|
+
# The command will be executed immediately regardless of the entity's
|
|
99
|
+
# state.
|
|
100
|
+
#
|
|
101
|
+
# @example Send a command as a string
|
|
102
|
+
# character.perform "take the key"
|
|
103
|
+
#
|
|
104
|
+
# @example Send a command as a verb with parameters
|
|
105
|
+
# character.perform :take, @key
|
|
106
|
+
#
|
|
107
|
+
# @return [Gamefic::Action]
|
|
108
|
+
def perform(*command)
|
|
109
|
+
actions = []
|
|
110
|
+
playbooks.reverse.each { |p| actions.concat p.dispatch(self, *command) }
|
|
111
|
+
execute_stack actions
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Quietly perform a command.
|
|
115
|
+
# This method executes the command exactly as #perform does, except it
|
|
116
|
+
# buffers the resulting output instead of sending it to the user.
|
|
117
|
+
#
|
|
118
|
+
# @return [String] The output that resulted from performing the command.
|
|
119
|
+
def quietly(*command)
|
|
120
|
+
clear_buffer if buffer_stack == 0
|
|
121
|
+
set_buffer_stack buffer_stack + 1
|
|
122
|
+
self.perform *command
|
|
123
|
+
set_buffer_stack buffer_stack - 1
|
|
124
|
+
buffer
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Perform an action.
|
|
128
|
+
# This is functionally identical to the `perform` method, except the
|
|
129
|
+
# action must be declared as a verb with a list of parameters. Use
|
|
130
|
+
# `perform` if you need to parse a string as a command.
|
|
131
|
+
#
|
|
132
|
+
# The command will be executed immediately regardless of the entity's
|
|
133
|
+
# state.
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# character.execute :take, @key
|
|
137
|
+
#
|
|
138
|
+
# @return [Gamefic::Action]
|
|
139
|
+
def execute(verb, *params, quietly: false)
|
|
140
|
+
actions = []
|
|
141
|
+
playbooks.reverse.each { |p| actions.concat p.dispatch_from_params(self, verb, params) }
|
|
142
|
+
execute_stack actions, quietly: quietly
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Proceed to the next Action in the current stack.
|
|
146
|
+
# This method is typically used in Action blocks to cascade through
|
|
147
|
+
# multiple implementations of the same verb.
|
|
148
|
+
#
|
|
149
|
+
# @example Proceed through two implementations of a verb
|
|
150
|
+
# introduction do |actor|
|
|
151
|
+
# actor[:has_eaten] = false # Initial value
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# respond :eat do |actor|
|
|
155
|
+
# actor.tell "You eat something."
|
|
156
|
+
# actor[:has_eaten] = true
|
|
157
|
+
# end
|
|
158
|
+
#
|
|
159
|
+
# respond :eat do |actor|
|
|
160
|
+
# # This version will be executed first because it was implemented last
|
|
161
|
+
# if actor[:has_eaten]
|
|
162
|
+
# actor.tell "You already ate."
|
|
163
|
+
# else
|
|
164
|
+
# actor.proceed # Execute the previous implementation
|
|
165
|
+
# end
|
|
166
|
+
# end
|
|
167
|
+
#
|
|
168
|
+
def proceed quietly: false
|
|
169
|
+
return if performance_stack.empty?
|
|
170
|
+
a = performance_stack.last.shift
|
|
171
|
+
unless a.nil?
|
|
172
|
+
if quietly
|
|
173
|
+
if buffer_stack == 0
|
|
174
|
+
@buffer = ""
|
|
175
|
+
end
|
|
176
|
+
set_buffer_stack(buffer_stack + 1)
|
|
177
|
+
end
|
|
178
|
+
a.execute
|
|
179
|
+
if quietly
|
|
180
|
+
set_buffer_stack(buffer_stack - 1)
|
|
181
|
+
@buffer
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Immediately start a new scene for the character.
|
|
187
|
+
# Use #prepare if you want to declare a scene to be started at the
|
|
188
|
+
# beginning of the next turn.
|
|
189
|
+
#
|
|
190
|
+
# @param new_scene [Class<Scene::Base>]
|
|
191
|
+
# @param data [Hash] Additional scene data
|
|
192
|
+
def cue new_scene, **data
|
|
193
|
+
@next_scene = nil
|
|
194
|
+
if new_scene.nil?
|
|
195
|
+
@scene = nil
|
|
196
|
+
else
|
|
197
|
+
@scene = new_scene.new(self, **data)
|
|
198
|
+
@scene.start
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Prepare a scene to be started for this character at the beginning of the
|
|
203
|
+
# next turn. As opposed to #cue, a prepared scene will not start until the
|
|
204
|
+
# current scene finishes.
|
|
205
|
+
#
|
|
206
|
+
# @param new_scene [Class<Scene::Base>]
|
|
207
|
+
# @oaram data [Hash] Additional scene data
|
|
208
|
+
def prepare new_scene, **data
|
|
209
|
+
@next_scene = new_scene
|
|
210
|
+
@next_options = data
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Return true if the character is expected to be in the specified scene on
|
|
214
|
+
# the next turn.
|
|
215
|
+
#
|
|
216
|
+
# @return [Boolean]
|
|
217
|
+
def will_cue? scene
|
|
218
|
+
(@scene.class == scene and @next_scene.nil?) || @next_scene == scene
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Cue a conclusion. This method works like #cue, except it will raise a
|
|
222
|
+
# NotConclusionError if the scene is not a Scene::Conclusion.
|
|
223
|
+
#
|
|
224
|
+
# @param new_scene [Class<Scene::Base>]
|
|
225
|
+
# @oaram data [Hash] Additional scene data
|
|
226
|
+
def conclude new_scene, **data
|
|
227
|
+
raise NotConclusionError unless new_scene <= Scene::Conclusion
|
|
228
|
+
cue new_scene, **data
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# True if the character is in a conclusion.
|
|
232
|
+
#
|
|
233
|
+
# @return [Boolean]
|
|
234
|
+
def concluded?
|
|
235
|
+
!scene.nil? && scene.kind_of?(Scene::Conclusion)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Record the last action the entity executed. This method is typically
|
|
239
|
+
# called when the entity performs an action in response to user input.
|
|
240
|
+
#
|
|
241
|
+
def performed action
|
|
242
|
+
action.freeze
|
|
243
|
+
@last_action = action
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def accessible?
|
|
247
|
+
false
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def inspect
|
|
251
|
+
to_s
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Track the entity's performance of a scene.
|
|
255
|
+
#
|
|
256
|
+
def entered scene
|
|
257
|
+
klass = (scene.kind_of?(Gamefic::Scene::Base) ? scene.class : scene)
|
|
258
|
+
entered_scenes.push klass unless entered_scenes.include?(klass)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Determine whether the entity has performed the specified scene.
|
|
262
|
+
#
|
|
263
|
+
# @return [Boolean]
|
|
264
|
+
def entered? scene
|
|
265
|
+
klass = (scene.kind_of?(Gamefic::Scene::Base) ? scene.class : scene)
|
|
266
|
+
entered_scenes.include?(klass)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private
|
|
270
|
+
|
|
271
|
+
# @return [Array<Gamefic::Scene::Base>]
|
|
272
|
+
def entered_scenes
|
|
273
|
+
@entered_scenes ||= []
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# @param actions [Array<Gamefic::Action>]
|
|
277
|
+
# @param quietly [Boolean]
|
|
278
|
+
def execute_stack actions, quietly: false
|
|
279
|
+
return nil if actions.empty?
|
|
280
|
+
a = actions.first
|
|
281
|
+
okay = true
|
|
282
|
+
unless a.meta?
|
|
283
|
+
playbooks.reverse.each do |playbook|
|
|
284
|
+
okay = validate_playbook playbook, a
|
|
285
|
+
break unless okay
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
if okay
|
|
289
|
+
performance_stack.push actions
|
|
290
|
+
proceed quietly: quietly
|
|
291
|
+
performance_stack.pop
|
|
292
|
+
end
|
|
293
|
+
a
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def validate_playbook playbook, action
|
|
297
|
+
okay = true
|
|
298
|
+
playbook.validators.each { |v|
|
|
299
|
+
result = v.call(self, action.verb, action.parameters)
|
|
300
|
+
okay = (result != false)
|
|
301
|
+
break unless okay
|
|
302
|
+
}
|
|
303
|
+
okay
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def buffer_stack
|
|
307
|
+
@buffer_stack ||= 0
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def set_buffer_stack num
|
|
311
|
+
@buffer_stack = num
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# @return [String]
|
|
315
|
+
def buffer
|
|
316
|
+
@buffer ||= ''
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def append_buffer str
|
|
320
|
+
@buffer += str
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def clear_buffer
|
|
324
|
+
@buffer = ''
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def performance_stack
|
|
328
|
+
@performance_stack ||= []
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|