ifmapper 0.9.6 → 0.9.7

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.
data/HISTORY.txt CHANGED
@@ -1,3 +1,60 @@
1
+ v0.9.7 What's new:
2
+ - IFM exporting was incorrectly exporting
3
+ map "";
4
+ for sections that were unnamed and also when there was only one
5
+ section.
6
+ Now, IFM exporting will not export map name if only one section
7
+ is present. And if multiple sections are present but are unnamed,
8
+ sections will be given the default name of "Section #".
9
+ [Bug Report: Eric Forgeot]
10
+
11
+ - Improved name finding for Classic transcripts, by actually dealing
12
+ with parenthesis in short names a tad more strictly.
13
+
14
+ - Added support for reading GUEMap maps (.gmp format).
15
+ For both, GUEMap v1.0+ and GUEMap v2.0 (beta).
16
+ Comments in GUEmaps will be placed in the task section of rooms.
17
+ As GUEmaps supports placing rooms with a finer grid, IFMapper will
18
+ try to translate grid positions, but may not always be entirely
19
+ successful.
20
+
21
+ - Fixed a crashing bug that could occur very rarely when loading
22
+ multiple maps due to the canvas not being created.
23
+
24
+ - Fixed my email in the README.txt.
25
+
26
+ - Improved warning box to allow for multi-line messages.
27
+ This helps with telling user about errors when reading from
28
+ a file.
29
+
30
+ - Rewrote a big chunk of the map readers (inform/tads). Reason was
31
+ two-fold:
32
+ * make the code smaller and simpler
33
+ * fix a logic problem in the layout algorithm which was
34
+ leading to maps being laid out much bigger when map had
35
+ a lot of one way connections. Now room layout is much, much
36
+ improved.
37
+ [Bug Report: Greg Boettcher]
38
+ * sped up the creation of map code a tad.
39
+
40
+ - Inform reader now understands most special international character
41
+ sequences (@:a, etc).
42
+
43
+ - TADS reader was marking some one way door exits as normal exits.
44
+ Fixed.
45
+
46
+ - TADS reader was not dealing with @location notation properly.
47
+ It was sometimes placing objects in the wrong room, or completely
48
+ ignoring them.
49
+
50
+ - TADS reader now handles exits that are <<replaceAction(Enter,*)>>
51
+
52
+ - Inform and TADS readers will now extract the name of the map from the
53
+ Story constant.
54
+
55
+ - Inform reader is a tad smarter about locating objects.
56
+
57
+
1
58
  v0.9.6 What's new:
2
59
 
3
60
  - IFM exporting had gotten broken around v0.9 due to a refactoring
data/IFMapper.gemspec CHANGED
@@ -2,7 +2,7 @@ require "rubygems"
2
2
 
3
3
  spec = Gem::Specification.new do |spec|
4
4
  spec.name = "ifmapper"
5
- spec.version = '0.9.6'
5
+ spec.version = '0.9.7'
6
6
  spec.author = "Gonzalo Garramuno"
7
7
  spec.email = 'ggarram@advance.dsl.com.ar'
8
8
  spec.homepage = 'http://www.rubyforge.org/projects/ifmapper/'
@@ -170,7 +170,7 @@ end
170
170
 
171
171
 
172
172
  #
173
- # Simple class used as a
173
+ # Simple class used as an adapter for our FXMap<->AStar
174
174
  #
175
175
  class MapNode
176
176
  attr_reader :x, :y
@@ -98,6 +98,16 @@ class Connection
98
98
  @exitText = [0, 0]
99
99
  end
100
100
 
101
+ #
102
+ # Given a room, return the opposite room in the connection.
103
+ # If room passed is not present in connection, raise an exception.
104
+ #
105
+ def opposite(room)
106
+ idx = self.index(room)
107
+ raise "Room #{room} not part of connection #{c}" unless idx
108
+ return @room[idx ^ 1]
109
+ end
110
+
101
111
  #
102
112
  # Given a room, return the index of that room in the room[] array
103
113
  # If room is not present in connection, return nil.
@@ -2,8 +2,8 @@
2
2
 
3
3
  #
4
4
  # Class used to do Postscript printing. This class has only the basic
5
- # functionality needed for doing so and replaces the still somewhat buggy version of
6
- # Fox's 1.2 FXDCPrint
5
+ # functionality needed for doing so and replaces the still somewhat buggy
6
+ # version of Fox's 1.2 FXDCPrint
7
7
  #
