gamefic 0.0.5 → 0.1.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/bin/gamefic +7 -0
  3. data/lib/gamefic/character.rb +2 -0
  4. data/lib/gamefic/core_ext/string.rb +2 -2
  5. data/lib/gamefic/director.rb +36 -3
  6. data/lib/gamefic/engine.rb +9 -5
  7. data/lib/gamefic/entity.rb +1 -1
  8. data/lib/gamefic/import/basics.rb +2 -0
  9. data/lib/gamefic/import/basics/actions.rb +1 -0
  10. data/lib/gamefic/import/basics/actions/container.rb +111 -0
  11. data/lib/gamefic/import/basics/actions/inventory.rb +49 -0
  12. data/lib/gamefic/import/basics/actions/look.rb +67 -0
  13. data/lib/gamefic/import/basics/actions/meta.rb +6 -0
  14. data/lib/gamefic/import/basics/actions/traversal.rb +35 -0
  15. data/lib/gamefic/import/basics/entities.rb +1 -0
  16. data/lib/gamefic/import/basics/entities/container.rb +24 -0
  17. data/lib/gamefic/import/basics/entities/fixture.rb +5 -0
  18. data/lib/gamefic/import/basics/entities/item.rb +7 -0
  19. data/lib/gamefic/import/basics/entities/itemized.rb +3 -0
  20. data/lib/gamefic/import/basics/entities/portable.rb +3 -0
  21. data/lib/gamefic/import/basics/entities/portal.rb +43 -0
  22. data/lib/gamefic/import/basics/entities/room.rb +24 -0
  23. data/lib/gamefic/import/basics/entities/scenery.rb +3 -0
  24. data/lib/gamefic/import/basics/room_modes.rb +48 -0
  25. data/lib/gamefic/plot.rb +89 -16
  26. data/lib/gamefic/shell.rb +317 -0
  27. data/lib/gamefic/syntax.rb +1 -1
  28. data/lib/gamefic/user.rb +3 -3
  29. metadata +23 -18
  30. data/lib/gamefic/action_ext.rb +0 -7
  31. data/lib/gamefic/action_ext/container.rb +0 -114
  32. data/lib/gamefic/action_ext/inventory.rb +0 -53
  33. data/lib/gamefic/action_ext/look.rb +0 -52
  34. data/lib/gamefic/action_ext/meta.rb +0 -10
  35. data/lib/gamefic/action_ext/traversal.rb +0 -39
  36. data/lib/gamefic/entity_ext.rb +0 -7
  37. data/lib/gamefic/entity_ext/container.rb +0 -28
  38. data/lib/gamefic/entity_ext/fixture.rb +0 -9
  39. data/lib/gamefic/entity_ext/item.rb +0 -11
  40. data/lib/gamefic/entity_ext/itemized.rb +0 -7
  41. data/lib/gamefic/entity_ext/portable.rb +0 -33
  42. data/lib/gamefic/entity_ext/portal.rb +0 -47
  43. data/lib/gamefic/entity_ext/room.rb +0 -28
  44. data/lib/gamefic/entity_ext/scenery.rb +0 -7
