ifmapper 1.0.0 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. data/HISTORY.txt +648 -627
  2. data/IFMapper.gemspec +29 -28
  3. data/IFMapper.rbw +31 -31
  4. data/TODO.txt +8 -7
  5. data/bin/IFMapper +31 -31
  6. data/docs/en/index.html +0 -0
  7. data/docs/en/start.html +3 -2
  8. data/docs/en/start.html~ +516 -0
  9. data/docs/es/index.html +0 -0
  10. data/docs/es/start.html +13 -14
  11. data/docs/es/start.html~ +1280 -0
  12. data/docs/images/IFMapper_main.gif +0 -0
  13. data/docs/images/automap.gif +0 -0
  14. data/docs/images/complex_connection.gif +0 -0
  15. data/docs/images/connection.gif +0 -0
  16. data/docs/images/connection_menu.gif +0 -0
  17. data/docs/images/room_description.gif +0 -0
  18. data/docs/images/room_small.gif +0 -0
  19. data/icons/copy.png +0 -0
  20. data/icons/cut.png +0 -0
  21. data/icons/filenew.png +0 -0
  22. data/icons/fileopen.png +0 -0
  23. data/icons/filesave.png +0 -0
  24. data/icons/filesaveas.png +0 -0
  25. data/icons/help.png +0 -0
  26. data/icons/kill.png +0 -0
  27. data/icons/nextpage.png +0 -0
  28. data/icons/paste.png +0 -0
  29. data/icons/prevpage.png +0 -0
  30. data/icons/printicon.png +0 -0
  31. data/icons/redo.png +0 -0
  32. data/icons/room_e.gif +0 -0
  33. data/icons/room_e.xpm +0 -0
  34. data/icons/room_n.gif +0 -0
  35. data/icons/room_n.xpm +0 -0
  36. data/icons/room_ne.gif +0 -0
  37. data/icons/room_ne.xpm +0 -0
  38. data/icons/room_nw.gif +0 -0
  39. data/icons/room_nw.xpm +0 -0
  40. data/icons/room_s.gif +0 -0
  41. data/icons/room_s.xpm +0 -0
  42. data/icons/room_se.gif +0 -0
  43. data/icons/room_se.xpm +0 -0
  44. data/icons/room_sw.gif +0 -0
  45. data/icons/room_sw.xpm +0 -0
  46. data/icons/room_w.gif +0 -0
  47. data/icons/room_w.xpm +0 -0
  48. data/icons/saveas.png +0 -0
  49. data/icons/undo.png +0 -0
  50. data/icons/winapp.png +0 -0
  51. data/icons/zoom.png +0 -0
  52. data/lib/IFMapper/AStar.rb +250 -250
  53. data/lib/IFMapper/Connection.rb +202 -202
  54. data/lib/IFMapper/FXAboutDialogBox.rb +32 -32
  55. data/lib/IFMapper/FXConnection.rb +364 -364
  56. data/lib/IFMapper/FXConnectionDialogBox.rb +124 -124
  57. data/lib/IFMapper/FXDCPostscript.rb +404 -404
  58. data/lib/IFMapper/FXDCPrint.rb +15 -15
  59. data/lib/IFMapper/FXItemList.rb +108 -0
  60. data/lib/IFMapper/FXMap.rb +2147 -2116
  61. data/lib/IFMapper/FXMapColorBox.rb +88 -88
  62. data/lib/IFMapper/FXMapDialogBox.rb +127 -127
  63. data/lib/IFMapper/FXMapFileDialog.rb +34 -34
  64. data/lib/IFMapper/FXMapperSettings.rb +206 -205
  65. data/lib/IFMapper/FXMapperWindow.rb +1592 -1571
  66. data/lib/IFMapper/FXPDFMapExporterOptionsDialogBox.rb +46 -0
  67. data/lib/IFMapper/FXRoom.rb +263 -263
  68. data/lib/IFMapper/FXRoomDialogBox.rb +159 -159
  69. data/lib/IFMapper/FXRoomList.rb +95 -95
  70. data/lib/IFMapper/FXSearchDialogBox.rb +51 -51
  71. data/lib/IFMapper/FXSection.rb +33 -33
  72. data/lib/IFMapper/FXSectionDialogBox.rb +38 -38
  73. data/lib/IFMapper/FXSpline.rb +52 -52
  74. data/lib/IFMapper/FXWarningBox.rb +51 -50
  75. data/lib/IFMapper/GUEReader.rb +445 -445
  76. data/lib/IFMapper/IFMReader.rb +584 -584
  77. data/lib/IFMapper/IFMWriter.rb +245 -227
  78. data/lib/IFMapper/Inform7Writer.rb +579 -573
  79. data/lib/IFMapper/InformReader.rb +478 -478
  80. data/lib/IFMapper/InformWriter.rb +364 -359
  81. data/lib/IFMapper/Map.rb +202 -200
  82. data/lib/IFMapper/MapPrinting.rb +162 -162
  83. data/lib/IFMapper/MapReader.rb +900 -900
  84. data/lib/IFMapper/PDFMapExporter.rb +526 -483
  85. data/lib/IFMapper/Room.rb +153 -151
  86. data/lib/IFMapper/Section.rb +234 -234
  87. data/lib/IFMapper/TADSReader.rb +474 -471
  88. data/lib/IFMapper/TADSWriter.rb +375 -370
  89. data/lib/IFMapper/TranscriptDialogBox.rb +0 -0
  90. data/lib/IFMapper/TranscriptReader.rb +1361 -1359
  91. data/lib/IFMapper/locales/en/Messages.rb +446 -435
  92. data/lib/IFMapper/locales/es/Messages.rb +451 -440
  93. data/lib/IFMapper/locales/es/Messages_iso-8859-1.rb +455 -440
  94. data/lib/IFMapper/locales/es/runme.sh +3 -3
  95. data/maps/A New Life.map b/data/maps/A New → Life.map +0 -0
  96. data/maps/AMFV.map +0 -0
  97. data/maps/AllRoads.map +0 -0
  98. data/maps/Aotearoa.map +0 -0
  99. data/maps/Bronze.map +0 -0
  100. data/maps/Bureaucracy.ifm +0 -0
  101. data/maps/Bureaucracy.map +0 -0
  102. data/maps/CityOfSecrets.map +0 -0
  103. data/maps/DDIV.map +0 -0
  104. data/maps/Following_A_Star.map +0 -0
  105. data/maps/Heated.map +0 -0
  106. data/maps/Heroine.map +0 -0
  107. data/maps/History Repeating.map b/data/maps/History → Repeating.map +0 -0
  108. data/maps/Hollywood_Hijinx.ifm +0 -0
  109. data/maps/Janitor.map +0 -0
  110. data/maps/Jigsaw.ifm +0 -0
  111. data/maps/Jigsaw.map +0 -0
  112. data/maps/LGOP.ifm +0 -0
  113. data/maps/Mercy.ifm +0 -0
  114. data/maps/Ninjas_Fate.map +0 -0
  115. data/maps/Pen_and_Paint.map +0 -0
  116. data/maps/Planetfall.ifm +0 -0
  117. data/maps/Planetfall.map +0 -0
  118. data/maps/Plundered_Hearts.ifm +0 -0
  119. data/maps/QuietEvening.map +0 -0
  120. data/maps/Ralph.ifm +0 -0
  121. data/maps/Reliques_of_Tolti_Alph.map +0 -0
  122. data/maps/Revolution.map +0 -0
  123. data/maps/Robots_of_Dawn.ifm +0 -0
  124. data/maps/SavoirFare.map +0 -0
  125. data/maps/Seastalker.ifm +0 -0
  126. data/maps/Seastalker.map +0 -0
  127. data/maps/Sherlock.ifm +0 -0
  128. data/maps/SoFar.ifm +0 -0
  129. data/maps/Starcross.ifm +0 -0
  130. data/maps/Suspended.ifm +0 -0
  131. data/maps/Tangle.map +0 -0
  132. data/maps/The_Lost_Sheep.map +0 -0
  133. data/maps/Unforgotten.map +0 -0
  134. data/maps/Warbler's Nest.map +0 -0
  135. data/maps/Warbler's_Nest.map +0 -0
  136. data/maps/Westminster_Abbey.map +0 -0
  137. data/maps/WinterWonderland.map +0 -0
  138. data/maps/Wishbringer.ifm +0 -0
  139. data/maps/Wishbringer2.ifm +0 -0
  140. data/maps/Zork1.ifm +0 -0
  141. data/maps/Zork2.ifm +0 -0
  142. data/maps/Zork3.ifm +0 -0
  143. data/maps/Zork_Zero.ifm +0 -0
  144. data/maps/anchor.ifm +0 -0
  145. data/maps/anchor.map +0 -0
  146. data/maps/atrox.ifm +0 -0
  147. data/maps/awaken.ifm +0 -0
  148. data/maps/babel.ifm +0 -0
  149. data/maps/balances.map +0 -0
  150. data/maps/ballerina.map +0 -0
  151. data/maps/bear.map +0 -0
  152. data/maps/bluechairs.map +0 -0
  153. data/maps/break_in.map +0 -0
  154. data/maps/bse.ifm +0 -0
  155. data/maps/building.map +0 -0
  156. data/maps/change.ifm +0 -0
  157. data/maps/christminster.map +0 -0
  158. data/maps/curses.ifm +0 -0
  159. data/maps/curves.ifm +0 -0
  160. data/maps/deadline.map +0 -0
  161. data/maps/delusions.map +0 -0
  162. data/maps/devours.map +0 -0
  163. data/maps/distress.map +0 -0
  164. data/maps/djinni.map +0 -0
  165. data/maps/dreamhold.map +0 -0
  166. data/maps/drift3.map +0 -0
  167. data/maps/eas.map +0 -0
  168. data/maps/eas2.map +0 -0
  169. data/maps/eas3.map +0 -0
  170. data/maps/edifice.ifm +0 -0
  171. data/maps/fallacy.map +0 -0
  172. data/maps/frozen.ifm +0 -0
  173. data/maps/gamlet.map +0 -0
  174. data/maps/glow.ifm +0 -0
  175. data/maps/guilty_bastards.map +0 -0
  176. data/maps/heist.map +0 -0
  177. data/maps/heroes.map +0 -0
  178. data/maps/inhumane.map +0 -0
  179. data/maps/kaged.map +0 -0
  180. data/maps/library.ifm +0 -0
  181. data/maps/lurkinghorror.map +0 -0
  182. data/maps/metamorphoses.map +0 -0
  183. data/maps/mindelec.ifm +0 -0
  184. data/maps/minster.ifm +0 -0
  185. data/maps/mite.map +0 -0
  186. data/maps/moonmist.map +0 -0
  187. data/maps/muldoon_legacy.map +0 -0
  188. data/maps/muse.ifm +0 -0
  189. data/maps/paperchase.ifm +0 -0
  190. data/maps/party.map +0 -0
  191. data/maps/pawn.map +0 -0
  192. data/maps/photograph.map +0 -0
  193. data/maps/pkgirl.map +0 -0
  194. data/maps/pytho.map +0 -0
  195. data/maps/risorgimento.map +0 -0
  196. data/maps/sherbet.map +0 -0
  197. data/maps/simple.map +0 -0
  198. data/maps/slouch.map +0 -0
  199. data/maps/space_st.ifm +0 -0
  200. data/maps/splashdown.map +0 -0
  201. data/maps/spring.map +0 -0
  202. data/maps/squarecircle.map +0 -0
  203. data/maps/stationfall.ifm +0 -0
  204. data/maps/theatre.ifm +0 -0
  205. data/maps/toonesia.ifm +0 -0
  206. data/maps/tortoise.ifm +0 -0
  207. data/maps/trinity.map +0 -0
  208. data/maps/vespers.map +0 -0
  209. data/maps/vgame.ifm +0 -0
  210. data/maps/wasp.map +0 -0
  211. data/maps/weather.ifm +0 -0
  212. data/maps/windhall.ifm +0 -0
  213. data/maps/worlds.map +0 -0
  214. data/maps/xtcontest.map +0 -0
  215. data/maps/zdungeon.map +0 -0
  216. data/maps/zebulon.ifm +0 -0
  217. data/maps/zerosum.map +0 -0
  218. metadata +226 -183
@@ -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