gamefic 0.0.5 → 0.1.1

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