@@ -0,0 +1,5 @@
1
+ import 'basics/entities/itemized'
2
+
3
+ class Fixture < Entity
4
+ include Itemized
5
+ end
@@ -0,0 +1,7 @@
1
+ import 'basics/entities/itemized'
2
+ import 'basics/entities/portable'
3
+
4
+ class Item < Entity
5
+ include Itemized
6
+ include Portable
7
+ end
@@ -0,0 +1,3 @@
1
+ module Itemized
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ module Portable
2
+
3
+ end
@@ -0,0 +1,43 @@
1
+ class Portal < Entity
2
+ attr_accessor :destination
3
+ #def initialize
4
+ # super
5
+ # @destination = nil
6
+ #end
7
+ def find_reverse
8
+ rev = Portal.reverse(self.name)
9
+ if rev != nil
10
+ destination.children.that_are(Portal).each { |c|
11
+ if c.name == rev
12
+ return c
13
+ end
14
+ }
15
+ end
16
+ end
17
+ def self.reverse(direction)
18
+ case direction.downcase
19
+ when "north"
20
+ "south"
21
+ when "south"
22
+ "north"
23
+ when "west"
24
+ "east"
25
+ when "east"
26
+ "west"
27
+ when "northwest"
28
+ "southeast"
29
+ when "southeast"
30
+ "northwest"
31
+ when "northeast"
32
+ "southwest"
33
+ when "southwest"
34
+ "northeast"
35
+ when "up"
36
+ "down"
37
+ when "down"
38
+ "up"
39
+ else
40
+ nil
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ import 'basics/entities/portal'
2
+
3
+ class Room < Entity
4
+ def connect(destination, direction, type = Portal, two_way = true)
5
+ portal = type.new self.plot, :name => direction, :parent => self, :destination => destination
6
+ if two_way == true
7
+ reverse = Portal.reverse(direction)
8
+ if reverse == nil
9
+ raise "\"#{direction.cap_first}\" does not have an opposite direction"
10
+ end
11
+ portal2 = type.new(self.plot, {
12
+ :name => reverse,
13
+ :parent => destination,
14
+ :destination => self
15
+ })
16
+ end
17
+ portal
18
+ end
19
+ def tell(message, refresh = false)
20
+ children.each { |c|
21
+ c.tell message, refresh
22
+ }
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ class Scenery < Entity
2
+
3
+ end
@@ -0,0 +1,48 @@
1
+ module RoomModes
2
+ attr_writer :description_mode
3
+ def description_seen
4
+ @description_seen ||= Array.new
5
+ end
6
+ def description_seen=(value)
7
+ if value.kind_of?(Array)
8
+ @description_seen = value
9
+ else
10
+ raise "Character#visited must be an Array"
11
+ end
12
+ end
13
+ def description_mode
14
+ @description_mode ||= "brief"
15
+ end
16
+ end
17
+
18
+ class Character
19
+ include RoomModes
20
+ end
21
+
22
+ respond :go, Query.new(:siblings, Portal) do |actor, portal|
23
+ actor.tell "You go #{portal.name}."
24
+ actor.parent = portal.destination
25
+ if actor.description_mode == "superbrief" or (actor.description_mode == "brief" and actor.description_seen.include?(actor.parent))
26
+ actor.perform "itemize room"
27
+ else
28
+ actor.perform "itemize room full"
29
+ end
30
+ if actor.description_seen.include?(actor.parent) == false
31
+ actor.description_seen.push actor.parent
32
+ end
33
+ end
34
+
35
+ respond :brief do |actor|
36
+ actor.description_mode = "brief"
37
+ actor.tell "You are now in BRIEF mode. Detailed descriptions of rooms will only be displayed the first time you visit them. Other options are SUPERBRIEF and VERBOSE."
38
+ end
39
+
40
+ respond :verbose do |actor|
41
+ actor.description_mode = "verbose"
42
+ actor.tell "You are now in VERBOSE mode. Detailed descriptions will be displayed every time you enter a room. Other options are BRIEF and SUPERBRIEF."
43
+ end
44
+
45
+ respond :superbrief do |actor|
46
+ actor.description_mode = "superbrief"
47
+ actor.tell "You are now in VERBOSE mode. Detailed room descriptions will never be displayed unless you LOOK AROUND. Other options are BRIEF and VERBOSE."
48
+ end
data/lib/gamefic/plot.rb CHANGED
@@ -1,7 +1,25 @@
1
1
  module Gamefic
2
2
 
3
+ def self.bind(plot)
4
+ mod = Module.new do
5
+ def self.bind(plot)
6
+ @@plot = plot
7
+ end
8
+ def self.get_binding
9
+ binding
10
+ end
11
+ def self.method_missing(name, *args, &block)
12
+ if @@plot.respond_to?(name)
13
+ @@plot.send name, *args, &block
14
+ end
15
+ end
16
+ end
17
+ mod.bind plot
18
+ mod.get_binding
19
+ end
20
+
3
21
  class Plot
4
- attr_reader :scenes, :commands, :conclusions, :declared_scripts
22
+ attr_reader :scenes, :commands, :conclusions, :imported_scripts
5
23
  attr_accessor :story
6
24
  def commandwords
7
25
  words = Array.new
@@ -16,7 +34,8 @@ module Gamefic
16
34
  @syntaxes = Array.new
17
35
  @conclusions = Hash.new
18
36
  @update_procs = Array.new
19
- @declared_scripts = Array.new
37
+ @available_scripts = Hash.new
38
+ @imported_scripts = Array.new
20
39
  @entities = Array.new
21
40
  post_initialize
22
41
  end
@@ -82,30 +101,76 @@ module Gamefic
82
101
  @update_procs.each { |p|
83
102
  p.call
84
103
  }
85
- #@entities.flatten.each { |e|
86
104
  @entities.each { |e|
87
- #recursive_update e
88
105
  e.update
89
106
  }
90
107
  end
108
+
91
109
  def tell entities, message, refresh = false
