ifmapper 1.0.0 → 1.0.6
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 +648 -627
- data/IFMapper.gemspec +29 -28
- data/IFMapper.rbw +31 -31
- data/TODO.txt +8 -7
- data/bin/IFMapper +31 -31
- data/docs/en/index.html +0 -0
- data/docs/en/start.html +3 -2
- data/docs/en/start.html~ +516 -0
- data/docs/es/index.html +0 -0
- data/docs/es/start.html +13 -14
- data/docs/es/start.html~ +1280 -0
- data/docs/images/IFMapper_main.gif +0 -0
- data/docs/images/automap.gif +0 -0
- data/docs/images/complex_connection.gif +0 -0
- data/docs/images/connection.gif +0 -0
- data/docs/images/connection_menu.gif +0 -0
- data/docs/images/room_description.gif +0 -0
- data/docs/images/room_small.gif +0 -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/room_e.gif +0 -0
- data/icons/room_e.xpm +0 -0
- data/icons/room_n.gif +0 -0
- data/icons/room_n.xpm +0 -0
- data/icons/room_ne.gif +0 -0
- data/icons/room_ne.xpm +0 -0
- data/icons/room_nw.gif +0 -0
- data/icons/room_nw.xpm +0 -0
- data/icons/room_s.gif +0 -0
- data/icons/room_s.xpm +0 -0
- data/icons/room_se.gif +0 -0
- data/icons/room_se.xpm +0 -0
- data/icons/room_sw.gif +0 -0
- data/icons/room_sw.xpm +0 -0
- data/icons/room_w.gif +0 -0
- data/icons/room_w.xpm +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/AStar.rb +250 -250
- data/lib/IFMapper/Connection.rb +202 -202
- data/lib/IFMapper/FXAboutDialogBox.rb +32 -32
- data/lib/IFMapper/FXConnection.rb +364 -364
- data/lib/IFMapper/FXConnectionDialogBox.rb +124 -124
- data/lib/IFMapper/FXDCPostscript.rb +404 -404
- data/lib/IFMapper/FXDCPrint.rb +15 -15
- data/lib/IFMapper/FXItemList.rb +108 -0
- data/lib/IFMapper/FXMap.rb +2147 -2116
- data/lib/IFMapper/FXMapColorBox.rb +88 -88
- data/lib/IFMapper/FXMapDialogBox.rb +127 -127
- data/lib/IFMapper/FXMapFileDialog.rb +34 -34
- data/lib/IFMapper/FXMapperSettings.rb +206 -205
- data/lib/IFMapper/FXMapperWindow.rb +1592 -1571
- data/lib/IFMapper/FXPDFMapExporterOptionsDialogBox.rb +46 -0
- data/lib/IFMapper/FXRoom.rb +263 -263
- data/lib/IFMapper/FXRoomDialogBox.rb +159 -159
- data/lib/IFMapper/FXRoomList.rb +95 -95
- data/lib/IFMapper/FXSearchDialogBox.rb +51 -51
- data/lib/IFMapper/FXSection.rb +33 -33
- data/lib/IFMapper/FXSectionDialogBox.rb +38 -38
- data/lib/IFMapper/FXSpline.rb +52 -52
- data/lib/IFMapper/FXWarningBox.rb +51 -50
- data/lib/IFMapper/GUEReader.rb +445 -445
- data/lib/IFMapper/IFMReader.rb +584 -584
- data/lib/IFMapper/IFMWriter.rb +245 -227
- data/lib/IFMapper/Inform7Writer.rb +579 -573
- data/lib/IFMapper/InformReader.rb +478 -478
- data/lib/IFMapper/InformWriter.rb +364 -359
- data/lib/IFMapper/Map.rb +202 -200
- data/lib/IFMapper/MapPrinting.rb +162 -162
- data/lib/IFMapper/MapReader.rb +900 -900
- data/lib/IFMapper/PDFMapExporter.rb +526 -483
- data/lib/IFMapper/Room.rb +153 -151
- data/lib/IFMapper/Section.rb +234 -234
- data/lib/IFMapper/TADSReader.rb +474 -471
- data/lib/IFMapper/TADSWriter.rb +375 -370
- data/lib/IFMapper/TranscriptDialogBox.rb +0 -0
- data/lib/IFMapper/TranscriptReader.rb +1361 -1359
- data/lib/IFMapper/locales/en/Messages.rb +446 -435
- data/lib/IFMapper/locales/es/Messages.rb +451 -440
- data/lib/IFMapper/locales/es/Messages_iso-8859-1.rb +455 -440
- data/lib/IFMapper/locales/es/runme.sh +3 -3
- data/maps/A New Life.map b/data/maps/A New → Life.map +0 -0
- data/maps/AMFV.map +0 -0
- data/maps/AllRoads.map +0 -0
- data/maps/Aotearoa.map +0 -0
- data/maps/Bronze.map +0 -0
- data/maps/Bureaucracy.ifm +0 -0
- data/maps/Bureaucracy.map +0 -0
- data/maps/CityOfSecrets.map +0 -0
- data/maps/DDIV.map +0 -0
- data/maps/Following_A_Star.map +0 -0
- data/maps/Heated.map +0 -0
- data/maps/Heroine.map +0 -0
- data/maps/History Repeating.map b/data/maps/History → Repeating.map +0 -0
- data/maps/Hollywood_Hijinx.ifm +0 -0
- data/maps/Janitor.map +0 -0
- data/maps/Jigsaw.ifm +0 -0
- data/maps/Jigsaw.map +0 -0
- data/maps/LGOP.ifm +0 -0
- data/maps/Mercy.ifm +0 -0
- data/maps/Ninjas_Fate.map +0 -0
- data/maps/Pen_and_Paint.map +0 -0
- data/maps/Planetfall.ifm +0 -0
- data/maps/Planetfall.map +0 -0
- data/maps/Plundered_Hearts.ifm +0 -0
- data/maps/QuietEvening.map +0 -0
- data/maps/Ralph.ifm +0 -0
- data/maps/Reliques_of_Tolti_Alph.map +0 -0
- data/maps/Revolution.map +0 -0
- data/maps/Robots_of_Dawn.ifm +0 -0
- data/maps/SavoirFare.map +0 -0
- data/maps/Seastalker.ifm +0 -0
- data/maps/Seastalker.map +0 -0
- data/maps/Sherlock.ifm +0 -0
- data/maps/SoFar.ifm +0 -0
- data/maps/Starcross.ifm +0 -0
- data/maps/Suspended.ifm +0 -0
- data/maps/Tangle.map +0 -0
- data/maps/The_Lost_Sheep.map +0 -0
- data/maps/Unforgotten.map +0 -0
- data/maps/Warbler's Nest.map +0 -0
- data/maps/Warbler's_Nest.map +0 -0
- data/maps/Westminster_Abbey.map +0 -0
- data/maps/WinterWonderland.map +0 -0
- data/maps/Wishbringer.ifm +0 -0
- data/maps/Wishbringer2.ifm +0 -0
- data/maps/Zork1.ifm +0 -0
- data/maps/Zork2.ifm +0 -0
- data/maps/Zork3.ifm +0 -0
- data/maps/Zork_Zero.ifm +0 -0
- data/maps/anchor.ifm +0 -0
- data/maps/anchor.map +0 -0
- data/maps/atrox.ifm +0 -0
- data/maps/awaken.ifm +0 -0
- data/maps/babel.ifm +0 -0
- data/maps/balances.map +0 -0
- data/maps/ballerina.map +0 -0
- data/maps/bear.map +0 -0
- data/maps/bluechairs.map +0 -0
- data/maps/break_in.map +0 -0
- data/maps/bse.ifm +0 -0
- data/maps/building.map +0 -0
- data/maps/change.ifm +0 -0
- data/maps/christminster.map +0 -0
- data/maps/curses.ifm +0 -0
- data/maps/curves.ifm +0 -0
- data/maps/deadline.map +0 -0
- data/maps/delusions.map +0 -0
- data/maps/devours.map +0 -0
- data/maps/distress.map +0 -0
- data/maps/djinni.map +0 -0
- data/maps/dreamhold.map +0 -0
- data/maps/drift3.map +0 -0
- data/maps/eas.map +0 -0
- data/maps/eas2.map +0 -0
- data/maps/eas3.map +0 -0
- data/maps/edifice.ifm +0 -0
- data/maps/fallacy.map +0 -0
- data/maps/frozen.ifm +0 -0
- data/maps/gamlet.map +0 -0
- data/maps/glow.ifm +0 -0
- data/maps/guilty_bastards.map +0 -0
- data/maps/heist.map +0 -0
- data/maps/heroes.map +0 -0
- data/maps/inhumane.map +0 -0
- data/maps/kaged.map +0 -0
- data/maps/library.ifm +0 -0
- data/maps/lurkinghorror.map +0 -0
- data/maps/metamorphoses.map +0 -0
- data/maps/mindelec.ifm +0 -0
- data/maps/minster.ifm +0 -0
- data/maps/mite.map +0 -0
- data/maps/moonmist.map +0 -0
- data/maps/muldoon_legacy.map +0 -0
- data/maps/muse.ifm +0 -0
- data/maps/paperchase.ifm +0 -0
- data/maps/party.map +0 -0
- data/maps/pawn.map +0 -0
- data/maps/photograph.map +0 -0
- data/maps/pkgirl.map +0 -0
- data/maps/pytho.map +0 -0
- data/maps/risorgimento.map +0 -0
- data/maps/sherbet.map +0 -0
- data/maps/simple.map +0 -0
- data/maps/slouch.map +0 -0
- data/maps/space_st.ifm +0 -0
- data/maps/splashdown.map +0 -0
- data/maps/spring.map +0 -0
- data/maps/squarecircle.map +0 -0
- data/maps/stationfall.ifm +0 -0
- data/maps/theatre.ifm +0 -0
- data/maps/toonesia.ifm +0 -0
- data/maps/tortoise.ifm +0 -0
- data/maps/trinity.map +0 -0
- data/maps/vespers.map +0 -0
- data/maps/vgame.ifm +0 -0
- data/maps/wasp.map +0 -0
- data/maps/weather.ifm +0 -0
- data/maps/windhall.ifm +0 -0
- data/maps/worlds.map +0 -0
- data/maps/xtcontest.map +0 -0
- data/maps/zdungeon.map +0 -0
- data/maps/zebulon.ifm +0 -0
- data/maps/zerosum.map +0 -0
- metadata +226 -183
data/lib/IFMapper/IFMReader.rb
CHANGED
|
@@ -1,584 +1,584 @@
|
|
|
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
|
-
|
|
57
|
-
if @map.kind_of?(FXMap)
|
|
58
|
-
@map.options['Edit on Creation'] = false
|
|
59
|
-
@map.window.hide
|
|
60
|
-
end
|
|
61
|
-
@map.section = 0
|
|
62
|
-
|
|
63
|
-
@last_section = 0
|
|
64
|
-
@ignore_first_section = true
|
|
65
|
-
@room_idx = 0
|
|
66
|
-
line_number = 0
|
|
67
|
-
|
|
68
|
-
while not file.eof?
|
|
69
|
-
@line = ''
|
|
70
|
-
while not file.eof? and @line !~ /;\s*(#.*)?$/
|
|
71
|
-
@line << file.readline()
|
|
72
|
-
@line.sub!( /^\s*#.*/, '')
|
|
73
|
-
line_number += 1
|
|
74
|
-
end
|
|
75
|
-
@line.sub!( /;\s*#.*$/, '')
|
|
76
|
-
@line.sub! /^\s+/, ''
|
|
77
|
-
@line.gsub! /;\s*$/, ''
|
|
78
|
-
@line.gsub! /\n/, ' '
|
|
79
|
-
next if @line == ''
|
|
80
|
-
full_line = @line.dup
|
|
81
|
-
# puts "#{line_number}:'#{@line}'"
|
|
82
|
-
begin
|
|
83
|
-
parse_line
|
|
84
|
-
rescue ParseError => e
|
|
85
|
-
$stderr.puts
|
|
86
|
-
$stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
|
|
87
|
-
$stderr.puts ">>>> #{full_line};"
|
|
88
|
-
$stderr.puts
|
|
89
|
-
rescue => e
|
|
90
|
-
$stderr.puts
|
|
91
|
-
$stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
|
|
92
|
-
$stderr.puts ">>>> #{full_line};"
|
|
93
|
-
$stderr.puts e.backtrace if not e.kind_of?(MapError)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
# see if line is a variable line. if so, return true
|
|
100
|
-
#
|
|
101
|
-
def parse_variable
|
|
102
|
-
@line =~ /^\s*[\w_\-\.]+\s*=.*$/
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
#
|
|
106
|
-
# Get a quoted string from line
|
|
107
|
-
#
|
|
108
|
-
def get_string
|
|
109
|
-
str = nil
|
|
110
|
-
if @line =~ /^"(([^"\\]|\\")*)"/
|
|
111
|
-
str = $1
|
|
112
|
-
@line = @line[str.size+2..-1]
|
|
113
|
-
# Change any quoted " to normal "
|
|
114
|
-
str.gsub! /\\"/, '"'
|
|
115
|
-
end
|
|
116
|
-
return str
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
#
|
|
120
|
-
# Get a new token from line
|
|
121
|
-
#
|
|
122
|
-
def get_token
|
|
123
|
-
return nil if not @line
|
|
124
|
-
token, @line = @line.split(' ', 2)
|
|
125
|
-
return token
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
#
|
|
129
|
-
# Look-ahead a token, without modifying line
|
|
130
|
-
#
|
|
131
|
-
def next_token
|
|
132
|
-
return nil if not @line
|
|
133
|
-
token, = @line.split(' ', 2)
|
|
134
|
-
return token
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
#
|
|
138
|
-
# Return whether next token is a tag or a language keyword
|
|
139
|
-
#
|
|
140
|
-
def is_tag?
|
|
141
|
-
token = next_token
|
|
142
|
-
case token
|
|
143
|
-
when nil,
|
|
144
|
-
'to', 'cmd', 'from', 'need', 'lose', 'lost', 'tag', 'all', 'except',
|
|
145
|
-
'before', 'after', 'start', 'style', 'endstyle', 'follow', 'link',
|
|
146
|
-
'until', 'dir', 'start', 'get', 'drop', 'goto', 'give', 'given',
|
|
147
|
-
'exit', 'in', 'keep', 'any', 'safe', 'score', 'style', 'go',
|
|
148
|
-
'hidden', 'oneway', 'finish', 'length', 'nopath', 'note', 'leave',
|
|
149
|
-
'join', /^#/
|
|
150
|
-
return false
|
|
151
|
-
else
|
|
152
|
-
return true
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
# Get a tag
|
|
158
|
-
#
|
|
159
|
-
def get_tag
|
|
160
|
-
tag = get_token
|
|
161
|
-
if tag == 'it'
|
|
162
|
-
return [tag, (@item or @task or @room)]
|
|
163
|
-
elsif tag == 'last' or tag == 'all' or tag == 'any'
|
|
164
|
-
return [tag, nil]
|
|
165
|
-
else
|
|
166
|
-
if not @tags[tag]
|
|
167
|
-
if @resolve_tags
|
|
168
|
-
raise ParseError, "Tag <#{tag}> not known"
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
return [tag, @tags[tag]]
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
# Get a room
|
|
177
|
-
#
|
|
178
|
-
def get_room
|
|
179
|
-
tag, room = get_tag
|
|
180
|
-
return @room if tag == 'last'
|
|
181
|
-
if room and not room.kind_of?(Room)
|
|
182
|
-
raise ParseError, "Not a room tag <#{tag}:#{room}>"
|
|
183
|
-
end
|
|
184
|
-
return room
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
#
|
|
188
|
-
# Get an item
|
|
189
|
-
#
|
|
190
|
-
def get_item
|
|
191
|
-
tag, item = get_tag
|
|
192
|
-
return @item if tag == 'last'
|
|
193
|
-
if item and item.kind_of?(Room)
|
|
194
|
-
raise ParseError, "Not an item tag <#{tag}:#{item}>"
|
|
195
|
-
end
|
|
196
|
-
return item
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
#
|
|
200
|
-
# Get a task
|
|
201
|
-
#
|
|
202
|
-
def get_task
|
|
203
|
-
tag, task = get_tag
|
|
204
|
-
return @task if tag == 'last'
|
|
205
|
-
if task and task.kind_of?(Room)
|
|
206
|
-
raise ParseError, "Not a task tag <#{tag}:#{item}>"
|
|
207
|
-
end
|
|
208
|
-
return task
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
#
|
|
212
|
-
# Parse a line of file
|
|
213
|
-
#
|
|
214
|
-
def parse_line
|
|
215
|
-
return if parse_variable
|
|
216
|
-
|
|
217
|
-
roomname = tagname = nil
|
|
218
|
-
@task = @item = roomA = roomB =
|
|
219
|
-
from = one_way = nolink = go = nil
|
|
220
|
-
styles = []
|
|
221
|
-
links = []
|
|
222
|
-
dir = []
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
roomA = @room # add item to this room
|
|
226
|
-
|
|
227
|
-
while @line and token = get_token
|
|
228
|
-
case token
|
|
229
|
-
when 'map'
|
|
230
|
-
section = get_string
|
|
231
|
-
# Once we start a new section, we rest room and
|
|
232
|
-
# current position.
|
|
233
|
-
@room = nil
|
|
234
|
-
@x = @y = 1
|
|
235
|
-
# dont' add a section for first 'map' keyword
|
|
236
|
-
if @ignore_first_section
|
|
237
|
-
@ignore_first_section = false
|
|
238
|
-
@map.sections[0].name = section
|
|
239
|
-
next
|
|
240
|
-
end
|
|
241
|
-
if @resolve_tags
|
|
242
|
-
@map.section = @last_section + 1
|
|
243
|
-
@last_section = @map.section
|
|
244
|
-
else
|
|
245
|
-
@map.new_section
|
|
246
|
-
@map.sections[-1].name = section
|
|
247
|
-
end
|
|
248
|
-
when 'title'
|
|
249
|
-
@map.name = get_string
|
|
250
|
-
when 'room'
|
|
251
|
-
roomname = get_string
|
|
252
|
-
@ignore_first_section = false
|
|
253
|
-
when 'tag'
|
|
254
|
-
tagname = get_token
|
|
255
|
-
when 'dir'
|
|
256
|
-
# if not roomname
|
|
257
|
-
# raise ParseError, 'dir directive found but not for a room'
|
|
258
|
-
# end
|
|
259
|
-
token = next_token
|
|
260
|
-
while DIRECTIONS[token]
|
|
261
|
-
get_token
|
|
262
|
-
dir.push(DIRECTIONS[token])
|
|
263
|
-
token = next_token
|
|
264
|
-
|
|
265
|
-
if token =~ /^\d+$/
|
|
266
|
-
get_token
|
|
267
|
-
(token.to_i - 1).times { dir.push(dir[-1]) }
|
|
268
|
-
token = next_token
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
when 'nolink'
|
|
272
|
-
if dir.empty?
|
|
273
|
-
raise ParseError, 'nolink directive, but no dir directive before.'
|
|
274
|
-
end
|
|
275
|
-
nolink = true
|
|
276
|
-
when 'oneway'
|
|
277
|
-
one_way = true
|
|
278
|
-
when 'nopath'
|
|
279
|
-
when 'safe'
|
|
280
|
-
when 'exit'
|
|
281
|
-
if not roomname
|
|
282
|
-
raise ParseError, 'exit directive found but not for a room'
|
|
283
|
-
end
|
|
284
|
-
token = next_token
|
|
285
|
-
while DIRECTIONS[token]
|
|
286
|
-
get_token
|
|
287
|
-
token = next_token
|
|
288
|
-
end
|
|
289
|
-
when 'from'
|
|
290
|
-
if dir.empty?
|
|
291
|
-
raise ParseError, "'from' token found but no dir specified before"
|
|
292
|
-
end
|
|
293
|
-
from = get_room
|
|
294
|
-
when 'join'
|
|
295
|
-
# Joins are links that are not drawn.
|
|
296
|
-
# Mainly to give the engine knowledge that two locations
|
|
297
|
-
# are interconnected
|
|
298
|
-
get_room while is_tag?
|
|
299
|
-
to = next_token
|
|
300
|
-
if to == 'to'
|
|
301
|
-
get_token
|
|
302
|
-
get_room
|
|
303
|
-
end
|
|
304
|
-
when 'all'
|
|
305
|
-
when 'lost'
|
|
306
|
-
when 'except'
|
|
307
|
-
get_item while is_tag?
|
|
308
|
-
when 'length'
|
|
309
|
-
get_token
|
|
310
|
-
when 'until'
|
|
311
|
-
get_task while is_tag?
|
|
312
|
-
when 'link'
|
|
313
|
-
if roomname
|
|
314
|
-
while is_tag?
|
|
315
|
-
links.push( get_room )
|
|
316
|
-
end
|
|
317
|
-
else
|
|
318
|
-
roomA = get_room
|
|
319
|
-
to = next_token
|
|
320
|
-
if to == 'to'
|
|
321
|
-
get_token
|
|
322
|
-
roomB = get_room
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
when 'goto'
|
|
326
|
-
get_room
|
|
327
|
-
when 'go'
|
|
328
|
-
token = get_token
|
|
329
|
-
go = GO[token]
|
|
330
|
-
if not token
|
|
331
|
-
raise ParseError, "Token <#{token}> is an unknown go direction."
|
|
332
|
-
end
|
|
333
|
-
when 'item'
|
|
334
|
-
@item = get_string
|
|
335
|
-
item_tag = get_token if not @item
|
|
336
|
-
when 'in'
|
|
337
|
-
roomA = get_room # oh, well... this room
|
|
338
|
-
when 'note'
|
|
339
|
-
note = get_string
|
|
340
|
-
when 'keep'
|
|
341
|
-
token = next_token
|
|
342
|
-
if token == 'with'
|
|
343
|
-
get_token
|
|
344
|
-
item_keep = get_item
|
|
345
|
-
end
|
|
346
|
-
when 'given'
|
|
347
|
-
when 'give'
|
|
348
|
-
give_items = [ get_item ]
|
|
349
|
-
give_items.push(get_item) while is_tag?
|
|
350
|
-
when 'start'
|
|
351
|
-
when 'finish'
|
|
352
|
-
when 'follow'
|
|
353
|
-
task = get_task
|
|
354
|
-
when 'need'
|
|
355
|
-
need_items = [ get_item ]
|
|
356
|
-
need_items.push(get_item) while is_tag?
|
|
357
|
-
when 'after'
|
|
358
|
-
after_tasks = [ get_item ]
|
|
359
|
-
after_tasks.push(get_item) while is_tag?
|
|
360
|
-
when 'lose'
|
|
361
|
-
loose_items = [ get_item ]
|
|
362
|
-
loose_items.push(get_item) while is_tag?
|
|
363
|
-
when 'get'
|
|
364
|
-
get_items = [ get_item ]
|
|
365
|
-
get_items.push(get_item) while is_tag?
|
|
366
|
-
when 'drop'
|
|
367
|
-
drop_items = [ get_item ]
|
|
368
|
-
drop_items.push(get_item) while is_tag?
|
|
369
|
-
when 'hidden'
|
|
370
|
-
when 'leave'
|
|
371
|
-
leave_items = [ get_item ]
|
|
372
|
-
leave_items.push(get_item) while is_tag?
|
|
373
|
-
when 'before'
|
|
374
|
-
task = get_task
|
|
375
|
-
when 'cmd'
|
|
376
|
-
token = next_token
|
|
377
|
-
if token == 'to'
|
|
378
|
-
get_token
|
|
379
|
-
cmd = get_string
|
|
380
|
-
elsif token == 'from'
|
|
381
|
-
get_token
|
|
382
|
-
cmd = get_string
|
|
383
|
-
elsif token == 'none'
|
|
384
|
-
get_token
|
|
385
|
-
else
|
|
386
|
-
cmd = get_string
|
|
387
|
-
num = next_token
|
|
388
|
-
get_token if num =~ /\d+/
|
|
389
|
-
end
|
|
390
|
-
when 'score'
|
|
391
|
-
score = get_token.to_i
|
|
392
|
-
when 'length'
|
|
393
|
-
length = get_token.to_i
|
|
394
|
-
when 'task'
|
|
395
|
-
@task = get_string
|
|
396
|
-
task_tag = get_token if not @task
|
|
397
|
-
when 'style'
|
|
398
|
-
styles.push(get_token) while is_tag?
|
|
399
|
-
when 'endstyle'
|
|
400
|
-
get_token while is_tag?
|
|
401
|
-
when '', /^#/
|
|
402
|
-
get_token while @line
|
|
403
|
-
else
|
|
404
|
-
raise ParseError, "Token <#{token.inspect}> not understood"
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
# If a direction, move that way.
|
|
409
|
-
if dir.size > 0
|
|
410
|
-
# If from keyword present, move from that room on.
|
|
411
|
-
if from
|
|
412
|
-
roomA = from
|
|
413
|
-
# 'from' can also connect stuff not in current section...
|
|
414
|
-
# so we check we are in the right section.
|
|
415
|
-
@map.sections.each_with_index { |p, idx|
|
|
416
|
-
if p.rooms.include?(roomA)
|
|
417
|
-
@map.fit
|
|
418
|
-
@map.section = idx
|
|
419
|
-
break
|
|
420
|
-
end
|
|
421
|
-
}
|
|
422
|
-
end
|
|
423
|
-
# Okay, we start from roomA... and follow each dir
|
|
424
|
-
if roomA
|
|
425
|
-
@x = roomA.x
|
|
426
|
-
@y = roomA.y
|
|
427
|
-
end
|
|
428
|
-
dir.each { |d|
|
|
429
|
-
x, y = Room::DIR_TO_VECTOR[d]
|
|
430
|
-
@x += x
|
|
431
|
-
@y += y
|
|
432
|
-
}
|
|
433
|
-
end
|
|
434
|
-
|
|
435
|
-
# Create new room
|
|
436
|
-
if roomname
|
|
437
|
-
if @resolve_tags
|
|
438
|
-
# Room is already created. Find it.
|
|
439
|
-
roomB = @rooms[@room_idx]
|
|
440
|
-
@room_idx += 1
|
|
441
|
-
else
|
|
442
|
-
# Verify there's no room in that location yet
|
|
443
|
-
section = @map.sections[@map.section]
|
|
444
|
-
section.rooms.each { |r|
|
|
445
|
-
if r.x == @x and r.y == @y
|
|
446
|
-
err = "Section #{@map.section+1} already has location #{r} at #{@x}, #{@y}.\n"
|
|
447
|
-
err << "Cannot create '#{roomname}'"
|
|
448
|
-
raise MapError, err
|
|
449
|
-
end
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
# Remember original room for connection
|
|
453
|
-
roomB = @map.new_room( @x, @y )
|
|
454
|
-
roomB.selected = false
|
|
455
|
-
roomB.name = roomname
|
|
456
|
-
@rooms.push( roomB )
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
# Make roomB the current room
|
|
460
|
-
@room = roomB
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
if @item and roomA and @resolve_tags
|
|
464
|
-
roomA.objects << @item + "\n"
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
if @task and @room and @resolve_tags
|
|
468
|
-
@room.tasks << @task + "\n"
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
# Add a link between rooms
|
|
472
|
-
if roomA and roomB and not nolink and @resolve_tags
|
|
473
|
-
# Establish new simple connection
|
|
474
|
-
dirB = (dir[-1] + 4) % 8 if dir.size > 1
|
|
475
|
-
|
|
476
|
-
if dir.size > 1
|
|
477
|
-
dirB = [ @x - roomB.x, @y - roomB.y ]
|
|
478
|
-
if dirB[0] == 0 and dirB[1] == 0
|
|
479
|
-
dirB = (dir[-1] + 4) % 8
|
|
480
|
-
else
|
|
481
|
-
dirB = roomB.vector_to_dir( dirB[0], dirB[1] )
|
|
482
|
-
end
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
# 'from' and 'link' keywords can also connect stuff not in
|
|
486
|
-
# current section... so we check for that here
|
|
487
|
-
@map.sections.each_with_index { |p, idx|
|
|
488
|
-
if p.rooms.include?(roomA)
|
|
489
|
-
@map.fit
|
|
490
|
-
@map.section = idx
|
|
491
|
-
break
|
|
492
|
-
end
|
|
493
|
-
}
|
|
494
|
-
if not @map.sections[@map.section].rooms.include?(roomB)
|
|
495
|
-
raise MapError, "Linking #{roomA} and #{roomB} which are in different sections"
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
begin
|
|
499
|
-
c = map.new_connection( roomA, dir[0], roomB, dirB )
|
|
500
|
-
c.dir = Connection::AtoB if one_way
|
|
501
|
-
c.type = Connection::SPECIAL if styles.include?('special')
|
|
502
|
-
|
|
503
|
-
if go
|
|
504
|
-
c.exitAtext = go
|
|
505
|
-
if go % 2 == 0
|
|
506
|
-
c.exitBtext = go - 1
|
|
507
|
-
else
|
|
508
|
-
c.exitBtext = go + 1
|
|
509
|
-
end
|
|
510
|
-
end
|
|
511
|
-
rescue => e
|
|
512
|
-
puts e
|
|
513
|
-
end
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
if links.size > 0 and @resolve_tags
|
|
517
|
-
# Additional diagonal connection for room
|
|
518
|
-
links.each { |x|
|
|
519
|
-
next if not x
|
|
520
|
-
begin
|
|
521
|
-
c = map.new_connection( @room, nil, x, nil )
|
|
522
|
-
rescue => e
|
|
523
|
-
puts e
|
|
524
|
-
end
|
|
525
|
-
}
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
if tagname # and not @resolve_tags
|
|
529
|
-
if roomname
|
|
530
|
-
@tags[tagname] = @room
|
|
531
|
-
elsif @item
|
|
532
|
-
@tags[tagname] = @item
|
|
533
|
-
elsif @task
|
|
534
|
-
@tags[tagname] = @task
|
|
535
|
-
else
|
|
536
|
-
# join/link tag
|
|
537
|
-
@tags[tagname] = ''
|
|
538
|
-
end
|
|
539
|
-
end
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
#
|
|
543
|
-
# Okay, check all min/max locations in all sections
|
|
544
|
-
# and then do the following:
|
|
545
|
-
# a) Adjust map's width/height
|
|
546
|
-
# b) Shift all rooms so that no rooms are in negative locations
|
|
547
|
-
#
|
|
548
|
-
def initialize(file, map = Map.new('IFM Imported Map'))
|
|
549
|
-
@tags = {}
|
|
550
|
-
@map = map
|
|
551
|
-
@rooms = []
|
|
552
|
-
@resolve_tags = false
|
|
553
|
-
|
|
554
|
-
# --------------- first pass
|
|
555
|
-
File.open(file) { |f|
|
|
556
|
-
parse(f)
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
# --------------- second pass
|
|
560
|
-
@map.fit
|
|
561
|
-
@resolve_tags = true
|
|
562
|
-
File.open(file) { |f|
|
|
563
|
-
parse(f)
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
@map.section = 0
|
|
567
|
-
if @map.kind_of?(FXMap)
|
|
568
|
-
@map.filename = file.sub(/\.ifm$/i, '.map')
|
|
569
|
-
@map.navigation = true
|
|
570
|
-
@map.window.show
|
|
571
|
-
end
|
|
572
|
-
@tags = {} # save some memory by clearing the tag list
|
|
573
|
-
@rooms = nil # and room list
|
|
574
|
-
end
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if $0 == __FILE__
|
|
579
|
-
p "Opening file '#{ARGV[0]}'"
|
|
580
|
-
$LOAD_PATH << '..'
|
|
581
|
-
require "IFMapper/Map"
|
|
582
|
-
|
|
583
|
-
IFMReader.new(ARGV[0])
|
|
584
|
-
end
|
|
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
|
+
|
|
57
|
+
if @map.kind_of?(FXMap)
|
|
58
|
+
@map.options['Edit on Creation'] = false
|
|
59
|
+
@map.window.hide
|
|
60
|
+
end
|
|
61
|
+
@map.section = 0
|
|
62
|
+
|
|
63
|
+
@last_section = 0
|
|
64
|
+
@ignore_first_section = true
|
|
65
|
+
@room_idx = 0
|
|
66
|
+
line_number = 0
|
|
67
|
+
|
|
68
|
+
while not file.eof?
|
|
69
|
+
@line = ''
|
|
70
|
+
while not file.eof? and @line !~ /;\s*(#.*)?$/
|
|
71
|
+
@line << file.readline()
|
|
72
|
+
@line.sub!( /^\s*#.*/, '')
|
|
73
|
+
line_number += 1
|
|
74
|
+
end
|
|
75
|
+
@line.sub!( /;\s*#.*$/, '')
|
|
76
|
+
@line.sub!( /^\s+/, '' )
|
|
77
|
+
@line.gsub!( /;\s*$/, '' )
|
|
78
|
+
@line.gsub!( /\n/, ' ' )
|
|
79
|
+
next if @line == ''
|
|
80
|
+
full_line = @line.dup
|
|
81
|
+
# puts "#{line_number}:'#{@line}'"
|
|
82
|
+
begin
|
|
83
|
+
parse_line
|
|
84
|
+
rescue ParseError => e
|
|
85
|
+
$stderr.puts
|
|
86
|
+
$stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
|
|
87
|
+
$stderr.puts ">>>> #{full_line};"
|
|
88
|
+
$stderr.puts
|
|
89
|
+
rescue => e
|
|
90
|
+
$stderr.puts
|
|
91
|
+
$stderr.puts "#{e} for pass #{@resolve_tags}, at line #{line_number}:"
|
|
92
|
+
$stderr.puts ">>>> #{full_line};"
|
|
93
|
+
$stderr.puts e.backtrace if not e.kind_of?(MapError)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# see if line is a variable line. if so, return true
|
|
100
|
+
#
|
|
101
|
+
def parse_variable
|
|
102
|
+
@line =~ /^\s*[\w_\-\.]+\s*=.*$/
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Get a quoted string from line
|
|
107
|
+
#
|
|
108
|
+
def get_string
|
|
109
|
+
str = nil
|
|
110
|
+
if @line =~ /^"(([^"\\]|\\")*)"/
|
|
111
|
+
str = $1
|
|
112
|
+
@line = @line[str.size+2..-1]
|
|
113
|
+
# Change any quoted " to normal "
|
|
114
|
+
str.gsub!( /\\"/, '"' )
|
|
115
|
+
end
|
|
116
|
+
return str
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# Get a new token from line
|
|
121
|
+
#
|
|
122
|
+
def get_token
|
|
123
|
+
return nil if not @line
|
|
124
|
+
token, @line = @line.split(' ', 2)
|
|
125
|
+
return token
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#
|
|
129
|
+
# Look-ahead a token, without modifying line
|
|
130
|
+
#
|
|
131
|
+
def next_token
|
|
132
|
+
return nil if not @line
|
|
133
|
+
token, = @line.split(' ', 2)
|
|
134
|
+
return token
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
#
|
|
138
|
+
# Return whether next token is a tag or a language keyword
|
|
139
|
+
#
|
|
140
|
+
def is_tag?
|
|
141
|
+
token = next_token
|
|
142
|
+
case token
|
|
143
|
+
when nil,
|
|
144
|
+
'to', 'cmd', 'from', 'need', 'lose', 'lost', 'tag', 'all', 'except',
|
|
145
|
+
'before', 'after', 'start', 'style', 'endstyle', 'follow', 'link',
|
|
146
|
+
'until', 'dir', 'start', 'get', 'drop', 'goto', 'give', 'given',
|
|
147
|
+
'exit', 'in', 'keep', 'any', 'safe', 'score', 'style', 'go',
|
|
148
|
+
'hidden', 'oneway', 'finish', 'length', 'nopath', 'note', 'leave',
|
|
149
|
+
'join', /^#/
|
|
150
|
+
return false
|
|
151
|
+
else
|
|
152
|
+
return true
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
#
|
|
157
|
+
# Get a tag
|
|
158
|
+
#
|
|
159
|
+
def get_tag
|
|
160
|
+
tag = get_token
|
|
161
|
+
if tag == 'it'
|
|
162
|
+
return [tag, (@item or @task or @room)]
|
|
163
|
+
elsif tag == 'last' or tag == 'all' or tag == 'any'
|
|
164
|
+
return [tag, nil]
|
|
165
|
+
else
|
|
166
|
+
if not @tags[tag]
|
|
167
|
+
if @resolve_tags
|
|
168
|
+
raise ParseError, "Tag <#{tag}> not known"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
return [tag, @tags[tag]]
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# Get a room
|
|
177
|
+
#
|
|
178
|
+
def get_room
|
|
179
|
+
tag, room = get_tag
|
|
180
|
+
return @room if tag == 'last'
|
|
181
|
+
if room and not room.kind_of?(Room)
|
|
182
|
+
raise ParseError, "Not a room tag <#{tag}:#{room}>"
|
|
183
|
+
end
|
|
184
|
+
return room
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
#
|
|
188
|
+
# Get an item
|
|
189
|
+
#
|
|
190
|
+
def get_item
|
|
191
|
+
tag, item = get_tag
|
|
192
|
+
return @item if tag == 'last'
|
|
193
|
+
if item and item.kind_of?(Room)
|
|
194
|
+
raise ParseError, "Not an item tag <#{tag}:#{item}>"
|
|
195
|
+
end
|
|
196
|
+
return item
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#
|
|
200
|
+
# Get a task
|
|
201
|
+
#
|
|
202
|
+
def get_task
|
|
203
|
+
tag, task = get_tag
|
|
204
|
+
return @task if tag == 'last'
|
|
205
|
+
if task and task.kind_of?(Room)
|
|
206
|
+
raise ParseError, "Not a task tag <#{tag}:#{item}>"
|
|
207
|
+
end
|
|
208
|
+
return task
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
#
|
|
212
|
+
# Parse a line of file
|
|
213
|
+
#
|
|
214
|
+
def parse_line
|
|
215
|
+
return if parse_variable
|
|
216
|
+
|
|
217
|
+
roomname = tagname = nil
|
|
218
|
+
@task = @item = roomA = roomB =
|
|
219
|
+
from = one_way = nolink = go = nil
|
|
220
|
+
styles = []
|
|
221
|
+
links = []
|
|
222
|
+
dir = []
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
roomA = @room # add item to this room
|
|
226
|
+
|
|
227
|
+
while @line and token = get_token
|
|
228
|
+
case token
|
|
229
|
+
when 'map'
|
|
230
|
+
section = get_string
|
|
231
|
+
# Once we start a new section, we rest room and
|
|
232
|
+
# current position.
|
|
233
|
+
@room = nil
|
|
234
|
+
@x = @y = 1
|
|
235
|
+
# dont' add a section for first 'map' keyword
|
|
236
|
+
if @ignore_first_section
|
|
237
|
+
@ignore_first_section = false
|
|
238
|
+
@map.sections[0].name = section
|
|
239
|
+
next
|
|
240
|
+
end
|
|
241
|
+
if @resolve_tags
|
|
242
|
+
@map.section = @last_section + 1
|
|
243
|
+
@last_section = @map.section
|
|
244
|
+
else
|
|
245
|
+
@map.new_section
|
|
246
|
+
@map.sections[-1].name = section
|
|
247
|
+
end
|
|
248
|
+
when 'title'
|
|
249
|
+
@map.name = get_string
|
|
250
|
+
when 'room'
|
|
251
|
+
roomname = get_string
|
|
252
|
+
@ignore_first_section = false
|
|
253
|
+
when 'tag'
|
|
254
|
+
tagname = get_token
|
|
255
|
+
when 'dir'
|
|
256
|
+
# if not roomname
|
|
257
|
+
# raise ParseError, 'dir directive found but not for a room'
|
|
258
|
+
# end
|
|
259
|
+
token = next_token
|
|
260
|
+
while DIRECTIONS[token]
|
|
261
|
+
get_token
|
|
262
|
+
dir.push(DIRECTIONS[token])
|
|
263
|
+
token = next_token
|
|
264
|
+
|
|
265
|
+
if token =~ /^\d+$/
|
|
266
|
+
get_token
|
|
267
|
+
(token.to_i - 1).times { dir.push(dir[-1]) }
|
|
268
|
+
token = next_token
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
when 'nolink'
|
|
272
|
+
if dir.empty?
|
|
273
|
+
raise ParseError, 'nolink directive, but no dir directive before.'
|
|
274
|
+
end
|
|
275
|
+
nolink = true
|
|
276
|
+
when 'oneway'
|
|
277
|
+
one_way = true
|
|
278
|
+
when 'nopath'
|
|
279
|
+
when 'safe'
|
|
280
|
+
when 'exit'
|
|
281
|
+
if not roomname
|
|
282
|
+
raise ParseError, 'exit directive found but not for a room'
|
|
283
|
+
end
|
|
284
|
+
token = next_token
|
|
285
|
+
while DIRECTIONS[token]
|
|
286
|
+
get_token
|
|
287
|
+
token = next_token
|
|
288
|
+
end
|
|
289
|
+
when 'from'
|
|
290
|
+
if dir.empty?
|
|
291
|
+
raise ParseError, "'from' token found but no dir specified before"
|
|
292
|
+
end
|
|
293
|
+
from = get_room
|
|
294
|
+
when 'join'
|
|
295
|
+
# Joins are links that are not drawn.
|
|
296
|
+
# Mainly to give the engine knowledge that two locations
|
|
297
|
+
# are interconnected
|
|
298
|
+
get_room while is_tag?
|
|
299
|
+
to = next_token
|
|
300
|
+
if to == 'to'
|
|
301
|
+
get_token
|
|
302
|
+
get_room
|
|
303
|
+
end
|
|
304
|
+
when 'all'
|
|
305
|
+
when 'lost'
|
|
306
|
+
when 'except'
|
|
307
|
+
get_item while is_tag?
|
|
308
|
+
when 'length'
|
|
309
|
+
get_token
|
|
310
|
+
when 'until'
|
|
311
|
+
get_task while is_tag?
|
|
312
|
+
when 'link'
|
|
313
|
+
if roomname
|
|
314
|
+
while is_tag?
|
|
315
|
+
links.push( get_room )
|
|
316
|
+
end
|
|
317
|
+
else
|
|
318
|
+
roomA = get_room
|
|
319
|
+
to = next_token
|
|
320
|
+
if to == 'to'
|
|
321
|
+
get_token
|
|
322
|
+
roomB = get_room
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
when 'goto'
|
|
326
|
+
get_room
|
|
327
|
+
when 'go'
|
|
328
|
+
token = get_token
|
|
329
|
+
go = GO[token]
|
|
330
|
+
if not token
|
|
331
|
+
raise ParseError, "Token <#{token}> is an unknown go direction."
|
|
332
|
+
end
|
|
333
|
+
when 'item'
|
|
334
|
+
@item = get_string
|
|
335
|
+
item_tag = get_token if not @item
|
|
336
|
+
when 'in'
|
|
337
|
+
roomA = get_room # oh, well... this room
|
|
338
|
+
when 'note'
|
|
339
|
+
note = get_string
|
|
340
|
+
when 'keep'
|
|
341
|
+
token = next_token
|
|
342
|
+
if token == 'with'
|
|
343
|
+
get_token
|
|
344
|
+
item_keep = get_item
|
|
345
|
+
end
|
|
346
|
+
when 'given'
|
|
347
|
+
when 'give'
|
|
348
|
+
give_items = [ get_item ]
|
|
349
|
+
give_items.push(get_item) while is_tag?
|
|
350
|
+
when 'start'
|
|
351
|
+
when 'finish'
|
|
352
|
+
when 'follow'
|
|
353
|
+
task = get_task
|
|
354
|
+
when 'need'
|
|
355
|
+
need_items = [ get_item ]
|
|
356
|
+
need_items.push(get_item) while is_tag?
|
|
357
|
+
when 'after'
|
|
358
|
+
after_tasks = [ get_item ]
|
|
359
|
+
after_tasks.push(get_item) while is_tag?
|
|
360
|
+
when 'lose'
|
|
361
|
+
loose_items = [ get_item ]
|
|
362
|
+
loose_items.push(get_item) while is_tag?
|
|
363
|
+
when 'get'
|
|
364
|
+
get_items = [ get_item ]
|
|
365
|
+
get_items.push(get_item) while is_tag?
|
|
366
|
+
when 'drop'
|
|
367
|
+
drop_items = [ get_item ]
|
|
368
|
+
drop_items.push(get_item) while is_tag?
|
|
369
|
+
when 'hidden'
|
|
370
|
+
when 'leave'
|
|
371
|
+
leave_items = [ get_item ]
|
|
372
|
+
leave_items.push(get_item) while is_tag?
|
|
373
|
+
when 'before'
|
|
374
|
+
task = get_task
|
|
375
|
+
when 'cmd'
|
|
376
|
+
token = next_token
|
|
377
|
+
if token == 'to'
|
|
378
|
+
get_token
|
|
379
|
+
cmd = get_string
|
|
380
|
+
elsif token == 'from'
|
|
381
|
+
get_token
|
|
382
|
+
cmd = get_string
|
|
383
|
+
elsif token == 'none'
|
|
384
|
+
get_token
|
|
385
|
+
else
|
|
386
|
+
cmd = get_string
|
|
387
|
+
num = next_token
|
|
388
|
+
get_token if num =~ /\d+/
|
|
389
|
+
end
|
|
390
|
+
when 'score'
|
|
391
|
+
score = get_token.to_i
|
|
392
|
+
when 'length'
|
|
393
|
+
length = get_token.to_i
|
|
394
|
+
when 'task'
|
|
395
|
+
@task = get_string
|
|
396
|
+
task_tag = get_token if not @task
|
|
397
|
+
when 'style'
|
|
398
|
+
styles.push(get_token) while is_tag?
|
|
399
|
+
when 'endstyle'
|
|
400
|
+
get_token while is_tag?
|
|
401
|
+
when '', /^#/
|
|
402
|
+
get_token while @line
|
|
403
|
+
else
|
|
404
|
+
raise ParseError, "Token <#{token.inspect}> not understood"
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# If a direction, move that way.
|
|
409
|
+
if dir.size > 0
|
|
410
|
+
# If from keyword present, move from that room on.
|
|
411
|
+
if from
|
|
412
|
+
roomA = from
|
|
413
|
+
# 'from' can also connect stuff not in current section...
|
|
414
|
+
# so we check we are in the right section.
|
|
415
|
+
@map.sections.each_with_index { |p, idx|
|
|
416
|
+
if p.rooms.include?(roomA)
|
|
417
|
+
@map.fit
|
|
418
|
+
@map.section = idx
|
|
419
|
+
break
|
|
420
|
+
end
|
|
421
|
+
}
|
|
422
|
+
end
|
|
423
|
+
# Okay, we start from roomA... and follow each dir
|
|
424
|
+
if roomA
|
|
425
|
+
@x = roomA.x
|
|
426
|
+
@y = roomA.y
|
|
427
|
+
end
|
|
428
|
+
dir.each { |d|
|
|
429
|
+
x, y = Room::DIR_TO_VECTOR[d]
|
|
430
|
+
@x += x
|
|
431
|
+
@y += y
|
|
432
|
+
}
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Create new room
|
|
436
|
+
if roomname
|
|
437
|
+
if @resolve_tags
|
|
438
|
+
# Room is already created. Find it.
|
|
439
|
+
roomB = @rooms[@room_idx]
|
|
440
|
+
@room_idx += 1
|
|
441
|
+
else
|
|
442
|
+
# Verify there's no room in that location yet
|
|
443
|
+
section = @map.sections[@map.section]
|
|
444
|
+
section.rooms.each { |r|
|
|
445
|
+
if r.x == @x and r.y == @y
|
|
446
|
+
err = "Section #{@map.section+1} already has location #{r} at #{@x}, #{@y}.\n"
|
|
447
|
+
err << "Cannot create '#{roomname}'"
|
|
448
|
+
raise MapError, err
|
|
449
|
+
end
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
# Remember original room for connection
|
|
453
|
+
roomB = @map.new_room( @x, @y )
|
|
454
|
+
roomB.selected = false
|
|
455
|
+
roomB.name = roomname
|
|
456
|
+
@rooms.push( roomB )
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Make roomB the current room
|
|
460
|
+
@room = roomB
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if @item and roomA and @resolve_tags
|
|
464
|
+
roomA.objects << @item + "\n"
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
if @task and @room and @resolve_tags
|
|
468
|
+
@room.tasks << @task + "\n"
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Add a link between rooms
|
|
472
|
+
if roomA and roomB and not nolink and @resolve_tags
|
|
473
|
+
# Establish new simple connection
|
|
474
|
+
dirB = (dir[-1] + 4) % 8 if dir.size > 1
|
|
475
|
+
|
|
476
|
+
if dir.size > 1
|
|
477
|
+
dirB = [ @x - roomB.x, @y - roomB.y ]
|
|
478
|
+
if dirB[0] == 0 and dirB[1] == 0
|
|
479
|
+
dirB = (dir[-1] + 4) % 8
|
|
480
|
+
else
|
|
481
|
+
dirB = roomB.vector_to_dir( dirB[0], dirB[1] )
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# 'from' and 'link' keywords can also connect stuff not in
|
|
486
|
+
# current section... so we check for that here
|
|
487
|
+
@map.sections.each_with_index { |p, idx|
|
|
488
|
+
if p.rooms.include?(roomA)
|
|
489
|
+
@map.fit
|
|
490
|
+
@map.section = idx
|
|
491
|
+
break
|
|
492
|
+
end
|
|
493
|
+
}
|
|
494
|
+
if not @map.sections[@map.section].rooms.include?(roomB)
|
|
495
|
+
raise MapError, "Linking #{roomA} and #{roomB} which are in different sections"
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
begin
|
|
499
|
+
c = map.new_connection( roomA, dir[0], roomB, dirB )
|
|
500
|
+
c.dir = Connection::AtoB if one_way
|
|
501
|
+
c.type = Connection::SPECIAL if styles.include?('special')
|
|
502
|
+
|
|
503
|
+
if go
|
|
504
|
+
c.exitAtext = go
|
|
505
|
+
if go % 2 == 0
|
|
506
|
+
c.exitBtext = go - 1
|
|
507
|
+
else
|
|
508
|
+
c.exitBtext = go + 1
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
rescue => e
|
|
512
|
+
puts e
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
if links.size > 0 and @resolve_tags
|
|
517
|
+
# Additional diagonal connection for room
|
|
518
|
+
links.each { |x|
|
|
519
|
+
next if not x
|
|
520
|
+
begin
|
|
521
|
+
c = map.new_connection( @room, nil, x, nil )
|
|
522
|
+
rescue => e
|
|
523
|
+
puts e
|
|
524
|
+
end
|
|
525
|
+
}
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
if tagname # and not @resolve_tags
|
|
529
|
+
if roomname
|
|
530
|
+
@tags[tagname] = @room
|
|
531
|
+
elsif @item
|
|
532
|
+
@tags[tagname] = @item
|
|
533
|
+
elsif @task
|
|
534
|
+
@tags[tagname] = @task
|
|
535
|
+
else
|
|
536
|
+
# join/link tag
|
|
537
|
+
@tags[tagname] = ''
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
#
|
|
543
|
+
# Okay, check all min/max locations in all sections
|
|
544
|
+
# and then do the following:
|
|
545
|
+
# a) Adjust map's width/height
|
|
546
|
+
# b) Shift all rooms so that no rooms are in negative locations
|
|
547
|
+
#
|
|
548
|
+
def initialize(file, map = Map.new('IFM Imported Map'))
|
|
549
|
+
@tags = {}
|
|
550
|
+
@map = map
|
|
551
|
+
@rooms = []
|
|
552
|
+
@resolve_tags = false
|
|
553
|
+
|
|
554
|
+
# --------------- first pass
|
|
555
|
+
File.open(file) { |f|
|
|
556
|
+
parse(f)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
# --------------- second pass
|
|
560
|
+
@map.fit
|
|
561
|
+
@resolve_tags = true
|
|
562
|
+
File.open(file) { |f|
|
|
563
|
+
parse(f)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
@map.section = 0
|
|
567
|
+
if @map.kind_of?(FXMap)
|
|
568
|
+
@map.filename = file.sub(/\.ifm$/i, '.map')
|
|
569
|
+
@map.navigation = true
|
|
570
|
+
@map.window.show
|
|
571
|
+
end
|
|
572
|
+
@tags = {} # save some memory by clearing the tag list
|
|
573
|
+
@rooms = nil # and room list
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
if $0 == __FILE__
|
|
579
|
+
p "Opening file '#{ARGV[0]}'"
|
|
580
|
+
$LOAD_PATH << '..'
|
|
581
|
+
require "IFMapper/Map"
|
|
582
|
+
|
|
583
|
+
IFMReader.new(ARGV[0])
|
|
584
|
+
end
|