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