92
110
  entities.each { |entity|
93
111
  entity.tell message, refresh
94
112
  }
95
113
  end
96
- def load_script filename
97
- plot = self
98
- eval File.read(filename), nil, filename, 1
99
- end
100
- def require_script filename
101
- if @declared_scripts.include?(filename) == false
102
- @declared_scripts.push(filename)
103
- load_script filename
104
- end
105
- end
114
+
115
+ def load script, with_libs = true
116
+ code = File.read(script)
117
+ code.untaint
118
+ @source_directory = File.dirname(script)
119
+ if with_libs == true
120
+ $LOAD_PATH.reverse.each { |path|
121
+ get_scripts path + '/gamefic/import'
122
+ }
123
+ end
124
+ get_scripts @source_directory + '/import'
125
+ proc {
126
+ $SAFE = 3
127
+ eval code, ::Gamefic.bind(self), script, 1
128
+ }.call
129
+ end
106
130
 
107
- private
131
+ def import script
132
+ if script[-2, 2] == '/*'
133
+ directory = script[0..-3]
134
+ resolved = directory
135
+ @available_scripts.each { |f, c|
136
+ if f.start_with?(resolved)
137
+ self.import f
138
+ end
139
+ }
140
+ else
141
+ resolved = script
142
+ if !@available_scripts.has_key?(resolved)
143
+ if @available_scripts.has_key?(resolved + '.rb')
144
+ resolved = resolved + '.rb'
145
+ end
146
+ end
147
+ if @available_scripts.has_key?(resolved)
148
+ if @available_scripts[resolved] != nil
149
+ script_object = @available_scripts[resolved]
150
+ @available_scripts[resolved] = nil
151
+ proc {
152
+ $SAFE = 3
153
+ @imported_scripts.push script_object
154
+ eval script_object.code, Gamefic.bind(self), script_object.filename, 1
155
+ }.call
156
+ end
157
+ else
158
+ raise "Unavailable import: #{resolved}"
159
+ end
160
+ end
161
+ end
108
162
 
163
+ private
164
+ def get_scripts(directory)
165
+ Dir[directory + '/*'].each { |f|
166
+ if File.directory?(f)
167
+ get_scripts f
168
+ else
169
+ relative = f[(f.index('/import/')+8)..-1]
170
+ @available_scripts[relative] = Script.new(f)
171
+ end
172
+ }
173
+ end
109
174
  def rem_entity(entity)
110
175
  @entities.delete(entity)
111
176
  end
@@ -170,6 +235,14 @@ module Gamefic
170
235
  def add_entity(entity)
171
236
  @entities.push entity
172
237
  end
173
- end
238
+ class Script
239
+ attr_reader :filename, :code
240
+ def initialize filename
241
+ @filename = filename
242
+ @code = File.read(filename)
243
+ @code.untaint
244
+ end
245
+ end
246
+ end
174
247
 
175
248
  end
