ifmapper 0.5

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 (87) hide show
  1. data/HISTORY.txt +2 -0
  2. data/IFMapper.gemspec +21 -0
  3. data/IFMapper.rb +27 -0
  4. data/icons/copy.png +0 -0
  5. data/icons/cut.png +0 -0
  6. data/icons/filenew.png +0 -0
  7. data/icons/fileopen.png +0 -0
  8. data/icons/filesave.png +0 -0
  9. data/icons/filesaveas.png +0 -0
  10. data/icons/help.png +0 -0
  11. data/icons/kill.png +0 -0
  12. data/icons/nextpage.png +0 -0
  13. data/icons/paste.png +0 -0
  14. data/icons/prevpage.png +0 -0
  15. data/icons/printicon.png +0 -0
  16. data/icons/redo.png +0 -0
  17. data/icons/saveas.png +0 -0
  18. data/icons/undo.png +0 -0
  19. data/icons/winapp.png +0 -0
  20. data/icons/zoom.png +0 -0
  21. data/lib/IFMapper/Connection.rb +63 -0
  22. data/lib/IFMapper/FXAboutDialogBox.rb +32 -0
  23. data/lib/IFMapper/FXConnection.rb +283 -0
  24. data/lib/IFMapper/FXConnectionDialogBox.rb +126 -0
  25. data/lib/IFMapper/FXMap.rb +1614 -0
  26. data/lib/IFMapper/FXMapDialogBox.rb +51 -0
  27. data/lib/IFMapper/FXMapFileDialog.rb +29 -0
  28. data/lib/IFMapper/FXMapperSettings.rb +45 -0
  29. data/lib/IFMapper/FXMapperWindow.rb +1051 -0
  30. data/lib/IFMapper/FXPage.rb +24 -0
  31. data/lib/IFMapper/FXPageDialogBox.rb +38 -0
  32. data/lib/IFMapper/FXRoom.rb +218 -0
  33. data/lib/IFMapper/FXRoomDialogBox.rb +119 -0
  34. data/lib/IFMapper/FXSearchDialogBox.rb +51 -0
  35. data/lib/IFMapper/FXSpline.rb +54 -0
  36. data/lib/IFMapper/FXWarningBox.rb +45 -0
  37. data/lib/IFMapper/IFMReader.rb +613 -0
  38. data/lib/IFMapper/Map.rb +110 -0
  39. data/lib/IFMapper/PDFMapExporter.rb +315 -0
  40. data/lib/IFMapper/Page.rb +158 -0
  41. data/lib/IFMapper/Room.rb +104 -0
  42. data/maps/Bureaucracy.ifm +75 -0
  43. data/maps/Hollywood_Hijinx.ifm +149 -0
  44. data/maps/Jigsaw.ifm +806 -0
  45. data/maps/LGOP.ifm +705 -0
  46. data/maps/Mercy.ifm +76 -0
  47. data/maps/Planetfall.ifm +186 -0
  48. data/maps/Plundered_Hearts.ifm +251 -0
  49. data/maps/Ralph.ifm +50 -0
  50. data/maps/Robots_of_Dawn.ifm +224 -0
  51. data/maps/Seastalker.ifm +149 -0
  52. data/maps/Sherlock.ifm +209 -0
  53. data/maps/SoFar.ifm +72 -0
  54. data/maps/Starcross.ifm +170 -0
  55. data/maps/Suspended.ifm +82 -0
  56. data/maps/Wishbringer.ifm +277 -0
  57. data/maps/Wishbringer2.ifm +246 -0
  58. data/maps/Zork1.ifm +410 -0
  59. data/maps/Zork2.ifm +150 -0
  60. data/maps/Zork3.ifm +136 -0
  61. data/maps/Zork_Zero.ifm +557 -0
  62. data/maps/anchor.ifm +645 -0
  63. data/maps/atrox.ifm +134 -0
  64. data/maps/awaken.ifm +116 -0
  65. data/maps/babel.ifm +279 -0
  66. data/maps/bse.ifm +150 -0
  67. data/maps/change.ifm +128 -0
  68. data/maps/curses.ifm +307 -0
  69. data/maps/curves.ifm +529 -0
  70. data/maps/edifice.ifm +158 -0
  71. data/maps/frozen.ifm +126 -0
  72. data/maps/glow.ifm +101 -0
  73. data/maps/library.ifm +93 -0
  74. data/maps/mindelec.ifm +89 -0
  75. data/maps/minster.ifm +234 -0
  76. data/maps/muse.ifm +154 -0
  77. data/maps/paperchase.ifm +110 -0
  78. data/maps/space_st.ifm +104 -0
  79. data/maps/stationfall.ifm +320 -0
  80. data/maps/theatre.ifm +182 -0
  81. data/maps/toonesia.ifm +54 -0
  82. data/maps/tortoise.ifm +72 -0
  83. data/maps/vgame.ifm +219 -0
  84. data/maps/weather.ifm +98 -0
  85. data/maps/windhall.ifm +154 -0
  86. data/maps/zebulon.ifm +68 -0
  87. metadata +144 -0