8
8
  class FXDCPostscript < FXDC
9
9
  @@printcmd = 'lpr -P%s -o l -' # -#%d'
@@ -1867,7 +1867,7 @@ class FXMap < Map
1867
1867
  # Draw map
1868
1868
  #
1869
1869
  def draw(sender = nil, sel = nil, event = nil)
1870
- return if @mutex.locked?
1870
+ return if @mutex.locked? or not @canvas.created?
1871
1871
 
1872
1872
  if not @image.created?
1873
1873
  puts "Image was not created. Try again"
@@ -1901,7 +1901,6 @@ class FXMap < Map
1901
1901
  dc.end
1902
1902
 
1903
1903
 
1904
-
1905
1904
  # Blit the off-screen image into canvas
1906
1905
  dc = FXDCWindow.new(@canvas)
1907
1906
  dc.setClipRectangle( cx, cy, w, h)
@@ -8,7 +8,7 @@ class FXMapFileDialog < FXFileDialog
8
8
  @@last_path = nil
9
9
 
10
10
  KNOWN_EXTENSIONS = [
11
- "Map Files (*.map,*.ifm,*.inf,*.t,*.t3m)",
11
+ "Map Files (*.map,*.gmp,*.ifm,*.inf,*.t,*.t3m)",
12
12
  "All Files (*)",
13
13
  ]
14
14
 
@@ -59,7 +59,7 @@ require 'IFMapper/FXRoomList'
59
59
  class FXMapperWindow < FXMainWindow
60
60
 
61
61
  PROGRAM_NAME = "Interactive Fiction Mapper"
62
- VERSION = '0.9.6'
62
+ VERSION = '0.9.7'
63
63
  AUTHOR = "Gonzalo Garramuno"
64
64
  TITLE = "#{PROGRAM_NAME} v#{VERSION} - Written by #{AUTHOR}"
65
65
 
@@ -94,7 +94,7 @@ class FXMapperWindow < FXMainWindow
94
94
  begin
95
95
  TADSReader.new(file, map)
96
96
  rescue => e
97
- return "#{e} #{e.backtrace}"
97
+ return "#{e}"
98
98
  end
99
99
  return map
100
100
  end
@@ -104,7 +104,17 @@ class FXMapperWindow < FXMainWindow
104
104
  begin
105
105
  InformReader.new(file, map)
106
106
  rescue => e
107
- return "#{e} #{e.backtrace}"
107
+ return "#{e}"
108
+ end
109
+ return map
110
+ end
111
+
112
+ def open_guemap(file, map)
113
+ require 'IFMapper/GUEReader'
114
+ begin
115
+ GUEReader.new(file, map)
116
+ rescue => e
117
+ return "#{e}"
108
118
  end
109
119
  return map
110
120
  end
@@ -174,13 +184,18 @@ class FXMapperWindow < FXMainWindow
174
184
  tmp = open_inform(file, map)
175
185
  elsif file =~ /\.t$/i or file =~ /\.t3m$/
176
186
  tmp = open_tads(file, map)
187
+ elsif file =~ /\.gmp$/
188
+ tmp = open_guemap(file, map)
177
189
  else
178
190
  tmp = open_map(file)
179
191
  end
180
192
 
181
193
  if not tmp.kind_of?(Map) and not tmp.kind_of?(FXMap)
182
194
  $stderr.puts tmp
183
- status "Could not load '#{file}'. Error: #{tmp}."
195
+ w = FXWarningBox.new( self,
196
+ "#{tmp}")
197
+ w.execute
198
+ status "Could not load '#{file}'."
184
199
  if make_new_map
185
200
  if map.close_cb
186
201
  @maps.delete(map)
@@ -1,6 +1,8 @@
1
1
 
2
2
 
3
-
3
+ #
4
+ # Class used to evaluate a bspline.
5
+ #
4
6
  class FXSpline
5
7
  OS = 1.0 / 6.0
6
8
  FS = 4.0 / 6.0
@@ -20,7 +20,12 @@ class FXWarningBox < FXDialogBox
20
20
  oops.borderColor = 'white'
21
21
  oops.font = font
22
22
 
23
- FXLabel.new(f, text, nil, 0)
23
+ t = FXText.new(f)
24
+ t.text = text
25
+ t.visibleRows = 4
26
+ t.visibleColumns = 80
27
+ t.backColor = f.backColor
28
+ t.disable
24
29
 
25
30
  # Separator
