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