gamefic 1.7.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|