@@ -0,0 +1,317 @@
1
+ require 'rubygems/package'
2
+ require 'zlib'
3
+ require 'tmpdir'
4
+ require 'getoptlong'
5
+
6
+ # Crazy hack to set file mtimes in tar file
7
+ class Gem::Package::TarHeader
8
+ @@mtime = Time.now
9
+ def self.set_mtime(time)
10
+ @@mtime = time
11
+ end
12
+ alias :initialize_orig :initialize
13
+ def initialize(vals)
14
+ initialize_orig(vals)
15
+ @mtime = @@mtime
16
+ end
17
+ end
18
+
19
+ module Gamefic
20
+
21
+ class Shell
22
+ attr_accessor :argv
23
+ def initialize
24
+
25
+ end
26
+ def execute
27
+ if ARGV.length == 0
28
+ ARGV.push 'help'
29
+ end
30
+ cmd = ARGV.shift
31
+ case cmd
32
+ when 'play'
33
+ play ARGV.shift
34
+ when 'test'
35
+ test ARGV.shift
36
+ when 'init'
37
+ init ARGV.shift
38
+ when 'build'
39
+ build ARGV.shift
40
+ when 'fetch'
41
+ fetch ARGV.shift
42
+ when 'help'
43
+ help ARGV.shift
44
+ else
45
+ play cmd
46
+ end
47
+ end
48
+ private
49
+ def play file
50
+ if !File.exist?(file)
51
+ puts "'#{file}' does not exist."
52
+ exit 1
53
+ end
54
+ if File.directory?(file)
55
+ puts "'#{file}' is not a Gamefic file."
56
+ exit 1
57
+ end
58
+ Dir.mktmpdir 'gamefic_' do |dir|
59
+ puts "Loading..."
60
+ story = Story.new
61
+ begin
62
+ decompress file, dir
63
+ rescue Exception => e
64
+ puts "'#{file}' does not appear to be a valid Gamefic file."
65
+ puts "#{e}"
66
+ exit 1
67
+ end
68
+ story.load dir + '/main.rb'
69
+ engine = Engine.new story
70
+ puts "\n"
71
+ engine.run
72
+ end
73
+ end
74
+ def test path
75
+ puts "Loading..."
76
+ story = Story.new
77
+ #begin
78
+ if File.directory?(path)
79
+ if !File.file?(path + '/main.rb')
80
+ raise "#{path}/main.rb does not exist"
81
+ end
82
+ story.load path + '/main.rb', true
83
+ else
84
+ story.load path
85
+ end
86
+ #rescue Exception => e
87
+ # puts "An error occurred in #{path}:"
88
+ # puts "#{e.inspect}"
89
+ # exit 1
90
+ #end
91
+ engine = Engine.new story
92
+ engine.run
93
+ end
94
+ def init directory
95
+ if directory.to_s == ''
96
+ puts "No directory specified."
97
+ exit 1
98
+ elsif File.exist?(directory)
99
+ if !File.directory?(directory)
100
+ files = Dir[directory + '/*']
101
+ if files.length > 0
102
+ puts "'#{directory}' is not an empty directory."
103
+ exit 1
104
+ end
105
+ else
106
+ puts "'#{directory}' is not an empty directory."
107
+ exit 1
108
+ end
109
+ else
110
+ Dir.mkdir(directory)
111
+ end
112
+ Dir.mkdir(directory + '/import')
113
+ main_rb = File.new(directory + '/main.rb', 'w')
114
+ main_rb.write <<EOS
115
+ import 'basics'
116
+
117
+ room = make Room, :name => 'room'
118
+
119
+ introduction do |player|
120
+ player.parent = room
121
+ player.perform "look"
122
+ end
123
+ EOS
124
+ main_rb.close
125
+ fetch directory
126
+ puts "Game directory '#{directory}' initialized."
127
+ end
128
+ def fetch directory
129
+ if directory.to_s == ''
130
+ puts "No source directory was specified."
131
+ exit 1
132
+ end
133
+ if !File.directory?(directory)
134
+ puts "#{directory} is not a directory."
135
+ exit 1
136
+ end
137
+ puts "Loading game data..."
138
+ story = Story.new
139
+ begin
140
+ story.load directory + '/main.rb', true
141
+ rescue Exception => e
142
+ puts "'#{directory}' has errors or is not a valid source directory."
143
+ puts "#{e}"
144
+ exit 1
145
+ end
146
+ puts "Checking for external script references..."
147
+ fetched = 0
148
+ story.imported_scripts.each { |script|
149
+ if !script.filename.start_with?(directory)
150
+ base = script.filename[(script.filename.rindex('import/') + 7)..-1]
151
+ puts "Fetching #{base}"
152
+ FileUtils.mkdir_p directory + '/import/' + File.dirname(base)
153
+ FileUtils.copy script.filename, directory + '/import/' + base
154
+ fetched += 1
155
+ end
156
+ }
157
+ if fetched == 0
158
+ puts "Nothing to fetch."
159
+ else
160
+ puts "Done."
161
+ end
162
+ end
163
+ def build directory
164
+ if directory.to_s == ''
165
+ puts "No source directory was specified."
166
+ exit 1
167
+ end
168
+ if !File.directory?(directory)
169
+ puts "#{directory} is not a directory."
170
+ exit 1
171
+ end
172
+ filename = File.basename(directory) + '.gfic'
173
+ opts = GetoptLong.new(
174
+ [ '-o', '--output', GetoptLong::REQUIRED_ARGUMENT ]
175
+ )
176
+ opts.quiet = true
177
+ begin
178
+ opts.each { |opt, arg|
179
+ case opt
180
+ when '-o'
181
+ filename = arg
182
+ end
183
+ }
184
+ rescue Exception => e
185
+ puts "#{e}"
186
+ exit 1
187
+ end
188
+ if File.exist?(filename)
189
+ puts "The file #{filename} already exists."
190
+ exit 1
191
+ end
192
+ story = Story.new
193
+ puts "Loading game data..."
194
+ begin
195
+ story.load directory + '/main.rb', true
196
+ rescue Exception => e
197
+ puts "'#{directory}' has errors or is not a valid source directory."
198
+ puts "#{e}"
199
+ exit 1
200
+ end
201
+ puts "Building file..."
202
+ stream = StringIO.new("")
203
+ Gem::Package::TarWriter.new(stream) do |tar|
204
+ Gem::Package::TarHeader.set_mtime Time.now
205
+ tar.add_file('main.rb', 0600) do |io|
206
+ File.open(directory + '/main.rb', "rb") { |f| io.write f.read }
207
+ end
208
+ if story.imported_scripts.length > 0
209
+ Gem::Package::TarHeader.set_mtime Time.now
210
+ tar.mkdir('import', 0700)
211
+ story.imported_scripts.each { |script|
212
+ base = script.filename[script.filename.rindex('import/') + 7..-1]
213
+ Gem::Package::TarHeader.set_mtime Time.now
214
+ tar.add_file('import/' + base, 0700) do |io|
215
+ io.write script.code
216
+ end
217
+ }
218
+ end
219
+ end
220
+ gz = StringIO.new("")
221
+ z = Zlib::GzipWriter.new(gz)
222
+ z.mtime = Time.now
223
+ z.write stream.string
224
+ z.close
225
+ file = File.new(filename, "w")
226
+ file.write gz.string
227
+ file.close
228
+ puts "Gamefic file '#{filename}' complete."
229
+ end
230
+ def help command
231
+ shell_script = File.basename($0)
232
+ case command
233
+ when "play"
234
+ puts <<EOS
235
+ #{shell_script} play [file]
236
+ Play a Gamefic file on the command line.
237
+ EOS
238
+ when "test"
239
+ puts <<EOS
240
+ #{shell_script} test [path]
241
+ Test a Gamefic source directory or script.
242
+ EOS
243
+ when "init"
244
+ puts <<EOS
245
+ #{shell_script} init [directory]
246
+ Initialize a Gamefic source directory. The resulting directory will contain
247
+ source files ready to build into a Gamefic file.
248
+ EOS
249
+ when "fetch"
250
+ puts <<EOS
251
+ #{shell_script} fetch [directory]
252
+ Copy shared scripts to the source directory.
253
+ If the specified game directory imnports external scripts, such as the ones
254
+ that are distributed with the Gamefic gem, this command will copy them into
255
+ the game's import directory. Fetching can be useful if you want to customize
256
+ common features.
257
+ EOS
258
+ when "build"
259
+ puts <<EOS
260
+ #{shell_script} build [directory] [-o | --output filename]
261
+ Build a distributable Gamefic file from the source directory. The default
262
+ filename is [directory].gfic. You can change the filename with the -o option.
263
+ EOS
264
+ when nil, "help"
265
+ puts <<EOS
266
+ #{shell_script} play [file] - play a Gamefic file
267
+ #{shell_script} init [dir] - initialize a Gamefic source directory
268
+ #{shell_script} test [path] - test a Gamefic source directory or script
269
+ #{shell_script} fetch [directory] - copy shared scripts into directory
270
+ #{shell_script} build [directory] - build a Gamefic file
271
+ #{shell_script} help - display this message
272
+ #{shell_script} help [command] - display info about command
273
+ EOS
274
+ else
275
+ puts "Unrecognized command '#{command}'"
276
+ exit 1
277
+ end
278
+ end
279
+ def decompress(tarfile, destination)
280
+ tar_longlink = '././@LongLink'
281
+ Gem::Package::TarReader.new( Zlib::GzipReader.open tarfile ) do |tar|
282
+ dest = nil
283
+ tar.each do |entry|
284
+ if entry.full_name == tar_longlink
285
+ dest = File.join destination, entry.read.strip
286
+ next
287
+ end
288
+ dest ||= File.join destination, entry.full_name
289
+ if entry.directory?
290
+ FileUtils.rm_rf dest unless File.directory? dest
291
+ FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
292
+ elsif entry.file?
293
+ cur_stack = dest.split('/')
294
+ cur_stack.pop
295
+ cur_dir = ''
296
+ while cur_stack.length > 0
297
+ cur_dir += '/' + cur_stack.shift
298
+ if !File.exist?(destination + cur_dir)
299
+ FileUtils.mkdir_p dest, :mode => 0700, :verbose => false
300
+ end
301
+ end
302
+ FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
303
+ FileUtils.rm_rf dest unless File.file? dest
304
+ File.open dest, "wb" do |f|
305
+ f.print entry.read
306
+ end
307
+ FileUtils.chmod entry.header.mode, dest, :verbose => false
308
+ elsif entry.header.typeflag == '2' #Symlink!
309
+ File.symlink entry.header.linkname, dest
310
+ end
311
+ dest = nil
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ end