ifmapper 0.8.5 → 0.9

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/lib/IFMapper/Room.rb CHANGED
@@ -41,7 +41,10 @@ class Room
41
41
  @darkness = vars.shift
42
42
  @x = vars.shift
43
43
  @y = vars.shift
44
- @desc = vars.shift if not vars.empty? and vars[0].kind_of?(String)
44
+ if not vars.empty? and vars[0].kind_of?(String)
45
+ @desc = vars.shift
46
+ @desc.sub!(/\n/, ' ')
47
+ end
45
48
  end
46
49
 
47
50
  def marshal_dump
@@ -118,6 +118,16 @@ class Section
118
118
  # Create a new connection among two rooms thru their exits
119
119
  #
120
120
  def new_connection( roomA, exitA, roomB, exitB = nil )
121
+
122
+ # Verify rooms exist in section (ie. don't allow links across
123
+ # sections)
124
+ if not @rooms.include?(roomA)
125
+ raise ConnectionError, "Room #{roomA} not in section #{self}"
126
+ end
127
+ if roomB and not @rooms.include?(roomB)
128
+ raise ConnectionError, "Room #{roomB} not in section #{self}"
129
+ end
130
+
121
131
  c = Connection.new( roomA, roomB )
122
132
  _new_connection( c, roomA, exitA, roomB, exitB )
123
133
  end
@@ -179,9 +189,9 @@ EOF
179
189
 
180
190
  if roomA[exitA]
181
191
  if not @connections.include?(roomA[exitA])
182
- raise ConnectionError, "room exit #{exitA} for #{roomA} filled but not in section"
192
+ raise ConnectionError, "roomA exit #{exitA} for #{roomA} filled but not in section"
183
193
  end
184
- raise ConnectionError, "room exit #{exitA} for #{roomA} is filled"
194
+ raise ConnectionError, "roomA exit #{exitA} for #{roomA} is filled"
185
195
  end
186
196
 
187
197
  roomA[exitA] = c
@@ -201,7 +211,7 @@ EOF
201
211
 
202
212
  if roomB and roomB[exitB] and roomB[exitB] != c
203
213
  roomA[exitA] = nil
204
- raise ConnectionError, "room exit #{exitB} for #{roomB} is filled"
214
+ raise ConnectionError, "roomB exit #{exitB} for #{roomB} is filled"
205
215
  end
206
216
  roomB[exitB] = c if roomB
207
217
 
@@ -209,4 +219,8 @@ EOF
209
219
  return c
210
220
  end
211
221
 
222
+ def to_s
223
+ return @name
224
+ end
225
+
212
226
  end
