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