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.
- data/HISTORY.txt +57 -0
- data/IFMapper.gemspec +1 -1
- data/lib/IFMapper/AStar.rb +1 -1
- data/lib/IFMapper/Connection.rb +10 -0
- data/lib/IFMapper/FXDCPostscript.rb +2 -2
- data/lib/IFMapper/FXMap.rb +1 -2
- data/lib/IFMapper/FXMapFileDialog.rb +1 -1
- data/lib/IFMapper/FXMapperWindow.rb +19 -4
- data/lib/IFMapper/FXSpline.rb +3 -1
- data/lib/IFMapper/FXWarningBox.rb +6 -1
- data/lib/IFMapper/GUEReader.rb +445 -0
- data/lib/IFMapper/IFMWriter.rb +11 -3
- data/lib/IFMapper/InformReader.rb +190 -529
- data/lib/IFMapper/MapReader.rb +900 -0
- data/lib/IFMapper/Section.rb +7 -6
- data/lib/IFMapper/TADSReader.rb +103 -531
- data/lib/IFMapper/TranscriptReader.rb +11 -9
- data/maps/A New Life.map +0 -0
- data/maps/History Repeating.map +0 -0
- data/maps/Unforgotten.map +0 -0
- data/maps/devours.map +0 -0
- data/maps/djinni.map +0 -0
- data/maps/party.map +0 -0
- data/maps/pkgirl.map +0 -0
- data/maps/splashdown.map +0 -0
- metadata +17 -9
- data/lib/IFMapper/InformReaderOld.rb +0 -778
data/lib/IFMapper/Section.rb
CHANGED
@@ -35,7 +35,7 @@ class Section
|
|
35
35
|
# Return the min and max coordinates of all rooms in page
|
36
36
|
#
|
37
37
|
def min_max_rooms
|
38
|
-
return [0, 0] if @rooms.empty?
|
38
|
+
return [[0, 0],[0, 0]] if @rooms.empty?
|
39
39
|
|
40
40
|
minXY = [ @rooms[0].x, @rooms[0].y ]
|
41
41
|
maxXY = minXY.dup
|
@@ -122,10 +122,10 @@ class Section
|
|
122
122
|
# Verify rooms exist in section (ie. don't allow links across
|
123
123
|
# sections)
|
124
124
|
if not @rooms.include?(roomA)
|
125
|
-
raise ConnectionError, "Room #{roomA} not in section #{self}"
|
125
|
+
raise ConnectionError, "Room '#{roomA}' not in section #{self}"
|
126
126
|
end
|
127
127
|
if roomB and not @rooms.include?(roomB)
|
128
|
-
raise ConnectionError, "Room #{roomB} not in section #{self}"
|
128
|
+
raise ConnectionError, "Room '#{roomB}' not in section #{self}"
|
129
129
|
end
|
130
130
|
|
131
131
|
c = Connection.new( roomA, roomB )
|
@@ -167,6 +167,7 @@ class Section
|
|
167
167
|
def initialize()
|
168
168
|
@rooms = []
|
169
169
|
@connections = []
|
170
|
+
@name = ''
|
170
171
|
end
|
171
172
|
|
172
173
|
protected
|
@@ -189,9 +190,9 @@ EOF
|
|
189
190
|
|
190
191
|
if roomA[exitA]
|
191
192
|
if not @connections.include?(roomA[exitA])
|
192
|
-
raise ConnectionError, "roomA exit #{exitA} for #{roomA} filled but not in section"
|
193
|
+
raise ConnectionError, "roomA exit #{exitA} for '#{roomA}' filled but not in section"
|
193
194
|
end
|
194
|
-
raise ConnectionError, "roomA exit #{exitA} for #{roomA} is filled"
|
195
|
+
raise ConnectionError, "roomA exit #{exitA} for '#{roomA}' is filled"
|
195
196
|
end
|
196
197
|
|
197
198
|
roomA[exitA] = c
|
@@ -211,7 +212,7 @@ EOF
|
|
211
212
|
|
212
213
|
if roomB and roomB[exitB] and roomB[exitB] != c
|
213
214
|
roomA[exitA] = nil
|
214
|
-
raise ConnectionError, "roomB exit #{exitB} for #{roomB} is filled"
|
215
|
+
raise ConnectionError, "roomB exit #{exitB} for #{roomB} is filled with #{roomB[exitB]}"
|
215
216
|
end
|
216
217
|
roomB[exitB] = c if roomB
|
217
218
|
|
data/lib/IFMapper/TADSReader.rb
CHANGED
@@ -1,16 +1,25 @@
|
|
1
1
|
|
2
|
-
require
|
2
|
+
require 'IFMapper/MapReader'
|
3
|
+
|
4
|
+
|
5
|
+
# Take a quoted TADS string and return a valid ASCII one, replacing
|
6
|
+
# TADS's special characters.
|
7
|
+
module TADSUnquote
|
8
|
+
def unquote(text)
|
9
|
+
return '' unless text
|
10
|
+
text.gsub!(/\\'/, "'") # restore quotes
|
11
|
+
text.gsub!(/\\"/, '"') # restore quotes
|
12
|
+
text.gsub!(/<<.*>>/, '') # remove embedded functions
|
13
|
+
text.gsub!(/<\/?q>/, '"') # Change quotes
|
14
|
+
return text
|
15
|
+
end
|
16
|
+
end
|
3
17
|
|
4
|
-
class FXMap; end
|
5
18
|
|
6
19
|
#
|
7
20
|
# Class that allows creating a map from an TADS source file.
|
8
21
|
#
|
9
|
-
class TADSReader
|
10
|
-
|
11
|
-
class ParseError < StandardError; end
|
12
|
-
class MapError < StandardError; end
|
13
|
-
|
22
|
+
class TADSReader < MapReader
|
14
23
|
|
15
24
|
# TADS 3 directional properties need to be spelled fully
|
16
25
|
DIRECTIONS = {
|
@@ -41,8 +50,6 @@ class TADSReader
|
|
41
50
|
# Equal sign (TADS supports C or Pascal-like =)
|
42
51
|
EQ = '\s*(?:=|\:=)\s*'
|
43
52
|
|
44
|
-
# Direction list in order of positioning preference.
|
45
|
-
DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
|
46
53
|
|
47
54
|
DIR_EQ = "(#{DIRECTIONS.keys.join('|')})#{EQ}"
|
48
55
|
DIR_TO = /(?:^|\s+)#{DIR_EQ}/
|
@@ -51,7 +58,7 @@ class TADSReader
|
|
51
58
|
DIR_INSELF = /(?:^|\s+)#{DIR_EQ}\(\s*[\d\w]+\.isIn\(\s*self\s*\)\s*\?\s*([\d\w]+)/
|
52
59
|
DIR_MSG = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s*\:\s*TravelMessage\s*\{\s*\->\s*(\w+)/
|
53
60
|
|
54
|
-
ENTER_DIR =
|
61
|
+
ENTER_DIR = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s*:\s*.*<<replaceAction\(Enter\s*,\s*(\w+)/i
|
55
62
|
|
56
63
|
OBJLOCATION = /(?:^|\s+)(?:(?:destination|location)#{EQ}(\w+)|@(\w+)|locationList#{EQ}\[([\w\s,]+)\])/
|
57
64
|
OBJNAME = /(?:^|\s+)name#{EQ}(?:'#{SQ}'|\(\s*described\s*\?\s*'#{SQ}')/
|
@@ -76,84 +83,47 @@ class TADSReader
|
|
76
83
|
STD_LIB = [
|
77
84
|
'adv3.h',
|
78
85
|
'en_us.h',
|
86
|
+
'bignum.h',
|
87
|
+
'array.h',
|
88
|
+
'tok.h',
|
89
|
+
'bytearr.h',
|
90
|
+
'charset.h',
|
91
|
+
'dict.h',
|
92
|
+
'reflect.h',
|
93
|
+
'gramprod.h',
|
94
|
+
'systype.h',
|
95
|
+
'tads.h',
|
96
|
+
'tadsgen.h',
|
97
|
+
'tadsio.h',
|
98
|
+
't3.h',
|
99
|
+
'vector.h',
|
100
|
+
'file.h',
|
101
|
+
't3test.h',
|
102
|
+
'strcomp.h',
|
79
103
|
]
|
80
104
|
|
81
105
|
|
82
|
-
|
83
|
-
# TADS's special characters.
|
84
|
-
def self.unquote(text)
|
85
|
-
return '' unless text
|
86
|
-
text.gsub!(/\\'/, "'") # restore quotes
|
87
|
-
text.gsub!(/\\"/, '"') # restore quotes
|
88
|
-
text.gsub!(/<<.*>>/, '') # remove embedded functions
|
89
|
-
text.gsub!(/<\/?q>/, '"') # Change quotes
|
90
|
-
return text
|
91
|
-
end
|
92
|
-
|
93
|
-
# Temporary classes used to store inform room information
|
94
|
-
class TADSObject
|
95
|
-
attr_reader :name
|
96
|
-
attr_accessor :tag, :location, :enterable
|
97
|
-
|
98
|
-
def name=(x)
|
99
|
-
@name = TADSReader::unquote(x)
|
100
|
-
end
|
106
|
+
attr_reader :map
|
101
107
|
|
102
|
-
def to_s
|
103
|
-
"#@name tag:#@tag"
|
104
|
-
end
|
105
108
|
|
106
|
-
def method_missing(*a)
|
107
|
-
end
|
108
109
|
|
109
|
-
|
110
|
-
if location
|
111
|
-
@location = Array[*location]
|
112
|
-
else
|
113
|
-
@location = []
|
114
|
-
end
|
115
|
-
@enterable = []
|
116
|
-
end
|
117
|
-
end
|
110
|
+
include TADSUnquote
|
118
111
|
|
119
|
-
class
|
120
|
-
|
121
|
-
def method_missing(*x)
|
122
|
-
end
|
123
|
-
def initialize
|
124
|
-
@location = []
|
125
|
-
end
|
112
|
+
class TADSRoom < MapRoom
|
113
|
+
include TADSUnquote
|
126
114
|
end
|
127
115
|
|
128
|
-
class
|
129
|
-
|
130
|
-
attr_accessor :exits, :tag, :light, :desc
|
131
|
-
def to_s
|
132
|
-
"#@name tag:#@tag"
|
133
|
-
end
|
134
|
-
def num_exits
|
135
|
-
return @exits.nitems
|
136
|
-
end
|
137
|
-
def name=(x)
|
138
|
-
@name = TADSReader::unquote(x)
|
139
|
-
end
|
140
|
-
def method_missing(*x)
|
141
|
-
end
|
142
|
-
def initialize
|
143
|
-
@desc = ''
|
144
|
-
@light = true
|
145
|
-
@exits = Array.new(12, nil)
|
146
|
-
end
|
116
|
+
class TADSObject < MapObject
|
117
|
+
include TADSUnquote
|
147
118
|
end
|
148
119
|
|
149
120
|
|
150
|
-
|
121
|
+
def new_room
|
122
|
+
super(TADSRoom)
|
123
|
+
end
|
151
124
|
|
152
|
-
|
153
|
-
|
154
|
-
return unless @@debug
|
155
|
-
$stdout.puts x
|
156
|
-
$stdout.flush
|
125
|
+
def new_obj(loc = nil)
|
126
|
+
super(loc, TADSObject)
|
157
127
|
end
|
158
128
|
|
159
129
|
#
|
@@ -198,10 +168,11 @@ class TADSReader
|
|
198
168
|
begin
|
199
169
|
parse_line
|
200
170
|
rescue ParseError, MapError => e
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
171
|
+
message = "#{e}.\nat #{file.path}, line #{line_number}:\n>>>> #{full_line};\n"
|
172
|
+
raise message
|
173
|
+
rescue => e
|
174
|
+
message = "#{e}\n#{e.backtrace}"
|
175
|
+
raise message
|
205
176
|
end
|
206
177
|
end
|
207
178
|
debug "...End Parse..."
|
@@ -209,26 +180,6 @@ class TADSReader
|
|
209
180
|
|
210
181
|
|
211
182
|
|
212
|
-
def new_room
|
213
|
-
# We assume we are a room (albeit we could be an obj)
|
214
|
-
@room = TADSRoom.new
|
215
|
-
@room.tag = @tag
|
216
|
-
@room.name = @name
|
217
|
-
@room.desc = @desc
|
218
|
-
@tags[@tag] = @room
|
219
|
-
@rooms << @room
|
220
|
-
end
|
221
|
-
|
222
|
-
def new_obj(loc = nil)
|
223
|
-
debug "+++ OBJECT #@name"
|
224
|
-
@obj = TADSObject.new(loc)
|
225
|
-
@obj.tag = @tag
|
226
|
-
@obj.name = @name
|
227
|
-
@tags[@tag] = @obj
|
228
|
-
@objects << @obj
|
229
|
-
end
|
230
|
-
|
231
|
-
|
232
183
|
def find_file(file)
|
233
184
|
return file if File.exists?(file)
|
234
185
|
@include_dirs.each { |d|
|
@@ -265,7 +216,7 @@ class TADSReader
|
|
265
216
|
if file
|
266
217
|
File.open(file, 'r') { |f| parse(f) }
|
267
218
|
else
|
268
|
-
raise ParseError, "Include file #{name} not found"
|
219
|
+
raise ParseError, "Include file '#{name}' not found"
|
269
220
|
end
|
270
221
|
end
|
271
222
|
end
|
@@ -307,18 +258,29 @@ class TADSReader
|
|
307
258
|
return
|
308
259
|
end
|
309
260
|
|
261
|
+
if @gameid and @line =~ /name\s*=\s*'(#{SQ})'/
|
262
|
+
@map.name = $1
|
263
|
+
end
|
310
264
|
|
311
265
|
# TADS3 definition is like:
|
312
266
|
# [+] [TAG:] Classes ['adjective nouns'] 'Name' @location
|
313
267
|
re = /^#{PLUS}#{TAG}#{CLS}#{NOMS}#{NAME}#{LOC}/
|
314
268
|
if @line =~ re
|
315
269
|
|
270
|
+
|
316
271
|
prev = $1
|
317
272
|
@name = [$5, $4]
|
318
273
|
@tag = $2
|
319
274
|
@clas = $3 # can be several classes
|
320
275
|
@in_desc = false
|
321
276
|
|
277
|
+
if @tag
|
278
|
+
if @clas == "GameID"
|
279
|
+
@gameid = true
|
280
|
+
else
|
281
|
+
@gameid = false
|
282
|
+
end
|
283
|
+
end
|
322
284
|
|
323
285
|
loc = $6
|
324
286
|
if prev and @room
|
@@ -344,36 +306,28 @@ class TADSReader
|
|
344
306
|
elsif @clas =~ /\bRoomConnector\b/
|
345
307
|
debug "+++ CONNECTOR TAG: #@tag"
|
346
308
|
@desc = ''
|
347
|
-
|
309
|
+
new_door(loc)
|
348
310
|
@obj.connector = true
|
349
|
-
@obj.tag = @tag
|
350
|
-
@tags[@tag] = @obj
|
351
|
-
@doors << @obj
|
352
311
|
elsif @clas =~ /\b(?:(?:Hidden|Secret)?Door|ThroughPassage|PathPassage|TravelWithMessage|Stairway(?:Up|Down))\b/
|
353
312
|
@desc = ''
|
354
313
|
@name = @name[0] || @name[1]
|
355
314
|
@tag = @name if not @tag
|
356
315
|
debug "+++ DOOR #@tag"
|
357
316
|
if @clas =~ /\s+->\s*(\w+)/
|
358
|
-
debug "\tmatching side: #{@tag} -> #$1"
|
359
317
|
# this is the other side of the door... find matching side
|
360
318
|
@obj = @tags[$1]
|
361
319
|
if not @obj
|
362
|
-
|
363
|
-
@obj.tag = @tag
|
320
|
+
new_door(loc)
|
364
321
|
@tags[$1] = @obj
|
322
|
+
else
|
323
|
+
@tags[@tag] = @obj
|
324
|
+
@obj.location << loc if loc
|
365
325
|
end
|
366
|
-
@tags[@tag] = @obj
|
367
|
-
@obj.location << loc if loc
|
368
326
|
else
|
369
|
-
|
327
|
+
new_door(loc) if @tag
|
370
328
|
@obj.locked = true if @clas =~ /\bLockable(WithKey)?\b/
|
371
|
-
@obj.tag = @tag
|
372
|
-
@obj.location = [loc] if loc
|
373
|
-
@tags[@tag] = @obj
|
374
|
-
@doors << @obj
|
375
329
|
end
|
376
|
-
@obj.connector = true if @clas
|
330
|
+
@obj.connector = true if @clas !~ /\b(?:Hidden|Secret)?Door\b/
|
377
331
|
elsif @clas =~ /\b(?:Thing|Food|Person)\b/
|
378
332
|
@obj = nil
|
379
333
|
@desc = ''
|
@@ -381,7 +335,19 @@ class TADSReader
|
|
381
335
|
@name = '' if @name =~ /\//
|
382
336
|
@tag = @name if not @tag
|
383
337
|
new_obj(loc) if @tag
|
338
|
+
elsif @clas =~ /\bEnterable\b/ and @tag
|
339
|
+
@desc = ''
|
340
|
+
@name = @name[0] || @name[1]
|
341
|
+
@name = '' if @name =~ /\//
|
342
|
+
@tag = @name if not @tag
|
343
|
+
new_obj(loc)
|
344
|
+
if @clas =~ /\s+->\s*(\w+)/
|
345
|
+
@obj.enterable << $1
|
346
|
+
end
|
384
347
|
else
|
348
|
+
if @tag
|
349
|
+
@obj = nil
|
350
|
+
end
|
385
351
|
@name = (@name[0] || @name[1] || @tag)
|
386
352
|
end
|
387
353
|
|
@@ -396,19 +362,20 @@ class TADSReader
|
|
396
362
|
end
|
397
363
|
|
398
364
|
if @obj
|
399
|
-
|
400
365
|
if @obj.name == '' and @line =~ /^\s*#{NOMS}#{NAME}\s*$/
|
401
366
|
name = $2 || $1
|
402
367
|
@obj.name = name
|
403
368
|
end
|
404
369
|
@obj.name = $1 || $2 if @line =~ OBJNAME
|
405
370
|
if @line =~ OBJLOCATION
|
406
|
-
|
407
|
-
|
371
|
+
loc = $1 || $2 || $3
|
372
|
+
if loc
|
373
|
+
locs = loc.split(/\s*,\s*/)
|
408
374
|
@obj.location += locs
|
375
|
+
@obj.location.uniq!
|
409
376
|
end
|
410
377
|
end
|
411
|
-
@obj.location <<
|
378
|
+
@obj.location << loc if @obj.connector and @line =~ CONNECTOR
|
412
379
|
end
|
413
380
|
|
414
381
|
if @room and @line =~ ROOMNAME
|
@@ -422,385 +389,23 @@ class TADSReader
|
|
422
389
|
end
|
423
390
|
|
424
391
|
# dirs = @line.scan(DIR_TO) + @line.scan(DIR) + @line.scan(DIR_MSG)
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
end
|
435
|
-
|
436
|
-
|
437
|
-
def shift_link(room, dir)
|
438
|
-
idx = dir + 1
|
439
|
-
idx = 0 if idx > 7
|
440
|
-
while idx != dir
|
441
|
-
break if not room[idx]
|
442
|
-
idx += 1
|
443
|
-
idx = 0 if idx > 7
|
444
|
-
end
|
445
|
-
if idx != dir
|
446
|
-
room[idx] = room[dir]
|
447
|
-
room[dir] = nil
|
448
|
-
# get position of other room
|
449
|
-
ox, oy = Room::DIR_TO_VECTOR[dir]
|
450
|
-
c = room[idx]
|
451
|
-
if c.roomA == room
|
452
|
-
b = c.roomB
|
453
|
-
else
|
454
|
-
b = c.roomA
|
455
|
-
end
|
456
|
-
x, y = [b.x, b.y]
|
457
|
-
x -= ox
|
458
|
-
y -= oy
|
459
|
-
dx, dy = Room::DIR_TO_VECTOR[idx]
|
460
|
-
@map.shift(x, y, -dx, -dy)
|
461
|
-
else
|
462
|
-
# raise "Warning. Cannot shift connection for #{room}."
|
463
|
-
end
|
464
|
-
end
|
465
|
-
|
466
|
-
|
467
|
-
def oneway_link?(a, b)
|
468
|
-
# First, check if room already has exit moving towards other room
|
469
|
-
a.exits.each_with_index { |e, idx|
|
470
|
-
next if not e or e.dir != Connection::AtoB
|
471
|
-
roomA = e.roomA
|
472
|
-
roomB = e.roomB
|
473
|
-
if roomA == a and roomB == b
|
474
|
-
return e
|
475
|
-
end
|
476
|
-
}
|
477
|
-
return nil
|
478
|
-
end
|
479
|
-
|
480
|
-
|
481
|
-
# Choose a direction to represent up/down/in/out.
|
482
|
-
def choose_dir(a, b, go = nil, exitB = nil)
|
483
|
-
if go
|
484
|
-
rgo = go % 2 == 0? go - 1 : go + 1
|
485
|
-
# First, check if room already has exit moving towards other room
|
486
|
-
a.exits.each_with_index { |e, idx|
|
487
|
-
next if not e
|
488
|
-
roomA = e.roomA
|
489
|
-
roomB = e.roomB
|
490
|
-
if roomA == a and roomB == b
|
491
|
-
e.exitAtext = go
|
492
|
-
return idx
|
493
|
-
elsif roomB == a and roomA == b
|
494
|
-
e.exitBtext = go
|
495
|
-
return idx
|
496
|
-
end
|
497
|
-
}
|
498
|
-
end
|
499
|
-
|
500
|
-
# We prefer directions that travel less... so we need to figure
|
501
|
-
# out where we start from...
|
502
|
-
if b
|
503
|
-
x = b.x
|
504
|
-
y = b.y
|
505
|
-
else
|
506
|
-
x = a.x
|
507
|
-
y = a.y
|
508
|
-
end
|
509
|
-
if exitB
|
510
|
-
dx, dy = Room::DIR_TO_VECTOR[exitB]
|
511
|
-
x += dx
|
512
|
-
y += dy
|
513
|
-
end
|
514
|
-
|
515
|
-
# No such luck... Pick a direction.
|
516
|
-
best = nil
|
517
|
-
bestscore = nil
|
518
|
-
|
519
|
-
DIRLIST.each { |dir|
|
520
|
-
# We prefer straight directions to diagonal ones
|
521
|
-
inc = dir % 2 == 1 ? 100 : 140
|
522
|
-
score = 1000
|
523
|
-
# We prefer directions where both that dir and the opposite side
|
524
|
-
# are empty.
|
525
|
-
if (not a[dir]) or a[dir].stub?
|
526
|
-
score += inc
|
527
|
-
score += 4 if a[dir] #attaching to stubs is better
|
528
|
-
end
|
529
|
-
# rdir = (dir + 4) % 8
|
530
|
-
# score += 1 unless a[rdir]
|
531
|
-
|
532
|
-
# Measure distance for that exit, we prefer shorter
|
533
|
-
# paths
|
534
|
-
dx, dy = Room::DIR_TO_VECTOR[dir]
|
535
|
-
dx = (a.x + dx) - x
|
536
|
-
dy = (a.y + dy) - y
|
537
|
-
d = dx * dx + dy * dy
|
538
|
-
score -= d
|
539
|
-
next if bestscore and score <= bestscore
|
540
|
-
bestscore = score
|
541
|
-
best = dir
|
542
|
-
}
|
543
|
-
|
544
|
-
if not bestscore
|
545
|
-
raise "No free exit for choose_dir"
|
546
|
-
end
|
547
|
-
|
548
|
-
return best
|
549
|
-
end
|
550
|
-
|
551
|
-
def make_room(to, x, y, dx = 1, dy = 0)
|
552
|
-
if not @map.free?(x, y)
|
553
|
-
@map.shift(x, y, dx, dy)
|
554
|
-
end
|
555
|
-
room = @map.new_room(x, y)
|
556
|
-
room.name = to.name
|
557
|
-
desc = to.desc
|
558
|
-
desc.gsub!(/[\t\n]/, ' ')
|
559
|
-
desc.squeeze!(' ')
|
560
|
-
room.desc = TADSReader::unquote(desc)
|
561
|
-
room.darkness = !to.light
|
562
|
-
@tags[to.tag] = room
|
563
|
-
return room
|
564
|
-
end
|
565
|
-
|
566
|
-
def get_exit(from, to, x, y, dx = 1, dy = 0 )
|
567
|
-
elem = @tags[to.tag]
|
568
|
-
if elem.kind_of?(TADSRoom)
|
569
|
-
room = create_room(to, x, y, dx, dy)
|
570
|
-
return [room, Connection::FREE]
|
571
|
-
elsif elem.kind_of?(TADSDoor)
|
572
|
-
if elem.connector
|
573
|
-
type = Connection::FREE
|
574
|
-
elsif elem.locked
|
575
|
-
type = Connection::LOCKED_DOOR
|
576
|
-
else
|
577
|
-
type = Connection::CLOSED_DOOR
|
578
|
-
end
|
579
|
-
|
580
|
-
@rooms.each { |o|
|
581
|
-
next if @tags[o.tag] == from
|
582
|
-
o.exits.each { |e|
|
583
|
-
next unless e
|
584
|
-
if @tags[e] == elem
|
585
|
-
res = create_room( o, x, y, dx, dy )
|
586
|
-
return [ res, type ]
|
587
|
-
end
|
392
|
+
if @room
|
393
|
+
dirs = ( @line.scan(DIR) + @line.scan(DIR_MSG) +
|
394
|
+
@line.scan(DIR_OPEN) + @line.scan(DIR_INSELF) +
|
395
|
+
@line.scan(ENTER_DIR) )
|
396
|
+
if dirs.size > 0
|
397
|
+
dirs.each { |d, room|
|
398
|
+
next if not room or room == 'nil' or room =~ /^noTravel/
|
399
|
+
dir = DIRECTIONS[d]
|
400
|
+
@room.exits[dir] = room
|
588
401
|
}
|
589
|
-
}
|
590
|
-
|
591
|
-
# Okay, connecting room is missing. Check door's locations property
|
592
|
-
elem.location.each { |tag|
|
593
|
-
next if @tags[tag] == from
|
594
|
-
@rooms.each { |o|
|
595
|
-
next if o.tag != tag
|
596
|
-
res = create_room( o, x, y, dx, dy )
|
597
|
-
return [ res, type ]
|
598
|
-
}
|
599
|
-
}
|
600
|
-
|
601
|
-
#raise MapError, "error: no room with door #{to.name} #{elem.name}"
|
602
|
-
return [nil, nil]
|
603
|
-
else
|
604
|
-
return [elem, Connection::FREE]
|
605
|
-
end
|
606
|
-
end
|
607
|
-
|
608
|
-
def create_room(r, x, y, dx = 2, dy = 0)
|
609
|
-
return @tags[r.tag] if @tags[r.tag].kind_of?(Room)
|
610
|
-
from, = make_room(r, x, y, dx, dy)
|
611
|
-
debug "CREATE ROOM #{r.name} TAG:#{r.tag}"
|
612
|
-
|
613
|
-
r.exits.each_with_index { |e, exit|
|
614
|
-
next unless e
|
615
|
-
next if e == 'nothing' or e == '0'
|
616
|
-
debug "#{r.name} EXIT:#{exit} points to #{e}"
|
617
|
-
|
618
|
-
to = @tags[e]
|
619
|
-
if not to
|
620
|
-
next if @functions.include?(e)
|
621
|
-
if not to
|
622
|
-
$stderr.puts "Exit to #{e} (#{e.class}) not found, ignored."
|
623
|
-
next
|
624
|
-
end
|
625
|
-
end
|
626
|
-
|
627
|
-
go = c = nil
|
628
|
-
|
629
|
-
dir = exit
|
630
|
-
type = 0
|
631
|
-
|
632
|
-
# # If exit leads to an enterable object, find out where does that
|
633
|
-
# # enterable object lead to.
|
634
|
-
# if to.kind_of?(TADSObject)
|
635
|
-
# rooms = to.enterable
|
636
|
-
# rooms.each { |room|
|
637
|
-
# next if room == r
|
638
|
-
# to = @tags[room]
|
639
|
-
# break
|
640
|
-
# }
|
641
|
-
# # Skip it if we are still an object. This means we are just
|
642
|
-
# # a container, like the phone booth in the Fate game demo.
|
643
|
-
# next if to.kind_of?(TADSObject)
|
644
|
-
# end
|
645
|
-
|
646
|
-
if to.kind_of?(TADSRoom) or to.kind_of?(TADSDoor)
|
647
|
-
if dir > 7
|
648
|
-
# choose a dir for up/down/in/out
|
649
|
-
go = dir - 7
|
650
|
-
dir = choose_dir(from, nil, go)
|
651
|
-
end
|
652
|
-
|
653
|
-
dx, dy = Room::DIR_TO_VECTOR[dir]
|
654
|
-
x = from.x + dx
|
655
|
-
y = from.y + dy
|
656
|
-
debug "#{exit} CREATE TO #{from} -> #{to.tag}"
|
657
|
-
to, type = get_exit(from, to, x, y, dx, dy)
|
658
|
-
next if not to
|
659
|
-
end
|
660
|
-
|
661
|
-
if exit > 7
|
662
|
-
# choose a dir for up/down/in/out
|
663
|
-
go = exit - 7
|
664
|
-
dir = choose_dir(from, to, go)
|
665
|
-
end
|
666
|
-
|
667
|
-
b = @rooms.find { |r2| r2.tag == e }
|
668
|
-
odir = nil
|
669
|
-
odir = b.exits.rindex(r.tag) if b
|
670
|
-
odir = (dir + 4) % 8 if not odir or odir > 7
|
671
|
-
|
672
|
-
if from[dir]
|
673
|
-
c = from[dir]
|
674
|
-
if to.exits.rindex(c) and c.roomB == from
|
675
|
-
debug "LINK TRAVELLED BOTH"
|
676
|
-
c.dir = Connection::BOTH
|
677
|
-
c.exitBtext = go if go
|
678
|
-
next
|
679
|
-
else
|
680
|
-
debug "#{exit} FROM #{from}->#{to} BLOCKED DIR: #{dir}"
|
681
|
-
shift_link(from, dir)
|
682
|
-
end
|
683
|
-
end
|
684
|
-
|
685
|
-
# Check we don't have a connection already
|
686
|
-
if to[odir]
|
687
|
-
c = to[odir]
|
688
|
-
debug "#{from} #{dir} -> #{to} dir:#{odir} filled. Swap..."
|
689
|
-
|
690
|
-
# We need to change odir to something else
|
691
|
-
rgo = 0
|
692
|
-
if go
|
693
|
-
rgo = go % 2 == 0? go - 1 : go + 1
|
694
|
-
end
|
695
|
-
|
696
|
-
# First, check if we have a dangling one-way link going to->from
|
697
|
-
# If we do, we use it.
|
698
|
-
c = oneway_link?(from, to)
|
699
|
-
if not c
|
700
|
-
odir = choose_dir(to, from, rgo, dir)
|
701
|
-
debug "Swapped to #{odir}"
|
702
|
-
else
|
703
|
-
debug "FOUND LINK #{c} -- filling it."
|
704
|
-
idx = from.exits.index(c)
|
705
|
-
from[idx] = nil
|
706
|
-
from[dir] = c
|
707
|
-
c.dir = Connection::BOTH
|
708
|
-
c.exitBtext = go if go
|
709
|
-
end
|
710
|
-
else
|
711
|
-
debug "to[odir] empty."
|
712
|
-
# First, check if we have a dangling one-way link going to->from
|
713
|
-
# If we do, we use it.
|
714
|
-
c = oneway_link?(to, from)
|
715
|
-
if c
|
716
|
-
debug "FOUND LINK #{c} -- filling it."
|
717
|
-
idx = from.exits.index(c)
|
718
|
-
from[idx] = nil
|
719
|
-
from[dir] = c
|
720
|
-
c.dir = Connection::BOTH
|
721
|
-
c.exitBtext = go if go
|
722
|
-
end
|
723
|
-
end
|
724
|
-
|
725
|
-
if not c
|
726
|
-
debug "NEW LINK #{from} #{dir} to #{to} #{odir}"
|
727
|
-
begin
|
728
|
-
c = @map.new_connection(from, dir, to, odir)
|
729
|
-
c.exitAtext = go if go
|
730
|
-
c.dir = Connection::AtoB
|
731
|
-
c.type = type
|
732
|
-
rescue Section::ConnectionError
|
733
|
-
end
|
734
402
|
end
|
735
|
-
|
736
|
-
|
737
|
-
return from
|
738
|
-
end
|
739
|
-
|
740
|
-
#
|
741
|
-
# Create all the stuff we found
|
742
|
-
#
|
743
|
-
def create
|
744
|
-
@rooms = @rooms.sort_by { |r| r.num_exits }
|
745
|
-
@rooms.reverse!
|
403
|
+
end
|
746
404
|
|
747
|
-
@rooms.each { |r|
|
748
|
-
min, max = @map.sections[@map.section].min_max_rooms
|
749
|
-
create_room(r, max[0] + 2, 0)
|
750
|
-
}
|
751
|
-
@rooms = []
|
752
|
-
|
753
|
-
# Add objects to rooms
|
754
|
-
@objects.each { |obj|
|
755
|
-
obj.location.each { |loc|
|
756
|
-
r = @tags[loc]
|
757
|
-
next unless r and r.kind_of?(Room)
|
758
|
-
r.objects << obj.name + "\n"
|
759
|
-
}
|
760
|
-
}
|
761
405
|
end
|
762
406
|
|
763
407
|
|
764
|
-
if RUBY_PLATFORM =~ /win/
|
765
|
-
SEP = ';'
|
766
|
-
else
|
767
|
-
SEP = ':'
|
768
|
-
end
|
769
408
|
|
770
|
-
#
|
771
|
-
# Bring up the TADS properties window, to allow user to change
|
772
|
-
# settings
|
773
|
-
#
|
774
|
-
def properties
|
775
|
-
decor = DECOR_TITLE|DECOR_BORDER
|
776
|
-
|
777
|
-
dlg = FXDialogBox.new( @map.window.parent, "TADS Settings", decor )
|
778
|
-
mainFrame = FXVerticalFrame.new(dlg,
|
779
|
-
FRAME_SUNKEN|FRAME_THICK|
|
780
|
-
LAYOUT_FILL_X|LAYOUT_FILL_Y)
|
781
|
-
|
782
|
-
frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
|
783
|
-
|
784
|
-
FXLabel.new(frame, "Include Dirs: ", nil, 0, LAYOUT_FILL_X)
|
785
|
-
inc = FXTextField.new(frame, 80, nil, 0, LAYOUT_FILL_ROW)
|
786
|
-
inc.text = @include_dirs.join(SEP)
|
787
|
-
|
788
|
-
buttons = FXHorizontalFrame.new(mainFrame,
|
789
|
-
LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
|
790
|
-
PACK_UNIFORM_WIDTH)
|
791
|
-
# Accept
|
792
|
-
FXButton.new(buttons, "&Accept", nil, dlg, FXDialogBox::ID_ACCEPT,
|
793
|
-
FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
794
|
-
|
795
|
-
# Cancel
|
796
|
-
FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
|
797
|
-
FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
798
|
-
if dlg.execute != 0
|
799
|
-
@include_dirs = inc.text.split(SEP)
|
800
|
-
return true
|
801
|
-
end
|
802
|
-
return false
|
803
|
-
end
|
804
409
|
|
805
410
|
def parse_makefile(file)
|
806
411
|
dir = File.dirname(file)
|
@@ -835,26 +440,7 @@ class TADSReader
|
|
835
440
|
}
|
836
441
|
end
|
837
442
|
|
838
|
-
def
|
839
|
-
debug "Initialize"
|
840
|
-
@classes = { 'Object' => {} }
|
841
|
-
@tags = {}
|
842
|
-
@map = map
|
843
|
-
@objects = []
|
844
|
-
@doors = []
|
845
|
-
@functions = []
|
846
|
-
@rooms = []
|
847
|
-
|
848
|
-
@include_dirs = [File.dirname(file)]
|
849
|
-
set_include_dirs
|
850
|
-
|
851
|
-
|
852
|
-
debug "Get properties"
|
853
|
-
if @map.kind_of?(FXMap)
|
854
|
-
return unless properties
|
855
|
-
end
|
856
|
-
|
857
|
-
|
443
|
+
def read_file(file)
|
858
444
|
if file =~ /.t3m/
|
859
445
|
files = parse_makefile(file)
|
860
446
|
else
|
@@ -862,28 +448,14 @@ class TADSReader
|
|
862
448
|
end
|
863
449
|
|
864
450
|
files.each_with_index { |file, idx|
|
865
|
-
|
866
|
-
File.open(file) { |f|
|
867
|
-
parse(f)
|
868
|
-
}
|
869
|
-
debug "Done parsing #{file}"
|
451
|
+
super(file)
|
870
452
|
}
|
453
|
+
end
|
871
454
|
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
create
|
877
|
-
debug "Done creating #{file}"
|
878
|
-
|
879
|
-
if @map.kind_of?(FXMap)
|
880
|
-
@map.filename = file.sub(/\.t$/i, '.map')
|
881
|
-
@map.options['Location Description'] = true
|
882
|
-
@map.window.show
|
883
|
-
end
|
884
|
-
@objects = nil
|
885
|
-
@tags = nil # save some memory by clearing the tag list
|
886
|
-
@rooms = nil # and room list
|
455
|
+
def initialize(file, map = Map.new('TADS Map'))
|
456
|
+
debug "Initialize"
|
457
|
+
@classes = { 'Object' => {} }
|
458
|
+
super
|
887
459
|
end
|
888
460
|
end
|
889
461
|
|