ifmapper 0.9.6 → 0.9.7

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