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,900 +1,900 @@
1
-
2
- require 'IFMapper/Map'
3
-
4
- class FXMap; end
5
-
6
- #
7
- # Function used to unquote a string. Formats such as TADS/Inform use special
8
- # syntax rules in quotes.
9
- #
10
- module MapUnquote
11
- def unquote(x)
12
- return x
13
- end
14
- end
15
-
16
- #
17
- # A generic abstract class for reading a map from some file format.
18
- #
19
- # Map readers are expected to parse their file formats and call:
20
- #
21
- # new_room
22
- # new_door
23
- # new_obj
24
- #
25
- # with @tag and @name defined.
26
- #
27
- # These functions create arrays of temporary Map* classes (like MapRoom)
28
- # that represent the room and their connections.
29
- #
30
- # Later on, the create() function is invoked to actually create those rooms
31
- # as actual FXRooms and similar. The reason this is done this way is because
32
- # in order to position FXRooms around, we need to previously know the location
33
- # of all rooms and their exits.
34
- #
35
- class MapReader
36
-
37
- class ParseError < StandardError; end
38
- class MapError < StandardError; end
39
-
40
- # Path separator in environment variables
41
- if RUBY_PLATFORM =~ /win/
42
- SEP = ';'
43
- else
44
- SEP = ':'
45
- end
46
-
47
- # Direction list in order of positioning preference.
48
- DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
49
-
50
- include MapUnquote
51
-
52
- @@debug = nil
53
- def debug(*x)
54
- return unless @@debug
55
- $stdout.puts x
56
- $stdout.flush
57
- end
58
-
59
- # Temporary classes used to store inform room information
60
- class MapObject
61
- include MapUnquote
62
-
63
- attr_reader :name, :connector
64
- attr_accessor :tag, :location, :enterable, :scenery
65
-
66
- def name=(x)
67
- @name = unquote(x)
68
- end
69
-
70
- def to_s
71
- "#@name tag:#@tag"
72
- end
73
-
74
- # def method_missing(*a)
75
- # end
76
-
77
- def initialize(location)
78
- if location
79
- @location = Array[*location]
80
- else
81
- @location = []
82
- end
83
- @enterable = []
84
- end
85
- end
86
-
87
- class MapDoor
88
- attr_accessor :name, :location, :locked, :connector, :tag
89
- # def method_missing(*x)
90
- # end
91
- def initialize
92
- @location = []
93
- end
94
- def to_s
95
- if locked
96
- door = '<|>'
97
- else
98
- door = '<->'
99
- end
100
- c = ''
101
- if connector
102
- c = ' (connector)'
103
- end
104
- "#{location[0]}#{door}#{location[1]}#{c}"
105
- end
106
- end
107
-
108
- class MapOneWay
109
- attr_reader :room
110
- def initialize(room)
111
- @room = room
112
- end
113
- def to_s
114
- "ONE WAY: #{room}"
115
- end
116
- end
117
-
118
- class MapSpecialExit
119
- attr_reader :go
120
- attr_reader :to
121
- def initialize(go, to)
122
- @go = go
123
- @to = to
124
- end
125
- def to_s
126
- "SPECIAL: #{go} #{to}"
127
- end
128
- end
129
-
130
-
131
- class MapRoom
132
- include MapUnquote
133
-
134
- attr_reader :name
135
- attr_accessor :exits, :tag, :light, :desc
136
- attr_accessor :oneways
137
- def inspect
138
- r = "#{to_s}\n"
139
- @exits.each_with_index { |e, idx|
140
- next if not e
141
- if idx > 7
142
- dir = Connection::EXIT_TEXT[idx-7]
143
- else
144
- dir = Room::DIRECTIONS[idx]
145
- end
146
- r += "\t#{dir.upcase}: #{e}"
147
- }
148
- return r
149
- end
150
- def to_s
151
- "#@name tag:#@tag"
152
- end
153
- def num_exits
154
- return @exits.nitems + @oneways
155
- end
156
- def name=(x)
157
- @name = unquote(x)
158
- end
159
-
160
- # def method_missing(*x)
161
- # end
162
- def initialize
163
- @tag = nil
164
- @desc = ''
165
- @oneways = 0
166
- @light = true
167
- @exits = Array.new(12, nil)
168
- end
169
- end
170
-
171
-
172
-
173
- def new_door(loc = nil)
174
- @obj = @tags[@tag] || MapDoor.new
175
- @obj.tag = @tag
176
- @obj.name = @name || @tag
177
- @obj.location << loc if loc
178
- @tags[@tag] = @obj
179
- @doors << @obj
180
- end
181
-
182
-
183
-
184
- def new_room(klass = MapRoom)
185
- # We assume we are a room (albeit we could be an obj)
186
- @room = klass.new
187
- @room.tag = @tag
188
- @room.name = @name || @tag
189
- @room.desc = ''
190
- @tags[@tag] = @room
191
- @rooms << @room
192
- end
193
-
194
- def make_room(to, x, y, dx = 1, dy = 0)
195
- if not @map.free?(x, y)
196
- @map.shift(x, y, dx, dy)
197
- end
198
- room = @map.new_room(x, y)
199
- room.name = to.name
200
- desc = to.desc.to_s
201
- desc.gsub!(/[\t\n]/, ' ')
202
- desc.squeeze!(' ')
203
- room.desc = unquote(desc)
204
- room.darkness = !to.light
205
- @tags[to.tag] = room
206
- return room
207
- end
208
-
209
-
210
- def new_obj(loc = nil, klass = MapObject)
211
- debug "+++ OBJECT #@name"
212
- @obj = klass.new(loc)
213
- @obj.tag = @tag
214
- @obj.name = @name # || @tag
215
- @tags[@tag] = @obj
216
- @objects << @obj
217
- end
218
-
219
-
220
- attr_reader :map, :doors, :rooms, :tags, :functions
221
- attr_reader :include_dirs
222
-
223
- #
224
- # Bring up the properties window, to allow user to change
225
- # settings
226
- #
227
- def properties
228
- decor = DECOR_TITLE|DECOR_BORDER
229
-
230
- dlg = FXDialogBox.new( @map.window.parent, "Include Settings", decor )
231
- mainFrame = FXVerticalFrame.new(dlg,
232
- FRAME_SUNKEN|FRAME_THICK|
233
- LAYOUT_FILL_X|LAYOUT_FILL_Y)
234
-
235
- frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
236
-
237
- FXLabel.new(frame, "Include Dirs: ", nil, 0, LAYOUT_FILL_X)
238
- inc = FXTextField.new(frame, 80, nil, 0, LAYOUT_FILL_ROW)
239
- inc.text = @include_dirs.join(SEP)
240
-
241
- buttons = FXHorizontalFrame.new(mainFrame,
242
- LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
243
- PACK_UNIFORM_WIDTH)
244
- # Accept
245
- FXButton.new(buttons, "&Accept", nil, dlg, FXDialogBox::ID_ACCEPT,
246
- FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
247
-
248
- # Cancel
249
- FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
250
- FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
251
- if dlg.execute != 0
252
- @include_dirs = inc.text.split(SEP)
253
- return true
254
- end
255
- return false
256
- end
257
-
258
- def set_include_dirs
259
- end
260
-
261
- def read_file(file)
262
- debug "Start parsing #{file}"
263
- File.open(file) { |f|
264
- parse(f)
265
- }
266
- debug "Done parsing #{file}"
267
- end
268
-
269
- def best_dir(r, dirA)
270
- start = (dirA + 4) % 8
271
-
272
- dirs = [
273
- start,
274
- (start - 1) % 8,
275
- (start + 1) % 8,
276
- (start - 2) % 8,
277
- (start + 2) % 8,
278
- (start - 3) % 8,
279
- (start + 3) % 8,
280
- dirA
281
- ]
282
-
283
- dirs.each { |d|
284
- return d if not r.exits[d]
285
- }
286
-
287
- return nil
288
- end
289
-
290
- def has_exit_to?(a, b)
291
- idx = a.exits.rindex(b)
292
- return idx if idx
293
- a.exits.each_with_index { |e, idx|
294
- next unless e
295
- if e.kind_of?(MapSpecialExit)
296
- e = e.to
297
- elsif e.kind_of?(MapObject)
298
- e.enterable.each { |x|
299
- return idx if @tags[x] == b
300
- }
301
- end
302
- case e
303
- when MapDoor
304
- tag = e.location.find { |t| t != a.tag }
305
- e = @tags[tag]
306
- return idx if e == b
307
- when MapOneWay
308
- return idx if e.room == b
309
- else
310
- return idx if e == b
311
- end
312
- }
313
- return false
314
- end
315
-
316
- #
317
- # Look for one way exits and add them to the proper room exit on the
318
- # destination side. This is needed so that we properly count room exits
319
- # and start mapping from rooms with more exits.
320
- #
321
- # Also, resolve up/down/in/out exits into one of the proper 8 directions.
322
- #
323
- def resolve_exits
324
-
325
- if @tags[nil]
326
- raise "error"
327
- end
328
-
329
- #
330
- # First, we resolve tags to the corresponding Map* class.
331
- # If we deal with a MapDoor that is really a connector, we simplify and
332
- # just attach the destination room directly.
333
- # If we deal with a MapDoor with no matching other room, we remove. This
334
- # can happen in TADS games or if user just read a portion of the full map.
335
- #
336
- @rooms.each { |r|
337
- r.exits.each_with_index { |tag, idx|
338
- next unless tag
339
- to = @tags[tag]
340
-
341
- if to.kind_of?(MapDoor)
342
- if to.location.size == 1
343
- to = nil
344
- else
345
- if to.connector
346
- t = to.location.find { |t| t != r.tag }
347
- to = @tags[t]
348
- end
349
- end
350
- end
351
-
352
- if idx > 7
353
- # if Up/Down/In/Out exit and we have a similar exit
354
- # going in one direction, remove this exit
355
- if r.exits[0,8].index(to)
356
- r.exits[idx] = nil
357
- next
358
- end
359
- end
360
-
361
- if not to
362
- if not @functions.include?(tag)
363
- $stderr.puts "Exit to #{tag} not found, ignored."
364
- end
365
- end
366
-
367
- r.exits[idx] = to
368
- }
369
- }
370
-
371
- @rooms.each { |r|
372
- r.exits.each_with_index { |e, dirA|
373
- next if not e or e.kind_of?(MapOneWay)
374
- next if e == r
375
-
376
- ##
377
- # Do we have a special exit?
378
- if dirA > 7
379
- # First, get dirB and see if it is not a proper directional
380
- # exit
381
- dirB = nil
382
- if e.kind_of?(MapRoom)
383
- dirB = has_exit_to?(e, r)
384
- elsif e.kind_of?(MapDoor)
385
- t = e.location.find { |t| t != r.tag }
386
- to = @tags[t]
387
- if to
388
- to.exits.each_with_index { |d, idx|
389
- if d == e or d == r
390
- dirB = idx
391
- break
392
- end
393
- }
394
- end
395
- end
396
-
397
- dirB = 4 if not dirB or dirB > 7
398
-
399
- go = dirA - 7
400
- r.exits[dirA] = nil
401
- dirA = best_dir(r, dirB)
402
- next if not dirA
403
- r.exits[dirA] = MapSpecialExit.new(go, e)
404
- end
405
-
406
-
407
- ###############
408
-
409
- # we have a room or door exit
410
-
411
- if e.kind_of?(MapRoom)
412
- # check if destination room also has a connection towards us.
413
- next if has_exit_to?(e, r)
414
-
415
- dirB = best_dir(e, dirA)
416
- next unless dirB
417
-
418
- e.exits[dirB] = MapOneWay.new(r)
419
-
420
- elsif e.kind_of?(MapDoor)
421
- t = e.location.find { |t| t != r.tag }
422
- found = false
423
- to = @tags[t]
424
- if to
425
- to.exits.each_with_index { |d, idx|
426
- next if not d
427
- if d == e or d == r
428
- found = true
429
- break
430
- end
431
- }
432
- end
433
-
434
- if to and not found
435
- dirB = best_dir(to, dirA)
436
- next unless dirB
437
- to.exits[dirB] = MapOneWay.new(r)
438
- end
439
- elsif e.kind_of?(MapObject)
440
- else
441
- raise "Unknown exit type #{e.class}"
442
- end
443
- }
444
- }
445
-
446
-
447
-
448
- end
449
-
450
- def shift_link(room, dir)
451
- idx = dir + 1
452
- idx = 0 if idx > 7
453
- while idx != dir
454
- break if not room[idx]
455
- idx += 1
456
- idx = 0 if idx > 7
457
- end
458
- if idx != dir
459
- room[idx] = room[dir]
460
- room[dir] = nil
461
- # get position of other room
462
- ox, oy = Room::DIR_TO_VECTOR[dir]
463
- c = room[idx]
464
- if c.roomA == room
465
- b = c.roomB
466
- else
467
- b = c.roomA
468
- end
469
- x, y = [b.x, b.y]
470
- x -= ox
471
- y -= oy
472
- dx, dy = Room::DIR_TO_VECTOR[idx]
473
- @map.shift(x, y, -dx, -dy)
474
- else
475
- # raise "Warning. Cannot shift connection for #{room}."
476
- end
477
- end
478
-
479
-
480
- def oneway_link?(a, b)
481
- # First, check if room already has exit moving towards other room
482
- a.exits.each_with_index { |e, idx|
483
- next if not e or e.dir != Connection::AtoB
484
- roomA = e.roomA
485
- roomB = e.roomB
486
- if roomA == a and roomB == b
487
- return e
488
- end
489
- }
490
- return nil
491
- end
492
-
493
-
494
- # Choose a direction to represent up/down/in/out.
495
- def choose_dir(a, b, go = nil, exitB = nil)
496
- if go
497
- rgo = go % 2 == 0? go - 1 : go + 1
498
- # First, check if room already has exit moving towards other room
499
- a.exits.each_with_index { |e, idx|
500
- next if not e
501
- roomA = e.roomA
502
- roomB = e.roomB
503
- if roomA == a and roomB == b
504
- e.exitAtext = go
505
- return idx
506
- elsif roomB == a and roomA == b
507
- e.exitBtext = go
508
- return idx
509
- end
510
- }
511
- end
512
-
513
- # We prefer directions that travel less... so we need to figure
514
- # out where we start from...
515
- if b
516
- x = b.x
517
- y = b.y
518
- else
519
- x = a.x
520
- y = a.y
521
- end
522
- if exitB
523
- dx, dy = Room::DIR_TO_VECTOR[exitB]
524
- x += dx
525
- y += dy
526
- end
527
-
528
- # No such luck... Pick a direction.
529
- best = nil
530
- bestscore = nil
531
-
532
- DIRLIST.each { |dir|
533
- # We prefer straight directions to diagonal ones
534
- inc = dir % 2 == 1 ? 100 : 140
535
- score = 1000
536
- # We prefer directions where both that dir and the opposite side
537
- # are empty.
538
- if (not a[dir]) or a[dir].stub?
539
- score += inc
540
- score += 4 if a[dir] #attaching to stubs is better
541
- end
542
- # rdir = (dir + 4) % 8
543
- # score += 1 unless a[rdir]
544
-
545
- # Measure distance for that exit, we prefer shorter
546
- # paths
547
- dx, dy = Room::DIR_TO_VECTOR[dir]
548
- dx = (a.x + dx) - x
549
- dy = (a.y + dy) - y
550
- d = dx * dx + dy * dy
551
- score -= d
552
- next if bestscore and score <= bestscore
553
- bestscore = score
554
- best = dir
555
- }
556
-
557
- if not bestscore
558
- raise "No free exit for choose_dir"
559
- end
560
-
561
- return best
562
- end
563
-
564
-
565
- def get_exit(r, from, to, x, y, dx = 1, dy = 0 )
566
- elem = @tags[to.tag]
567
- if elem.kind_of?(MapRoom)
568
- dirB = has_exit_to?(to, r)
569
- if dirB
570
- dbx, dby = Room::DIR_TO_VECTOR[dirB]
571
- if x + dbx != from.x or y + dby != from.y
572
- x -= dbx
573
- y -= dby
574
- end
575
- end
576
-
577
- room = create_room(to, x, y, dx, dy)
578
- return [room, Connection::FREE]
579
- elsif elem.kind_of?(MapDoor)
580
- if elem.connector
581
- type = Connection::FREE
582
- elsif elem.locked
583
- type = Connection::LOCKED_DOOR
584
- else
585
- type = Connection::CLOSED_DOOR
586
- end
587
-
588
- # Okay, connecting room is missing. Check door's locations property
589
- elem.location.each { |tag|
590
- next if @tags[tag] == from
591
- @rooms.each { |o|
592
- next if o.tag != tag
593
- res = create_room( o, x, y, dx, dy )
594
- return [ res, type ]
595
- }
596
- }
597
-
598
- # @rooms.each { |o|
599
- # next if @tags[o.tag] == from
600
- # o.exits.each { |e|
601
- # next unless e
602
- # if @tags[e] == elem
603
- # res = create_room( o, x, y, dx, dy )
604
- # return [ res, type ]
605
- # end
606
- # }
607
- # }
608
-
609
- $stderr.puts "No room for door #{to.name}: #{to}"
610
- return [nil, nil]
611
- else
612
- return [elem, Connection::FREE]
613
- end
614
- end
615
-
616
- def create_room(r, x, y, dx = 2, dy = 0)
617
- return @tags[r.tag] if @tags[r.tag].kind_of?(Room)
618
-
619
- debug "+++ CREATE ROOM #{r.name} (#{x},#{y}) TAG:#{r.tag}"
620
- from, = make_room(r, x, y, dx, dy)
621
-
622
- r.exits[0,8].each_with_index { |to, exit|
623
- next unless to
624
-
625
- debug "#{r.name} EXIT:#{exit} points to #{to}"
626
-
627
- go = c = nil
628
-
629
- if to.kind_of?(MapOneWay)
630
- dx, dy = Room::DIR_TO_VECTOR[exit]
631
- x = from.x + dx
632
- y = from.y + dy
633
- @tabs += 1
634
- create_room(to.room, x, y, dx, dy)
635
- @tabs -= 1
636
- next
637
- elsif to.kind_of?(MapSpecialExit)
638
- go = to.go
639
- to = to.to
640
- end
641
-
642
- b = to
643
-
644
-
645
- dir = exit
646
- type = 0
647
-
648
- # If exit leads to an enterable object, find out where does that
649
- # enterable object lead to.
650
- if to.kind_of?(MapObject)
651
- rooms = to.enterable
652
- rooms.each { |room|
653
- next if room == r
654
- to = b = @tags[room]
655
- break
656
- }
657
- # Skip it if we are still an object. This means we are just
658
- # a container, like the phone booth in the Fate game demo.
659
- next if to.kind_of?(MapObject)
660
- end
661
-
662
- odir = nil
663
- if b.kind_of?(MapRoom)
664
- odir = b.exits.rindex(r.tag)
665
- if not odir
666
- b.exits[0,8].each_with_index { |e, idx|
667
- next if not e
668
- if (e.kind_of?(MapOneWay) and e.room == r) or
669
- (e.kind_of?(MapSpecialExit) and e.to == r)
670
- odir = idx
671
- break
672
- end
673
- }
674
- end
675
- elsif b.kind_of?(MapDoor)
676
- t = b.location.find { |t| t != r.tag }
677
- other = @rooms.find { |x| x.tag == t }
678
- # other = @tags[t]
679
- if other
680
- other.exits.each_with_index { |d, idx|
681
- next if not d
682
- if d == b or d == r.tag or
683
- (d.kind_of?(MapOneWay) and d.room == r) or
684
- (d.kind_of?(MapSpecialExit) and (d.to == b or d == r.tag))
685
- odir = idx
686
- break
687
- end
688
- }
689
- end
690
- end
691
- odir = (dir + 4) % 8 if not odir
692
-
693
- if to.kind_of?(MapRoom) or to.kind_of?(MapDoor)
694
- dx, dy = Room::DIR_TO_VECTOR[dir]
695
- x = from.x + dx
696
- y = from.y + dy
697
- @tabs += 1
698
- to, type = get_exit(r, from, to, x, y, dx, dy)
699
- @tabs -= 1
700
- next if not to
701
- end
702
-
703
-
704
- # b = @rooms.find { |r2| r2.tag == tag }
705
- # odir = nil
706
- # odir = b.exits.rindex(r.tag) if b
707
- # odir = (dir + 4) % 8 if not odir
708
-
709
- if from[dir]
710
- c = from[dir]
711
- if to.exits.rindex(c) and c.roomB == from
712
- debug "LINK TRAVELLED BOTH"
713
- c.dir = Connection::BOTH
714
- c.exitBtext = go if go
715
- next
716
- else
717
- debug "#{exit} FROM #{from}->#{to} BLOCKED DIR: #{dir}"
718
- shift_link(from, dir)
719
- end
720
- end
721
-
722
- # Check we don't have a connection already
723
- if to[odir]
724
- c = to[odir]
725
-
726
- # We need to change odir to something else
727
- rgo = 0
728
- if go
729
- rgo = go % 2 == 0? go - 1 : go + 1
730
- end
731
-
732
- # First, check if we have a dangling one-way link going to->from
733
- # If we do, we use it.
734
- c = oneway_link?(from, to)
735
- if not c
736
- odir = choose_dir(to, from, rgo, dir)
737
- else
738
- debug "FOUND LINK #{c} -- filling it."
739
- idx = from.exits.index(c)
740
- from[idx] = nil
741
- from[dir] = c
742
- c.dir = Connection::BOTH
743
- c.exitBtext = go if go
744
- end
745
- else
746
- debug "to[odir] empty."
747
- # First, check if we have a dangling one-way link going to->from
748
- # If we do, we use it.
749
- c = oneway_link?(to, from)
750
- if c
751
- debug "FOUND LINK #{c} -- filling it."
752
- idx = from.exits.index(c)
753
- from[idx] = nil
754
- from[dir] = c
755
- c.dir = Connection::BOTH
756
- c.exitBtext = go if go
757
- end
758
- end
759
-
760
- if not c
761
- debug "NEW LINK #{from} #{dir} to #{to} #{odir}"
762
- begin
763
- c = @map.new_connection(from, dir, to, odir)
764
- c.exitAtext = go if go
765
- c.dir = Connection::AtoB
766
- c.type = type
767
- rescue Section::ConnectionError
768
- end
769
- end
770
- }
771
-
772
- return from
773
- end
774
-
775
- #
776
- # Try to move rooms around to make map smaller.
777
- #
778
- def compact
779
- # First, verify all one-exit rooms lie next to its opposite room.
780
- sect = @map.sections[@map.section]
781
- rooms = sect.rooms.find_all { |r| r.num_exits == 1 }
782
-
783
- rooms.each { |a|
784
- c = a.exits.find { |e| e != nil }
785
- b = c.opposite(a)
786
-
787
- dx = (a.x - b.x).abs
788
- dy = (a.y - b.y).abs
789
- next if dx <= 1 and dy <= 1
790
-
791
- x, y = [b.x, b.y]
792
- dirB = b.exits.index(c)
793
-
794
- dx, dy = Room::DIR_TO_VECTOR[dirB]
795
- x += dx
796
- y += dy
797
-
798
- dirA = a.exits.index(c)
799
- dx, dy = Room::DIR_TO_VECTOR[dirA]
800
- # Check if exits are complementary. If not, we need to move room
801
- # again.
802
- if x + dx != b.x or y + dy != b.y
803
- x -= dx
804
- y -= dy
805
- end
806
- next if x == a.x and y == a.y
807
-
808
- # Place room here. Check if filled.
809
- if sect.free?(x, y)
810
- a.x, a.y = x, y
811
- else
812
- if dx != 0
813
- dy = 0
814
- end
815
- sect.shift(x, y, -dx, -dy)
816
- a.x, a.y = x, y
817
- end
818
- }
819
- end
820
-
821
- #
822
- # Create all the stuff we found
823
- #
824
- def create
825
- @rooms = @rooms.sort_by { |r| r.num_exits }
826
- @rooms.reverse!
827
-
828
- @rooms.each { |r|
829
- @tabs = 0
830
- min, max = @map.sections[@map.section].min_max_rooms
831
- create_room(r, max[0] + 2, 0)
832
- }
833
- @rooms = []
834
-
835
- @objects.delete_if { |obj| obj.scenery }
836
-
837
- # Add objects to rooms
838
- @objects.each { |obj|
839
- obj.location.each { |loc|
840
- r = @tags[loc]
841
- while r and not r.kind_of?(Room)
842
- r = @tags[ r.location[0] ]
843
- end
844
- next unless r
845
- r.objects << obj.name + "\n"
846
- }
847
- }
848
- end
849
-
850
- def create_map(file)
851
- read_file(file)
852
-
853
- puts "Rooms: #{@rooms.size}"
854
- puts "Doors: #{@doors.size}"
855
- puts "Objects: #{@objects.size}"
856
-
857
- begin
858
- resolve_exits
859
- create
860
- compact
861
- rescue => e
862
- puts e
863
- puts e.backtrace
864
- raise
865
- end
866
- end
867
-
868
- def initialize(file, map = Map.new('Read Map'))
869
- @map = map
870
-
871
- @doors = []
872
- @functions = []
873
- @tags = {}
874
- @rooms = []
875
- @objects = []
876
-
877
- @include_dirs = [File.dirname(file)]
878
- set_include_dirs
879
-
880
- if @map.kind_of?(FXMap)
881
- return unless properties
882
- end
883
-
884
- create_map(file)
885
-
886
- debug "Done creating #{file}"
887
-
888
- @tags = nil # save some memory by clearing the tag list
889
- @objects = nil
890
- @functions = nil
891
- @doors = nil
892
- @rooms = nil
893
-
894
- if @map.kind_of?(FXMap)
895
- @map.filename = file.sub(/\.t$/i, '.map')
896
- @map.options['Location Description'] = true
897
- @map.window.show
898
- end
899
- end
900
- end
1
+
2
+ require 'IFMapper/Map'
3
+
4
+ class FXMap; end
5
+
6
+ #
7
+ # Function used to unquote a string. Formats such as TADS/Inform use special
8
+ # syntax rules in quotes.
9
+ #
10
+ module MapUnquote
11
+ def unquote(x)
12
+ return x
13
+ end
14
+ end
15
+
16
+ #
17
+ # A generic abstract class for reading a map from some file format.
18
+ #
19
+ # Map readers are expected to parse their file formats and call:
20
+ #
21
+ # new_room
22
+ # new_door
23
+ # new_obj
24
+ #
25
+ # with @tag and @name defined.
26
+ #
27
+ # These functions create arrays of temporary Map* classes (like MapRoom)
28
+ # that represent the room and their connections.
29
+ #
30
+ # Later on, the create() function is invoked to actually create those rooms
31
+ # as actual FXRooms and similar. The reason this is done this way is because
32
+ # in order to position FXRooms around, we need to previously know the location
33
+ # of all rooms and their exits.
34
+ #
35
+ class MapReader
36
+
37
+ class ParseError < StandardError; end
38
+ class MapError < StandardError; end
39
+
40
+ # Path separator in environment variables
41
+ if RUBY_PLATFORM =~ /win/
42
+ SEP = ';'
43
+ else
44
+ SEP = ':'
45
+ end
46
+
47
+ # Direction list in order of positioning preference.
48
+ DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
49
+
50
+ include MapUnquote
51
+
52
+ @@debug = nil
53
+ def debug(*x)
54
+ return unless @@debug
55
+ $stdout.puts x
56
+ $stdout.flush
57
+ end
58
+
59
+ # Temporary classes used to store inform room information
60
+ class MapObject
61
+ include MapUnquote
62
+
63
+ attr_reader :name, :connector
64
+ attr_accessor :tag, :location, :enterable, :scenery
65
+
66
+ def name=(x)
67
+ @name = unquote(x)
68
+ end
69
+
70
+ def to_s
71
+ "#@name tag:#@tag"
72
+ end
73
+
74
+ # def method_missing(*a)
75
+ # end
76
+
77
+ def initialize(location)
78
+ if location
79
+ @location = Array[*location]
80
+ else
81
+ @location = []
82
+ end
83
+ @enterable = []
84
+ end
85
+ end
86
+
87
+ class MapDoor
88
+ attr_accessor :name, :location, :locked, :connector, :tag
89
+ # def method_missing(*x)
90
+ # end
91
+ def initialize
92
+ @location = []
93
+ end
94
+ def to_s
95
+ if locked
96
+ door = '<|>'
97
+ else
98
+ door = '<->'
99
+ end
100
+ c = ''
101
+ if connector
102
+ c = ' (connector)'
103
+ end
104
+ "#{location[0]}#{door}#{location[1]}#{c}"
105
+ end
106
+ end
107
+
108
+ class MapOneWay
109
+ attr_reader :room
110
+ def initialize(room)
111
+ @room = room
112
+ end
113
+ def to_s
114
+ "ONE WAY: #{room}"
115
+ end
116
+ end
117
+
118
+ class MapSpecialExit
119
+ attr_reader :go
120
+ attr_reader :to
121
+ def initialize(go, to)
122
+ @go = go
123
+ @to = to
124
+ end
125
+ def to_s
126
+ "SPECIAL: #{go} #{to}"
127
+ end
128
+ end
129
+
130
+
131
+ class MapRoom
132
+ include MapUnquote
133
+
134
+ attr_reader :name
135
+ attr_accessor :exits, :tag, :light, :desc
136
+ attr_accessor :oneways
137
+ def inspect
138
+ r = "#{to_s}\n"
139
+ @exits.each_with_index { |e, idx|
140
+ next if not e
141
+ if idx > 7
142
+ dir = Connection::EXIT_TEXT[idx-7]
143
+ else
144
+ dir = Room::DIRECTIONS[idx]
145
+ end
146
+ r += "\t#{dir.upcase}: #{e}"
147
+ }
148
+ return r
149
+ end
150
+ def to_s
151
+ "#@name tag:#@tag"
152
+ end
153
+ def num_exits
154
+ return @exits.nitems + @oneways
155
+ end
156
+ def name=(x)
157
+ @name = unquote(x)
158
+ end
159
+
160
+ # def method_missing(*x)
161
+ # end
162
+ def initialize
163
+ @tag = nil
164
+ @desc = ''
165
+ @oneways = 0
166
+ @light = true
167
+ @exits = Array.new(12, nil)
168
+ end
169
+ end
170
+
171
+
172
+
173
+ def new_door(loc = nil)
174
+ @obj = @tags[@tag] || MapDoor.new
175
+ @obj.tag = @tag
176
+ @obj.name = @name || @tag
177
+ @obj.location << loc if loc
178
+ @tags[@tag] = @obj
179
+ @doors << @obj
180
+ end
181
+
182
+
183
+
184
+ def new_room(klass = MapRoom)
185
+ # We assume we are a room (albeit we could be an obj)
186
+ @room = klass.new
187
+ @room.tag = @tag
188
+ @room.name = @name || @tag
189
+ @room.desc = ''
190
+ @tags[@tag] = @room
191
+ @rooms << @room
192
+ end
193
+
194
+ def make_room(to, x, y, dx = 1, dy = 0)
195
+ if not @map.free?(x, y)
196
+ @map.shift(x, y, dx, dy)
197
+ end
198
+ room = @map.new_room(x, y)
199
+ room.name = to.name
200
+ desc = to.desc.to_s
201
+ desc.gsub!(/[\t\n]/, ' ')
202
+ desc.squeeze!(' ')
203
+ room.desc = unquote(desc)
204
+ room.darkness = !to.light
205
+ @tags[to.tag] = room
206
+ return room
207
+ end
208
+
209
+
210
+ def new_obj(loc = nil, klass = MapObject)
211
+ debug "+++ OBJECT #@name"
212
+ @obj = klass.new(loc)
213
+ @obj.tag = @tag
214
+ @obj.name = @name # || @tag
215
+ @tags[@tag] = @obj
216
+ @objects << @obj
217
+ end
218
+
219
+
220
+ attr_reader :map, :doors, :rooms, :tags, :functions
221
+ attr_reader :include_dirs
222
+
223
+ #
224
+ # Bring up the properties window, to allow user to change
225
+ # settings
226
+ #
227
+ def properties
228
+ decor = DECOR_TITLE|DECOR_BORDER
229
+
230
+ dlg = FXDialogBox.new( @map.window.parent, "Include Settings", decor )
231
+ mainFrame = FXVerticalFrame.new(dlg,
232
+ FRAME_SUNKEN|FRAME_THICK|
233
+ LAYOUT_FILL_X|LAYOUT_FILL_Y)
234
+
235
+ frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
236
+
237
+ FXLabel.new(frame, "Include Dirs: ", nil, 0, LAYOUT_FILL_X)
238
+ inc = FXTextField.new(frame, 80, nil, 0, LAYOUT_FILL_ROW)
239
+ inc.text = @include_dirs.join(SEP)
240
+
241
+ buttons = FXHorizontalFrame.new(mainFrame,
242
+ LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
243
+ PACK_UNIFORM_WIDTH)
244
+ # Accept
245
+ FXButton.new(buttons, "&Accept", nil, dlg, FXDialogBox::ID_ACCEPT,
246
+ FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
247
+
248
+ # Cancel
249
+ FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
250
+ FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
251
+ if dlg.execute != 0
252
+ @include_dirs = inc.text.split(SEP)
253
+ return true
254
+ end
255
+ return false
256
+ end
257
+
258
+ def set_include_dirs
259
+ end
260
+
261
+ def read_file(file)
262
+ debug "Start parsing #{file}"
263
+ File.open(file) { |f|
264
+ parse(f)
265
+ }
266
+ debug "Done parsing #{file}"
267
+ end
268
+
269
+ def best_dir(r, dirA)
270
+ start = (dirA + 4) % 8
271
+
272
+ dirs = [
273
+ start,
274
+ (start - 1) % 8,
275
+ (start + 1) % 8,
276
+ (start - 2) % 8,
277
+ (start + 2) % 8,
278
+ (start - 3) % 8,
279
+ (start + 3) % 8,
280
+ dirA
281
+ ]
282
+
283
+ dirs.each { |d|
284
+ return d if not r.exits[d]
285
+ }
286
+
287
+ return nil
288
+ end
289
+
290
+ def has_exit_to?(a, b)
291
+ idx = a.exits.rindex(b)
292
+ return idx if idx
293
+ a.exits.each_with_index { |e, idx|
294
+ next unless e
295
+ if e.kind_of?(MapSpecialExit)
296
+ e = e.to
297
+ elsif e.kind_of?(MapObject)
298
+ e.enterable.each { |x|
299
+ return idx if @tags[x] == b
300
+ }
301
+ end
302
+ case e
303
+ when MapDoor
304
+ tag = e.location.find { |t| t != a.tag }
305
+ e = @tags[tag]
306
+ return idx if e == b
307
+ when MapOneWay
308
+ return idx if e.room == b
309
+ else
310
+ return idx if e == b
311
+ end
312
+ }
313
+ return false
314
+ end
315
+
316
+ #
317
+ # Look for one way exits and add them to the proper room exit on the
318
+ # destination side. This is needed so that we properly count room exits
319
+ # and start mapping from rooms with more exits.
320
+ #
321
+ # Also, resolve up/down/in/out exits into one of the proper 8 directions.
322
+ #
323
+ def resolve_exits
324
+
325
+ if @tags[nil]
326
+ raise "error"
327
+ end
328
+
329
+ #
330
+ # First, we resolve tags to the corresponding Map* class.
331
+ # If we deal with a MapDoor that is really a connector, we simplify and
332
+ # just attach the destination room directly.
333
+ # If we deal with a MapDoor with no matching other room, we remove. This
334
+ # can happen in TADS games or if user just read a portion of the full map.
335
+ #
336
+ @rooms.each { |r|
337
+ r.exits.each_with_index { |tag, idx|
338
+ next unless tag
339
+ to = @tags[tag]
340
+
341
+ if to.kind_of?(MapDoor)
342
+ if to.location.size == 1
343
+ to = nil
344
+ else
345
+ if to.connector
346
+ t = to.location.find { |t| t != r.tag }
347
+ to = @tags[t]
348
+ end
349
+ end
350
+ end
351
+
352
+ if idx > 7
353
+ # if Up/Down/In/Out exit and we have a similar exit
354
+ # going in one direction, remove this exit
355
+ if r.exits[0,8].index(to)
356
+ r.exits[idx] = nil
357
+ next
358
+ end
359
+ end
360
+
361
+ if not to
362
+ if not @functions.include?(tag)
363
+ $stderr.puts "Exit to #{tag} not found, ignored."
364
+ end
365
+ end
366
+
367
+ r.exits[idx] = to
368
+ }
369
+ }
370
+
371
+ @rooms.each { |r|
372
+ r.exits.each_with_index { |e, dirA|
373
+ next if not e or e.kind_of?(MapOneWay)
374
+ next if e == r
375
+
376
+ ##
377
+ # Do we have a special exit?
378
+ if dirA > 7
379
+ # First, get dirB and see if it is not a proper directional
380
+ # exit
381
+ dirB = nil
382
+ if e.kind_of?(MapRoom)
383
+ dirB = has_exit_to?(e, r)
384
+ elsif e.kind_of?(MapDoor)
385
+ t = e.location.find { |t| t != r.tag }
386
+ to = @tags[t]
387
+ if to
388
+ to.exits.each_with_index { |d, idx|
389
+ if d == e or d == r
390
+ dirB = idx
391
+ break
392
+ end
393
+ }
394
+ end
395
+ end
396
+
397
+ dirB = 4 if not dirB or dirB > 7
398
+
399
+ go = dirA - 7
400
+ r.exits[dirA] = nil
401
+ dirA = best_dir(r, dirB)
402
+ next if not dirA
403
+ r.exits[dirA] = MapSpecialExit.new(go, e)
404
+ end
405
+
406
+
407
+ ###############
408
+
409
+ # we have a room or door exit
410
+
411
+ if e.kind_of?(MapRoom)
412
+ # check if destination room also has a connection towards us.
413
+ next if has_exit_to?(e, r)
414
+
415
+ dirB = best_dir(e, dirA)
416
+ next unless dirB
417
+
418
+ e.exits[dirB] = MapOneWay.new(r)
419
+
420
+ elsif e.kind_of?(MapDoor)
421
+ t = e.location.find { |t| t != r.tag }
422
+ found = false
423
+ to = @tags[t]
424
+ if to
425
+ to.exits.each_with_index { |d, idx|
426
+ next if not d
427
+ if d == e or d == r
428
+ found = true
429
+ break
430
+ end
431
+ }
432
+ end
433
+
434
+ if to and not found
435
+ dirB = best_dir(to, dirA)
436
+ next unless dirB
437
+ to.exits[dirB] = MapOneWay.new(r)
438
+ end
439
+ elsif e.kind_of?(MapObject)
440
+ else
441
+ raise "Unknown exit type #{e.class}"
442
+ end
443
+ }
444
+ }
445
+
446
+
447
+
448
+ end
449
+
450
+ def shift_link(room, dir)
451
+ idx = dir + 1
452
+ idx = 0 if idx > 7
453
+ while idx != dir
454
+ break if not room[idx]
455
+ idx += 1
456
+ idx = 0 if idx > 7
457
+ end
458
+ if idx != dir
459
+ room[idx] = room[dir]
460
+ room[dir] = nil
461
+ # get position of other room
462
+ ox, oy = Room::DIR_TO_VECTOR[dir]
463
+ c = room[idx]
464
+ if c.roomA == room
465
+ b = c.roomB
466
+ else
467
+ b = c.roomA
468
+ end
469
+ x, y = [b.x, b.y]
470
+ x -= ox
471
+ y -= oy
472
+ dx, dy = Room::DIR_TO_VECTOR[idx]
473
+ @map.shift(x, y, -dx, -dy)
474
+ else
475
+ # raise "Warning. Cannot shift connection for #{room}."
476
+ end
477
+ end
478
+
479
+
480
+ def oneway_link?(a, b)
481
+ # First, check if room already has exit moving towards other room
482
+ a.exits.each_with_index { |e, idx|
483
+ next if not e or e.dir != Connection::AtoB
484
+ roomA = e.roomA
485
+ roomB = e.roomB
486
+ if roomA == a and roomB == b
487
+ return e
488
+ end
489
+ }
490
+ return nil
491
+ end
492
+
493
+
494
+ # Choose a direction to represent up/down/in/out.
495
+ def choose_dir(a, b, go = nil, exitB = nil)
496
+ if go
497
+ rgo = go % 2 == 0? go - 1 : go + 1
498
+ # First, check if room already has exit moving towards other room
499
+ a.exits.each_with_index { |e, idx|
500
+ next if not e
501
+ roomA = e.roomA
502
+ roomB = e.roomB
503
+ if roomA == a and roomB == b
504
+ e.exitAtext = go
505
+ return idx
506
+ elsif roomB == a and roomA == b
507
+ e.exitBtext = go
508
+ return idx
509
+ end
510
+ }
511
+ end
512
+
513
+ # We prefer directions that travel less... so we need to figure
514
+ # out where we start from...
515
+ if b
516
+ x = b.x
517
+ y = b.y
518
+ else
519
+ x = a.x
520
+ y = a.y
521
+ end
522
+ if exitB
523
+ dx, dy = Room::DIR_TO_VECTOR[exitB]
524
+ x += dx
525
+ y += dy
526
+ end
527
+
528
+ # No such luck... Pick a direction.
529
+ best = nil
530
+ bestscore = nil
531
+
532
+ DIRLIST.each { |dir|
533
+ # We prefer straight directions to diagonal ones
534
+ inc = dir % 2 == 1 ? 100 : 140
535
+ score = 1000
536
+ # We prefer directions where both that dir and the opposite side
537
+ # are empty.
538
+ if (not a[dir]) or a[dir].stub?
539
+ score += inc
540
+ score += 4 if a[dir] #attaching to stubs is better
541
+ end
542
+ # rdir = (dir + 4) % 8
543
+ # score += 1 unless a[rdir]
544
+
545
+ # Measure distance for that exit, we prefer shorter
546
+ # paths
547
+ dx, dy = Room::DIR_TO_VECTOR[dir]
548
+ dx = (a.x + dx) - x
549
+ dy = (a.y + dy) - y
550
+ d = dx * dx + dy * dy
551
+ score -= d
552
+ next if bestscore and score <= bestscore
553
+ bestscore = score
554
+ best = dir
555
+ }
556
+
557
+ if not bestscore
558
+ raise "No free exit for choose_dir"
559
+ end
560
+
561
+ return best
562
+ end
563
+
564
+
565
+ def get_exit(r, from, to, x, y, dx = 1, dy = 0 )
566
+ elem = @tags[to.tag]
567
+ if elem.kind_of?(MapRoom)
568
+ dirB = has_exit_to?(to, r)
569
+ if dirB
570
+ dbx, dby = Room::DIR_TO_VECTOR[dirB]
571
+ if x + dbx != from.x or y + dby != from.y
572
+ x -= dbx
573
+ y -= dby
574
+ end
575
+ end
576
+
577
+ room = create_room(to, x, y, dx, dy)
578
+ return [room, Connection::FREE]
579
+ elsif elem.kind_of?(MapDoor)
580
+ if elem.connector
581
+ type = Connection::FREE
582
+ elsif elem.locked
583
+ type = Connection::LOCKED_DOOR
584
+ else
585
+ type = Connection::CLOSED_DOOR
586
+ end
587
+
588
+ # Okay, connecting room is missing. Check door's locations property
589
+ elem.location.each { |tag|
590
+ next if @tags[tag] == from
591
+ @rooms.each { |o|
592
+ next if o.tag != tag
593
+ res = create_room( o, x, y, dx, dy )
594
+ return [ res, type ]
595
+ }
596
+ }
597
+
598
+ # @rooms.each { |o|
599
+ # next if @tags[o.tag] == from
600
+ # o.exits.each { |e|
601
+ # next unless e
602
+ # if @tags[e] == elem
603
+ # res = create_room( o, x, y, dx, dy )
604
+ # return [ res, type ]
605
+ # end
606
+ # }
607
+ # }
608
+
609
+ $stderr.puts "No room for door #{to.name}: #{to}"
610
+ return [nil, nil]
611
+ else
612
+ return [elem, Connection::FREE]
613
+ end
614
+ end
615
+
616
+ def create_room(r, x, y, dx = 2, dy = 0)
617
+ return @tags[r.tag] if @tags[r.tag].kind_of?(Room)
618
+
619
+ debug "+++ CREATE ROOM #{r.name} (#{x},#{y}) TAG:#{r.tag}"
620
+ from, = make_room(r, x, y, dx, dy)
621
+
622
+ r.exits[0,8].each_with_index { |to, exit|
623
+ next unless to
624
+
625
+ debug "#{r.name} EXIT:#{exit} points to #{to}"
626
+
627
+ go = c = nil
628
+
629
+ if to.kind_of?(MapOneWay)
630
+ dx, dy = Room::DIR_TO_VECTOR[exit]
631
+ x = from.x + dx
632
+ y = from.y + dy
633
+ @tabs += 1
634
+ create_room(to.room, x, y, dx, dy)
635
+ @tabs -= 1
636
+ next
637
+ elsif to.kind_of?(MapSpecialExit)
638
+ go = to.go
639
+ to = to.to
640
+ end
641
+
642
+ b = to
643
+
644
+
645
+ dir = exit
646
+ type = 0
647
+
648
+ # If exit leads to an enterable object, find out where does that
649
+ # enterable object lead to.
650
+ if to.kind_of?(MapObject)
651
+ rooms = to.enterable
652
+ rooms.each { |room|
653
+ next if room == r
654
+ to = b = @tags[room]
655
+ break
656
+ }
657
+ # Skip it if we are still an object. This means we are just
658
+ # a container, like the phone booth in the Fate game demo.
659
+ next if to.kind_of?(MapObject)
660
+ end
661
+
662
+ odir = nil
663
+ if b.kind_of?(MapRoom)
664
+ odir = b.exits.rindex(r.tag)
665
+ if not odir
666
+ b.exits[0,8].each_with_index { |e, idx|
667
+ next if not e
668
+ if (e.kind_of?(MapOneWay) and e.room == r) or
669
+ (e.kind_of?(MapSpecialExit) and e.to == r)
670
+ odir = idx
671
+ break
672
+ end
673
+ }
674
+ end
675
+ elsif b.kind_of?(MapDoor)
676
+ t = b.location.find { |t| t != r.tag }
677
+ other = @rooms.find { |x| x.tag == t }
678
+ # other = @tags[t]
679
+ if other
680
+ other.exits.each_with_index { |d, idx|
681
+ next if not d
682
+ if d == b or d == r.tag or
683
+ (d.kind_of?(MapOneWay) and d.room == r) or
684
+ (d.kind_of?(MapSpecialExit) and (d.to == b or d == r.tag))
685
+ odir = idx
686
+ break
687
+ end
688
+ }
689
+ end
690
+ end
691
+ odir = (dir + 4) % 8 if not odir
692
+
693
+ if to.kind_of?(MapRoom) or to.kind_of?(MapDoor)
694
+ dx, dy = Room::DIR_TO_VECTOR[dir]
695
+ x = from.x + dx
696
+ y = from.y + dy
697
+ @tabs += 1
698
+ to, type = get_exit(r, from, to, x, y, dx, dy)
699
+ @tabs -= 1
700
+ next if not to
701
+ end
702
+
703
+
704
+ # b = @rooms.find { |r2| r2.tag == tag }
705
+ # odir = nil
706
+ # odir = b.exits.rindex(r.tag) if b
707
+ # odir = (dir + 4) % 8 if not odir
708
+
709
+ if from[dir]
710
+ c = from[dir]
711
+ if to.exits.rindex(c) and c.roomB == from
712
+ debug "LINK TRAVELLED BOTH"
713
+ c.dir = Connection::BOTH
714
+ c.exitBtext = go if go
715
+ next
716
+ else
717
+ debug "#{exit} FROM #{from}->#{to} BLOCKED DIR: #{dir}"
718
+ shift_link(from, dir)
719
+ end
720
+ end
721
+
722
+ # Check we don't have a connection already
723
+ if to[odir]
724
+ c = to[odir]
725
+
726
+ # We need to change odir to something else
727
+ rgo = 0
728
+ if go
729
+ rgo = go % 2 == 0? go - 1 : go + 1
730
+ end
731
+
732
+ # First, check if we have a dangling one-way link going to->from
733
+ # If we do, we use it.
734
+ c = oneway_link?(from, to)
735
+ if not c
736
+ odir = choose_dir(to, from, rgo, dir)
737
+ else
738
+ debug "FOUND LINK #{c} -- filling it."
739
+ idx = from.exits.index(c)
740
+ from[idx] = nil
741
+ from[dir] = c
742
+ c.dir = Connection::BOTH
743
+ c.exitBtext = go if go
744
+ end
745
+ else
746
+ debug "to[odir] empty."
747
+ # First, check if we have a dangling one-way link going to->from
748
+ # If we do, we use it.
749
+ c = oneway_link?(to, from)
750
+ if c
751
+ debug "FOUND LINK #{c} -- filling it."
752
+ idx = from.exits.index(c)
753
+ from[idx] = nil
754
+ from[dir] = c
755
+ c.dir = Connection::BOTH
756
+ c.exitBtext = go if go
757
+ end
758
+ end
759
+
760
+ if not c
761
+ debug "NEW LINK #{from} #{dir} to #{to} #{odir}"
762
+ begin
763
+ c = @map.new_connection(from, dir, to, odir)
764
+ c.exitAtext = go if go
765
+ c.dir = Connection::AtoB
766
+ c.type = type
767
+ rescue Section::ConnectionError
768
+ end
769
+ end
770
+ }
771
+
772
+ return from
773
+ end
774
+
775
+ #
776
+ # Try to move rooms around to make map smaller.
777
+ #
778
+ def compact
779
+ # First, verify all one-exit rooms lie next to its opposite room.
780
+ sect = @map.sections[@map.section]
781
+ rooms = sect.rooms.find_all { |r| r.num_exits == 1 }
782
+
783
+ rooms.each { |a|
784
+ c = a.exits.find { |e| e != nil }
785
+ b = c.opposite(a)
786
+
787
+ dx = (a.x - b.x).abs
788
+ dy = (a.y - b.y).abs
789
+ next if dx <= 1 and dy <= 1
790
+
791
+ x, y = [b.x, b.y]
792
+ dirB = b.exits.index(c)
793
+
794
+ dx, dy = Room::DIR_TO_VECTOR[dirB]
795
+ x += dx
796
+ y += dy
797
+
798
+ dirA = a.exits.index(c)
799
+ dx, dy = Room::DIR_TO_VECTOR[dirA]
800
+ # Check if exits are complementary. If not, we need to move room
801
+ # again.
802
+ if x + dx != b.x or y + dy != b.y
803
+ x -= dx
804
+ y -= dy
805
+ end
806
+ next if x == a.x and y == a.y
807
+
808
+ # Place room here. Check if filled.
809
+ if sect.free?(x, y)
810
+ a.x, a.y = x, y
811
+ else
812
+ if dx != 0
813
+ dy = 0
814
+ end
815
+ sect.shift(x, y, -dx, -dy)
816
+ a.x, a.y = x, y
817
+ end
818
+ }
819
+ end
820
+
821
+ #
822
+ # Create all the stuff we found
823
+ #
824
+ def create
825
+ @rooms = @rooms.sort_by { |r| r.num_exits }
826
+ @rooms.reverse!
827
+
828
+ @rooms.each { |r|
829
+ @tabs = 0
830
+ min, max = @map.sections[@map.section].min_max_rooms
831
+ create_room(r, max[0] + 2, 0)
832
+ }
833
+ @rooms = []
834
+
835
+ @objects.delete_if { |obj| obj.scenery }
836
+
837
+ # Add objects to rooms
838
+ @objects.each { |obj|
839
+ obj.location.each { |loc|
840
+ r = @tags[loc]
841
+ while r and not r.kind_of?(Room)
842
+ r = @tags[ r.location[0] ]
843
+ end
844
+ next unless r
845
+ r.objects << obj.name + "\n"
846
+ }
847
+ }
848
+ end
849
+
850
+ def create_map(file)
851
+ read_file(file)
852
+
853
+ puts "Rooms: #{@rooms.size}"
854
+ puts "Doors: #{@doors.size}"
855
+ puts "Objects: #{@objects.size}"
856
+
857
+ begin
858
+ resolve_exits
859
+ create
860
+ compact
861
+ rescue => e
862
+ puts e
863
+ puts e.backtrace
864
+ raise
865
+ end
866
+ end
867
+
868
+ def initialize(file, map = Map.new('Read Map'))
869
+ @map = map
870
+
871
+ @doors = []
872
+ @functions = []
873
+ @tags = {}
874
+ @rooms = []
875
+ @objects = []
876
+
877
+ @include_dirs = [File.dirname(file)]
878
+ set_include_dirs
879
+
880
+ if @map.kind_of?(FXMap)
881
+ return unless properties
882
+ end
883
+
884
+ create_map(file)
885
+
886
+ debug "Done creating #{file}"
887
+
888
+ @tags = nil # save some memory by clearing the tag list
889
+ @objects = nil
890
+ @functions = nil
891
+ @doors = nil
892
+ @rooms = nil
893
+
894
+ if @map.kind_of?(FXMap)
895
+ @map.filename = file.sub(/\.t$/i, '.map')
896
+ @map.options['Location Description'] = true
897
+ @map.window.show
898
+ end
899
+ end
900
+ end