ifmapper 1.0.0 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +648 -627
- data/IFMapper.gemspec +29 -28
- data/IFMapper.rbw +31 -31
- data/TODO.txt +8 -7
- data/bin/IFMapper +31 -31
- data/docs/en/index.html +0 -0
- data/docs/en/start.html +3 -2
- data/docs/en/start.html~ +516 -0
- data/docs/es/index.html +0 -0
- data/docs/es/start.html +13 -14
- data/docs/es/start.html~ +1280 -0
- data/docs/images/IFMapper_main.gif +0 -0
- data/docs/images/automap.gif +0 -0
- data/docs/images/complex_connection.gif +0 -0
- data/docs/images/connection.gif +0 -0
- data/docs/images/connection_menu.gif +0 -0
- data/docs/images/room_description.gif +0 -0
- data/docs/images/room_small.gif +0 -0
- data/icons/copy.png +0 -0
- data/icons/cut.png +0 -0
- data/icons/filenew.png +0 -0
- data/icons/fileopen.png +0 -0
- data/icons/filesave.png +0 -0
- data/icons/filesaveas.png +0 -0
- data/icons/help.png +0 -0
- data/icons/kill.png +0 -0
- data/icons/nextpage.png +0 -0
- data/icons/paste.png +0 -0
- data/icons/prevpage.png +0 -0
- data/icons/printicon.png +0 -0
- data/icons/redo.png +0 -0
- data/icons/room_e.gif +0 -0
- data/icons/room_e.xpm +0 -0
- data/icons/room_n.gif +0 -0
- data/icons/room_n.xpm +0 -0
- data/icons/room_ne.gif +0 -0
- data/icons/room_ne.xpm +0 -0
- data/icons/room_nw.gif +0 -0
- data/icons/room_nw.xpm +0 -0
- data/icons/room_s.gif +0 -0
- data/icons/room_s.xpm +0 -0
- data/icons/room_se.gif +0 -0
- data/icons/room_se.xpm +0 -0
- data/icons/room_sw.gif +0 -0
- data/icons/room_sw.xpm +0 -0
- data/icons/room_w.gif +0 -0
- data/icons/room_w.xpm +0 -0
- data/icons/saveas.png +0 -0
- data/icons/undo.png +0 -0
- data/icons/winapp.png +0 -0
- data/icons/zoom.png +0 -0
- data/lib/IFMapper/AStar.rb +250 -250
- data/lib/IFMapper/Connection.rb +202 -202
- data/lib/IFMapper/FXAboutDialogBox.rb +32 -32
- data/lib/IFMapper/FXConnection.rb +364 -364
- data/lib/IFMapper/FXConnectionDialogBox.rb +124 -124
- data/lib/IFMapper/FXDCPostscript.rb +404 -404
- data/lib/IFMapper/FXDCPrint.rb +15 -15
- data/lib/IFMapper/FXItemList.rb +108 -0
- data/lib/IFMapper/FXMap.rb +2147 -2116
- data/lib/IFMapper/FXMapColorBox.rb +88 -88
- data/lib/IFMapper/FXMapDialogBox.rb +127 -127
- data/lib/IFMapper/FXMapFileDialog.rb +34 -34
- data/lib/IFMapper/FXMapperSettings.rb +206 -205
- data/lib/IFMapper/FXMapperWindow.rb +1592 -1571
- data/lib/IFMapper/FXPDFMapExporterOptionsDialogBox.rb +46 -0
- data/lib/IFMapper/FXRoom.rb +263 -263
- data/lib/IFMapper/FXRoomDialogBox.rb +159 -159
- data/lib/IFMapper/FXRoomList.rb +95 -95
- data/lib/IFMapper/FXSearchDialogBox.rb +51 -51
- data/lib/IFMapper/FXSection.rb +33 -33
- data/lib/IFMapper/FXSectionDialogBox.rb +38 -38
- data/lib/IFMapper/FXSpline.rb +52 -52
- data/lib/IFMapper/FXWarningBox.rb +51 -50
- data/lib/IFMapper/GUEReader.rb +445 -445
- data/lib/IFMapper/IFMReader.rb +584 -584
- data/lib/IFMapper/IFMWriter.rb +245 -227
- data/lib/IFMapper/Inform7Writer.rb +579 -573
- data/lib/IFMapper/InformReader.rb +478 -478
- data/lib/IFMapper/InformWriter.rb +364 -359
- data/lib/IFMapper/Map.rb +202 -200
- data/lib/IFMapper/MapPrinting.rb +162 -162
- data/lib/IFMapper/MapReader.rb +900 -900
- data/lib/IFMapper/PDFMapExporter.rb +526 -483
- data/lib/IFMapper/Room.rb +153 -151
- data/lib/IFMapper/Section.rb +234 -234
- data/lib/IFMapper/TADSReader.rb +474 -471
- data/lib/IFMapper/TADSWriter.rb +375 -370
- data/lib/IFMapper/TranscriptDialogBox.rb +0 -0
- data/lib/IFMapper/TranscriptReader.rb +1361 -1359
- data/lib/IFMapper/locales/en/Messages.rb +446 -435
- data/lib/IFMapper/locales/es/Messages.rb +451 -440
- data/lib/IFMapper/locales/es/Messages_iso-8859-1.rb +455 -440
- data/lib/IFMapper/locales/es/runme.sh +3 -3
- data/maps/A New Life.map b/data/maps/A New → Life.map +0 -0
- data/maps/AMFV.map +0 -0
- data/maps/AllRoads.map +0 -0
- data/maps/Aotearoa.map +0 -0
- data/maps/Bronze.map +0 -0
- data/maps/Bureaucracy.ifm +0 -0
- data/maps/Bureaucracy.map +0 -0
- data/maps/CityOfSecrets.map +0 -0
- data/maps/DDIV.map +0 -0
- data/maps/Following_A_Star.map +0 -0
- data/maps/Heated.map +0 -0
- data/maps/Heroine.map +0 -0
- data/maps/History Repeating.map b/data/maps/History → Repeating.map +0 -0
- data/maps/Hollywood_Hijinx.ifm +0 -0
- data/maps/Janitor.map +0 -0
- data/maps/Jigsaw.ifm +0 -0
- data/maps/Jigsaw.map +0 -0
- data/maps/LGOP.ifm +0 -0
- data/maps/Mercy.ifm +0 -0
- data/maps/Ninjas_Fate.map +0 -0
- data/maps/Pen_and_Paint.map +0 -0
- data/maps/Planetfall.ifm +0 -0
- data/maps/Planetfall.map +0 -0
- data/maps/Plundered_Hearts.ifm +0 -0
- data/maps/QuietEvening.map +0 -0
- data/maps/Ralph.ifm +0 -0
- data/maps/Reliques_of_Tolti_Alph.map +0 -0
- data/maps/Revolution.map +0 -0
- data/maps/Robots_of_Dawn.ifm +0 -0
- data/maps/SavoirFare.map +0 -0
- data/maps/Seastalker.ifm +0 -0
- data/maps/Seastalker.map +0 -0
- data/maps/Sherlock.ifm +0 -0
- data/maps/SoFar.ifm +0 -0
- data/maps/Starcross.ifm +0 -0
- data/maps/Suspended.ifm +0 -0
- data/maps/Tangle.map +0 -0
- data/maps/The_Lost_Sheep.map +0 -0
- data/maps/Unforgotten.map +0 -0
- data/maps/Warbler's Nest.map +0 -0
- data/maps/Warbler's_Nest.map +0 -0
- data/maps/Westminster_Abbey.map +0 -0
- data/maps/WinterWonderland.map +0 -0
- data/maps/Wishbringer.ifm +0 -0
- data/maps/Wishbringer2.ifm +0 -0
- data/maps/Zork1.ifm +0 -0
- data/maps/Zork2.ifm +0 -0
- data/maps/Zork3.ifm +0 -0
- data/maps/Zork_Zero.ifm +0 -0
- data/maps/anchor.ifm +0 -0
- data/maps/anchor.map +0 -0
- data/maps/atrox.ifm +0 -0
- data/maps/awaken.ifm +0 -0
- data/maps/babel.ifm +0 -0
- data/maps/balances.map +0 -0
- data/maps/ballerina.map +0 -0
- data/maps/bear.map +0 -0
- data/maps/bluechairs.map +0 -0
- data/maps/break_in.map +0 -0
- data/maps/bse.ifm +0 -0
- data/maps/building.map +0 -0
- data/maps/change.ifm +0 -0
- data/maps/christminster.map +0 -0
- data/maps/curses.ifm +0 -0
- data/maps/curves.ifm +0 -0
- data/maps/deadline.map +0 -0
- data/maps/delusions.map +0 -0
- data/maps/devours.map +0 -0
- data/maps/distress.map +0 -0
- data/maps/djinni.map +0 -0
- data/maps/dreamhold.map +0 -0
- data/maps/drift3.map +0 -0
- data/maps/eas.map +0 -0
- data/maps/eas2.map +0 -0
- data/maps/eas3.map +0 -0
- data/maps/edifice.ifm +0 -0
- data/maps/fallacy.map +0 -0
- data/maps/frozen.ifm +0 -0
- data/maps/gamlet.map +0 -0
- data/maps/glow.ifm +0 -0
- data/maps/guilty_bastards.map +0 -0
- data/maps/heist.map +0 -0
- data/maps/heroes.map +0 -0
- data/maps/inhumane.map +0 -0
- data/maps/kaged.map +0 -0
- data/maps/library.ifm +0 -0
- data/maps/lurkinghorror.map +0 -0
- data/maps/metamorphoses.map +0 -0
- data/maps/mindelec.ifm +0 -0
- data/maps/minster.ifm +0 -0
- data/maps/mite.map +0 -0
- data/maps/moonmist.map +0 -0
- data/maps/muldoon_legacy.map +0 -0
- data/maps/muse.ifm +0 -0
- data/maps/paperchase.ifm +0 -0
- data/maps/party.map +0 -0
- data/maps/pawn.map +0 -0
- data/maps/photograph.map +0 -0
- data/maps/pkgirl.map +0 -0
- data/maps/pytho.map +0 -0
- data/maps/risorgimento.map +0 -0
- data/maps/sherbet.map +0 -0
- data/maps/simple.map +0 -0
- data/maps/slouch.map +0 -0
- data/maps/space_st.ifm +0 -0
- data/maps/splashdown.map +0 -0
- data/maps/spring.map +0 -0
- data/maps/squarecircle.map +0 -0
- data/maps/stationfall.ifm +0 -0
- data/maps/theatre.ifm +0 -0
- data/maps/toonesia.ifm +0 -0
- data/maps/tortoise.ifm +0 -0
- data/maps/trinity.map +0 -0
- data/maps/vespers.map +0 -0
- data/maps/vgame.ifm +0 -0
- data/maps/wasp.map +0 -0
- data/maps/weather.ifm +0 -0
- data/maps/windhall.ifm +0 -0
- data/maps/worlds.map +0 -0
- data/maps/xtcontest.map +0 -0
- data/maps/zdungeon.map +0 -0
- data/maps/zebulon.ifm +0 -0
- data/maps/zerosum.map +0 -0
- metadata +226 -183
data/lib/IFMapper/MapReader.rb
CHANGED
@@ -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
|