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,359 +1,364 @@
1
-
2
-
3
-
4
- class InformWriter
5
-
6
-
7
-
8
- DIRECTIONS = [
9
- 'n_to',
10
- 'ne_to',
11
- 'e_to',
12
- 'se_to',
13
- 's_to',
14
- 'sw_to',
15
- 'w_to',
16
- 'nw_to',
17
- ]
18
-
19
- OTHERDIRS = [
20
- '',
21
- 'u_to',
22
- 'd_to',
23
- 'in_to',
24
- 'out_to',
25
- ]
26
-
27
-
28
- def get_door_tag(e)
29
- dirA = e.roomA.exits.index(e)
30
- dirB = e.roomB.exits.rindex(e)
31
- name = Room::DIRECTIONS[dirA] + "_" + Room::DIRECTIONS[dirB]
32
- name << "_door"
33
- get_tag(e, name)
34
- end
35
-
36
- def new_tag(elem, str)
37
- tag = str.dup
38
- utf = Iconv.new( 'iso-8859-1', 'utf-8' )
39
- tag = utf.iconv( tag )
40
-
41
- # Invalid tag characters, replaced with _
42
- tag.gsub!(/[\s"'\/\\\-#\,.:;!\?\n\(\)]/,'_')
43
-
44
- tag.gsub!(/__/, '') # remove reduntant __ repetitions
45
- tag.sub!(/^([\d]+)_?(.*)/, '\2\1') # No numbers allowed at start of tag
46
- tag.downcase! # All tags are lowercase
47
-
48
-
49
-
50
- tag = tag[0..31] # Max. 32 chars. in tag (inform limit)
51
-
52
- # tag cannot be keyword (Doorway, Room, Class, etc)
53
- if tag =~ @keyword
54
- tag << '1'
55
- end
56
-
57
- if @tags.values.include?(tag)
58
- idx = 2
59
- root = tag[0..29] # to leave room for number
60
- while @tags.values.include?(tag)
61
- tag = "#{root}#{idx}"
62
- idx += 1
63
- end
64
- end
65
- if elem.kind_of?(String)
66
- @tags[tag] = tag
67
- else
68
- @tags[elem] = tag
69
- end
70
- return tag
71
- end
72
-
73
- def get_tag(elem, name = elem.name)
74
- return @tags[elem] if @tags[elem]
75
- return new_tag(elem, name)
76
- end
77
-
78
-
79
- def wrap_text(text, width = 65, indent = 79 - width)
80
- return 'UNDER CONSTRUCTION' unless text
81
- str = inform_quote( text )
82
-
83
- if text.size > width
84
- r = ''
85
- while str
86
- if str.size >= width
87
- idx = str.rindex(/[ -]/, width)
88
- idx = str.size unless idx
89
- else
90
- idx = str.size
91
- end
92
- r << str[0..idx]
93
- str = str[idx+1..-1]
94
- r << "\n" << ' ' * indent if str
95
- end
96
- return r
97
- else
98
- return str
99
- end
100
- end
101
-
102
-
103
- #
104
- # Take a text and quote it for inform's double-quote text areas.
105
- #
106
- def inform_quote(text)
107
- str = text.dup
108
-
109
- # Take text from Unicode utf-8 to iso-8859-1
110
- utf = Iconv.new( 'iso-8859-1', 'utf-8' )
111
- str = utf.iconv( str )
112
-
113
- # Quote special characters
114
- str.gsub!(/@/, '@@64')
115
- str.gsub!(/"/, '@@34')
116
- str.gsub!(/~/, '@@126')
117
- str.gsub!(/\\/, '@@92')
118
- str.gsub!(/\^/, '@@94')
119
- return str
120
- end
121
-
122
-
123
- def objects(r)
124
- room = get_tag(r)
125
- objs = r.objects.split("\n")
126
- objs.each { |o|
127
-
128
- tag = new_tag(o, o)
129
- names = o.dup
130
- names.gsub!(/"/, '') # remove any quotes
131
- names.gsub!(/'/, '^') # change ' to ^ (inform convention)
132
- names.gsub!(/\b\w\b/, '') # remove single letter words
133
- names = names.split(' ')
134
- names = "'" + names.join("' '") + "'"
135
-
136
- name = inform_quote(o)
137
-
138
- classname = 'Object'
139
-
140
- # If name begins with uppercase, assume it is an NPC
141
- if name =~ /[A-Z]/
142
- classname = 'NPC'
143
- end
144
-
145
- @f.print <<"EOF"
146
-
147
- #{classname} #{tag} "#{name}" #{room}
148
- with name #{names},
149
- description "#{name} is UNDER CONSTRUCTION.";
150
- EOF
151
- }
152
-
153
-
154
- end
155
-
156
- def door(e)
157
- tag = get_door_tag(e)
158
- roomA = get_tag(e.roomA)
159
- roomB = get_tag(e.roomB)
160
- dirA = e.roomA.exits.index(e)
161
- dirB = e.roomB.exits.rindex(e)
162
- dirA = Room::DIRECTIONS[dirA]
163
- dirB = Room::DIRECTIONS[dirB]
164
-
165
- props = ''
166
- if e.type == Connection::LOCKED_DOOR
167
- props = "\n has lockable locked"
168
- elsif e.type == Connection::CLOSED_DOOR
169
- props = "\n has ~open"
170
- end
171
-
172
- if e.dir == Connection::BOTH
173
- found_in = "#{roomA} #{roomB}"
174
- elsif e.dir == Connection::AtoB
175
- found_in = roomA
176
- elsif e.dir == Connection::BtoA
177
- found_in = roomB
178
- end
179
-
180
- @f.puts <<"EOF"
181
-
182
- ! Door connecting #{e}
183
- Doorway #{tag} "door"
184
- with name 'door',
185
- side1_to #{roomA},
186
- side1_dir #{dirA}_to,
187
- side2_to #{roomB},
188
- side2_dir #{dirB}_to,
189
- found_in #{found_in}#{props};
190
- EOF
191
-
192
- end
193
-
194
-
195
- def room(r)
196
- tag = get_tag(r)
197
- name = inform_quote(r.name)
198
- @f.puts <<-"EOF"
199
-
200
- !-------------------- #{r.name}
201
- Room #{tag} "#{name}"
202
- EOF
203
-
204
- @f.puts " with description"
205
- @f.print " \"#{wrap_text(r.desc)}\""
206
-
207
- # Now, handle exits...
208
- r.exits.each_with_index { |e, dir|
209
- next if (not e) or e.stub? or e.type == Connection::SPECIAL
210
- if e.roomB == r
211
- next if e.dir == Connection::AtoB
212
- text = e.exitBtext
213
- b = e.roomA
214
- else
215
- next if e.dir == Connection::BtoA
216
- text = e.exitAtext
217
- b = e.roomB
218
- end
219
- @f.puts ','
220
- @f.print ' '
221
- if text == 0
222
- @f.print "#{DIRECTIONS[dir]} "
223
- else
224
- @f.print "#{OTHERDIRS[text]} "
225
- end
226
- if e.type == Connection::CLOSED_DOOR or
227
- e.type == Connection::LOCKED_DOOR
228
- @f.print get_door_tag(e)
229
- else
230
- @f.print get_tag(b)
231
- end
232
- }
233
- if r.darkness
234
- @f.print " has ~light"
235
- end
236
- @f.puts ";"
237
-
238
- objects(r)
239
- end
240
-
241
- def section(sect, idx)
242
- @f = File.open("#@root-#{idx}.inf", "w")
243
- @f.puts '!' + '=' * 78
244
- @f.puts "! Section: #{sect.name} "
245
- @f.puts '!' + '=' * 78
246
- sect.rooms.each { |r|
247
- room(r)
248
- }
249
-
250
- @f.puts '!' + '-' * 78
251
- @f.puts '! Doorways'
252
- @f.puts '!' + '-' * 78
253
- sect.connections.each { |e|
254
- next if (e.type != Connection::LOCKED_DOOR and
255
- e.type != Connection::CLOSED_DOOR)
256
- door(e)
257
- }
258
- @f.close
259
- end
260
-
261
- def start
262
- @f = File.open("#@root.inf", "w")
263
- @f.puts '!' + '=' * 78
264
- story = @map.name
265
- story = 'Untitled' if story == ''
266
- today = Date.today
267
- serial = today.strftime("%y%m%d")
268
- @f.puts <<"EOF"
269
- Constant Story "#{story}";
270
- Constant Headline
271
- "^A game map done with IFMapper
272
- ^by #{@map.creator}.^";
273
- Release 1;
274
- Serial "#{serial}"; ! #{Time.now}
275
-
276
- ! These constants are to make the game also potentially Glulx compatible
277
- #ifndef WORDSIZE;
278
- Constant TARGET_ZCODE;
279
- Constant WORDSIZE 2;
280
- #endif;
281
-
282
- !Constant ZDEBUG;
283
-
284
- Include "Parser";
285
- Include "VerbLib";
286
-
287
-
288
- EOF
289
- @f.puts '!' + '=' * 78
290
- @f.puts "! Object classes"
291
- @f.puts <<"EOF"
292
-
293
- ! We use Andrew MacKinnon's EasyDoors for our doors.
294
- Include "easydoors";
295
-
296
- ! We define a class for NPC (non-player characters)
297
- class NPC
298
- has animate;
299
-
300
- EOF
301
-
302
- @f.puts '!' + '=' * 78
303
- @f.puts "! Map sections"
304
- @map.sections.each_index { |idx|
305
- @f.puts "#include \"#@base-#{idx}.inf\";"
306
- }
307
-
308
- start_location = ''
309
- if @map.sections.size > 0 and @map.sections[0].rooms[0]
310
- r = @map.sections[0].rooms[0]
311
- tag = get_tag(r)
312
- start_location = "location = #{tag}"
313
- end
314
-
315
- @f.puts <<"EOF";
316
- !============================================================================
317
- ! Entry point routines
318
-
319
- [ Initialise;
320
- #{start_location};
321
- lookmode = 2; ! verbose
322
- inventory_style = FULLINV_BIT + ENGLISH_BIT + RECURSE_BIT; ! wide
323
- ];
324
- EOF
325
-
326
- @f.puts <<"EOF"
327
- !============================================================================
328
- ! Standard and extended grammar
329
-
330
- Include "Grammar";
331
- EOF
332
-
333
- @f.close
334
-
335
- @map.sections.each_with_index { |sect, idx|
336
- section(sect, idx)
337
- }
338
- end
339
-
340
- def initialize(map, fileroot)
341
- @tags = {}
342
- @root = fileroot
343
- @base = File.basename(@root)
344
- @map = map
345
-
346
- keywords = [
347
- 'doorway',
348
- 'class',
349
- 'include',
350
- 'room',
351
- 'has',
352
- 'with',
353
- 'description',
354
- ] + DIRECTIONS
355
-
356
- @keyword = /^(?:#{keywords.join('|')})$/i
357
- start
358
- end
359
- end
1
+
2
+
3
+
4
+ class InformWriter
5
+
6
+
7
+
8
+ DIRECTIONS = [
9
+ 'n_to',
10
+ 'ne_to',
11
+ 'e_to',
12
+ 'se_to',
13
+ 's_to',
14
+ 'sw_to',
15
+ 'w_to',
16
+ 'nw_to',
17
+ ]
18
+
19
+ OTHERDIRS = [
20
+ '',
21
+ 'u_to',
22
+ 'd_to',
23
+ 'in_to',
24
+ 'out_to',
25
+ ]
26
+
27
+
28
+ def get_door_tag(e)
29
+ dirA = e.roomA.exits.index(e)
30
+ dirB = e.roomB.exits.rindex(e)
31
+ name = Room::DIRECTIONS[dirA] + "_" + Room::DIRECTIONS[dirB]
32
+ name << "_door"
33
+ get_tag(e, name)
34
+ end
35
+
36
+ def new_tag(elem, str)
37
+ tag = str.dup
38
+ utf = Iconv.new( 'iso-8859-1', 'utf-8' )
39
+ tag = utf.iconv( tag )
40
+
41
+ # Invalid tag characters, replaced with _
42
+ tag.gsub!(/[\s"'\/\\\-#\,.:;!\?\n\(\)]/,'_')
43
+
44
+ tag.gsub!(/__/, '') # remove reduntant __ repetitions
45
+ tag.sub!(/^([\d]+)_?(.*)/, '\2\1') # No numbers allowed at start of tag
46
+ tag.downcase! # All tags are lowercase
47
+
48
+
49
+
50
+ tag = tag[0..31] # Max. 32 chars. in tag (inform limit)
51
+
52
+ # tag cannot be keyword (Doorway, Room, Class, etc)
53
+ if tag =~ @keyword
54
+ tag << '1'
55
+ end
56
+
57
+ if @tags.values.include?(tag)
58
+ idx = 2
59
+ root = tag[0..29] # to leave room for number
60
+ while @tags.values.include?(tag)
61
+ tag = "#{root}#{idx}"
62
+ idx += 1
63
+ end
64
+ end
65
+ if elem.kind_of?(String)
66
+ @tags[tag] = tag
67
+ else
68
+ @tags[elem] = tag
69
+ end
70
+ return tag
71
+ end
72
+
73
+ def get_tag(elem, name = elem.name)
74
+ return @tags[elem] if @tags[elem]
75
+ return new_tag(elem, name)
76
+ end
77
+
78
+
79
+ def wrap_text(text, width = 65, indent = 79 - width)
80
+ return 'UNDER CONSTRUCTION' unless text
81
+ str = inform_quote( text )
82
+
83
+ if text.size > width
84
+ r = ''
85
+ while str
86
+ if str.size >= width
87
+ idx = str.rindex(/[ -]/, width)
88
+ idx = str.size unless idx
89
+ else
90
+ idx = str.size
91
+ end
92
+ r << str[0..idx]
93
+ str = str[idx+1..-1]
94
+ r << "\n" << ' ' * indent if str
95
+ end
96
+ return r
97
+ else
98
+ return str
99
+ end
100
+ end
101
+
102
+
103
+ #
104
+ # Take a text and quote it for inform's double-quote text areas.
105
+ #
106
+ def inform_quote(text)
107
+ str = text.dup
108
+
109
+ # Take text from Unicode utf-8 to iso-8859-1
110
+ utf = Iconv.new( 'iso-8859-1', 'utf-8' )
111
+ str = utf.iconv( str )
112
+
113
+ # Quote special characters
114
+ str.gsub!(/@/, '@@64')
115
+ str.gsub!(/"/, '@@34')
116
+ str.gsub!(/~/, '@@126')
117
+ str.gsub!(/\\/, '@@92')
118
+ str.gsub!(/\^/, '@@94')
119
+ return str
120
+ end
121
+
122
+
123
+ def objects(r)
124
+ room = get_tag(r)
125
+ objs = r.objects.split("\n")
126
+ objs.each { |o|
127
+
128
+ tag = new_tag(o, o)
129
+ names = o.dup
130
+ names.gsub!(/"/, '') # remove any quotes
131
+ names.gsub!(/'/, '^') # change ' to ^ (inform convention)
132
+ names.gsub!(/\b\w\b/, '') # remove single letter words
133
+ names = names.split(' ')
134
+ names = "'" + names.join("' '") + "'"
135
+
136
+ name = inform_quote(o)
137
+
138
+ classname = 'Object'
139
+
140
+ # If name begins with uppercase, assume it is an NPC
141
+ if name =~ /[A-Z]/
142
+ classname = 'NPC'
143
+ end
144
+
145
+ @f.print <<"EOF"
146
+
147
+ #{classname} #{tag} "#{name}" #{room}
148
+ with name #{names},
149
+ description "#{name} is UNDER CONSTRUCTION.";
150
+ EOF
151
+ }
152
+
153
+
154
+ end
155
+
156
+ def door(e)
157
+ tag = get_door_tag(e)
158
+ roomA = get_tag(e.roomA)
159
+ roomB = get_tag(e.roomB)
160
+ dirA = e.roomA.exits.index(e)
161
+ dirB = e.roomB.exits.rindex(e)
162
+ dirA = Room::DIRECTIONS[dirA]
163
+ dirB = Room::DIRECTIONS[dirB]
164
+
165
+ props = ''
166
+ if e.type == Connection::LOCKED_DOOR
167
+ props = "\n has lockable locked"
168
+ elsif e.type == Connection::CLOSED_DOOR
169
+ props = "\n has ~open"
170
+ end
171
+
172
+ if e.dir == Connection::BOTH
173
+ found_in = "#{roomA} #{roomB}"
174
+ elsif e.dir == Connection::AtoB
175
+ found_in = roomA
176
+ elsif e.dir == Connection::BtoA
177
+ found_in = roomB
178
+ end
179
+
180
+ @f.puts <<"EOF"
181
+
182
+ ! Door connecting #{e}
183
+ Doorway #{tag} "door"
184
+ with name 'door',
185
+ side1_to #{roomA},
186
+ side1_dir #{dirA}_to,
187
+ side2_to #{roomB},
188
+ side2_dir #{dirB}_to,
189
+ found_in #{found_in}#{props};
190
+ EOF
191
+
192
+ end
193
+
194
+
195
+ def room(r)
196
+ tag = get_tag(r)
197
+ name = inform_quote(r.name)
198
+ @f.puts <<-"EOF"
199
+
200
+ !-------------------- #{r.name}
201
+ Room #{tag} "#{name}"
202
+ EOF
203
+
204
+ @f.puts " with description"
205
+ @f.print " \"#{wrap_text(r.desc)}\""
206
+
207
+ # Now, handle exits...
208
+ r.exits.each_with_index { |e, dir|
209
+ next if (not e) or e.stub? or e.type == Connection::SPECIAL
210
+ if e.loop?
211
+ text = e.exitAtext
212
+ b = e.roomB
213
+ else
214
+ if e.roomB == r
215
+ next if e.dir == Connection::AtoB
216
+ text = e.exitBtext
217
+ b = e.roomA
218
+ else
219
+ next if e.dir == Connection::BtoA
220
+ text = e.exitAtext
221
+ b = e.roomB
222
+ end
223
+ end
224
+ @f.puts ','
225
+ @f.print ' '
226
+ if text == 0
227
+ @f.print "#{DIRECTIONS[dir]} "
228
+ else
229
+ @f.print "#{OTHERDIRS[text]} "
230
+ end
231
+ if e.type == Connection::CLOSED_DOOR or
232
+ e.type == Connection::LOCKED_DOOR
233
+ @f.print get_door_tag(e)
234
+ else
235
+ @f.print get_tag(b)
236
+ end
237
+ }
238
+ if r.darkness
239
+ @f.print " has ~light"
240
+ end
241
+ @f.puts ";"
242
+
243
+ objects(r)
244
+ end
245
+
246
+ def section(sect, idx)
247
+ @f = File.open("#@root-#{idx}.inf", "w")
248
+ @f.puts '!' + '=' * 78
249
+ @f.puts "! Section: #{sect.name} "
250
+ @f.puts '!' + '=' * 78
251
+ sect.rooms.each { |r|
252
+ room(r)
253
+ }
254
+
255
+ @f.puts '!' + '-' * 78
256
+ @f.puts '! Doorways'
257
+ @f.puts '!' + '-' * 78
258
+ sect.connections.each { |e|
259
+ next if (e.type != Connection::LOCKED_DOOR and
260
+ e.type != Connection::CLOSED_DOOR)
261
+ door(e)
262
+ }
263
+ @f.close
264
+ end
265
+
266
+ def start
267
+ @f = File.open("#@root.inf", "w")
268
+ @f.puts '!' + '=' * 78
269
+ story = @map.name
270
+ story = 'Untitled' if story == ''
271
+ today = Date.today
272
+ serial = today.strftime("%y%m%d")
273
+ @f.puts <<"EOF"
274
+ Constant Story "#{story}";
275
+ Constant Headline
276
+ "^A game map done with IFMapper
277
+ ^by #{@map.creator}.^";
278
+ Release 1;
279
+ Serial "#{serial}"; ! #{Time.now}
280
+
281
+ ! These constants are to make the game also potentially Glulx compatible
282
+ #ifndef WORDSIZE;
283
+ Constant TARGET_ZCODE;
284
+ Constant WORDSIZE 2;
285
+ #endif;
286
+
287
+ !Constant ZDEBUG;
288
+
289
+ Include "Parser";
290
+ Include "VerbLib";
291
+
292
+
293
+ EOF
294
+ @f.puts '!' + '=' * 78
295
+ @f.puts "! Object classes"
296
+ @f.puts <<"EOF"
297
+
298
+ ! We use Andrew MacKinnon's EasyDoors for our doors.
299
+ Include "easydoors";
300
+
301
+ ! We define a class for NPC (non-player characters)
302
+ class NPC
303
+ has animate;
304
+
305
+ EOF
306
+
307
+ @f.puts '!' + '=' * 78
308
+ @f.puts "! Map sections"
309
+ @map.sections.each_index { |idx|
310
+ @f.puts "#include \"#@base-#{idx}.inf\";"
311
+ }
312
+
313
+ start_location = ''
314
+ if @map.sections.size > 0 and @map.sections[0].rooms[0]
315
+ r = @map.sections[0].rooms[0]
316
+ tag = get_tag(r)
317
+ start_location = "location = #{tag}"
318
+ end
319
+
320
+ @f.puts <<"EOF";
321
+ !============================================================================
322
+ ! Entry point routines
323
+
324
+ [ Initialise;
325
+ #{start_location};
326
+ lookmode = 2; ! verbose
327
+ inventory_style = FULLINV_BIT + ENGLISH_BIT + RECURSE_BIT; ! wide
328
+ ];
329
+ EOF
330
+
331
+ @f.puts <<"EOF"
332
+ !============================================================================
333
+ ! Standard and extended grammar
334
+
335
+ Include "Grammar";
336
+ EOF
337
+
338
+ @f.close
339
+
340
+ @map.sections.each_with_index { |sect, idx|
341
+ section(sect, idx)
342
+ }
343
+ end
344
+
345
+ def initialize(map, fileroot)
346
+ @tags = {}
347
+ @root = fileroot
348
+ @base = File.basename(@root)
349
+ @map = map
350
+
351
+ keywords = [
352
+ 'doorway',
353
+ 'class',
354
+ 'include',
355
+ 'room',
356
+ 'has',
357
+ 'with',
358
+ 'description',
359
+ ] + DIRECTIONS
360
+
361
+ @keyword = /^(?:#{keywords.join('|')})$/i
362
+ start
363
+ end
364
+ end