@@ -0,0 +1,54 @@
1
+
2
+
3
+ class FXPoint
4
+ def [](idx)
5
+ return x if idx == 0
6
+ return y if idx == 1
7
+ end
8
+ end
9
+
10
+ class FXSpline
11
+ def self.bspline_solve( r, p, num )
12
+ ax = -1.0/6 * p[0][0] + 0.5 * p[1][0] - 0.5 * p[2][0] + 1.0/6 * p[3][0]
13
+ bx = 0.5 * p[0][0] - p[1][0] + 0.5 * p[2][0]
14
+ cx = -0.5 * p[0][0] + 0.5 * p[2][0]
15
+ dx = 1.0/6 * p[0][0] + 4.0/6 * p[1][0] + 1.0/6 * p[2][0]
16
+
17
+ ay = -1.0/6 * p[0][1] + 0.5 * p[1][1] - 0.5 * p[2][1] + 1.0/6 * p[3][1]
18
+ by = 0.5 * p[0][1] - p[1][1] + 0.5 * p[2][1]
19
+ cy = -0.5 * p[0][1] + 0.5 * p[2][1]
20
+ dy = 1.0/6 * p[0][1] + 4.0/6 * p[1][1] + 1.0/6 * p[2][1]
21
+
22
+ t = 0.0
23
+ tinc = 1.0 / num
24
+ 0.upto(num) {
25
+ x = t * (t * (t * ax + bx) + cx) + dx
26
+ y = t * (t * (t * ay + by) + cy) + dy
27
+ r << [ x, y ]
28
+ t += tinc
29
+ }
30
+ end
31
+
32
+ def self.bspline(p)
33
+ pts = []
34
+ 0.upto(p.size-4) { |x|
35
+ x2 = p[x+2][0] - p[x+1][0]
36
+ x2 = x2 * x2
37
+ y2 = p[x+2][1] - p[x+1][1]
38
+ y2 = y2 * y2
39
+ num = Math.sqrt( x2 + y2 ).to_i / 8
40
+ bspline_solve(pts, p[x..x+3], num)
41
+ }
42
+ return pts
43
+ end
44
+ end
45
+
46
+ class FXDC
47
+ # Draw a bspline curve of any number of segments
48
+ def drawBSpline(p)
49
+ tmp = FXSpline::bspline(p)
50
+ pts = tmp.collect { |x| FXPoint.new( x[0].to_i, x[1].to_i) }
51
+ drawLines(pts)
52
+ return pts
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+
2
+
3
+ class FXWarningBox < FXDialogBox
4
+ def initialize(parent, text)
5
+ super( parent, "Warning", DECOR_ALL, 0, 0, 400, 130)
6
+ # Frame
7
+ s = FXVerticalFrame.new(self,
8
+ LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
9
+
10
+ f = FXHorizontalFrame.new(s, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y)
11
+
12
+ font = FXFont.new(app, "Helvetica", 30)
13
+ oops = FXLabel.new(f, "!", nil, 0, LAYOUT_SIDE_LEFT|LAYOUT_FILL_X|
14
+ LAYOUT_CENTER_Y)
15
+ oops.frameStyle = FRAME_RAISED|FRAME_THICK
16
+ oops.baseColor = 'dark grey'
17
+ oops.textColor = 'red'
18
+ oops.padLeft = oops.padRight = 15
19
+ oops.shadowColor = 'black'
20
+ oops.borderColor = 'white'
21
+ oops.font = font
22
+
23
+ FXLabel.new(f, text, nil, 0)
24
+
25
+ # Separator
26
+ FXHorizontalSeparator.new(s,
27
+ LAYOUT_SIDE_TOP|LAYOUT_FILL_X|SEPARATOR_GROOVE)
28
+
29
+ # Bottom buttons
30
+ buttons = FXHorizontalFrame.new(s,
31
+ LAYOUT_SIDE_BOTTOM|FRAME_NONE|
32
+ LAYOUT_FILL_X|PACK_UNIFORM_WIDTH)
33
+ # Accept
34
+ yes = FXButton.new(buttons, "&Yes", nil, self, FXDialogBox::ID_ACCEPT,
35
+ FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|
36
+ LAYOUT_RIGHT|LAYOUT_CENTER_Y)
37
+
38
+ # Cancel
39
+ no = FXButton.new(buttons, "&No", nil, self, FXDialogBox::ID_CANCEL,
40
+ FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|
41
+ LAYOUT_RIGHT|LAYOUT_CENTER_Y)
42
+ no.setDefault
43
+ no.setFocus
44
+ end
45
+ end
@@ -0,0 +1,613 @@
1
+
2
+ require "IFMapper/Map"
3
+
4
+ class FXMap; end
5
+
6
+ #
7
+ # Class that allows importing an IFM map file.
8
+ #
9
+ class IFMReader
10
+
11
+ class ParseError < StandardError; end
12
+ class MapError < StandardError; end
13
+
14
+
15
+ DIRECTIONS = {
16
+ 'north' => 0,
17
+ 'n' => 0,
18
+ 'northeast' => 1,
19
+ 'ne' => 1,
20
+ 'east' => 2,
21
+ 'e' => 2,
22
+ 'southeast' => 3,
23
+ 'se' => 3,
24
+ 'south' => 4,
25
+ 's' => 4,
26
+ 'southwest' => 5,
27
+ 'sw' => 5,
28
+ 'west' => 6,
29
+ 'w' => 6,
30
+ 'northwest' => 7,
31
+ 'nw' => 7,
32
+ }
33
+
34
+ GO = {
35
+ 'd' => 2,
36
+ 'down' => 2,
37
+ 'up' => 1,
38
+ 'u' => 1,
39
+ 'i' => 3,
40
+ 'in' => 3,
41
+ 'o' => 4,
42
+ 'out' => 4,
43
+ }
44
+
45
+ attr_reader :map
46
+
47
+ #
48
+ # Main parsing loop. We basically parse the file twice to
49
+ # solve dependencies. Yes, this is inefficient, but the alternative
50
+ # was to build a full parser that understands forward dependencies.
51
+ #
52
+ def parse(file)
53
+ # We start map at 1, 1
54
+ @x, @y = [0, 0]
55
+ @room = @item = @task = nil
56
+ @map.page = 0
57
+ @last_page = 0
58
+ @ignore_first_section = true
59
+ @room_idx = 0
60
+ line_number = 0
61
+
62
+ while not file.eof?
63
+ @line = ''
64
+ while not file.eof? and @line !~ /;\s*(#.*)?$/
65
+ @line << file.readline()
66
+ @line.sub!( /^\s*#.*/, '')
67
+ line_number += 1
68
+ end
69
+ @line.sub!( /;\s*#.*$/, '')
70
+ @line.sub! /^\s+/, ''
71
+ @line.gsub! /;\s*$/, ''
72
+ @line.gsub! /\n/, ' '
73
+ next if @line == ''
74
+ full_line = @line.dup
75
+ # puts "#{line_number}:'#{@line}'"
76
+ begin
77
+ parse_line
78
+ rescue ParseError => e
79
+ $stderr.puts
80
+ $stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
81
+ $stderr.puts ">>>> #{full_line};"
82
+ $stderr.puts
83
+ rescue => e
84
+ $stderr.puts
85
+ $stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
86
+ $stderr.puts ">>>> #{full_line};"
87
+ $stderr.puts e.backtrace if not e.kind_of?(MapError)
88
+ end
89
+ end
90
+ end
91
+
92
+ #
93
+ # see if line is a variable line. if so, return true
94
+ #
95
+ def parse_variable
96
+ @line =~ /^\s*[\w_\-\.]+\s*=.*$/
97
+ end
98
+
99
+ #
100
+ # Get a quoted string from line
101
+ #
102
+ def get_string
103
+ str = nil
104
+ if @line =~ /^"(([^"\\]|\\")*)"/
105
+ str = $1
106
+ @line = @line[str.size+2..-1]
107
+ # Change any quoted " to normal "
108
+ str.gsub! /\\"/, '"'
109
+ end
110
+ return str
111
+ end
112
+
113
+ #
114
+ # Get a new token from line
115
+ #
116
+ def get_token
117
+ return nil if not @line
118
+ token, @line = @line.split(' ', 2)
119
+ return token
120
+ end
121
+
122
+ #
123
+ # Look-ahead a token, without modifying line
124
+ #
125
+ def next_token
126
+ return nil if not @line
127
+ token, = @line.split(' ', 2)
128
+ return token
129
+ end
130
+
131
+ #
132
+ # Return whether next token is a tag or a language keyword
133
+ #
134
+ def is_tag?
135
+ token = next_token
136
+ case token
137
+ when nil,
138
+ 'to', 'cmd', 'from', 'need', 'lose', 'lost', 'tag', 'all', 'except',
139
+ 'before', 'after', 'start', 'style', 'endstyle', 'follow', 'link',
140
+ 'until', 'dir', 'start', 'get', 'drop', 'goto', 'give', 'given',
141
+ 'exit', 'in', 'keep', 'any', 'safe', 'score', 'style', 'go',
142
+ 'hidden', 'oneway', 'finish', 'length', 'nopath', 'note', 'leave',
143
+ 'join', /^#/
144
+ return false
145
+ else
146
+ return true
147
+ end
148
+ end
149
+
150
+ #
151
+ # Get a tag
152
+ #
153
+ def get_tag
154
+ tag = get_token
155
+ if tag == 'it'
156
+ return [tag, (@item or @task or @room)]
157
+ elsif tag == 'last' or tag == 'all' or tag == 'any'
158
+ return [tag, nil]
159
+ else
160
+ if not @tags[tag]
161
+ if @resolve_tags
162
+ raise ParseError, "Tag <#{tag}> not known"
163
+ end
164
+ end
165
+ return [tag, @tags[tag]]
166
+ end
167
+ end
168
+
169
+ #
170
+ # Get a room
171
+ #
172
+ def get_room
173
+ tag, room = get_tag
174
+ return @room if tag == 'last'
175
+ if room and not room.kind_of?(Room)
176
+ raise ParseError, "Not a room tag <#{tag}:#{room}>"
177
+ end
178
+ return room
179
+ end
180
+
181
+ #
182
+ # Get an item
183
+ #
184
+ def get_item
185
+ tag, item = get_tag
186
+ return @item if tag == 'last'
187
+ if item and item.kind_of?(Room)
188
+ raise ParseError, "Not an item tag <#{tag}:#{item}>"
189
+ end
190
+ return item
191
+ end
192
+
193
+ #
194
+ # Get a task
195
+ #
196
+ def get_task
197
+ tag, task = get_tag
198
+ return @task if tag == 'last'
199
+ if task and task.kind_of?(Room)
200
+ raise ParseError, "Not an task tag <#{tag}:#{item}>"
201
+ end
202
+ return task
203
+ end
204
+
205
+ #
206
+ # Parse a line of file
207
+ #
208
+ def parse_line
209
+ return if parse_variable
210
+
211
+ roomname = tagname = nil
212
+ @task = @item = roomA = roomB =
213
+ from = one_way = nolink = go = nil
214
+ styles = []
215
+ links = []
216
+ dir = []
217
+
218
+
219
+ roomA = @room # add item to this room
220
+
221
+ while @line and token = get_token
222
+ case token
223
+ when 'map'
224
+ section = get_string
225
+ # Once we start a new section, we rest room and
226
+ # current position.
227
+ @room = nil
228
+ @x = @y = 1
229
+ # dont' add a page for first 'map' keyword
230
+ if @ignore_first_section
231
+ @ignore_first_section = false
232
+ @map.pages[0].name = section
233
+ next
234
+ end
235
+ if @resolve_tags
236
+ @map.page = @last_page + 1
237
+ @last_page = @map.page
238
+ else
239
+ @map.new_page
240
+ @map.pages[-1].name = section
241
+ end
242
+ when 'title'
243
+ @map.name = get_string
244
+ when 'room'
245
+ roomname = get_string
246
+ @ignore_first_section = false
247
+ when 'tag'
248
+ tagname = get_token
249
+ when 'dir'
250
+ # if not roomname
251
+ # raise ParseError, 'dir directive found but not for a room'
252
+ # end
253
+ token = next_token
254
+ while DIRECTIONS[token]
255
+ get_token
256
+ dir.push(DIRECTIONS[token])
257
+ token = next_token
258
+
259
+ if token =~ /^\d+$/
260
+ get_token
261
+ (token.to_i - 1).times { dir.push(dir[-1]) }
262
+ token = next_token
263
+ end
264
+ end
265
+ when 'nolink'
266
+ if dir.empty?
267
+ raise ParseError, 'nolink directive, but no dir directive before.'
268
+ end
269
+ nolink = true
270
+ when 'oneway'
271
+ one_way = true
272
+ when 'nopath'
273
+ when 'safe'
274
+ when 'exit'
275
+ if not roomname
276
+ raise ParseError, 'exit directive found but not for a room'
277
+ end
278
+ token = next_token
279
+ while DIRECTIONS[token]
280
+ get_token
281
+ token = next_token
282
+ end
283
+ when 'from'
284
+ if dir.empty?
285
+ raise ParseError, "'from' token found but no dir specified before"
286
+ end
287
+ from = get_room
288
+ when 'join'
289
+ # Joins are links that are not drawn.
290
+ # Mainly to give the engine knowledge that two locations
291
+ # are interconnected
292
+ get_room while is_tag?
293
+ to = next_token
294
+ if to == 'to'
295
+ get_token
296
+ get_room
297
+ end
298
+ when 'all'
299
+ when 'lost'
300
+ when 'except'
301
+ get_item while is_tag?
302
+ when 'length'
303
+ get_token
304
+ when 'until'
305
+ get_task while is_tag?
306
+ when 'link'
307
+ if roomname
308
+ while is_tag?
309
+ links.push( get_room )
310
+ end
311
+ else
312
+ roomA = get_room
313
+ to = next_token
314
+ if to == 'to'
315
+ get_token
316
+ roomB = get_room
317
+ end
318
+ end
319
+ when 'goto'
320
+ get_room
321
+ when 'go'
322
+ token = get_token
323
+ go = GO[token]
324
+ if not token
325
+ raise ParseError, "Token <#{token}> is an unknown go direction."
326
+ end
327
+ when 'item'
328
+ @item = get_string
329
+ item_tag = get_token if not @item
330
+ when 'in'
331
+ roomA = get_room # oh, well... this room
332
+ when 'note'
333
+ note = get_string
334
+ when 'keep'
335
+ token = next_token
336
+ if token == 'with'
337
+ get_token
338
+ item_keep = get_item
339
+ end
340
+ when 'given'
341
+ when 'give'
342
+ give_items = [ get_item ]
343
+ give_items.push(get_item) while is_tag?
344
+ when 'start'
345
+ when 'finish'
346
+ when 'follow'
347
+ task = get_task
348
+ when 'need'
349
+ need_items = [ get_item ]
350
+ need_items.push(get_item) while is_tag?
351
+ when 'after'
352
+ after_tasks = [ get_item ]
353
+ after_tasks.push(get_item) while is_tag?
354
+ when 'lose'
355
+ loose_items = [ get_item ]
356
+ loose_items.push(get_item) while is_tag?
357
+ when 'get'
358
+ get_items = [ get_item ]
359
+ get_items.push(get_item) while is_tag?
360
+ when 'drop'
361
+ drop_items = [ get_item ]
362
+ drop_items.push(get_item) while is_tag?
363
+ when 'hidden'
364
+ when 'leave'
365
+ leave_items = [ get_item ]
366
+ leave_items.push(get_item) while is_tag?
367
+ when 'before'
368
+ task = get_task
369
+ when 'cmd'
370
+ token = next_token
371
+ if token == 'to'
372
+ get_token
373
+ cmd = get_string
374
+ elsif token == 'from'
375
+ get_token
376
+ cmd = get_string
377
+ elsif token == 'none'
378
+ get_token
379
+ else
380
+ cmd = get_string
381
+ num = next_token
382
+ get_token if num =~ /\d+/
383
+ end
384
+ when 'score'
385
+ score = get_token.to_i
386
+ when 'length'
387
+ length = get_token.to_i
388
+ when 'task'
389
+ @task = get_string
390
+ task_tag = get_token if not @task
391
+ when 'style'
392
+ styles.push(get_token) while is_tag?
393
+ when 'endstyle'
394
+ get_token while is_tag?
395
+ when '', /^#/
396
+ get_token while @line
397
+ else
398
+ raise ParseError, "Token <#{token.inspect}> not understood"
399
+ end
400
+ end
401
+
402
+ # If a direction, move that way.
403
+ if dir.size > 0
404
+ # If from keyword present, move from that room on.
405
+ if from
406
+ roomA = from
407
+ # 'from' can also connect stuff not in current page...
408
+ # so we check we are in the right page.
409
+ @map.pages.each_with_index { |p, idx|
410
+ if p.rooms.include?(roomA)
411
+ @map.page = idx
412
+ break
413
+ end
414
+ }
415
+ end
416
+ # Okay, we start from roomA... and follow each dir
417
+ if roomA
418
+ @x = roomA.x
419
+ @y = roomA.y
420
+ end
421
+ dir.each { |d|
422
+ x, y = Room::DIR_TO_VECTOR[d]
423
+ @x += x
424
+ @y += y
425
+ }
426
+ end
427
+
428
+ # Create new room
429
+ if roomname
430
+ if @resolve_tags
431
+ # Room is already created. Find it.
432
+ roomB = @rooms[@room_idx]
433
+ @room_idx += 1
434
+ else
435
+ # Verify there's no room in that location yet
436
+ page = @map.pages[@map.page]
437
+ page.rooms.each { |r|
438
+ if r.x == @x and r.y == @y
439
+ err = "Page #{@map.page+1} already has location #{r} at #{@x}, #{@y}.\n"
440
+ err << "Cannot create '#{roomname}'"
441
+ raise MapError, err
442
+ end
443
+ }
444
+
445
+ # Remember original room for connection
446
+ roomB = @map.new_room( @x, @y )
447
+ roomB.name = roomname
448
+ @rooms.push( roomB )
449
+ end
450
+
451
+ # Make roomB the current room
452
+ @room = roomB
453
+ end
454
+
455
+ if @item and roomA and @resolve_tags
456
+ roomA.objects << @item + "\n"
457
+ end
458
+
459
+ if @task and @room and @resolve_tags
460
+ @room.tasks << @task + "\n"
461
+ end
462
+
463
+ # Add a link between rooms
464
+ if roomA and roomB and not nolink and @resolve_tags
465
+ # Establish new simple connection
466
+ dirB = (dir[-1] + 4) % 8 if dir.size > 1
467
+
468
+ if dir.size > 1
469
+ dirB = [ @x - roomB.x, @y - roomB.y ]
470
+ if dirB[0] == 0 and dirB[1] == 0
471
+ dirB = (dir[-1] + 4) % 8
472
+ else
473
+ dirB = roomB.vector_to_dir( dirB[0], dirB[1] )
474
+ end
475
+ end
476
+
477
+ # 'from' and 'link' keywords can also connect stuff not in
478
+ # current page... so we check for that here
479
+ @map.pages.each_with_index { |p, idx|
480
+ if p.rooms.include?(roomA)
481
+ @map.page = idx
482
+ break
483
+ end
484
+ }
485
+ if not @map.pages[@map.page].rooms.include?(roomB)
486
+ raise MapError, "Linking #{roomA} and #{roomB} which are in different pages"
487
+ end
488
+
489
+ begin
490
+ c = map.new_connection( roomA, dir[0], roomB, dirB )
491
+ c.dir = Connection::AtoB if one_way
492
+ c.type = Connection::SPECIAL if styles.include?('special')
493
+
494
+ if go
495
+ c.exitAtext = go
496
+ if go % 2 == 0
497
+ c.exitBtext = go - 1
498
+ else
499
+ c.exitBtext = go + 1
500
+ end
501
+ end
502
+ rescue => e
503
+ puts e
504
+ end
505
+ end
506
+
507
+ if links.size > 0 and @resolve_tags
508
+ # Additional diagonal connection for room
509
+ links.each { |x|
510
+ next if not x
511
+ begin
512
+ c = map.new_connection( @room, nil, x, nil )
513
+ rescue => e
514
+ puts e
515
+ end
516
+ }
517
+ end
518
+
519
+ if tagname # and not @resolve_tags
520
+ if roomname
521
+ @tags[tagname] = @room
522
+ elsif @item
523
+ @tags[tagname] = @item
524
+ elsif @task
525
+ @tags[tagname] = @task
526
+ else
527
+ # join/link tag
528
+ @tags[tagname] = ''
529
+ end
530
+ end
531
+ end
532
+
533
+ #
534
+ # Okay, check all min/max locations in all pages
535
+ # and then do the following:
536
+ # a) Adjust map's width/height
537
+ # b) Shift all rooms so that no rooms are in negative locations
538
+ #
539
+ def adjust_map
540
+ # First, adjust map's width and height
541
+ @map.width = @map.height = 1
542
+ minXY = []
543
+ maxXY = []
544
+ @map.pages.each { |page|
545
+ next if page.rooms.empty?
546
+
547
+ sizes = page.min_max_rooms
548
+ minXY.push sizes[0]
549
+ maxXY.push sizes[1]
550
+
551
+ w = maxXY[-1][0] - minXY[-1][0]
552
+ h = maxXY[-1][1] - minXY[-1][1]
553
+
554
+ # We store +3 to allow for complex connections if needed.
555
+ @map.width = w + 3 if w >= @map.width - 2
556
+ @map.height = h + 3 if h >= @map.height - 2
557
+ }
558
+
559
+
560
+ # Okay, minXY[]/maxXY[] contains all the minXY/maxXY positions of
561
+ # each page. With that info and @map.weight/height, we can
562
+ # start shifting all nodes in the page so that they will fit.
563
+ idx = 0
564
+ @map.pages.each { |p|
565
+ next if p.rooms.size == 0
566
+ x = y = 0
567
+ x = 1 - minXY[idx][0] if minXY[idx][0] < 1
568
+ y = 1 - minXY[idx][1] if minXY[idx][1] < 1
569
+ x = @map.width - maxXY[idx][0] - 1 if maxXY[idx][0] >= @map.width - 1
570
+ y = @map.height - maxXY[idx][1] - 1 if maxXY[idx][1] >= @map.height - 1
571
+ idx += 1
572
+ next if x == 0 and y == 0 # nothing to shift
573
+ p.rooms.each { |r|
574
+ r.x += x
575
+ r.y += y
576
+ }
577
+ }
578
+
579
+ @map.create_pathmap if @map.kind_of?(FXMap)
580
+ end
581
+
582
+ def initialize(file, map = Map.new('IMF Imported Map'))
583
+ @tags = {}
584
+ @map = map
585
+ @rooms = []
586
+ @resolve_tags = false
587
+ # puts '--------------- first pass'
588
+ File.open(file) { |f|
589
+ parse(f)
590
+ }
591
+ # puts '--------------- second pass'
592
+ @resolve_tags = true
593
+ File.open(file) { |f|
594
+ parse(f)
595
+ }
596
+ # puts '--------------- second pass done'
597
+ @map.page = 0
598
+ @map.filename = file.sub(/\.ifm$/i, '.map')
599
+ @map.navigation = true
600
+ adjust_map
601
+ @tags = {} # save some memory by clearing the tag list
602
+ @rooms = nil # and room list
603
+ end
604
+ end
605
+
606
+
607
+ if $0 == __FILE__
608
+ p "Opening file '#{ARGV[0]}'"
609
+ $LOAD_PATH << '..'
610
+ require "IFMapper/Map"
611
+
612
+ IFMReader.new(ARGV[0])
613
+ end