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.
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