26
31
  FXHorizontalSeparator.new(s,
@@ -0,0 +1,445 @@
1
+
2
+ require "IFMapper/Map"
3
+
4
+ class FXMap; end
5
+
6
+ #
7
+ # Class that allows importing a GUE map file.
8
+ #
9
+ class GUEReader
10
+
11
+ class ParseError < StandardError; end
12
+ class MapError < StandardError; end
13
+
14
+ # This is the GUEmap magic number/ID at the beginning of the file.
15
+ GUE_MAGIC = "\307U\305m\341p"
16
+
17
+ DIRS = {
18
+ 0 => 0, # n
19
+ 1 => 4, # s
20
+ 2 => 2, # e
21
+ 3 => 6, # w
22
+ 4 => 1, # ne
23
+ 5 => 7, # nw
24
+ 6 => 3, # se
25
+ 7 => 5, # sw
26
+
27
+ # GUE Map also supports connections to the sides of centers
28
+ #
29
+ # -*--*-
30
+ # | |
31
+ # -*--*-
32
+ #
33
+ # We translate these as corner connections.
34
+ #
35
+ 8 => [0, 1], # ----*-
36
+ 9 => [0, 7],
37
+ 10 => [4, 3], # ----*-
38
+ 11 => [4, 5],
39
+
40
+ # DOT directions
41
+ # 12 => 4,
42
+ }
43
+
44
+
45
+ class Complex
46
+ attr_accessor :roomA, :dirA, :roomB, :dirB
47
+ end
48
+
49
+
50
+ attr_reader :map
51
+
52
+ @@debug = nil
53
+
54
+ def debug(x)
55
+ if @@debug.to_i > 0
56
+ $stderr.puts x
57
+ end
58
+ end
59
+
60
+ #
61
+ # Read GUEMap header from file stream
62
+ #
63
+ def header
64
+ magic = @f.read(6)
65
+ if magic != GUE_MAGIC
66
+ raise ParseError, "Not a GUEMap"
67
+ end
68
+ @f.read(4)
69
+ version = @f.read(2).unpack('s')[0]
70
+ debug "VERSION: #{version}"
71
+ case version
72
+ when 4
73
+ crap = @f.read(2)
74
+ crap = @f.read(1)[0]
75
+ if crap == 32
76
+ len = @f.read(1)[0]
77
+ @map.name = @f.read(len)
78
+ crap = @f.read(1)[0]
79
+ end
80
+ if crap == 40
81
+ len = @f.read(1)[0]
82
+ if len == 255
83
+ len = @f.read(2).unpack('s')[0]
84
+ end
85
+ @f.read(len)
86
+ crap = @f.read(1)[0]
87
+ end
88
+ hdr = @f.read(15)
89
+ when 3,2
90
+ crap = @f.read(2)
91
+ crap = @f.read(1)[0]
92
+ if crap == 32
93
+ len = @f.read(1)[0]
94
+ @map.name = @f.read(len)
95
+ crap = @f.read(1)[0]
96
+ end
97
+ if crap == 40
98
+ len = @f.read(1)[0]
99
+ if len == 255
100
+ len = @f.read(2).unpack('s')[0]
101
+ end
102
+ @f.read(len)
103
+ crap = @f.read(1)[0]
104
+ end
105
+ hdr = @f.read(8)
106
+ else
107
+ raise ParseError, "Unknown GUEMap version #{version}"
108
+ end
109
+ end
110
+
111
+ #
112
+ # Read a connection from file stream
113
+ #
114
+ def read_connection
115
+ type = Connection::FREE
116
+ dir = Connection::BOTH
117
+ dA = @f.read(1)[0]
118
+ a = @f.read(1)[0]
119
+ stub = false
120
+
121
+ opt = @f.read(1)[0]
122
+ exitAtext = exitBtext = 0
123
+
124
+ case opt
125
+ when 26
126
+ #normal connection
127
+ when 34
128
+ # up/down/in/out connection
129
+ exitAtext = @f.read(1)[0]
130
+ if exitAtext & 8 != 0
131
+ exitAtext -= 8
132
+ type = Connection::SPECIAL
133
+ end
134
+ if exitAtext & 16 != 0
135
+ # no exit connection
136
+ stub = true
137
+ exitAtext -= 16
138
+ b = a
139
+ dB = dA
140
+ end
141
+ if exitAtext & 32 != 0
142
+ # stub connection
143
+ stub = true
144
+ exitAtext -= 32
145
+ end
146
+ if exitAtext & 64 != 0
147
+ exitAtext -= 64
148
+ dir = Connection::AtoB
149
+ end
150
+ exitBtext = @f.read(1)[0]
151
+ data = @f.read(1)[0]
152
+ else
153
+ raise ParseError, "option: #{opt} unknown"
154
+ end
155
+
156
+ unless stub
157
+ dB = @f.read(1)[0]
158
+ b = @f.read(1)[0]
159
+ opt = @f.read(1)[0]
160
+ end
161
+
162
+ data = @f.read(1)[0]
163
+ data = @f.read(1)[0] unless @f.eof?
164
+
165
+ section = @map.sections[@map.section]
166
+ roomA = section.rooms[a]
167
+ roomB = section.rooms[b] if b
168
+
169
+
170
+ debug "Index : #{a}->#{b}"
171
+ debug "dA: #{dA} dB: #{dB}"
172
+ if roomA.name == '*'
173
+ if roomB.x != roomA.x or roomB.y != roomA.y
174
+ dirA = roomA.exit_to(roomB)
175
+ else
176
+ dirA = 0
177
+ end
178
+ else
179
+ dirA = DIRS[dA]
180
+ if dirA.kind_of?(Array)
181
+ dirA.each { |d|
182
+ next if roomA[d]
183
+ dirA = d
184
+ break
185
+ }
186
+ if dirA.kind_of?(Array)
187
+ dirA = dirA[0]
188
+ end
189
+ end
190
+ raise "Unknown direction #{dA}" unless dirA
191
+ end
192
+
193
+ if b
194
+ if roomB.name == '*'
195
+ dirB = (dirA + 4) % 8
196
+ else
197
+ dirB = DIRS[dB]
198
+ if dirB.kind_of?(Array)
199
+ dirB.each { |d|
200
+ next if roomB[d]
201
+ dirB = d
202
+ break
203
+ }
204
+ if dirB.kind_of?(Array)
205
+ dirB = dirB[0]
206
+ end
207
+ end
208
+ raise "Unknown direction #{dB}" unless dirB
209
+ end
210
+ end
211
+
212
+
213
+ if roomA[dirA]
214
+ dirA = roomA.exits.index(nil)
215
+ end
216
+
217
+ if roomB
218
+ if roomB[dirB]
219
+ dirB = roomB.exits.rindex(nil)
220
+ end
221
+ end
222
+
223
+
224
+ debug "Connect: #{roomA} ->#{roomB} "
225
+ debug "dirA: #{dirA} dirB: #{dirB}"
226
+ debug "exitA: #{exitAtext} exitB: #{exitBtext}"
227
+
228
+ begin
229
+ c = map.new_connection( roomA, dirA, roomB, dirB )
230
+
231
+ c.exitAtext = exitAtext
232
+ c.exitBtext = exitBtext
233
+ c.dir = dir
234
+ c.type = type
235
+
236
+ debug c
237
+ rescue Section::ConnectionError
238
+ end
239
+
240
+ end
241
+
242
+
243
+ #
244
+ # Read a room from file stream
245
+ #
246
+ def read_room
247
+ x = @f.read(1)[0]
248
+ data = @f.read(1)
249
+ y = @f.read(1)[0]
250
+ data = @f.read(1)[0]
251
+
252
+ type = @f.read(1)[0]
253
+
254
+ case type
255
+ when 41
256
+ r = @map.new_room(x, y)
257
+ r.name = "*"
258
+ data = @f.read(4)
259
+ if data[0] == 0
260
+ r.name = ''
261
+ end
262
+ return
263
+ when 0
264
+ r = @map.new_room(x, y)
265
+ r.name = ''
266
+ @f.read(2)
267
+ return
268
+ end
269
+
270
+ len = @f.read(1)[0]
271
+ name = ''
272
+ comment = ''
273
+
274
+ name = @f.read(len)
275
+ test = @f.read(1)[0]
276
+
277
+ case test
278
+ when 41
279
+ @f.read(4)
280
+ when 0
281
+ data = @f.read(2)
282
+ else
283
+ len = @f.read(1)[0]
284
+ if len == 255
285
+ len = @f.read(2).unpack('s')[0]
286
+ end
287
+ comment = @f.read(len)
288
+ data = @f.read(3)
289
+ end
290
+ debug "Room: #{name.inspect} pos: #{x}, #{y}"
291
+ debug "Comment: #{comment.inspect}"
292
+
293
+ # r = @map.new_room(x, y)
294
+ r = @map.new_room(x, y)
295
+ name.gsub!(/[\r\n]+/, ' ')
296
+ comment.gsub!(/\r/, '')
297
+
298
+ r.name = name
299
+ r.tasks = comment
300
+ end
301
+
302
+ #
303
+ # Main parsing loop.
304
+ #
305
+ def parse
306
+ header
307
+ while not @f.eof?
308
+ type = @f.read(1)[0]
309
+ data = @f.read(1)[0]
310
+
311
+ debug "type: #{type}"
312
+ debug "data: #{data}"
313
+
314
+ case type
315
+ when 2
316
+ read_room
317
+ when 3
318
+ read_connection
319
+ when 4
320
+ break
321
+ else
322
+ raise "Unknown type: #{type}"
323
+ end
324
+ end
325
+ end
326
+
327
+ def remove_dots
328
+ @map.sections.each do |sect|
329
+ sect.connections.each do |c|
330
+ a, b = c.room
331
+ next unless b
332
+ next if a.name == '*' or b.name != '*'
333
+ # OK. We have a ROOM->* connection. Follow it to find other room.
334
+ # And turn it into a complex connection.
335
+ dirA = a.exits.index(c)
336
+ dirB = b.exits.rindex(c)
337
+ origB = dirB
338
+ dir = Connection::BOTH
339
+ dirB = nil
340
+ cc = c
341
+ while b.name == '*'
342
+ found = false
343
+ b.exits.each { |e|
344
+ next if not e or e == cc
345
+ found = true
346
+ idx = e.index(b) ^ 1
347
+ b = e.room[idx]
348
+ dirB = b.exits.index(e)
349
+ dir = e.dir
350
+ cc = e
351
+ break
352
+ }
353
+ if not found
354
+ debug "not found complement for #{cc}"
355
+ break
356
+ end
357
+ end
358
+
359
+ next if b.name == '*'
360
+
361
+ # remove the other connection
362
+ sect.delete_connection(cc)
363
+
364
+ c.roomB[origB] = nil
365
+
366
+ # Make connection a complex connection and remove old
367
+ # connection in b room
368
+ c.roomB = b
369
+ c.dir = dir
370
+ b[dirB] = c
371
+ end
372
+ end
373
+
374
+
375
+ # Now, remove all DOT rooms and their connections
376
+ @map.sections.each { |sect|
377
+ del = sect.rooms.find_all { |r| r.name == '*' }
378
+ del.each { |r|
379
+ sect.delete_room(r)
380
+ }
381
+ }
382
+ end
383
+
384
+ def reposition
385
+ @map.sections.each do |sect|
386
+ minXY, = sect.min_max_rooms
387
+ sect.rooms.each do |r|
388
+ r.x -= minXY[0]
389
+ r.y -= minXY[1]
390
+ r.x /= 4.0
391
+ r.y /= 4.0
392
+ end
393
+ end
394
+
395
+ @map.sections.each do |sect|
396
+ sect.rooms.each_with_index do |r, idx|
397
+ x = r.x
398
+ y = r.y
399
+ xf = x.floor
400
+ yf = y.floor
401
+ if x != xf or y != yf
402
+ sect.rooms.each do |r2|
403
+ next if r == r2
404
+ if xf == r2.x.floor and yf == r2.y.floor
405
+ dx = (x - xf).ceil
406
+ dy = (y - yf).ceil
407
+ sect.shift(x, y, dx, dy)
408
+ break
409
+ end
410
+ end
411
+ end
412
+ r.x = r.x.floor.to_i
413
+ r.y = r.y.floor.to_i
414
+ end
415
+ end
416
+ end
417
+
418
+
419
+ def initialize(file, map = Map.new('GUE Imported Map'))
420
+ @map = map
421
+ @complex = []
422
+
423
+ @f = File.open(file, 'rb')
424
+ parse
425
+
426
+ reposition
427
+ remove_dots
428
+
429
+ @map.fit
430
+ @map.section = 0
431
+ if @map.kind_of?(FXMap)
432
+ @map.filename = file.sub(/\.gmp$/i, '.map')
433
+ @map.navigation = true
434
+ @map.window.show
435
+ end
436
+ end
437
+ end
438
+
439
+
440
+ if $0 == __FILE__
441
+ puts "Opening file '#{ARGV[0]}'"
442
+ require "IFMapper/Map"
443
+
444
+ GUEReader.new(ARGV[0])
445
+ end