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.
@@ -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