@@ -0,0 +1,832 @@
1
+
2
+ require "IFMapper/Map"
3
+
4
+ class FXMap; end
5
+
6
+ #
7
+ # Class that allows creating a map from an TADS source file.
8
+ #
9
+ class TADSReader
10
+
11
+ class ParseError < StandardError; end
12
+ class MapError < StandardError; end
13
+
14
+ # Take a quoted TADS string and return a valid ASCII one, replacing
15
+ # TADS's special characters.
16
+ def self.unquote(text)
17
+ return '' unless text
18
+ text.gsub!(/\\'/, "'") # restore quotes
19
+ text.gsub!(/\\"/, '"') # restore quotes
20
+ text.gsub!(/<<.*>>/, '') # remove embedded functions
21
+ text.gsub!(/<\/?q>/, '"') # Change quotes
22
+ return text
23
+ end
24
+
25
+ # Temporary classes used to store inform room information
26
+ class TADSObject
27
+ attr_reader :name
28
+ attr_accessor :tag, :location, :enterable
29
+
30
+ def name=(x)
31
+ @name = TADSReader::unquote(x)
32
+ end
33
+
34
+ def to_s
35
+ "#@name tag:#@tag"
36
+ end
37
+
38
+ def method_missing(*a)
39
+ end
40
+
41
+ def initialize(location)
42
+ if location
43
+ @location = Array[*location]
44
+ else
45
+ @location = []
46
+ end
47
+ @enterable = []
48
+ end
49
+ end
50
+
51
+ class TADSDoor
52
+ attr_accessor :location, :locked, :connector, :tag
53
+ def method_missing(*x)
54
+ end
55
+ def initialize
56
+ @location = []
57
+ end
58
+ end
59
+
60
+ class TADSRoom
61
+ attr_reader :name
62
+ attr_accessor :exits, :tag, :light, :desc
63
+ def to_s
64
+ "#@name tag:#@tag"
65
+ end
66
+ def name=(x)
67
+ @name = TADSReader::unquote(x)
68
+ end
69
+ def method_missing(*x)
70
+ end
71
+ def initialize
72
+ @desc = ''
73
+ @light = true
74
+ @exits = Array.new(12, nil)
75
+ end
76
+ end
77
+
78
+ # TADS 3 directional properties need to be spelled fully
79
+ DIRECTIONS = {
80
+ 'north' => 0,
81
+ 'northeast' => 1,
82
+ 'east' => 2,
83
+ 'southeast' => 3,
84
+ 'south' => 4,
85
+ 'southwest' => 5,
86
+ 'west' => 6,
87
+ 'northwest' => 7,
88
+ 'up' => 8,
89
+ 'down' => 9,
90
+ 'in' => 10,
91
+ 'out' => 11
92
+ }
93
+
94
+ FUNCTION = /^\[ (\w+);/
95
+
96
+ GO_OBJ = /\b(#{DIRECTIONS.keys.join('|').gsub(/_to/, '_obj')})\s*:/i
97
+
98
+ # SINGLE QUOTED STRING
99
+ SQ = '((?:\\\'|[^\'])+)'
100
+
101
+ # DOUBLE QUOTED STRING
102
+ DQ = '((?:\\\"|[^"])+)'
103
+
104
+ # Equal sign (TADS supports C or Pascal-like =)
105
+ EQ = '\s*(?:=|\:=)\s*'
106
+
107
+ # Direction list in order of positioning preference.
108
+ DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
109
+
110
+ DIR_TO = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})#{EQ}/
111
+ DIR = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})#{EQ}(\w+)/
112
+ ENTER_DIR = //i # needed?
113
+
114
+ OBJLOCATION = /(?:^|\s+)(?:(?:destination|location)#{EQ}(\w+)|@(\w+)|locationList#{EQ}\[([\w\s,]+)\])/
115
+ OBJNAME = /(?:^|\s+)name#{EQ}'#{SQ}'/
116
+ CONNECTOR = /(?:^|\s+)room\d+\s*#{EQ}\s*(\w+)/
117
+ ROOMNAME = /(?:^|\s+)roomName#{EQ}'#{SQ}'/
118
+ DESCRIPTION = /(?:^|\s+)desc#{EQ}(?:\s*$|\s+"#{DQ}?("?))/
119
+
120
+ attr_reader :map
121
+
122
+ @@debug = nil
123
+ def debug(*x)
124
+ return unless @@debug
125
+ $stdout.puts x
126
+ $stdout.flush
127
+ end
128
+
129
+ #
130
+ # Main parsing loop. We basically parse the file twice to
131
+ # solve dependencies. Yes, this is inefficient, but the alternative
132
+ # was to build a full parser that understands forward dependencies.
133
+ #
134
+ def parse(file)
135
+ # We start map at 0, 0
136
+ @x, @y = [0, 0]
137
+ @room = nil
138
+
139
+ if @map.kind_of?(FXMap)
140
+ @map.options['Edit on Creation'] = false
141
+ @map.window.hide
142
+ end
143
+ @map.section = 0
144
+
145
+ @parsing = nil
146
+ @last_section = 0
147
+ @ignore_first_section = true
148
+ @room_idx = 0
149
+ line_number = 0
150
+
151
+ debug "...Parse... #{file.path}"
152
+ while not file.eof?
153
+ @line = ''
154
+ while not file.eof? and @line == ''
155
+ @line << file.readline()
156
+ @line.sub!( /^\s*\/\/.*$/, '')
157
+ line_number += 1
158
+ end
159
+ # todo: Remove multi-line comments
160
+ # Remove comments at end of line
161
+ @line.sub!( /\s+\/\/[^"]*$/, '')
162
+ # Remove starting spaces (if any)
163
+ @line.sub! /^\s+/, ''
164
+ # Replace \n with simple space
165
+ @line.gsub! /\n/, ' '
166
+ next if @line == ''
167
+ full_line = @line.dup
168
+ begin
169
+ parse_line
170
+ rescue ParseError => e
171
+ $stderr.puts
172
+ $stderr.puts "#{e} at #{file.path}, line #{line_number}:"
173
+ $stderr.puts ">>>> #{full_line};"
174
+ $stderr.puts
175
+ end
176
+ end
177
+ debug "...End Parse..."
178
+ end
179
+
180
+
181
+ CLASS = /^class\s+(\w+)\s*:\s*([\w,\s]+)/i
182
+ DOOR = /(?:^|\s+)door_to(?:\s+([^,;]*)|$)/i
183
+ INCLUDE = /^#include\s+[<"]([^">]+)[">]/
184
+
185
+ STD_LIB = [
186
+ 'adv3.h',
187
+ 'en_us.h',
188
+ ]
189
+
190
+
191
+ def new_room
192
+ # We assume we are a room (albeit we could be an obj)
193
+ @room = TADSRoom.new
194
+ @room.tag = @tag
195
+ @room.name = @name
196
+ @room.desc = @desc
197
+ @tags[@tag] = @room
198
+ @rooms << @room
199
+ end
200
+
201
+ def new_obj(loc = nil)
202
+ debug "+++ OBJECT #@name"
203
+ @obj = TADSObject.new(loc)
204
+ @obj.tag = @tag
205
+ @obj.name = @name
206
+ @tags[@tag] = @obj
207
+ @objects << @obj
208
+ end
209
+
210
+
211
+ def find_file(file)
212
+ return file if File.exists?(file)
213
+ @include_dirs.each { |d|
214
+ [ "#{d}/#{file}",
215
+ "#{d}/#{file}.h",
216
+ "#{d}/#{file}.inf", ].each { |full|
217
+ return full if File.exists?(full)
218
+ }
219
+ }
220
+ return nil
221
+ end
222
+
223
+ def get_parent_classes(clist)
224
+ ret = []
225
+ clist.each { |c|
226
+ c.strip!
227
+ if @classes[c]
228
+ ret += get_parent_classes(@classes[c])
229
+ else
230
+ ret << c
231
+ end
232
+ }
233
+ return ret
234
+ end
235
+
236
+ #
237
+ # Parse a line of file
238
+ #
239
+ def parse_line
240
+ if @line =~ INCLUDE
241
+ name = $1
242
+ unless STD_LIB.include?(name)
243
+ file = find_file(name)
244
+ if file
245
+ File.open(file, 'r') { |f| parse(f) }
246
+ else
247
+ raise ParseError, "Include file #{name} not found"
248
+ end
249
+ end
250
+ end
251
+
252
+ if @line =~ CLASS
253
+ @clas = $1
254
+ inherits = $2
255
+ debug "CLASS: #@clas : #{inherits}"
256
+ @classes[@clas] = inherits.split(/\s*,\s*/)
257
+ @tag = @name = nil
258
+ return
259
+ end
260
+
261
+
262
+
263
+ #
264
+ # Handle room description
265
+ #
266
+ if @in_desc == 1 and @line =~ /\s*"#{DQ}("\s*)?/
267
+ # check for possible start description
268
+ @room.desc << $1
269
+ if $2
270
+ @in_desc = nil
271
+ else
272
+ @in_desc = 2
273
+ end
274
+ return
275
+ end
276
+
277
+ if @in_desc == 2
278
+ @line =~ /\s*#{DQ}?("\s*)?/
279
+ @room.desc << $1 if @room and $1
280
+ @in_desc = nil if $2
281
+ return
282
+ end
283
+
284
+
285
+ # TADS3 definition is like:
286
+ # [+] [TAG:] Classes ['adjective nouns'] 'Name' @location
287
+ plus = '\s*([\+]+\s*)?'
288
+ tag = '(?:(\w+)\s*:)?\s*'
289
+ cls = '([\w\s\-\>,]+)'
290
+ noms = "(?:\'#{SQ}\'\s+)?"
291
+ name = "(?:\'#{SQ}\')?"
292
+ loc = '\s+(?:@(\w+))?'
293
+ re = /^#{plus}#{tag}#{cls}#{noms}#{name}#{loc}/
294
+ if @line =~ re
295
+
296
+ prev = $1
297
+ @name = [$5, $4]
298
+ @tag = $2
299
+ @clas = $3 # can be several classes
300
+ @in_desc = false
301
+
302
+
303
+ loc = $6
304
+ if prev and @room
305
+ loc = @room.tag
306
+ end
307
+
308
+ ## For user classes, flatten inheritance
309
+ clist = @clas.split(/(?:\s*,\s*|\s)/)
310
+ flist = get_parent_classes(clist)
311
+ @clas = flist.join(' ')
312
+
313
+ if @clas =~ /\b(Outdoor|Dark)?Room\b/
314
+ @obj = nil
315
+ @name = @name[1] || @name[0] || @tag
316
+ debug "+++ ROOM #@name TAG: #@tag"
317
+ @tag = @name if not @tag
318
+ @desc = ''
319
+ new_room
320
+ @room.light = false if $1 == 'Dark'
321
+ @in_desc = 1
322
+ elsif @clas =~ /\bFakeConnector\b/
323
+ @functions << @tag
324
+ elsif @clas =~ /\bRoomConnector\b/
325
+ debug "+++ CONNECTOR TAG: #@tag"
326
+ @desc = ''
327
+ @obj = TADSDoor.new
328
+ @obj.connector = true
329
+ @obj.tag = @tag
330
+ @tags[@tag] = @obj
331
+ @doors << @obj
332
+ elsif @clas =~ /\b(?:(?:Hidden|Secret)?Door|ThroughPassage|Stairway(?:Up|Down))\b/
333
+ @desc = ''
334
+ @name = @name[0] || @name[1]
335
+ @tag = @name if not @tag
336
+ debug "+++ DOOR #@tag"
337
+ if @clas =~ /\s+->\s*(\w+)/
338
+ debug "\tmatching side: #{@tag} -> #$1"
339
+ # this is the other side of the door... find matching side
340
+ @obj = @tags[$1]
341
+ if not @obj
342
+ @obj = TADSDoor.new
343
+ @obj.tag = @tag
344
+ @tags[$1] = @obj
345
+ end
346
+ @tags[@tag] = @obj
347
+ @obj.location << loc if loc
348
+ else
349
+ @obj = @tags[@tag] || TADSDoor.new
350
+ @obj.locked = true if @clas =~ /\bLockable(WithKey)?\b/
351
+ @obj.tag = @tag
352
+ @obj.location = [loc] if loc
353
+ @tags[@tag] = @obj
354
+ @doors << @obj
355
+ end
356
+ elsif @clas =~ /\b(?:Thing|Food|Person)\b/
357
+ @obj = nil
358
+ @desc = ''
359
+ @name = @name[0] || @name[1]
360
+ @tag = @name if not @tag
361
+ new_obj(loc) if @tag
362
+ else
363
+ @name = (@name[0] || @name[1] || @tag)
364
+ end
365
+
366
+ # debug <<"EOF"
367
+ # Name: #@name
368
+ # Classes : #@clas
369
+ # Tag : #@tag
370
+ # Location: #{loc} #{loc.class}
371
+ # EOF
372
+
373
+
374
+ end
375
+
376
+ if @obj
377
+ @obj.name = $1 if @line =~ OBJNAME
378
+ if @line =~ OBJLOCATION
379
+ if $1
380
+ locs = $1.split(/\s*,\s*/)
381
+ @obj.location += locs
382
+ end
383
+ end
384
+ @obj.location << $1 if @obj.connector and @line =~ CONNECTOR
385
+ end
386
+
387
+ if @room and @line =~ ROOMNAME
388
+ @room.name = $1
389
+ return
390
+ end
391
+
392
+ if @line =~ DESCRIPTION
393
+ @desc << $1 if @desc and $1
394
+ @in_desc = 2 if $2 == ''
395
+ end
396
+
397
+ dirs = @line.scan(DIR_TO) + @line.scan(DIR) # + @line.scan(ENTER_DIR)
398
+ if dirs.size > 0
399
+ dirs.each { |d, room|
400
+ next if room == 'nil'
401
+ dir = DIRECTIONS[d]
402
+ @room.exits[dir] = room
403
+ }
404
+ end
405
+
406
+ end
407
+
408
+
409
+ def shift_link(room, dir)
410
+ idx = dir + 1
411
+ idx = 0 if idx > 7
412
+ while idx != dir
413
+ break if not room[idx]
414
+ idx += 1
415
+ idx = 0 if idx > 7
416
+ end
417
+ if idx != dir
418
+ room[idx] = room[dir]
419
+ room[dir] = nil
420
+ # get position of other room
421
+ ox, oy = Room::DIR_TO_VECTOR[dir]
422
+ c = room[idx]
423
+ if c.roomA == room
424
+ b = c.roomB
425
+ else
426
+ b = c.roomA
427
+ end
428
+ x, y = [b.x, b.y]
429
+ x -= ox
430
+ y -= oy
431
+ dx, dy = Room::DIR_TO_VECTOR[idx]
432
+ @map.shift(x, y, -dx, -dy)
433
+ else
434
+ # raise "Warning. Cannot shift connection for #{room}."
435
+ end
436
+ end
437
+
438
+
439
+ def oneway_link?(a, b)
440
+ # First, check if room already has exit moving towards other room
441
+ a.exits.each_with_index { |e, idx|
442
+ next if not e or e.dir != Connection::AtoB
443
+ roomA = e.roomA
444
+ roomB = e.roomB
445
+ if roomA == a and roomB == b
446
+ return e
447
+ end
448
+ }
449
+ return nil
450
+ end
451
+
452
+
453
+ # Choose a direction to represent up/down/in/out.
454
+ def choose_dir(a, b, go = nil, exitB = nil)
455
+ if go
456
+ rgo = go % 2 == 0? go - 1 : go + 1
457
+ # First, check if room already has exit moving towards other room
458
+ a.exits.each_with_index { |e, idx|
459
+ next if not e
460
+ roomA = e.roomA
461
+ roomB = e.roomB
462
+ if roomA == a and roomB == b
463
+ e.exitAtext = go
464
+ return idx
465
+ elsif roomB == a and roomA == b
466
+ e.exitBtext = go
467
+ return idx
468
+ end
469
+ }
470
+ end
471
+
472
+ # We prefer directions that travel less... so we need to figure
473
+ # out where we start from...
474
+ if b
475
+ x = b.x
476
+ y = b.y
477
+ else
478
+ x = a.x
479
+ y = a.y
480
+ end
481
+ if exitB
482
+ dx, dy = Room::DIR_TO_VECTOR[exitB]
483
+ x += dx
484
+ y += dy
485
+ end
486
+
487
+ # No such luck... Pick a direction.
488
+ best = nil
489
+ bestscore = nil
490
+
491
+ DIRLIST.each { |dir|
492
+ # We prefer straight directions to diagonal ones
493
+ inc = dir % 2 == 1 ? 100 : 140
494
+ score = 1000
495
+ # We prefer directions where both that dir and the opposite side
496
+ # are empty.
497
+ if (not a[dir]) or a[dir].stub?
498
+ score += inc
499
+ score += 4 if a[dir] #attaching to stubs is better
500
+ end
501
+ # rdir = (dir + 4) % 8
502
+ # score += 1 unless a[rdir]
503
+
504
+ # Measure distance for that exit, we prefer shorter
505
+ # paths
506
+ dx, dy = Room::DIR_TO_VECTOR[dir]
507
+ dx = (a.x + dx) - x
508
+ dy = (a.y + dy) - y
509
+ d = dx * dx + dy * dy
510
+ score -= d
511
+ next if bestscore and score <= bestscore
512
+ bestscore = score
513
+ best = dir
514
+ }
515
+
516
+ if not bestscore
517
+ raise "No free exit for choose_dir"
518
+ end
519
+
520
+ return best
521
+ end
522
+
523
+ def make_room(from, to, x, y, dx = 1, dy = 0 )
524
+ elem = @tags[to.tag]
525
+ if elem.kind_of?(TADSRoom)
526
+ if not @map.free?(x, y)
527
+ @map.shift(x, y, dx, dy)
528
+ end
529
+ room = @map.new_room(x, y)
530
+ room.name = to.name
531
+ desc = to.desc
532
+ desc.gsub!(/[\t\n]/, ' ')
533
+ desc.squeeze!(' ')
534
+ room.desc = TADSReader::unquote(desc)
535
+ room.darkness = !to.light
536
+ @tags[to.tag] = room
537
+ return [room, Connection::FREE]
538
+ elsif elem.kind_of?(TADSDoor)
539
+ if elem.locked
540
+ type = Connection::LOCKED_DOOR
541
+ else
542
+ type = Connection::CLOSED_DOOR
543
+ end
544
+
545
+ @rooms.each { |o|
546
+ next if @tags[o.tag] == from
547
+ o.exits.each { |e|
548
+ next unless e
549
+ if @tags[e] == elem
550
+ res = make_room( o, o, x, y, dx, dy )
551
+ return [ res[0], type ]
552
+ end
553
+ }
554
+ }
555
+
556
+ # Okay, connecting room is missing. Check door's locations property
557
+ elem.location.each { |tag|
558
+ next if @tags[tag] == from
559
+ @rooms.each { |o|
560
+ next if o.tag != tag
561
+ res = make_room( o, o, x, y, dx, dy )
562
+ return [ res[0], type ]
563
+ }
564
+ }
565
+
566
+ #raise "error: no room with door #{to.name} #{elem.name}"
567
+ return [nil, nil]
568
+ else
569
+ return [elem, Connection::FREE]
570
+ end
571
+ end
572
+
573
+ def create_room(r, x, y, dx = 1, dy = 0)
574
+ from, = make_room(r, r, x, y)
575
+ debug "CREATE ROOM #{r.name} TAG:#{r.tag} SET FROM TO: #{from}"
576
+
577
+ r.exits.each_with_index { |e, exit|
578
+ next unless e
579
+ next if e == 'nothing' or e == '0'
580
+ debug "#{r.name} EXIT:#{exit} points to #{e}"
581
+
582
+ to = @tags[e]
583
+ if not to
584
+ next if @functions.include?(e)
585
+ raise "Room #{e} #{e.class} not found." if not to
586
+ end
587
+
588
+ go = c = nil
589
+
590
+ dir = exit
591
+ type = 0
592
+
593
+ # # If exit leads to an enterable object, find out where does that
594
+ # # enterable object lead to.
595
+ # if to.kind_of?(TADSObject)
596
+ # rooms = to.enterable
597
+ # rooms.each { |room|
598
+ # next if room == r
599
+ # to = @tags[room]
600
+ # break
601
+ # }
602
+ # # Skip it if we are still an object. This means we are just
603
+ # # a container, like the phone booth in the Fate game demo.
604
+ # next if to.kind_of?(TADSObject)
605
+ # end
606
+
607
+ if to.kind_of?(TADSRoom) or to.kind_of?(TADSDoor)
608
+ if dir > 7
609
+ # choose a dir for up/down/in/out
610
+ go = dir - 7
611
+ dir = choose_dir(from, nil, go)
612
+ end
613
+
614
+ dx, dy = Room::DIR_TO_VECTOR[dir]
615
+ x = from.x + dx
616
+ y = from.y + dy
617
+ debug "#{exit} CREATE TO #{from} -> #{to.tag}"
618
+ to, type = make_room(from, to, x, y, dx, dy)
619
+ next if not to
620
+ end
621
+
622
+ if exit > 7
623
+ # choose a dir for up/down/in/out
624
+ go = exit - 7
625
+ p "GO: #{go} choose_dir"
626
+ dir = choose_dir(from, to, go)
627
+ end
628
+
629
+ b = @rooms.find { |r2| r2.tag == e }
630
+ odir = nil
631
+ odir = b.exits.rindex(r.tag) if b
632
+ odir = (dir + 4) % 8 if not odir or odir > 7
633
+
634
+ if from[dir]
635
+ c = from[dir]
636
+ if to.exits.rindex(c) and c.roomB == from
637
+ debug "LINK TRAVELLED BOTH"
638
+ c.dir = Connection::BOTH
639
+ c.exitBtext = go if go
640
+ next
641
+ else
642
+ debug "#{exit} FROM #{from}->#{to} BLOCKED DIR: #{dir}"
643
+ shift_link(from, dir)
644
+ end
645
+ end
646
+
647
+ # Check we don't have a connection already
648
+ if to[odir]
649
+ c = to[odir]
650
+ debug "#{from} #{dir} -> #{to} dir:#{odir} filled. Swap..."
651
+
652
+ # We need to change odir to something else
653
+ rgo = 0
654
+ if go
655
+ rgo = go % 2 == 0? go - 1 : go + 1
656
+ end
657
+
658
+ # First, check if we have a dangling one-way link going to->from
659
+ # If we do, we use it.
660
+ c = oneway_link?(from, to)
661
+ if not c
662
+ odir = choose_dir(to, from, rgo, dir)
663
+ debug "Swapped to #{odir}"
664
+ else
665
+ debug "FOUND LINK #{c} -- filling it."
666
+ idx = from.exits.index(c)
667
+ from[idx] = nil
668
+ from[dir] = c
669
+ c.dir = Connection::BOTH
670
+ c.exitBtext = go if go
671
+ end
672
+ else
673
+ debug "to[odir] empty."
674
+ # First, check if we have a dangling one-way link going to->from
675
+ # If we do, we use it.
676
+ c = oneway_link?(to, from)
677
+ if c
678
+ debug "FOUND LINK #{c} -- filling it."
679
+ idx = from.exits.index(c)
680
+ from[idx] = nil
681
+ from[dir] = c
682
+ c.dir = Connection::BOTH
683
+ c.exitBtext = go if go
684
+ end
685
+ end
686
+
687
+ if not c
688
+ debug "NEW LINK #{from} #{dir} to #{to} #{odir}"
689
+ begin
690
+ c = @map.new_connection(from, dir, to, odir)
691
+ c.exitAtext = go if go
692
+ c.dir = Connection::AtoB
693
+ c.type = type
694
+ rescue Section::ConnectionError
695
+ end
696
+ end
697
+ }
698
+
699
+ return r
700
+ end
701
+
702
+ #
703
+ # Create all the stuff we found
704
+ #
705
+ def create
706
+ @rooms.each { |r| create_room(r, 0, 0) }
707
+ @rooms = []
708
+
709
+ # Add objects to rooms
710
+ @objects.each { |obj|
711
+ obj.location.each { |loc|
712
+ r = @tags[loc]
713
+ next unless r and r.kind_of?(Room)
714
+ r.objects << obj.name + "\n"
715
+ }
716
+ }
717
+ end
718
+
719
+
720
+ if RUBY_PLATFORM =~ /win/
721
+ SEP = ';'
722
+ else
723
+ SEP = ':'
724
+ end
725
+
726
+ #
727
+ # Bring up the TADS properties window, to allow user to change
728
+ # settings
729
+ #
730
+ def properties
731
+ decor = DECOR_TITLE|DECOR_BORDER
732
+
733
+ dlg = FXDialogBox.new( @map.window.parent, "TADS Settings", decor )
734
+ mainFrame = FXVerticalFrame.new(dlg,
735
+ FRAME_SUNKEN|FRAME_THICK|
736
+ LAYOUT_FILL_X|LAYOUT_FILL_Y)
737
+
738
+ frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
739
+
740
+ FXLabel.new(frame, "Include Dirs: ", nil, 0, LAYOUT_FILL_X)
741
+ inc = FXTextField.new(frame, 80, nil, 0, LAYOUT_FILL_ROW)
742
+ inc.text = @include_dirs.join(SEP)
743
+
744
+ buttons = FXHorizontalFrame.new(mainFrame,
745
+ LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
746
+ PACK_UNIFORM_WIDTH)
747
+ # Accept
748
+ FXButton.new(buttons, "&Accept", nil, dlg, FXDialogBox::ID_ACCEPT,
749
+ FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
750
+
751
+ # Cancel
752
+ FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
753
+ FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
754
+ if dlg.execute != 0
755
+ @include_dirs = inc.text.split(SEP)
756
+ return true
757
+ end
758
+ return false
759
+ end
760
+
761
+
762
+
763
+ def set_include_dirs
764
+ # Try to find t3make(.exe) in path.
765
+ paths = ENV['PATH'].split(SEP)
766
+ paths.each { |p|
767
+ next if not File.directory?(p)
768
+ Dir.foreach(p) { |x|
769
+ if x =~ /^t3make(.exe)?$/i
770
+ @include_dirs << p
771
+ @include_dirs << p + "/include"
772
+ @include_dirs << p + "/lib"
773
+ @include_dirs << p + "/contrib"
774
+ break
775
+ end
776
+ }
777
+ }
778
+ end
779
+
780
+ def initialize(file, map = Map.new('TADS Map'))
781
+ debug "Initialize"
782
+ @classes = { 'Object' => {} }
783
+ @tags = {}
784
+ @map = map
785
+ @objects = []
786
+ @doors = []
787
+ @functions = []
788
+ @rooms = []
789
+
790
+ @include_dirs = [File.dirname(file)]
791
+ set_include_dirs
792
+
793
+
794
+ debug "Get properties"
795
+ if @map.kind_of?(FXMap)
796
+ return unless properties
797
+ end
798
+
799
+ debug "Start parsing #{file}"
800
+ File.open(file) { |f|
801
+ parse(f)
802
+ }
803
+ debug "Done parsing #{file}"
804
+ puts "Rooms: #{@rooms.size}"
805
+ puts "Doors: #{@doors.size}"
806
+ puts "Objects: #{@objects.size}"
807
+
808
+ create
809
+ debug "Done creating #{file}"
810
+
811
+ if @map.kind_of?(FXMap)
812
+ @map.filename = file.sub(/\.t$/i, '.map')
813
+ @map.navigation = true
814
+ @map.options['Location Description'] = true
815
+ @map.window.show
816
+ end
817
+ @objects = nil
818
+ @tags = nil # save some memory by clearing the tag list
819
+ @rooms = nil # and room list
820
+ end
821
+ end
822
+
823
+
824
+ if $0 == __FILE__
825
+ p "Opening file '#{ARGV[0]}'"
826
+ BEGIN {
827
+ $LOAD_PATH << 'C:\Windows\Escritorio\IFMapper\lib'
828
+ }
829
+
830
+ require "IFMapper/Map"
831
+ TADSReader.new(ARGV[0])
832
+ end