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