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