gamefic 1.7.0 → 2.0.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 +5 -5
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.solargraph.yml +5 -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 +7 -6
- data/lib/gamefic/action.rb +38 -28
- data/lib/gamefic/active.rb +325 -280
- data/lib/gamefic/actor.rb +8 -5
- data/lib/gamefic/command.rb +9 -7
- data/lib/gamefic/core_ext/array.rb +24 -49
- data/lib/gamefic/core_ext/string.rb +25 -16
- data/lib/gamefic/describable.rb +21 -23
- data/lib/gamefic/element.rb +43 -31
- data/lib/gamefic/entity.rb +6 -12
- data/lib/gamefic/index.rb +121 -0
- data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
- data/lib/gamefic/messaging.rb +43 -44
- data/lib/gamefic/node.rb +14 -5
- data/lib/gamefic/plot.rb +69 -89
- data/lib/gamefic/plot/darkroom.rb +92 -264
- data/lib/gamefic/plot/host.rb +42 -48
- data/lib/gamefic/plot/snapshot.rb +5 -18
- data/lib/gamefic/query.rb +14 -18
- data/lib/gamefic/query/base.rb +30 -18
- data/lib/gamefic/query/children.rb +0 -0
- data/lib/gamefic/query/external.rb +18 -14
- data/lib/gamefic/query/family.rb +1 -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 +2 -1
- data/lib/gamefic/scene.rb +0 -2
- data/lib/gamefic/scene/activity.rb +24 -26
- data/lib/gamefic/scene/base.rb +64 -8
- data/lib/gamefic/scene/conclusion.rb +0 -2
- data/lib/gamefic/scene/custom.rb +0 -2
- data/lib/gamefic/scene/multiple_choice.rb +18 -3
- 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 +87 -0
- data/lib/gamefic/serialize.rb +68 -0
- data/lib/gamefic/subplot.rb +29 -35
- data/lib/gamefic/syntax.rb +14 -13
- data/lib/gamefic/version.rb +3 -3
- data/lib/gamefic/world.rb +16 -0
- data/lib/gamefic/world/callbacks.rb +135 -0
- data/lib/gamefic/world/commands.rb +184 -0
- data/lib/gamefic/{plot → world}/entities.rb +30 -29
- data/lib/gamefic/{plot → world}/playbook.rb +255 -240
- data/lib/gamefic/world/players.rb +21 -0
- data/lib/gamefic/world/scenes.rb +226 -0
- metadata +41 -92
- data/bin/gamefic +0 -9
- data/lib/gamefic/engine.rb +0 -7
- data/lib/gamefic/engine/base.rb +0 -59
- 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 -106
- 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 -126
- data/lib/gamefic/plot/commands.rb +0 -120
- data/lib/gamefic/plot/players.rb +0 -15
- data/lib/gamefic/plot/scenes.rb +0 -187
- 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 -7
- data/lib/gamefic/user/base.rb +0 -29
- data/lib/gamefic/user/tty.rb +0 -38
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Index
|
5
|
+
@@elements = []
|
6
|
+
@@stuck_length = 0
|
7
|
+
|
8
|
+
def initialize **data
|
9
|
+
data.each_pair do |k, v|
|
10
|
+
public_send "#{k}=", v
|
11
|
+
end
|
12
|
+
@@elements.push self
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_serial
|
16
|
+
index = @@elements.index(self)
|
17
|
+
raise RuntimeError, "#{self} is not an indexed element" unless index
|
18
|
+
"#<ELE_#{index}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy
|
22
|
+
@@elements.delete self unless Index.stuck?(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.elements
|
26
|
+
@@elements
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.serials
|
30
|
+
result = []
|
31
|
+
@@elements.each do |e|
|
32
|
+
d = {}
|
33
|
+
d['class'] = e.class.to_s
|
34
|
+
e.instance_variables.each do |k|
|
35
|
+
v = e.instance_variable_get(k)
|
36
|
+
d[k] = v.to_serial
|
37
|
+
end
|
38
|
+
result.push d
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.from_serial serial
|
44
|
+
if serial.is_a?(Hash) && serial['class']
|
45
|
+
klass = eval(serial['class'])
|
46
|
+
object = klass.allocate
|
47
|
+
serial.each_pair do |k, v|
|
48
|
+
next unless k.to_s.start_with?('@')
|
49
|
+
object.instance_variable_set(k, from_serial(v))
|
50
|
+
end
|
51
|
+
object
|
52
|
+
elsif serial.is_a?(Numeric)
|
53
|
+
serial
|
54
|
+
elsif serial.is_a?(String)
|
55
|
+
match = serial.match(/#<ELE_([0-9]+)>/)
|
56
|
+
return Gamefic::Index.elements[match[1].to_i] if match
|
57
|
+
match = serial.match(/#<SYM:([a-z0-9_\?\!]+)>/i)
|
58
|
+
return match[1].to_sym if match
|
59
|
+
serial
|
60
|
+
elsif serial.is_a?(Array)
|
61
|
+
result = serial.map { |e| from_serial(e) }
|
62
|
+
result = "#<UNKNOWN>" if result.any? { |e| e == "#<UNKNOWN>" }
|
63
|
+
result
|
64
|
+
elsif serial.is_a?(Hash)
|
65
|
+
result = {}
|
66
|
+
unknown = false
|
67
|
+
serial.each_pair do |k, v|
|
68
|
+
k2 = from_serial(k)
|
69
|
+
v2 = from_serial(v)
|
70
|
+
if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
|
71
|
+
unknown = true
|
72
|
+
break
|
73
|
+
end
|
74
|
+
result[k2] = v2
|
75
|
+
end
|
76
|
+
result = "#<UNKNOWN>" if unknown
|
77
|
+
result
|
78
|
+
elsif serial && serial != true
|
79
|
+
STDERR.puts "Unable to unserialize #{serial.class}"
|
80
|
+
nil
|
81
|
+
else
|
82
|
+
# true, false, or nil
|
83
|
+
serial
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.unserialize serials
|
88
|
+
serials.each_with_index do |s, i|
|
89
|
+
next if elements[i]
|
90
|
+
klass = eval(s['class'])
|
91
|
+
klass.new
|
92
|
+
end
|
93
|
+
serials.each_with_index do |s, i|
|
94
|
+
s.each_pair do |k, v|
|
95
|
+
next unless k.to_s.start_with?('@')
|
96
|
+
next if v == "#<UNKNOWN>"
|
97
|
+
elements[i].instance_variable_set(k, from_serial(v))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
elements
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.stick
|
104
|
+
@@stuck_length = @@elements.length
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.stuck
|
108
|
+
@@stuck_length
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.clear
|
112
|
+
@@stuck_length = 0
|
113
|
+
@@elements.clear
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.stuck? thing
|
117
|
+
index = @@elements.index(thing)
|
118
|
+
index && index <= @@stuck_length - 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -1,50 +1,52 @@
|
|
1
|
-
module Gamefic
|
2
|
-
module
|
3
|
-
SPLIT_REGEXP = /[\s]+/
|
4
|
-
|
5
|
-
# Get an array of keywords associated with this object.
|
6
|
-
# The default implementation splits the value of self.to_s into an array.
|
7
|
-
#
|
8
|
-
# @return [Array<String>]
|
9
|
-
def keywords
|
10
|
-
self.to_s.downcase.split(SPLIT_REGEXP).uniq
|
11
|
-
end
|
12
|
-
|
13
|
-
# Determine if this object matches the provided description.
|
14
|
-
# In a regular match, every word in the description must be a keyword.
|
15
|
-
# Fuzzy matches accept words if a keyword starts with it, e.g., "red"
|
16
|
-
# would be a fuzzy match for "reddish."
|
17
|
-
#
|
18
|
-
# @example
|
19
|
-
# dog = "big red dog"
|
20
|
-
# dog.extend Gamefic::Matchable
|
21
|
-
#
|
22
|
-
# dog.
|
23
|
-
# dog.
|
24
|
-
# dog.
|
25
|
-
#
|
26
|
-
# dog.
|
27
|
-
# dog.
|
28
|
-
#
|
29
|
-
# @
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
1
|
+
module Gamefic
|
2
|
+
module Keywords
|
3
|
+
SPLIT_REGEXP = /[\s]+/
|
4
|
+
|
5
|
+
# Get an array of keywords associated with this object.
|
6
|
+
# The default implementation splits the value of self.to_s into an array.
|
7
|
+
#
|
8
|
+
# @return [Array<String>]
|
9
|
+
def keywords
|
10
|
+
self.to_s.downcase.split(SPLIT_REGEXP).uniq
|
11
|
+
end
|
12
|
+
|
13
|
+
# Determine if this object matches the provided description.
|
14
|
+
# In a regular match, every word in the description must be a keyword.
|
15
|
+
# Fuzzy matches accept words if a keyword starts with it, e.g., "red"
|
16
|
+
# would be a fuzzy match for "reddish."
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# dog = "big red dog"
|
20
|
+
# dog.extend Gamefic::Matchable
|
21
|
+
#
|
22
|
+
# dog.specified?("red dog") #=> true
|
23
|
+
# dog.specified?("gray dog") #=> false
|
24
|
+
# dog.specified?("red do") #=> false
|
25
|
+
#
|
26
|
+
# dog.specified?("re do", fuzzy: true) #=> true
|
27
|
+
# dog.specified?("red og", fuzzy: true) #=> false
|
28
|
+
#
|
29
|
+
# @param description [String] The description to be compared
|
30
|
+
# @param fuzzy [Boolean] Use fuzzy matching (default is false)
|
31
|
+
# @return [Boolean]
|
32
|
+
def specified? description, fuzzy: false
|
33
|
+
words = description.split(SPLIT_REGEXP)
|
34
|
+
return false if words.empty?
|
35
|
+
matches = 0
|
36
|
+
available = keywords
|
37
|
+
words.each { |w|
|
38
|
+
if fuzzy
|
39
|
+
available.each { |k|
|
40
|
+
if k.gsub(/[^a-z0-9]/, '').start_with?(w.downcase.gsub(/[^a-z0-9]/, ''))
|
41
|
+
matches +=1
|
42
|
+
break
|
43
|
+
end
|
44
|
+
}
|
45
|
+
else
|
46
|
+
matches +=1 if available.include?(w.downcase)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
matches == words.length
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/gamefic/messaging.rb
CHANGED
@@ -1,44 +1,43 @@
|
|
1
|
-
module Gamefic
|
2
|
-
module Messaging
|
3
|
-
# Send a message to the entity.
|
4
|
-
# This method will automatically wrap the message in HTML paragraphs.
|
5
|
-
# To send a message without paragraph formatting, use #stream instead.
|
6
|
-
#
|
7
|
-
# @param message [String]
|
8
|
-
def tell(message)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
1
|
+
module Gamefic
|
2
|
+
module Messaging
|
3
|
+
# Send a message to the entity.
|
4
|
+
# This method will automatically wrap the message in HTML paragraphs.
|
5
|
+
# To send a message without paragraph formatting, use #stream instead.
|
6
|
+
#
|
7
|
+
# @param message [String]
|
8
|
+
def tell(message)
|
9
|
+
@messages = @messages.to_s + format(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Send a message to the Character as raw text.
|
13
|
+
# Unlike #tell, this method will not wrap the message in HTML paragraphs.
|
14
|
+
#
|
15
|
+
# @param message [String]
|
16
|
+
def stream(message)
|
17
|
+
@messages = @messages.to_s + message
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get all the currently buffered messages consolidated in a single string.
|
21
|
+
#
|
22
|
+
# @return [String]
|
23
|
+
def messages
|
24
|
+
@messages ||= ''
|
25
|
+
end
|
26
|
+
|
27
|
+
# Clear the buffered messages.
|
28
|
+
#
|
29
|
+
def flush
|
30
|
+
@messages = ''
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def format message
|
36
|
+
"<p>#{message.strip}</p>"
|
37
|
+
.gsub(/[ \t\r]*\n[ \t\r]*\n[ \t\r]*/, "</p><p>")
|
38
|
+
.gsub(/[ \t]*\n[ \t]*/, ' ')
|
39
|
+
.gsub(/<p>[\s]*<p>/, '<p>')
|
40
|
+
.gsub(/<\/p>[\s]*<\/p>/, '</p>')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/gamefic/node.rb
CHANGED
@@ -1,31 +1,37 @@
|
|
1
1
|
# Exception raised when setting a node's parent would cause
|
2
2
|
# a circular reference, e.g., A -> A or A -> B -> A
|
3
|
-
class CircularNodeReferenceError <
|
4
|
-
end
|
3
|
+
class CircularNodeReferenceError < RuntimeError; end
|
5
4
|
|
6
5
|
module Gamefic
|
7
|
-
|
8
6
|
module Node
|
7
|
+
# An array of the object's children.
|
8
|
+
#
|
9
9
|
# @return [Array]
|
10
10
|
def children
|
11
11
|
@children ||= []
|
12
12
|
@children.clone
|
13
13
|
end
|
14
14
|
|
15
|
+
# Get a flat array of all descendants.
|
16
|
+
#
|
15
17
|
# @return [Array]
|
16
18
|
def flatten
|
17
19
|
array = Array.new
|
18
20
|
children.each { |child|
|
19
21
|
array = array + recurse_flatten(child)
|
20
22
|
}
|
21
|
-
|
23
|
+
array
|
22
24
|
end
|
23
25
|
|
26
|
+
# The object's parent.
|
27
|
+
#
|
24
28
|
# @return [Object]
|
25
29
|
def parent
|
26
30
|
@parent
|
27
31
|
end
|
28
32
|
|
33
|
+
# Set the object's parent.
|
34
|
+
#
|
29
35
|
def parent=(node)
|
30
36
|
return if node == @parent
|
31
37
|
if node == self
|
@@ -49,6 +55,10 @@ module Gamefic
|
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
58
|
+
# Determine if external objects can interact with this object's children.
|
59
|
+
# For example, a game can designate that the contents of a bowl are
|
60
|
+
# accessible, while the contents of a locked safe are not.
|
61
|
+
#
|
52
62
|
# @return [Boolean]
|
53
63
|
def accessible?
|
54
64
|
true
|
@@ -81,5 +91,4 @@ module Gamefic
|
|
81
91
|
return array
|
82
92
|
end
|
83
93
|
end
|
84
|
-
|
85
94
|
end
|
data/lib/gamefic/plot.rb
CHANGED
@@ -1,133 +1,113 @@
|
|
1
|
-
require 'gamefic/tester'
|
2
|
-
require 'gamefic/source'
|
3
|
-
require 'gamefic/script'
|
4
1
|
require 'gamefic/query'
|
5
2
|
|
6
3
|
module Gamefic
|
7
|
-
|
4
|
+
# A plot controls the game narrative and manages the world model.
|
5
|
+
# Authors typically build plots through scripts that are executed in a
|
6
|
+
# special container called a stage. All of the elements that compose the
|
7
|
+
# narrative (characters, locations, scenes, etc.) reside in the stage's
|
8
|
+
# scope. Game engines use the plot to receive game data and process user
|
9
|
+
# input.
|
10
|
+
#
|
8
11
|
class Plot
|
9
|
-
autoload :Scenes, 'gamefic/plot/scenes'
|
10
|
-
autoload :Commands, 'gamefic/plot/commands'
|
11
|
-
autoload :Entities, 'gamefic/plot/entities'
|
12
|
-
autoload :Articles, 'gamefic/plot/articles'
|
13
|
-
autoload :YouMount, 'gamefic/plot/you_mount'
|
14
12
|
autoload :Snapshot, 'gamefic/plot/snapshot'
|
15
13
|
autoload :Darkroom, 'gamefic/plot/darkroom'
|
16
14
|
autoload :Host, 'gamefic/plot/host'
|
17
|
-
autoload :Players, 'gamefic/plot/players'
|
18
|
-
autoload :Playbook, 'gamefic/plot/playbook'
|
19
|
-
autoload :Callbacks, 'gamefic/plot/callbacks'
|
20
|
-
autoload :Theater, 'gamefic/plot/theater'
|
21
15
|
|
22
|
-
|
16
|
+
# @return [Hash]
|
17
|
+
attr_reader :metadata
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
include
|
28
|
-
include
|
19
|
+
include World
|
20
|
+
include Scriptable
|
21
|
+
# @!parse extend Scriptable::ClassMethods
|
22
|
+
include Snapshot
|
23
|
+
include Host
|
29
24
|
|
30
|
-
# @param
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@
|
35
|
-
|
36
|
-
|
25
|
+
# @param structure [Gamefic::Structure]
|
26
|
+
# @param metadata [Hash]
|
27
|
+
def initialize metadata: {}
|
28
|
+
Gamefic::Index.clear
|
29
|
+
@metadata = metadata
|
30
|
+
run_scripts
|
31
|
+
mark_static_entities
|
32
|
+
Gamefic::Index.stick
|
37
33
|
end
|
38
34
|
|
39
35
|
def player_class cls = nil
|
40
36
|
@player_class = cls unless cls.nil?
|
41
|
-
@player_class
|
42
|
-
end
|
43
|
-
|
44
|
-
# @return [Gamefic::Plot::Playbook]
|
45
|
-
def playbook
|
46
|
-
@playbook ||= Gamefic::Plot::Playbook.new
|
37
|
+
@player_class ||= Gamefic::Actor
|
47
38
|
end
|
48
39
|
|
49
|
-
def running?
|
50
|
-
@running
|
51
|
-
end
|
52
|
-
|
53
|
-
# Get an Array of all scripts that have been imported into the Plot.
|
54
|
-
#
|
55
|
-
# @return [Array<Script>] The imported scripts
|
56
|
-
def imported_scripts
|
57
|
-
@imported_scripts ||= []
|
58
|
-
end
|
59
|
-
|
60
|
-
def post_initialize
|
61
|
-
# TODO: Should this method be required by extended classes?
|
62
|
-
end
|
63
|
-
|
64
40
|
# Get an Array of the Plot's current Syntaxes.
|
65
41
|
#
|
66
42
|
# @return [Array<Syntax>]
|
67
43
|
def syntaxes
|
68
44
|
playbook.syntaxes
|
69
45
|
end
|
70
|
-
|
46
|
+
|
71
47
|
# Prepare the Plot for the next turn of gameplay.
|
72
|
-
# This method is typically called by the Engine that manages game
|
48
|
+
# This method is typically called by the Engine that manages game
|
49
|
+
# execution.
|
50
|
+
#
|
73
51
|
def ready
|
74
52
|
playbook.freeze
|
75
|
-
@running = true
|
76
|
-
# Call the initial state to make sure it's set
|
77
|
-
initial_state
|
78
53
|
call_ready
|
79
54
|
call_player_ready
|
80
|
-
|
55
|
+
subplots.each { |s| s.ready }
|
56
|
+
players.each do |p|
|
57
|
+
p.state.replace(
|
58
|
+
p.scene.state
|
59
|
+
.merge({
|
60
|
+
messages: p.messages,
|
61
|
+
last_prompt: p.last_prompt,
|
62
|
+
last_input: p.last_input
|
63
|
+
})
|
64
|
+
.merge(p.state)
|
65
|
+
)
|
66
|
+
p.output.replace(p.state)
|
67
|
+
p.state.clear
|
68
|
+
end
|
81
69
|
end
|
82
|
-
|
70
|
+
|
83
71
|
# Update the Plot's current turn of gameplay.
|
84
|
-
# This method is typically called by the Engine that manages game
|
72
|
+
# This method is typically called by the Engine that manages game
|
73
|
+
# execution.
|
74
|
+
#
|
85
75
|
def update
|
86
76
|
entities.each { |e| e.flush }
|
87
77
|
call_before_player_update
|
88
|
-
|
78
|
+
players.each do |p|
|
89
79
|
p.performed nil
|
80
|
+
next unless p.scene
|
81
|
+
p.last_input = p.queue.last
|
82
|
+
p.last_prompt = p.scene.prompt
|
90
83
|
p.scene.update
|
91
|
-
|
92
|
-
|
84
|
+
if p.scene.is_a?(Scene::Conclusion)
|
85
|
+
player_conclude_procs.each do |proc|
|
86
|
+
proc.call p
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
93
90
|
call_player_update
|
94
91
|
call_update
|
95
|
-
|
96
|
-
|
92
|
+
subplots.each { |s| s.update unless s.concluded? }
|
93
|
+
subplots.delete_if { |s| s.concluded? }
|
97
94
|
end
|
98
95
|
|
99
|
-
|
96
|
+
# Send a message to a group of entities.
|
97
|
+
#
|
98
|
+
# @param entities [Array<Entity>]
|
99
|
+
# @param message [String]
|
100
|
+
def tell entities, message
|
100
101
|
entities.each { |entity|
|
101
|
-
entity.tell message
|
102
|
+
entity.tell message
|
102
103
|
}
|
103
104
|
end
|
104
|
-
|
105
|
-
# Load a script into the current Plot.
|
106
|
-
# This method is similar to Kernel#require, except that the script is
|
107
|
-
# evaluated within the Plot's context via #stage.
|
108
|
-
#
|
109
|
-
# @param path [String] The path to the script being evaluated
|
110
|
-
# @return [Boolean] true if the script was loaded by this call or false if it was already loaded.
|
111
|
-
def script path
|
112
|
-
imported_script = source.export(path)
|
113
|
-
if imported_script.nil?
|
114
|
-
raise LoadError.new("cannot load script -- #{path}")
|
115
|
-
end
|
116
|
-
if !@working_scripts.include?(imported_script) and !imported_scripts.include?(imported_script)
|
117
|
-
@working_scripts.push imported_script
|
118
|
-
# @hack Arguments need to be in different order if source returns proc
|
119
|
-
if imported_script.read.kind_of?(Proc)
|
120
|
-
stage &imported_script.read
|
121
|
-
else
|
122
|
-
stage imported_script.read, imported_script.absolute_path
|
123
|
-
end
|
124
|
-
@working_scripts.pop
|
125
|
-
imported_scripts.push imported_script
|
126
|
-
true
|
127
|
-
else
|
128
|
-
false
|
129
|
-
end
|
130
|
-
end
|
131
105
|
end
|
106
|
+
end
|
132
107
|
|
108
|
+
module Gamefic
|
109
|
+
# @yieldself [Gamefic::Plot]
|
110
|
+
def self.script &block
|
111
|
+
Gamefic::Plot.script &block
|
112
|
+
end
|
133
113
|
end
|