ifmapper 0.5
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 +2 -0
- data/IFMapper.gemspec +21 -0
- data/IFMapper.rb +27 -0
- data/icons/copy.png +0 -0
- data/icons/cut.png +0 -0
- data/icons/filenew.png +0 -0
- data/icons/fileopen.png +0 -0
- data/icons/filesave.png +0 -0
- data/icons/filesaveas.png +0 -0
- data/icons/help.png +0 -0
- data/icons/kill.png +0 -0
- data/icons/nextpage.png +0 -0
- data/icons/paste.png +0 -0
- data/icons/prevpage.png +0 -0
- data/icons/printicon.png +0 -0
- data/icons/redo.png +0 -0
- data/icons/saveas.png +0 -0
- data/icons/undo.png +0 -0
- data/icons/winapp.png +0 -0
- data/icons/zoom.png +0 -0
- data/lib/IFMapper/Connection.rb +63 -0
- data/lib/IFMapper/FXAboutDialogBox.rb +32 -0
- data/lib/IFMapper/FXConnection.rb +283 -0
- data/lib/IFMapper/FXConnectionDialogBox.rb +126 -0
- data/lib/IFMapper/FXMap.rb +1614 -0
- data/lib/IFMapper/FXMapDialogBox.rb +51 -0
- data/lib/IFMapper/FXMapFileDialog.rb +29 -0
- data/lib/IFMapper/FXMapperSettings.rb +45 -0
- data/lib/IFMapper/FXMapperWindow.rb +1051 -0
- data/lib/IFMapper/FXPage.rb +24 -0
- data/lib/IFMapper/FXPageDialogBox.rb +38 -0
- data/lib/IFMapper/FXRoom.rb +218 -0
- data/lib/IFMapper/FXRoomDialogBox.rb +119 -0
- data/lib/IFMapper/FXSearchDialogBox.rb +51 -0
- data/lib/IFMapper/FXSpline.rb +54 -0
- data/lib/IFMapper/FXWarningBox.rb +45 -0
- data/lib/IFMapper/IFMReader.rb +613 -0
- data/lib/IFMapper/Map.rb +110 -0
- data/lib/IFMapper/PDFMapExporter.rb +315 -0
- data/lib/IFMapper/Page.rb +158 -0
- data/lib/IFMapper/Room.rb +104 -0
- data/maps/Bureaucracy.ifm +75 -0
- data/maps/Hollywood_Hijinx.ifm +149 -0
- data/maps/Jigsaw.ifm +806 -0
- data/maps/LGOP.ifm +705 -0
- data/maps/Mercy.ifm +76 -0
- data/maps/Planetfall.ifm +186 -0
- data/maps/Plundered_Hearts.ifm +251 -0
- data/maps/Ralph.ifm +50 -0
- data/maps/Robots_of_Dawn.ifm +224 -0
- data/maps/Seastalker.ifm +149 -0
- data/maps/Sherlock.ifm +209 -0
- data/maps/SoFar.ifm +72 -0
- data/maps/Starcross.ifm +170 -0
- data/maps/Suspended.ifm +82 -0
- data/maps/Wishbringer.ifm +277 -0
- data/maps/Wishbringer2.ifm +246 -0
- data/maps/Zork1.ifm +410 -0
- data/maps/Zork2.ifm +150 -0
- data/maps/Zork3.ifm +136 -0
- data/maps/Zork_Zero.ifm +557 -0
- data/maps/anchor.ifm +645 -0
- data/maps/atrox.ifm +134 -0
- data/maps/awaken.ifm +116 -0
- data/maps/babel.ifm +279 -0
- data/maps/bse.ifm +150 -0
- data/maps/change.ifm +128 -0
- data/maps/curses.ifm +307 -0
- data/maps/curves.ifm +529 -0
- data/maps/edifice.ifm +158 -0
- data/maps/frozen.ifm +126 -0
- data/maps/glow.ifm +101 -0
- data/maps/library.ifm +93 -0
- data/maps/mindelec.ifm +89 -0
- data/maps/minster.ifm +234 -0
- data/maps/muse.ifm +154 -0
- data/maps/paperchase.ifm +110 -0
- data/maps/space_st.ifm +104 -0
- data/maps/stationfall.ifm +320 -0
- data/maps/theatre.ifm +182 -0
- data/maps/toonesia.ifm +54 -0
- data/maps/tortoise.ifm +72 -0
- data/maps/vgame.ifm +219 -0
- data/maps/weather.ifm +98 -0
- data/maps/windhall.ifm +154 -0
- data/maps/zebulon.ifm +68 -0
- metadata +144 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class FXPoint
|
4
|
+
def [](idx)
|
5
|
+
return x if idx == 0
|
6
|
+
return y if idx == 1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class FXSpline
|
11
|
+
def self.bspline_solve( r, p, num )
|
12
|
+
ax = -1.0/6 * p[0][0] + 0.5 * p[1][0] - 0.5 * p[2][0] + 1.0/6 * p[3][0]
|
13
|
+
bx = 0.5 * p[0][0] - p[1][0] + 0.5 * p[2][0]
|
14
|
+
cx = -0.5 * p[0][0] + 0.5 * p[2][0]
|
15
|
+
dx = 1.0/6 * p[0][0] + 4.0/6 * p[1][0] + 1.0/6 * p[2][0]
|
16
|
+
|
17
|
+
ay = -1.0/6 * p[0][1] + 0.5 * p[1][1] - 0.5 * p[2][1] + 1.0/6 * p[3][1]
|
18
|
+
by = 0.5 * p[0][1] - p[1][1] + 0.5 * p[2][1]
|
19
|
+
cy = -0.5 * p[0][1] + 0.5 * p[2][1]
|
20
|
+
dy = 1.0/6 * p[0][1] + 4.0/6 * p[1][1] + 1.0/6 * p[2][1]
|
21
|
+
|
22
|
+
t = 0.0
|
23
|
+
tinc = 1.0 / num
|
24
|
+
0.upto(num) {
|
25
|
+
x = t * (t * (t * ax + bx) + cx) + dx
|
26
|
+
y = t * (t * (t * ay + by) + cy) + dy
|
27
|
+
r << [ x, y ]
|
28
|
+
t += tinc
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.bspline(p)
|
33
|
+
pts = []
|
34
|
+
0.upto(p.size-4) { |x|
|
35
|
+
x2 = p[x+2][0] - p[x+1][0]
|
36
|
+
x2 = x2 * x2
|
37
|
+
y2 = p[x+2][1] - p[x+1][1]
|
38
|
+
y2 = y2 * y2
|
39
|
+
num = Math.sqrt( x2 + y2 ).to_i / 8
|
40
|
+
bspline_solve(pts, p[x..x+3], num)
|
41
|
+
}
|
42
|
+
return pts
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class FXDC
|
47
|
+
# Draw a bspline curve of any number of segments
|
48
|
+
def drawBSpline(p)
|
49
|
+
tmp = FXSpline::bspline(p)
|
50
|
+
pts = tmp.collect { |x| FXPoint.new( x[0].to_i, x[1].to_i) }
|
51
|
+
drawLines(pts)
|
52
|
+
return pts
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class FXWarningBox < FXDialogBox
|
4
|
+
def initialize(parent, text)
|
5
|
+
super( parent, "Warning", DECOR_ALL, 0, 0, 400, 130)
|
6
|
+
# Frame
|
7
|
+
s = FXVerticalFrame.new(self,
|
8
|
+
LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
|
9
|
+
|
10
|
+
f = FXHorizontalFrame.new(s, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y)
|
11
|
+
|
12
|
+
font = FXFont.new(app, "Helvetica", 30)
|
13
|
+
oops = FXLabel.new(f, "!", nil, 0, LAYOUT_SIDE_LEFT|LAYOUT_FILL_X|
|
14
|
+
LAYOUT_CENTER_Y)
|
15
|
+
oops.frameStyle = FRAME_RAISED|FRAME_THICK
|
16
|
+
oops.baseColor = 'dark grey'
|
17
|
+
oops.textColor = 'red'
|
18
|
+
oops.padLeft = oops.padRight = 15
|
19
|
+
oops.shadowColor = 'black'
|
20
|
+
oops.borderColor = 'white'
|
21
|
+
oops.font = font
|
22
|
+
|
23
|
+
FXLabel.new(f, text, nil, 0)
|
24
|
+
|
25
|
+
# Separator
|
26
|
+
FXHorizontalSeparator.new(s,
|
27
|
+
LAYOUT_SIDE_TOP|LAYOUT_FILL_X|SEPARATOR_GROOVE)
|
28
|
+
|
29
|
+
# Bottom buttons
|
30
|
+
buttons = FXHorizontalFrame.new(s,
|
31
|
+
LAYOUT_SIDE_BOTTOM|FRAME_NONE|
|
32
|
+
LAYOUT_FILL_X|PACK_UNIFORM_WIDTH)
|
33
|
+
# Accept
|
34
|
+
yes = FXButton.new(buttons, "&Yes", nil, self, FXDialogBox::ID_ACCEPT,
|
35
|
+
FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|
|
36
|
+
LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
37
|
+
|
38
|
+
# Cancel
|
39
|
+
no = FXButton.new(buttons, "&No", nil, self, FXDialogBox::ID_CANCEL,
|
40
|
+
FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|
|
41
|
+
LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
42
|
+
no.setDefault
|
43
|
+
no.setFocus
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,613 @@
|
|
1
|
+
|
2
|
+
require "IFMapper/Map"
|
3
|
+
|
4
|
+
class FXMap; end
|
5
|
+
|
6
|
+
#
|
7
|
+
# Class that allows importing an IFM map file.
|
8
|
+
#
|
9
|
+
class IFMReader
|
10
|
+
|
11
|
+
class ParseError < StandardError; end
|
12
|
+
class MapError < StandardError; end
|
13
|
+
|
14
|
+
|
15
|
+
DIRECTIONS = {
|
16
|
+
'north' => 0,
|
17
|
+
'n' => 0,
|
18
|
+
'northeast' => 1,
|
19
|
+
'ne' => 1,
|
20
|
+
'east' => 2,
|
21
|
+
'e' => 2,
|
22
|
+
'southeast' => 3,
|
23
|
+
'se' => 3,
|
24
|
+
'south' => 4,
|
25
|
+
's' => 4,
|
26
|
+
'southwest' => 5,
|
27
|
+
'sw' => 5,
|
28
|
+
'west' => 6,
|
29
|
+
'w' => 6,
|
30
|
+
'northwest' => 7,
|
31
|
+
'nw' => 7,
|
32
|
+
}
|
33
|
+
|
34
|
+
GO = {
|
35
|
+
'd' => 2,
|
36
|
+
'down' => 2,
|
37
|
+
'up' => 1,
|
38
|
+
'u' => 1,
|
39
|
+
'i' => 3,
|
40
|
+
'in' => 3,
|
41
|
+
'o' => 4,
|
42
|
+
'out' => 4,
|
43
|
+
}
|
44
|
+
|
45
|
+
attr_reader :map
|
46
|
+
|
47
|
+
#
|
48
|
+
# Main parsing loop. We basically parse the file twice to
|
49
|
+
# solve dependencies. Yes, this is inefficient, but the alternative
|
50
|
+
# was to build a full parser that understands forward dependencies.
|
51
|
+
#
|
52
|
+
def parse(file)
|
53
|
+
# We start map at 1, 1
|
54
|
+
@x, @y = [0, 0]
|
55
|
+
@room = @item = @task = nil
|
56
|
+
@map.page = 0
|
57
|
+
@last_page = 0
|
58
|
+
@ignore_first_section = true
|
59
|
+
@room_idx = 0
|
60
|
+
line_number = 0
|
61
|
+
|
62
|
+
while not file.eof?
|
63
|
+
@line = ''
|
64
|
+
while not file.eof? and @line !~ /;\s*(#.*)?$/
|
65
|
+
@line << file.readline()
|
66
|
+
@line.sub!( /^\s*#.*/, '')
|
67
|
+
line_number += 1
|
68
|
+
end
|
69
|
+
@line.sub!( /;\s*#.*$/, '')
|
70
|
+
@line.sub! /^\s+/, ''
|
71
|
+
@line.gsub! /;\s*$/, ''
|
72
|
+
@line.gsub! /\n/, ' '
|
73
|
+
next if @line == ''
|
74
|
+
full_line = @line.dup
|
75
|
+
# puts "#{line_number}:'#{@line}'"
|
76
|
+
begin
|
77
|
+
parse_line
|
78
|
+
rescue ParseError => e
|
79
|
+
$stderr.puts
|
80
|
+
$stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
|
81
|
+
$stderr.puts ">>>> #{full_line};"
|
82
|
+
$stderr.puts
|
83
|
+
rescue => e
|
84
|
+
$stderr.puts
|
85
|
+
$stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
|
86
|
+
$stderr.puts ">>>> #{full_line};"
|
87
|
+
$stderr.puts e.backtrace if not e.kind_of?(MapError)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# see if line is a variable line. if so, return true
|
94
|
+
#
|
95
|
+
def parse_variable
|
96
|
+
@line =~ /^\s*[\w_\-\.]+\s*=.*$/
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Get a quoted string from line
|
101
|
+
#
|
102
|
+
def get_string
|
103
|
+
str = nil
|
104
|
+
if @line =~ /^"(([^"\\]|\\")*)"/
|
105
|
+
str = $1
|
106
|
+
@line = @line[str.size+2..-1]
|
107
|
+
# Change any quoted " to normal "
|
108
|
+
str.gsub! /\\"/, '"'
|
109
|
+
end
|
110
|
+
return str
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Get a new token from line
|
115
|
+
#
|
116
|
+
def get_token
|
117
|
+
return nil if not @line
|
118
|
+
token, @line = @line.split(' ', 2)
|
119
|
+
return token
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Look-ahead a token, without modifying line
|
124
|
+
#
|
125
|
+
def next_token
|
126
|
+
return nil if not @line
|
127
|
+
token, = @line.split(' ', 2)
|
128
|
+
return token
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Return whether next token is a tag or a language keyword
|
133
|
+
#
|
134
|
+
def is_tag?
|
135
|
+
token = next_token
|
136
|
+
case token
|
137
|
+
when nil,
|
138
|
+
'to', 'cmd', 'from', 'need', 'lose', 'lost', 'tag', 'all', 'except',
|
139
|
+
'before', 'after', 'start', 'style', 'endstyle', 'follow', 'link',
|
140
|
+
'until', 'dir', 'start', 'get', 'drop', 'goto', 'give', 'given',
|
141
|
+
'exit', 'in', 'keep', 'any', 'safe', 'score', 'style', 'go',
|
142
|
+
'hidden', 'oneway', 'finish', 'length', 'nopath', 'note', 'leave',
|
143
|
+
'join', /^#/
|
144
|
+
return false
|
145
|
+
else
|
146
|
+
return true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Get a tag
|
152
|
+
#
|
153
|
+
def get_tag
|
154
|
+
tag = get_token
|
155
|
+
if tag == 'it'
|
156
|
+
return [tag, (@item or @task or @room)]
|
157
|
+
elsif tag == 'last' or tag == 'all' or tag == 'any'
|
158
|
+
return [tag, nil]
|
159
|
+
else
|
160
|
+
if not @tags[tag]
|
161
|
+
if @resolve_tags
|
162
|
+
raise ParseError, "Tag <#{tag}> not known"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
return [tag, @tags[tag]]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Get a room
|
171
|
+
#
|
172
|
+
def get_room
|
173
|
+
tag, room = get_tag
|
174
|
+
return @room if tag == 'last'
|
175
|
+
if room and not room.kind_of?(Room)
|
176
|
+
raise ParseError, "Not a room tag <#{tag}:#{room}>"
|
177
|
+
end
|
178
|
+
return room
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# Get an item
|
183
|
+
#
|
184
|
+
def get_item
|
185
|
+
tag, item = get_tag
|
186
|
+
return @item if tag == 'last'
|
187
|
+
if item and item.kind_of?(Room)
|
188
|
+
raise ParseError, "Not an item tag <#{tag}:#{item}>"
|
189
|
+
end
|
190
|
+
return item
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# Get a task
|
195
|
+
#
|
196
|
+
def get_task
|
197
|
+
tag, task = get_tag
|
198
|
+
return @task if tag == 'last'
|
199
|
+
if task and task.kind_of?(Room)
|
200
|
+
raise ParseError, "Not an task tag <#{tag}:#{item}>"
|
201
|
+
end
|
202
|
+
return task
|
203
|
+
end
|
204
|
+
|
205
|
+
#
|
206
|
+
# Parse a line of file
|
207
|
+
#
|
208
|
+
def parse_line
|
209
|
+
return if parse_variable
|
210
|
+
|
211
|
+
roomname = tagname = nil
|
212
|
+
@task = @item = roomA = roomB =
|
213
|
+
from = one_way = nolink = go = nil
|
214
|
+
styles = []
|
215
|
+
links = []
|
216
|
+
dir = []
|
217
|
+
|
218
|
+
|
219
|
+
roomA = @room # add item to this room
|
220
|
+
|
221
|
+
while @line and token = get_token
|
222
|
+
case token
|
223
|
+
when 'map'
|
224
|
+
section = get_string
|
225
|
+
# Once we start a new section, we rest room and
|
226
|
+
# current position.
|
227
|
+
@room = nil
|
228
|
+
@x = @y = 1
|
229
|
+
# dont' add a page for first 'map' keyword
|
230
|
+
if @ignore_first_section
|
231
|
+
@ignore_first_section = false
|
232
|
+
@map.pages[0].name = section
|
233
|
+
next
|
234
|
+
end
|
235
|
+
if @resolve_tags
|
236
|
+
@map.page = @last_page + 1
|
237
|
+
@last_page = @map.page
|
238
|
+
else
|
239
|
+
@map.new_page
|
240
|
+
@map.pages[-1].name = section
|
241
|
+
end
|
242
|
+
when 'title'
|
243
|
+
@map.name = get_string
|
244
|
+
when 'room'
|
245
|
+
roomname = get_string
|
246
|
+
@ignore_first_section = false
|
247
|
+
when 'tag'
|
248
|
+
tagname = get_token
|
249
|
+
when 'dir'
|
250
|
+
# if not roomname
|
251
|
+
# raise ParseError, 'dir directive found but not for a room'
|
252
|
+
# end
|
253
|
+
token = next_token
|
254
|
+
while DIRECTIONS[token]
|
255
|
+
get_token
|
256
|
+
dir.push(DIRECTIONS[token])
|
257
|
+
token = next_token
|
258
|
+
|
259
|
+
if token =~ /^\d+$/
|
260
|
+
get_token
|
261
|
+
(token.to_i - 1).times { dir.push(dir[-1]) }
|
262
|
+
token = next_token
|
263
|
+
end
|
264
|
+
end
|
265
|
+
when 'nolink'
|
266
|
+
if dir.empty?
|
267
|
+
raise ParseError, 'nolink directive, but no dir directive before.'
|
268
|
+
end
|
269
|
+
nolink = true
|
270
|
+
when 'oneway'
|
271
|
+
one_way = true
|
272
|
+
when 'nopath'
|
273
|
+
when 'safe'
|
274
|
+
when 'exit'
|
275
|
+
if not roomname
|
276
|
+
raise ParseError, 'exit directive found but not for a room'
|
277
|
+
end
|
278
|
+
token = next_token
|
279
|
+
while DIRECTIONS[token]
|
280
|
+
get_token
|
281
|
+
token = next_token
|
282
|
+
end
|
283
|
+
when 'from'
|
284
|
+
if dir.empty?
|
285
|
+
raise ParseError, "'from' token found but no dir specified before"
|
286
|
+
end
|
287
|
+
from = get_room
|
288
|
+
when 'join'
|
289
|
+
# Joins are links that are not drawn.
|
290
|
+
# Mainly to give the engine knowledge that two locations
|
291
|
+
# are interconnected
|
292
|
+
get_room while is_tag?
|
293
|
+
to = next_token
|
294
|
+
if to == 'to'
|
295
|
+
get_token
|
296
|
+
get_room
|
297
|
+
end
|
298
|
+
when 'all'
|
299
|
+
when 'lost'
|
300
|
+
when 'except'
|
301
|
+
get_item while is_tag?
|
302
|
+
when 'length'
|
303
|
+
get_token
|
304
|
+
when 'until'
|
305
|
+
get_task while is_tag?
|
306
|
+
when 'link'
|
307
|
+
if roomname
|
308
|
+
while is_tag?
|
309
|
+
links.push( get_room )
|
310
|
+
end
|
311
|
+
else
|
312
|
+
roomA = get_room
|
313
|
+
to = next_token
|
314
|
+
if to == 'to'
|
315
|
+
get_token
|
316
|
+
roomB = get_room
|
317
|
+
end
|
318
|
+
end
|
319
|
+
when 'goto'
|
320
|
+
get_room
|
321
|
+
when 'go'
|
322
|
+
token = get_token
|
323
|
+
go = GO[token]
|
324
|
+
if not token
|
325
|
+
raise ParseError, "Token <#{token}> is an unknown go direction."
|
326
|
+
end
|
327
|
+
when 'item'
|
328
|
+
@item = get_string
|
329
|
+
item_tag = get_token if not @item
|
330
|
+
when 'in'
|
331
|
+
roomA = get_room # oh, well... this room
|
332
|
+
when 'note'
|
333
|
+
note = get_string
|
334
|
+
when 'keep'
|
335
|
+
token = next_token
|
336
|
+
if token == 'with'
|
337
|
+
get_token
|
338
|
+
item_keep = get_item
|
339
|
+
end
|
340
|
+
when 'given'
|
341
|
+
when 'give'
|
342
|
+
give_items = [ get_item ]
|
343
|
+
give_items.push(get_item) while is_tag?
|
344
|
+
when 'start'
|
345
|
+
when 'finish'
|
346
|
+
when 'follow'
|
347
|
+
task = get_task
|
348
|
+
when 'need'
|
349
|
+
need_items = [ get_item ]
|
350
|
+
need_items.push(get_item) while is_tag?
|
351
|
+
when 'after'
|
352
|
+
after_tasks = [ get_item ]
|
353
|
+
after_tasks.push(get_item) while is_tag?
|
354
|
+
when 'lose'
|
355
|
+
loose_items = [ get_item ]
|
356
|
+
loose_items.push(get_item) while is_tag?
|
357
|
+
when 'get'
|
358
|
+
get_items = [ get_item ]
|
359
|
+
get_items.push(get_item) while is_tag?
|
360
|
+
when 'drop'
|
361
|
+
drop_items = [ get_item ]
|
362
|
+
drop_items.push(get_item) while is_tag?
|
363
|
+
when 'hidden'
|
364
|
+
when 'leave'
|
365
|
+
leave_items = [ get_item ]
|
366
|
+
leave_items.push(get_item) while is_tag?
|
367
|
+
when 'before'
|
368
|
+
task = get_task
|
369
|
+
when 'cmd'
|
370
|
+
token = next_token
|
371
|
+
if token == 'to'
|
372
|
+
get_token
|
373
|
+
cmd = get_string
|
374
|
+
elsif token == 'from'
|
375
|
+
get_token
|
376
|
+
cmd = get_string
|
377
|
+
elsif token == 'none'
|
378
|
+
get_token
|
379
|
+
else
|
380
|
+
cmd = get_string
|
381
|
+
num = next_token
|
382
|
+
get_token if num =~ /\d+/
|
383
|
+
end
|
384
|
+
when 'score'
|
385
|
+
score = get_token.to_i
|
386
|
+
when 'length'
|
387
|
+
length = get_token.to_i
|
388
|
+
when 'task'
|
389
|
+
@task = get_string
|
390
|
+
task_tag = get_token if not @task
|
391
|
+
when 'style'
|
392
|
+
styles.push(get_token) while is_tag?
|
393
|
+
when 'endstyle'
|
394
|
+
get_token while is_tag?
|
395
|
+
when '', /^#/
|
396
|
+
get_token while @line
|
397
|
+
else
|
398
|
+
raise ParseError, "Token <#{token.inspect}> not understood"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# If a direction, move that way.
|
403
|
+
if dir.size > 0
|
404
|
+
# If from keyword present, move from that room on.
|
405
|
+
if from
|
406
|
+
roomA = from
|
407
|
+
# 'from' can also connect stuff not in current page...
|
408
|
+
# so we check we are in the right page.
|
409
|
+
@map.pages.each_with_index { |p, idx|
|
410
|
+
if p.rooms.include?(roomA)
|
411
|
+
@map.page = idx
|
412
|
+
break
|
413
|
+
end
|
414
|
+
}
|
415
|
+
end
|
416
|
+
# Okay, we start from roomA... and follow each dir
|
417
|
+
if roomA
|
418
|
+
@x = roomA.x
|
419
|
+
@y = roomA.y
|
420
|
+
end
|
421
|
+
dir.each { |d|
|
422
|
+
x, y = Room::DIR_TO_VECTOR[d]
|
423
|
+
@x += x
|
424
|
+
@y += y
|
425
|
+
}
|
426
|
+
end
|
427
|
+
|
428
|
+
# Create new room
|
429
|
+
if roomname
|
430
|
+
if @resolve_tags
|
431
|
+
# Room is already created. Find it.
|
432
|
+
roomB = @rooms[@room_idx]
|
433
|
+
@room_idx += 1
|
434
|
+
else
|
435
|
+
# Verify there's no room in that location yet
|
436
|
+
page = @map.pages[@map.page]
|
437
|
+
page.rooms.each { |r|
|
438
|
+
if r.x == @x and r.y == @y
|
439
|
+
err = "Page #{@map.page+1} already has location #{r} at #{@x}, #{@y}.\n"
|
440
|
+
err << "Cannot create '#{roomname}'"
|
441
|
+
raise MapError, err
|
442
|
+
end
|
443
|
+
}
|
444
|
+
|
445
|
+
# Remember original room for connection
|
446
|
+
roomB = @map.new_room( @x, @y )
|
447
|
+
roomB.name = roomname
|
448
|
+
@rooms.push( roomB )
|
449
|
+
end
|
450
|
+
|
451
|
+
# Make roomB the current room
|
452
|
+
@room = roomB
|
453
|
+
end
|
454
|
+
|
455
|
+
if @item and roomA and @resolve_tags
|
456
|
+
roomA.objects << @item + "\n"
|
457
|
+
end
|
458
|
+
|
459
|
+
if @task and @room and @resolve_tags
|
460
|
+
@room.tasks << @task + "\n"
|
461
|
+
end
|
462
|
+
|
463
|
+
# Add a link between rooms
|
464
|
+
if roomA and roomB and not nolink and @resolve_tags
|
465
|
+
# Establish new simple connection
|
466
|
+
dirB = (dir[-1] + 4) % 8 if dir.size > 1
|
467
|
+
|
468
|
+
if dir.size > 1
|
469
|
+
dirB = [ @x - roomB.x, @y - roomB.y ]
|
470
|
+
if dirB[0] == 0 and dirB[1] == 0
|
471
|
+
dirB = (dir[-1] + 4) % 8
|
472
|
+
else
|
473
|
+
dirB = roomB.vector_to_dir( dirB[0], dirB[1] )
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# 'from' and 'link' keywords can also connect stuff not in
|
478
|
+
# current page... so we check for that here
|
479
|
+
@map.pages.each_with_index { |p, idx|
|
480
|
+
if p.rooms.include?(roomA)
|
481
|
+
@map.page = idx
|
482
|
+
break
|
483
|
+
end
|
484
|
+
}
|
485
|
+
if not @map.pages[@map.page].rooms.include?(roomB)
|
486
|
+
raise MapError, "Linking #{roomA} and #{roomB} which are in different pages"
|
487
|
+
end
|
488
|
+
|
489
|
+
begin
|
490
|
+
c = map.new_connection( roomA, dir[0], roomB, dirB )
|
491
|
+
c.dir = Connection::AtoB if one_way
|
492
|
+
c.type = Connection::SPECIAL if styles.include?('special')
|
493
|
+
|
494
|
+
if go
|
495
|
+
c.exitAtext = go
|
496
|
+
if go % 2 == 0
|
497
|
+
c.exitBtext = go - 1
|
498
|
+
else
|
499
|
+
c.exitBtext = go + 1
|
500
|
+
end
|
501
|
+
end
|
502
|
+
rescue => e
|
503
|
+
puts e
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
if links.size > 0 and @resolve_tags
|
508
|
+
# Additional diagonal connection for room
|
509
|
+
links.each { |x|
|
510
|
+
next if not x
|
511
|
+
begin
|
512
|
+
c = map.new_connection( @room, nil, x, nil )
|
513
|
+
rescue => e
|
514
|
+
puts e
|
515
|
+
end
|
516
|
+
}
|
517
|
+
end
|
518
|
+
|
519
|
+
if tagname # and not @resolve_tags
|
520
|
+
if roomname
|
521
|
+
@tags[tagname] = @room
|
522
|
+
elsif @item
|
523
|
+
@tags[tagname] = @item
|
524
|
+
elsif @task
|
525
|
+
@tags[tagname] = @task
|
526
|
+
else
|
527
|
+
# join/link tag
|
528
|
+
@tags[tagname] = ''
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
#
|
534
|
+
# Okay, check all min/max locations in all pages
|
535
|
+
# and then do the following:
|
536
|
+
# a) Adjust map's width/height
|
537
|
+
# b) Shift all rooms so that no rooms are in negative locations
|
538
|
+
#
|
539
|
+
def adjust_map
|
540
|
+
# First, adjust map's width and height
|
541
|
+
@map.width = @map.height = 1
|
542
|
+
minXY = []
|
543
|
+
maxXY = []
|
544
|
+
@map.pages.each { |page|
|
545
|
+
next if page.rooms.empty?
|
546
|
+
|
547
|
+
sizes = page.min_max_rooms
|
548
|
+
minXY.push sizes[0]
|
549
|
+
maxXY.push sizes[1]
|
550
|
+
|
551
|
+
w = maxXY[-1][0] - minXY[-1][0]
|
552
|
+
h = maxXY[-1][1] - minXY[-1][1]
|
553
|
+
|
554
|
+
# We store +3 to allow for complex connections if needed.
|
555
|
+
@map.width = w + 3 if w >= @map.width - 2
|
556
|
+
@map.height = h + 3 if h >= @map.height - 2
|
557
|
+
}
|
558
|
+
|
559
|
+
|
560
|
+
# Okay, minXY[]/maxXY[] contains all the minXY/maxXY positions of
|
561
|
+
# each page. With that info and @map.weight/height, we can
|
562
|
+
# start shifting all nodes in the page so that they will fit.
|
563
|
+
idx = 0
|
564
|
+
@map.pages.each { |p|
|
565
|
+
next if p.rooms.size == 0
|
566
|
+
x = y = 0
|
567
|
+
x = 1 - minXY[idx][0] if minXY[idx][0] < 1
|
568
|
+
y = 1 - minXY[idx][1] if minXY[idx][1] < 1
|
569
|
+
x = @map.width - maxXY[idx][0] - 1 if maxXY[idx][0] >= @map.width - 1
|
570
|
+
y = @map.height - maxXY[idx][1] - 1 if maxXY[idx][1] >= @map.height - 1
|
571
|
+
idx += 1
|
572
|
+
next if x == 0 and y == 0 # nothing to shift
|
573
|
+
p.rooms.each { |r|
|
574
|
+
r.x += x
|
575
|
+
r.y += y
|
576
|
+
}
|
577
|
+
}
|
578
|
+
|
579
|
+
@map.create_pathmap if @map.kind_of?(FXMap)
|
580
|
+
end
|
581
|
+
|
582
|
+
def initialize(file, map = Map.new('IMF Imported Map'))
|
583
|
+
@tags = {}
|
584
|
+
@map = map
|
585
|
+
@rooms = []
|
586
|
+
@resolve_tags = false
|
587
|
+
# puts '--------------- first pass'
|
588
|
+
File.open(file) { |f|
|
589
|
+
parse(f)
|
590
|
+
}
|
591
|
+
# puts '--------------- second pass'
|
592
|
+
@resolve_tags = true
|
593
|
+
File.open(file) { |f|
|
594
|
+
parse(f)
|
595
|
+
}
|
596
|
+
# puts '--------------- second pass done'
|
597
|
+
@map.page = 0
|
598
|
+
@map.filename = file.sub(/\.ifm$/i, '.map')
|
599
|
+
@map.navigation = true
|
600
|
+
adjust_map
|
601
|
+
@tags = {} # save some memory by clearing the tag list
|
602
|
+
@rooms = nil # and room list
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
|
607
|
+
if $0 == __FILE__
|
608
|
+
p "Opening file '#{ARGV[0]}'"
|
609
|
+
$LOAD_PATH << '..'
|
610
|
+
require "IFMapper/Map"
|
611
|
+
|
612
|
+
IFMReader.new(ARGV[0])
|
613
|
+
end
|