ifmapper 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,900 @@
1
+
2
+ require 'IFMapper/Map'
3
+
4
+ class FXMap; end
5
+
6
+ #
7
+ # Function used to unquote a string. Formats such as TADS/Inform use special
8
+ # syntax rules in quotes.
9
+ #
10
+ module MapUnquote
11
+ def unquote(x)
12
+ return x
13
+ end
14
+ end
15
+
16
+ #
17
+ # A generic abstract class for reading a map from some file format.
18
+ #
19
+ # Map readers are expected to parse their file formats and call:
20
+ #
21
+ # new_room
22
+ # new_door
23
+ # new_obj
24
+ #
25
+ # with @tag and @name defined.
26
+ #
27
+ # These functions create arrays of temporary Map* classes (like MapRoom)
28
+ # that represent the room and their connections.
29
+ #
30
+ # Later on, the create() function is invoked to actually create those rooms
31
+ # as actual FXRooms and similar. The reason this is done this way is because
32
+ # in order to position FXRooms around, we need to previously know the location
33
+ # of all rooms and their exits.
34
+ #
35
+ class MapReader
36
+
37
+ class ParseError < StandardError; end
38
+ class MapError < StandardError; end
39
+
40
+ # Path separator in environment variables
41
+ if RUBY_PLATFORM =~ /win/
42
+ SEP = ';'
43
+ else
44
+ SEP = ':'
45
+ end
46
+
47
+ # Direction list in order of positioning preference.
48
+ DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
49
+
50
+ include MapUnquote
51
+
52
+ @@debug = nil
53
+ def debug(*x)
54
+ return unless @@debug
55
+ $stdout.puts x
56
+ $stdout.flush
57
+ end
58
+
59
+ # Temporary classes used to store inform room information
60
+ class MapObject
61
+ include MapUnquote
62
+
63
+ attr_reader :name, :connector
64
+ attr_accessor :tag, :location, :enterable, :scenery
65
+
66
+ def name=(x)
67
+ @name = unquote(x)
68
+ end
69
+
70
+ def to_s
71
+ "#@name tag:#@tag"
72
+ end
73
+
74
+ # def method_missing(*a)
75
+ # end
76
+
77
+ def initialize(location)
78
+ if location
79
+ @location = Array[*location]
80
+ else
81
+ @location = []
82
+ end
83
+ @enterable = []
84
+ end
85
+ end
86
+
87
+ class MapDoor
88
+ attr_accessor :name, :location, :locked, :connector, :tag
89
+ # def method_missing(*x)
90
+ # end
91
+ def initialize
92
+ @location = []
93
+ end
94
+ def to_s
95
+ if locked
96
+ door = '<|>'
97
+ else
98
+ door = '<->'
99
+ end
100
+ c = ''
101
+ if connector
102
+ c = ' (connector)'
103
+ end
104
+ "#{location[0]}#{door}#{location[1]}#{c}"
105
+ end
106
+ end
107
+
108
+ class MapOneWay
109
+ attr_reader :room
110
+ def initialize(room)
111
+ @room = room
112
+ end
113
+ def to_s
114
+ "ONE WAY: #{room}"
115
+ end
116
+ end
117
+
118
+ class MapSpecialExit
119
+ attr_reader :go
120
+ attr_reader :to
121
+ def initialize(go, to)
122
+ @go = go
123
+ @to = to
124
+ end
125
+ def to_s
126
+ "SPECIAL: #{go} #{to}"
127
+ end
128
+ end
129
+
130
+
131
+ class MapRoom
132
+ include MapUnquote
133
+
134
+ attr_reader :name
135
+ attr_accessor :exits, :tag, :light, :desc
136
+ attr_accessor :oneways
137
+ def inspect
138
+ r = "#{to_s}\n"
139
+ @exits.each_with_index { |e, idx|
140
+ next if not e
141
+ if idx > 7
142
+ dir = Connection::EXIT_TEXT[idx-7]
143
+ else
144
+ dir = Room::DIRECTIONS[idx]
145
+ end
146
+ r += "\t#{dir.upcase}: #{e}"
147
+ }
148
+ return r
149
+ end
150
+ def to_s
151
+ "#@name tag:#@tag"
152
+ end
153
+ def num_exits
154
+ return @exits.nitems + @oneways
155
+ end
156
+ def name=(x)
157
+ @name = unquote(x)
158
+ end
159
+
160
+ # def method_missing(*x)
161
+ # end
162
+ def initialize
163
+ @tag = nil
164
+ @desc = ''
165
+ @oneways = 0
166
+ @light = true
167
+ @exits = Array.new(12, nil)
168
+ end
169
+ end
170
+
171
+
172
+
173
+ def new_door(loc = nil)
174
+ @obj = @tags[@tag] || MapDoor.new
175
+ @obj.tag = @tag
176
+ @obj.name = @name || @tag
177
+ @obj.location << loc if loc
178
+ @tags[@tag] = @obj
179
+ @doors << @obj
180
+ end
181
+
182
+
183
+
184
+ def new_room(klass = MapRoom)
185
+ # We assume we are a room (albeit we could be an obj)
186
+ @room = klass.new
187
+ @room.tag = @tag
188
+ @room.name = @name || @tag
189
+ @room.desc = ''
190
+ @tags[@tag] = @room
191
+ @rooms << @room
192
+ end
193
+
194
+ def make_room(to, x, y, dx = 1, dy = 0)
195
+ if not @map.free?(x, y)
196
+ @map.shift(x, y, dx, dy)
197
+ end
198
+ room = @map.new_room(x, y)
199
+ room.name = to.name
200
+ desc = to.desc.to_s
201
+ desc.gsub!(/[\t\n]/, ' ')
202
+ desc.squeeze!(' ')
203
+ room.desc = unquote(desc)
204
+ room.darkness = !to.light
205
+ @tags[to.tag] = room
206
+ return room
207
+ end
208
+
209
+
210
+ def new_obj(loc = nil, klass = MapObject)
211
+ debug "+++ OBJECT #@name"
212
+ @obj = klass.new(loc)
213
+ @obj.tag = @tag
214
+ @obj.name = @name # || @tag
215
+ @tags[@tag] = @obj
216
+ @objects << @obj
217
+ end
218
+
219
+
220
+ attr_reader :map, :doors, :rooms, :tags, :functions
221
+ attr_reader :include_dirs
222
+
223
+ #
224
+ # Bring up the properties window, to allow user to change
225
+ # settings
226
+ #
227
+ def properties
228
+ decor = DECOR_TITLE|DECOR_BORDER
229
+
230
+ dlg = FXDialogBox.new( @map.window.parent, "Include Settings", decor )
231
+ mainFrame = FXVerticalFrame.new(dlg,
232
+ FRAME_SUNKEN|FRAME_THICK|
233
+ LAYOUT_FILL_X|LAYOUT_FILL_Y)
234
+
235
+ frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
236
+
237
+ FXLabel.new(frame, "Include Dirs: ", nil, 0, LAYOUT_FILL_X)
238
+ inc = FXTextField.new(frame, 80, nil, 0, LAYOUT_FILL_ROW)
239
+ inc.text = @include_dirs.join(SEP)
240
+
241
+ buttons = FXHorizontalFrame.new(mainFrame,
242
+ LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
243
+ PACK_UNIFORM_WIDTH)
244
+ # Accept
245
+ FXButton.new(buttons, "&Accept", nil, dlg, FXDialogBox::ID_ACCEPT,
246
+ FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
247
+
248
+ # Cancel
249
+ FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
250
+ FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
251
+ if dlg.execute != 0
252
+ @include_dirs = inc.text.split(SEP)
253
+ return true
254
+ end
255
+ return false
256
+ end
257
+
258
+ def set_include_dirs
259
+ end
260
+
261
+ def read_file(file)
262
+ debug "Start parsing #{file}"
263
+ File.open(file) { |f|
264
+ parse(f)
265
+ }
266
+ debug "Done parsing #{file}"
267
+ end
268
+
269
+ def best_dir(r, dirA)
270
+ start = (dirA + 4) % 8
271
+
272
+ dirs = [
273
+ start,
274
+ (start - 1) % 8,
275
+ (start + 1) % 8,
276
+ (start - 2) % 8,
277
+ (start + 2) % 8,
278
+ (start - 3) % 8,
279
+ (start + 3) % 8,
280
+ dirA
281
+ ]
282
+
283
+ dirs.each { |d|
284
+ return d if not r.exits[d]
285
+ }
286
+
287
+ return nil
288
+ end
289
+
290
+ def has_exit_to?(a, b)
291
+ idx = a.exits.rindex(b)
292
+ return idx if idx
293
+ a.exits.each_with_index { |e, idx|
294
+ next unless e
295
+ if e.kind_of?(MapSpecialExit)
296
+ e = e.to
297
+ elsif e.kind_of?(MapObject)
298
+ e.enterable.each { |x|
299
+ return idx if @tags[x] == b
300
+ }
301
+ end
302
+ case e
303
+ when MapDoor
304
+ tag = e.location.find { |t| t != a.tag }
305
+ e = @tags[tag]
306
+ return idx if e == b
307
+ when MapOneWay
308
+ return idx if e.room == b
309
+ else
310
+ return idx if e == b
311
+ end
312
+ }
313
+ return false
314
+ end
315
+
316
+ #
317
+ # Look for one way exits and add them to the proper room exit on the
318
+ # destination side. This is needed so that we properly count room exits
319
+ # and start mapping from rooms with more exits.
320
+ #
321
+ # Also, resolve up/down/in/out exits into one of the proper 8 directions.
322
+ #
323
+ def resolve_exits
324
+
325
+ if @tags[nil]
326
+ raise "error"
327
+ end
328
+
329
+ #
330
+ # First, we resolve tags to the corresponding Map* class.
331
+ # If we deal with a MapDoor that is really a connector, we simplify and
332
+ # just attach the destination room directly.
333
+ # If we deal with a MapDoor with no matching other room, we remove. This
334
+ # can happen in TADS games or if user just read a portion of the full map.
335
+ #
336
+ @rooms.each { |r|
337
+ r.exits.each_with_index { |tag, idx|
338
+ next unless tag
339
+ to = @tags[tag]
340
+
341
+ if to.kind_of?(MapDoor)
342
+ if to.location.size == 1
343
+ to = nil
344
+ else
345
+ if to.connector
346
+ t = to.location.find { |t| t != r.tag }
347
+ to = @tags[t]
348
+ end
349
+ end
350
+ end
351
+
352
+ if idx > 7
353
+ # if Up/Down/In/Out exit and we have a similar exit
354
+ # going in one direction, remove this exit
355
+ if r.exits[0,8].index(to)
356
+ r.exits[idx] = nil
357
+ next
358
+ end
359
+ end
360
+
361
+ if not to
362
+ if not @functions.include?(tag)
363
+ $stderr.puts "Exit to #{tag} not found, ignored."
364
+ end
365
+ end
366
+
367
+ r.exits[idx] = to
368
+ }
369
+ }
370
+
371
+ @rooms.each { |r|
372
+ r.exits.each_with_index { |e, dirA|
373
+ next if not e or e.kind_of?(MapOneWay)
374
+ next if e == r
375
+
376
+ ##
377
+ # Do we have a special exit?
378
+ if dirA > 7
379
+ # First, get dirB and see if it is not a proper directional
380
+ # exit
381
+ dirB = nil
382
+ if e.kind_of?(MapRoom)
383
+ dirB = has_exit_to?(e, r)
384
+ elsif e.kind_of?(MapDoor)
385
+ t = e.location.find { |t| t != r.tag }
386
+ to = @tags[t]
387
+ if to
388
+ to.exits.each_with_index { |d, idx|
389
+ if d == e or d == r
390
+ dirB = idx
391
+ break
392
+ end
393
+ }
394
+ end
395
+ end
396
+
397
+ dirB = 4 if not dirB or dirB > 7
398
+
399
+ go = dirA - 7
400
+ r.exits[dirA] = nil
401
+ dirA = best_dir(r, dirB)
402
+ next if not dirA
403
+ r.exits[dirA] = MapSpecialExit.new(go, e)
404
+ end
405
+
406
+
407
+ ###############
408
+
409
+ # we have a room or door exit
410
+
411
+ if e.kind_of?(MapRoom)
412
+ # check if destination room also has a connection towards us.
413
+ next if has_exit_to?(e, r)
414
+
415
+ dirB = best_dir(e, dirA)
416
+ next unless dirB
417
+
418
+ e.exits[dirB] = MapOneWay.new(r)
419
+
420
+ elsif e.kind_of?(MapDoor)
421
+ t = e.location.find { |t| t != r.tag }
422
+ found = false
423
+ to = @tags[t]
424
+ if to
425
+ to.exits.each_with_index { |d, idx|
426
+ next if not d
427
+ if d == e or d == r
428
+ found = true
429
+ break
430
+ end
431
+ }
432
+ end
433
+
434
+ if to and not found
435
+ dirB = best_dir(to, dirA)
436
+ next unless dirB
437
+ to.exits[dirB] = MapOneWay.new(r)
438
+ end
439
+ elsif e.kind_of?(MapObject)
440
+ else
441
+ raise "Unknown exit type #{e.class}"
442
+ end
443
+ }
444
+ }
445
+
446
+
447
+
448
+ end
449
+
450
+ def shift_link(room, dir)
451
+ idx = dir + 1
452
+ idx = 0 if idx > 7
453
+ while idx != dir
454
+ break if not room[idx]
455
+ idx += 1
456
+ idx = 0 if idx > 7
457
+ end
458
+ if idx != dir
459
+ room[idx] = room[dir]
460
+ room[dir] = nil
461
+ # get position of other room
462
+ ox, oy = Room::DIR_TO_VECTOR[dir]
463
+ c = room[idx]
464
+ if c.roomA == room
465
+ b = c.roomB
466
+ else
467
+ b = c.roomA
468
+ end
469
+ x, y = [b.x, b.y]
470
+ x -= ox
471
+ y -= oy
472
+ dx, dy = Room::DIR_TO_VECTOR[idx]
473
+ @map.shift(x, y, -dx, -dy)
474
+ else
475
+ # raise "Warning. Cannot shift connection for #{room}."
476
+ end
477
+ end
478
+
479
+
480
+ def oneway_link?(a, b)
481
+ # First, check if room already has exit moving towards other room
482
+ a.exits.each_with_index { |e, idx|
483
+ next if not e or e.dir != Connection::AtoB
484
+ roomA = e.roomA
485
+ roomB = e.roomB
486
+ if roomA == a and roomB == b
487
+ return e
488
+ end
489
+ }
490
+ return nil
491
+ end
492
+
493
+
494
+ # Choose a direction to represent up/down/in/out.
495
+ def choose_dir(a, b, go = nil, exitB = nil)
496
+ if go
497
+ rgo = go % 2 == 0? go - 1 : go + 1
498
+ # First, check if room already has exit moving towards other room
499
+ a.exits.each_with_index { |e, idx|
500
+ next if not e
501
+ roomA = e.roomA
502
+ roomB = e.roomB
503
+ if roomA == a and roomB == b
504
+ e.exitAtext = go
505
+ return idx
506
+ elsif roomB == a and roomA == b
507
+ e.exitBtext = go
508
+ return idx
509
+ end
510
+ }
511
+ end
512
+
513
+ # We prefer directions that travel less... so we need to figure
514
+ # out where we start from...
515
+ if b
516
+ x = b.x
517
+ y = b.y
518
+ else
519
+ x = a.x
520
+ y = a.y
521
+ end
522
+ if exitB
523
+ dx, dy = Room::DIR_TO_VECTOR[exitB]
524
+ x += dx
525
+ y += dy
526
+ end
527
+
528
+ # No such luck... Pick a direction.
529
+ best = nil
530
+ bestscore = nil
531
+
532
+ DIRLIST.each { |dir|
533
+ # We prefer straight directions to diagonal ones
534
+ inc = dir % 2 == 1 ? 100 : 140
535
+ score = 1000
536
+ # We prefer directions where both that dir and the opposite side
537
+ # are empty.
538
+ if (not a[dir]) or a[dir].stub?
539
+ score += inc
540
+ score += 4 if a[dir] #attaching to stubs is better
541
+ end
542
+ # rdir = (dir + 4) % 8
543
+ # score += 1 unless a[rdir]
544
+
545
+ # Measure distance for that exit, we prefer shorter
546
+ # paths
547
+ dx, dy = Room::DIR_TO_VECTOR[dir]
548
+ dx = (a.x + dx) - x
549
+ dy = (a.y + dy) - y
550
+ d = dx * dx + dy * dy
551
+ score -= d
552
+ next if bestscore and score <= bestscore
553
+ bestscore = score
554
+ best = dir
555
+ }
556
+
557
+ if not bestscore
558
+ raise "No free exit for choose_dir"
559
+ end
560
+
561
+ return best
562
+ end
563
+
564
+
565
+ def get_exit(r, from, to, x, y, dx = 1, dy = 0 )
566
+ elem = @tags[to.tag]
567
+ if elem.kind_of?(MapRoom)
568
+ dirB = has_exit_to?(to, r)
569
+ if dirB
570
+ dbx, dby = Room::DIR_TO_VECTOR[dirB]
571
+ if x + dbx != from.x or y + dby != from.y
572
+ x -= dbx
573
+ y -= dby
574
+ end
575
+ end
576
+
577
+ room = create_room(to, x, y, dx, dy)
578
+ return [room, Connection::FREE]
579
+ elsif elem.kind_of?(MapDoor)
580
+ if elem.connector
581
+ type = Connection::FREE
582
+ elsif elem.locked
583
+ type = Connection::LOCKED_DOOR
584
+ else
585
+ type = Connection::CLOSED_DOOR
586
+ end
587
+
588
+ # Okay, connecting room is missing. Check door's locations property
589
+ elem.location.each { |tag|
590
+ next if @tags[tag] == from
591
+ @rooms.each { |o|
592
+ next if o.tag != tag
593
+ res = create_room( o, x, y, dx, dy )
594
+ return [ res, type ]
595
+ }
596
+ }
597
+
598
+ # @rooms.each { |o|
599
+ # next if @tags[o.tag] == from
600
+ # o.exits.each { |e|
601
+ # next unless e
602
+ # if @tags[e] == elem
603
+ # res = create_room( o, x, y, dx, dy )
604
+ # return [ res, type ]
605
+ # end
606
+ # }
607
+ # }
608
+
609
+ $stderr.puts "No room for door #{to.name}: #{to}"
610
+ return [nil, nil]
611
+ else
612
+ return [elem, Connection::FREE]
613
+ end
614
+ end
615
+
616
+ def create_room(r, x, y, dx = 2, dy = 0)
617
+ return @tags[r.tag] if @tags[r.tag].kind_of?(Room)
618
+
619
+ debug "+++ CREATE ROOM #{r.name} (#{x},#{y}) TAG:#{r.tag}"
620
+ from, = make_room(r, x, y, dx, dy)
621
+
622
+ r.exits[0,8].each_with_index { |to, exit|
623
+ next unless to
624
+
625
+ debug "#{r.name} EXIT:#{exit} points to #{to}"
626
+
627
+ go = c = nil
628
+
629
+ if to.kind_of?(MapOneWay)
630
+ dx, dy = Room::DIR_TO_VECTOR[exit]
631
+ x = from.x + dx
632
+ y = from.y + dy
633
+ @tabs += 1
634
+ create_room(to.room, x, y, dx, dy)
635
+ @tabs -= 1
636
+ next
637
+ elsif to.kind_of?(MapSpecialExit)
638
+ go = to.go
639
+ to = to.to
640
+ end
641
+
642
+ b = to
643
+
644
+
645
+ dir = exit
646
+ type = 0
647
+
648
+ # If exit leads to an enterable object, find out where does that
649
+ # enterable object lead to.
650
+ if to.kind_of?(MapObject)
651
+ rooms = to.enterable
652
+ rooms.each { |room|
653
+ next if room == r
654
+ to = b = @tags[room]
655
+ break
656
+ }
657
+ # Skip it if we are still an object. This means we are just
658
+ # a container, like the phone booth in the Fate game demo.
659
+ next if to.kind_of?(MapObject)
660
+ end
661
+
662
+ odir = nil
663
+ if b.kind_of?(MapRoom)
664
+ odir = b.exits.rindex(r.tag)
665
+ if not odir
666
+ b.exits[0,8].each_with_index { |e, idx|
667
+ next if not e
668
+ if (e.kind_of?(MapOneWay) and e.room == r) or
669
+ (e.kind_of?(MapSpecialExit) and e.to == r)
670
+ odir = idx
671
+ break
672
+ end
673
+ }
674
+ end
675
+ elsif b.kind_of?(MapDoor)
676
+ t = b.location.find { |t| t != r.tag }
677
+ other = @rooms.find { |x| x.tag == t }
678
+ # other = @tags[t]
679
+ if other
680
+ other.exits.each_with_index { |d, idx|
681
+ next if not d
682
+ if d == b or d == r.tag or
683
+ (d.kind_of?(MapOneWay) and d.room == r) or
684
+ (d.kind_of?(MapSpecialExit) and (d.to == b or d == r.tag))
685
+ odir = idx
686
+ break
687
+ end
688
+ }
689
+ end
690
+ end
691
+ odir = (dir + 4) % 8 if not odir
692
+
693
+ if to.kind_of?(MapRoom) or to.kind_of?(MapDoor)
694
+ dx, dy = Room::DIR_TO_VECTOR[dir]
695
+ x = from.x + dx
696
+ y = from.y + dy
697
+ @tabs += 1
698
+ to, type = get_exit(r, from, to, x, y, dx, dy)
699
+ @tabs -= 1
700
+ next if not to
701
+ end
702
+
703
+
704
+ # b = @rooms.find { |r2| r2.tag == tag }
705
+ # odir = nil
706
+ # odir = b.exits.rindex(r.tag) if b
707
+ # odir = (dir + 4) % 8 if not odir
708
+
709
+ if from[dir]
710
+ c = from[dir]
711
+ if to.exits.rindex(c) and c.roomB == from
712
+ debug "LINK TRAVELLED BOTH"
713
+ c.dir = Connection::BOTH
714
+ c.exitBtext = go if go
715
+ next
716
+ else
717
+ debug "#{exit} FROM #{from}->#{to} BLOCKED DIR: #{dir}"
718
+ shift_link(from, dir)
719
+ end
720
+ end
721
+
722
+ # Check we don't have a connection already
723
+ if to[odir]
724
+ c = to[odir]
725
+
726
+ # We need to change odir to something else
727
+ rgo = 0
728
+ if go
729
+ rgo = go % 2 == 0? go - 1 : go + 1
730
+ end
731
+
732
+ # First, check if we have a dangling one-way link going to->from
733
+ # If we do, we use it.
734
+ c = oneway_link?(from, to)
735
+ if not c
736
+ odir = choose_dir(to, from, rgo, dir)
737
+ else
738
+ debug "FOUND LINK #{c} -- filling it."
739
+ idx = from.exits.index(c)
740
+ from[idx] = nil
741
+ from[dir] = c
742
+ c.dir = Connection::BOTH
743
+ c.exitBtext = go if go
744
+ end
745
+ else
746
+ debug "to[odir] empty."
747
+ # First, check if we have a dangling one-way link going to->from
748
+ # If we do, we use it.
749
+ c = oneway_link?(to, from)
750
+ if c
751
+ debug "FOUND LINK #{c} -- filling it."
752
+ idx = from.exits.index(c)
753
+ from[idx] = nil
754
+ from[dir] = c
755
+ c.dir = Connection::BOTH
756
+ c.exitBtext = go if go
757
+ end
758
+ end
759
+
760
+ if not c
761
+ debug "NEW LINK #{from} #{dir} to #{to} #{odir}"
762
+ begin
763
+ c = @map.new_connection(from, dir, to, odir)
764
+ c.exitAtext = go if go
765
+ c.dir = Connection::AtoB
766
+ c.type = type
767
+ rescue Section::ConnectionError
768
+ end
769
+ end
770
+ }
771
+
772
+ return from
773
+ end
774
+
775
+ #
776
+ # Try to move rooms around to make map smaller.
777
+ #
778
+ def compact
779
+ # First, verify all one-exit rooms lie next to its opposite room.
780
+ sect = @map.sections[@map.section]
781
+ rooms = sect.rooms.find_all { |r| r.num_exits == 1 }
782
+
783
+ rooms.each { |a|
784
+ c = a.exits.find { |e| e != nil }
785
+ b = c.opposite(a)
786
+
787
+ dx = (a.x - b.x).abs
788
+ dy = (a.y - b.y).abs
789
+ next if dx <= 1 and dy <= 1
790
+
791
+ x, y = [b.x, b.y]
792
+ dirB = b.exits.index(c)
793
+
794
+ dx, dy = Room::DIR_TO_VECTOR[dirB]
795
+ x += dx
796
+ y += dy
797
+
798
+ dirA = a.exits.index(c)
799
+ dx, dy = Room::DIR_TO_VECTOR[dirA]
800
+ # Check if exits are complementary. If not, we need to move room
801
+ # again.
802
+ if x + dx != b.x or y + dy != b.y
803
+ x -= dx
804
+ y -= dy
805
+ end
806
+ next if x == a.x and y == a.y
807
+
808
+ # Place room here. Check if filled.
809
+ if sect.free?(x, y)
810
+ a.x, a.y = x, y
811
+ else
812
+ if dx != 0
813
+ dy = 0
814
+ end
815
+ sect.shift(x, y, -dx, -dy)
816
+ a.x, a.y = x, y
817
+ end
818
+ }
819
+ end
820
+
821
+ #
822
+ # Create all the stuff we found
823
+ #
824
+ def create
825
+ @rooms = @rooms.sort_by { |r| r.num_exits }
826
+ @rooms.reverse!
827
+
828
+ @rooms.each { |r|
829
+ @tabs = 0
830
+ min, max = @map.sections[@map.section].min_max_rooms
831
+ create_room(r, max[0] + 2, 0)
832
+ }
833
+ @rooms = []
834
+
835
+ @objects.delete_if { |obj| obj.scenery }
836
+
837
+ # Add objects to rooms
838
+ @objects.each { |obj|
839
+ obj.location.each { |loc|
840
+ r = @tags[loc]
841
+ while r and not r.kind_of?(Room)
842
+ r = @tags[ r.location[0] ]
843
+ end
844
+ next unless r
845
+ r.objects << obj.name + "\n"
846
+ }
847
+ }
848
+ end
849
+
850
+ def create_map(file)
851
+ read_file(file)
852
+
853
+ puts "Rooms: #{@rooms.size}"
854
+ puts "Doors: #{@doors.size}"
855
+ puts "Objects: #{@objects.size}"
856
+
857
+ begin
858
+ resolve_exits
859
+ create
860
+ compact
861
+ rescue => e
862
+ puts e
863
+ puts e.backtrace
864
+ raise
865
+ end
866
+ end
867
+
868
+ def initialize(file, map = Map.new('Read Map'))
869
+ @map = map
870
+
871
+ @doors = []
872
+ @functions = []
873
+ @tags = {}
874
+ @rooms = []
875
+ @objects = []
876
+
877
+ @include_dirs = [File.dirname(file)]
878
+ set_include_dirs
879
+
880
+ if @map.kind_of?(FXMap)
881
+ return unless properties
882
+ end
883
+
884
+ create_map(file)
885
+
886
+ debug "Done creating #{file}"
887
+
888
+ @tags = nil # save some memory by clearing the tag list
889
+ @objects = nil
890
+ @functions = nil
891
+ @doors = nil
892
+ @rooms = nil
893
+
894
+ if @map.kind_of?(FXMap)
895
+ @map.filename = file.sub(/\.t$/i, '.map')
896
+ @map.options['Location Description'] = true
897
+ @map.window.show
898
+ end
899
+ end
900
+ end