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.
@@ -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
- v = [ (x2-x1)*0.25, (y2-y1)*0.25 ]
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