ruby-doom 0.9.0

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,4 @@
1
+ === 0.9 / 2009-09-18
2
+
3
+ * Switching to hoe
4
+
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/introspector
6
+ bin/bsp
7
+ lib/ruby-doom.rb
@@ -0,0 +1,45 @@
1
+ = ruby_doom
2
+
3
+ * http://ruby-doom.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ Generates DOOM maps.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+
12
+ == SYNOPSIS:
13
+
14
+
15
+ == REQUIREMENTS:
16
+
17
+
18
+ == INSTALL:
19
+
20
+ sudo gem install ruby-doom
21
+
22
+ == LICENSE:
23
+
24
+ (The MIT License)
25
+
26
+ Copyright (c) 2009 Tom Copeland
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining
29
+ a copy of this software and associated documentation files (the
30
+ 'Software'), to deal in the Software without restriction, including
31
+ without limitation the rights to use, copy, modify, merge, publish,
32
+ distribute, sublicense, and/or sell copies of the Software, and to
33
+ permit persons to whom the Software is furnished to do so, subject to
34
+ the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be
37
+ included in all copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
40
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
42
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
43
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
44
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
45
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec "ruby-doom" do
7
+ developer 'Tom Copeland', 'tom@infoether.com'
8
+ self.rubyforge_name = "ruby-doom"
9
+ end
10
+
11
+ # vim:syntax=ruby
data/bin/bsp ADDED
Binary file
Binary file
@@ -0,0 +1,920 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ class RubyDoom
4
+ VERSION = '0.9.0'
5
+ end
6
+
7
+ class RGBQuad
8
+ def initialize(bytes)
9
+ @r,@g,@b,@res = *bytes
10
+ end
11
+ def to_s
12
+ return @r.to_s + "," + @g.to_s + "," + @b.to_s
13
+ end
14
+ end
15
+
16
+ class Finder
17
+ def initialize(points, max_radius=5, debug=false)
18
+ @debug=debug
19
+ @max_radius = max_radius
20
+ @points = points
21
+ end
22
+ def next(current, sofar)
23
+ 1.upto(@max_radius) do |x|
24
+ points_at_radius(current, x).each {|p| return p if good(p, sofar) }
25
+ end
26
+ raise "Couldn't find next point!"
27
+ end
28
+ def points_at_radius(p, r)
29
+ res = []
30
+ puts "checking for points around " + p.to_s + " at radius " + r.to_s unless !@debug
31
+ # move up and then west to the upper left hand corner of the search box
32
+ p = p.translate(-r, r)
33
+ res << p
34
+ # move east
35
+ 1.upto(r*2) {
36
+ p = p.translate(1,0)
37
+ res << p
38
+ }
39
+ # move south
40
+ 1.upto(r*2) {
41
+ p = p.translate(0,-1)
42
+ res << p
43
+ }
44
+ # move west
45
+ 1.upto(r*2) {
46
+ p = p.translate(-1,0)
47
+ res << p
48
+ }
49
+ # move north
50
+ 1.upto((r*2)-1) {
51
+ p = p.translate(0,1)
52
+ res << p
53
+ }
54
+ puts "points array = " + res.to_s unless !@debug
55
+ res
56
+ end
57
+ def good(candidate, sofar)
58
+ puts "Testing " + candidate.to_s unless !@debug
59
+ @points.include?(candidate) && !sofar.include?(candidate)
60
+ end
61
+ end
62
+
63
+ class PointThinner
64
+ def initialize(p, factor)
65
+ @points = p
66
+ @factor = factor
67
+ end
68
+ def thin
69
+ newline = [@points[0]]
70
+ 1.upto(@points.size-1) {|x| newline << @points[x] if x % @factor == 0 }
71
+ newline
72
+ end
73
+ end
74
+
75
+ class PointsToLine
76
+ def initialize(points, debug=false)
77
+ @points = points
78
+ @debug = debug
79
+ end
80
+ def lower_left
81
+ @points.min {|a,b| a.distance_to(Point.new(0,0)) <=> b.distance_to(Point.new(0,0)) }
82
+ end
83
+ def line
84
+ @f = Finder.new(@points, 5, @debug)
85
+ found_so_far = [lower_left]
86
+ current = @f.next(found_so_far[0], found_so_far)
87
+ while found_so_far.size != @points.size - 1
88
+ found_so_far << current
89
+ puts "Current = " + current.to_s + "; points so far: " + found_so_far.size.to_s unless !@debug
90
+ begin
91
+ current = @f.next(current, found_so_far)
92
+ rescue
93
+ puts "Couldn't find next point, so skipping back to the origin" unless !@debug
94
+ break
95
+ end
96
+ end
97
+ found_so_far << current
98
+ end
99
+ end
100
+
101
+ class ArrayToPoints
102
+ def initialize(width, height, raw_data)
103
+ @width = width
104
+ @height = height
105
+ @raw_data = raw_data
106
+ end
107
+ def points
108
+ pts = []
109
+ # for each byte in the image
110
+ idx = 0
111
+ @raw_data.each do |byte|
112
+ # for each bit in the image
113
+ 0.upto(7) do |bit|
114
+ if (byte & 128 >> bit) == 0
115
+ tmp_pt = convert(idx, @width)
116
+ pts << Point.new(tmp_pt.x, @height-1-tmp_pt.y)
117
+ end
118
+ idx += 1
119
+ end
120
+ end
121
+ return pts
122
+ end
123
+ # converts an index into an array of width blah to a point on an x/y coordinate plane
124
+ # with 0,0 in the upper left hand corner
125
+ # so in an array that's 8 units wide, unit 12 converts to a point 4 across, 1 down
126
+ def convert(idx, width)
127
+ Point.new(idx % width, idx/width)
128
+ end
129
+ end
130
+
131
+ class BMPDecoder
132
+ attr_reader :type, :size, :offset_to_image_data, :info_header_size, :width, :height, :bit_planes, :bits_per_pixel, :compression, :size_of_image, :xpixels_per_meter, :ypixels_per_meter, :colors_used, :colors_important
133
+ attr_accessor :scale_factor, :thinning_factor
134
+ def initialize(filename, debug=false)
135
+ @debug = debug
136
+ @filename = filename
137
+ @scale_factor = 1
138
+ @thinning_factor = 5
139
+ end
140
+ def decode
141
+ # hm, wouldn't File.read(filename, "rb") do this?
142
+ bytes = []
143
+ File.open(@filename, "r").each_byte {|x| bytes << x }
144
+
145
+ # decode BITMAPFILEHEADER
146
+ @type = decode_word(bytes.slice(0,2))
147
+ raise "Can only decode bitmaps" unless @type == 19778
148
+
149
+ @size = decode_dword(bytes.slice(2,4))
150
+ res1 = decode_word(bytes.slice(6,2))
151
+ res1 = decode_word(bytes.slice(8,2))
152
+ @offset_to_image_data = decode_dword(bytes.slice(10,4))
153
+
154
+ # decode BITMAPINFOHEADER
155
+ @info_header_size = decode_dword(bytes.slice(14,4))
156
+ @width = decode_dword(bytes.slice(18,4)) # specified as a LONG... but DWORD works... (?)
157
+ @height = decode_dword(bytes.slice(22,4)) # specified as a LONG... but DWORD works... (?)
158
+ @bit_planes = decode_word(bytes.slice(26,2))
159
+ @bits_per_pixel = decode_word(bytes.slice(28,2))
160
+ raise "Can't process bitmaps with more than one bit per pixel. Save it as a monochrome bitmap to fix this." unless @bits_per_pixel == 1
161
+
162
+ @compression = decode_dword(bytes.slice(30,4))
163
+ raise "Can't process compressed bitmaps" unless @compression == 0
164
+
165
+ @size_of_image = decode_dword(bytes.slice(34,4))
166
+ @xpixels_per_meter = decode_dword(bytes.slice(38,4)) # specified as a LONG... but DWORD works... (?)
167
+ @ypixels_per_meter = decode_dword(bytes.slice(42,4)) # specified as a LONG... but DWORD works... (?)
168
+ @colors_used = decode_dword(bytes.slice(46,4))
169
+ @colors_important = decode_dword(bytes.slice(50,4))
170
+
171
+ # color table - assume monochrome bitmap for now
172
+ rgb1 = RGBQuad.new(bytes.slice(54,4))
173
+ rgb2 = RGBQuad.new(bytes.slice(58,4))
174
+
175
+ @raw_image = bytes.slice(62, bytes.size-62)
176
+ self
177
+ end
178
+ def raw_points
179
+ decode if @raw_image.nil?
180
+ ArrayToPoints.new(@width, @height, @raw_image).points
181
+ end
182
+ def line
183
+ PointsToLine.new(raw_points, @debug).line
184
+ end
185
+ def thin
186
+ pts = PointThinner.new(line, @thinning_factor).thin
187
+ pts.collect {|p| p.scale(@scale_factor) }
188
+ end
189
+ def decode_word(bytes)
190
+ bytes.pack("C2").unpack("S")[0]
191
+ end
192
+ def decode_dword(bytes)
193
+ bytes.pack("C4").unpack("L")[0]
194
+ end
195
+ end
196
+
197
+ class Codec
198
+ # Accepts a format string like "sl48" and a byte array
199
+ # s - short
200
+ # l - long
201
+ # 4 - 4 byte string
202
+ # 8 - 8 bytes string
203
+ def Codec.decode(format, bytes)
204
+ res = []
205
+ ptr = 0
206
+ format.split(//).each {|x|
207
+ if x == "s"
208
+ res << bytes.slice(ptr,2).pack("c2").unpack("s")[0]
209
+ ptr += 2
210
+ elsif x == "l"
211
+ res << bytes.slice(ptr,4).pack("C4").unpack("V")[0]
212
+ ptr += 4
213
+ elsif x == "4"
214
+ res << Codec.unmarshal_string(bytes.slice(ptr,4))
215
+ ptr += 4
216
+ elsif x == "8"
217
+ res << Codec.unmarshal_string(bytes.slice(ptr,8))
218
+ ptr += 8
219
+ else
220
+ raise "Unknown character in decode format string " + format
221
+ end
222
+ }
223
+ return res
224
+ end
225
+ # Accepts a format string like "sl48" and an array of values
226
+ def Codec.encode(format, values)
227
+ bytes = []
228
+ ptr = 0
229
+ format.split(//).each {|x|
230
+ if x == "s"
231
+ bytes += [values[ptr]].pack("S").unpack("C2")
232
+ elsif x == "l"
233
+ bytes += [values[ptr]].pack("N").unpack("C4").reverse
234
+ elsif x == "4"
235
+ bytes += Codec.marshal_string(values[ptr],4)
236
+ elsif x == "8"
237
+ bytes += Codec.marshal_string(values[ptr],8)
238
+ else
239
+ raise "Unknown character in decode format string " + format
240
+ end
241
+ ptr += 1
242
+ }
243
+ return bytes
244
+ end
245
+ def Codec.unmarshal_string(a)
246
+ a.pack("C*").strip
247
+ end
248
+ def Codec.marshal_string(n,len)
249
+ arr = n.unpack("C#{len}").compact
250
+ if arr.size < len
251
+ arr += Array.new(len-arr.size, 0)
252
+ end
253
+ arr
254
+ end
255
+ end
256
+
257
+ class ThingInfo
258
+ attr_reader :id, :name, :symbol
259
+ def initialize(id,name,symbol)
260
+ @id, @name, @symbol = id,name,symbol
261
+ end
262
+ end
263
+
264
+ class Dictionary
265
+ def Dictionary.get
266
+ if @self.nil?
267
+ @self = Dictionary.new
268
+ end
269
+ return @self
270
+ end
271
+ def initialize
272
+ @things = []
273
+ @things << ThingInfo.new(1, "Player 1", "@")
274
+ @things << ThingInfo.new(9, "Sergeant", "s")
275
+ @things << ThingInfo.new(65, "Commando", "c")
276
+ @things << ThingInfo.new(2001, "Shotgun", "g")
277
+ @things << ThingInfo.new(3001, "Imp", "i")
278
+ @things << ThingInfo.new(2035, "Barrel", "b")
279
+ end
280
+ def thing_for_type_id(id)
281
+ t = @things.find {|x| x.id == id}
282
+ return ThingInfo.new(-999, "Unknown thing", "?") if t.nil?
283
+ t
284
+ end
285
+ def thing_for_name(name)
286
+ @things.find {|x| x.name == name}
287
+ end
288
+ def direction_for_angle(angle)
289
+ case angle
290
+ when (0..45 or 316..360) then return "east"
291
+ when 46..135 then return "north"
292
+ when 136..225 then return "west"
293
+ when 225..315 then return "south"
294
+ end
295
+ raise "Angle must be between 0 and 360"
296
+ end
297
+ end
298
+
299
+ class Point
300
+ attr_accessor :x, :y
301
+ def initialize(x,y)
302
+ @x=x
303
+ @y=y
304
+ end
305
+ def scale(factor)
306
+ Point.new(x * factor, y * factor)
307
+ end
308
+ def ==(other)
309
+ return other.x == @x && other.y == @y
310
+ end
311
+ def slope_to(p1)
312
+ if (p1.x - @x) == 0
313
+ return nil
314
+ end
315
+ (p1.y - @y) / (p1.x - @x)
316
+ end
317
+ def distance_to(p1)
318
+ Math.sqrt(((p1.x - @x) ** 2) + ((p1.y - @y) ** 2))
319
+ end
320
+ def lineto(p1)
321
+ res = []
322
+ current_x = @x
323
+ current_y = @y
324
+ slope = slope_to(p1)
325
+ res << Point.new(current_x, current_y)
326
+ distance_to(p1).to_i.times {|s|
327
+ if slope == 0
328
+ current_x += (p1.x < @x ? -1 : 1)
329
+ res << Point.new(current_x, current_y)
330
+ next
331
+ end
332
+
333
+ if slope == nil
334
+ current_y += (p1.y < @y ? -1 : 1)
335
+ res << Point.new(current_x, current_y)
336
+ next
337
+ end
338
+
339
+ current_x += slope
340
+ cur += (1-slope)
341
+ res << Point.new(current_x, current_y)
342
+ }
343
+ res << p1
344
+ return res
345
+ end
346
+ def translate(x,y)
347
+ Point.new(@x + x, @y + y)
348
+ end
349
+ def to_s
350
+ "(" + @x.to_s + "," + @y.to_s + ")"
351
+ end
352
+ end
353
+
354
+ class Lump
355
+ attr_reader :name
356
+ def initialize(name)
357
+ @name = name
358
+ end
359
+ end
360
+
361
+ class DecodedLump < Lump
362
+ attr_reader :items
363
+ def initialize(name)
364
+ super(name)
365
+ @items = []
366
+ @index = 0
367
+ end
368
+ def add(i)
369
+ i.id = @index
370
+ @index += 1
371
+ @items << i
372
+ return i
373
+ end
374
+ def write
375
+ out = []
376
+ @items.each {|i| out += i.write }
377
+ out
378
+ end
379
+ end
380
+
381
+ class UndecodedLump < Lump
382
+ def initialize(name)
383
+ super(name)
384
+ @bytes = []
385
+ end
386
+ def read(bytes)
387
+ @bytes = bytes
388
+ end
389
+ def write
390
+ @bytes
391
+ end
392
+ def size
393
+ @bytes.size
394
+ end
395
+ def items
396
+ []
397
+ end
398
+ end
399
+
400
+ class Sectors < DecodedLump
401
+ BYTES_EACH=26
402
+ NAME="SECTORS"
403
+ def initialize
404
+ super(NAME)
405
+ end
406
+ def read(bytes)
407
+ (bytes.size / BYTES_EACH).times {|index|
408
+ s = Sector.new
409
+ s.read(bytes.slice(index*BYTES_EACH, BYTES_EACH))
410
+ @items << s
411
+ }
412
+ end
413
+ def size
414
+ @items.size * BYTES_EACH
415
+ end
416
+ end
417
+
418
+ class Sector
419
+ FORMAT="ss88sss"
420
+ attr_accessor :floor_height, :ceiling_height, :floor_texture, :ceiling_texture, :light_level, :special, :tag, :id
421
+ def initialize
422
+ @floor_height, @ceiling_height, @floor_texture, @ceiling_texture, @light_level, @special, @tag = 0, 128, "FLAT14", "FLAT14", 160, 0, 0
423
+ end
424
+ def read(bytes)
425
+ @floor_height, @ceiling_height, @floor_texture, @ceiling_texture, @light_level, @special, @tag = Codec.decode(FORMAT, bytes)
426
+ end
427
+ def write
428
+ Codec.encode(FORMAT, [@floor_height, @ceiling_height, @floor_texture, @ceiling_texture, @light_level, @special, @tag])
429
+ end
430
+ def to_s
431
+ " Sector floor/ceiling heights " + @floor_height.to_s + "/" + @ceiling_height.to_s + "; floor/ceiling textures " + @floor_texture.to_s + "/" + @ceiling_texture.to_s + "; light = " + @light_level.to_s + "; special = " + @special.to_s + "; tag = " + @tag.to_s
432
+ end
433
+ end
434
+
435
+ class Vertexes < DecodedLump
436
+ BYTES_EACH=4
437
+ NAME="VERTEXES"
438
+ def initialize
439
+ super(NAME)
440
+ end
441
+ def read(bytes)
442
+ (bytes.size / BYTES_EACH).times {|index|
443
+ v = Vertex.new
444
+ v.read(bytes.slice(index*BYTES_EACH, BYTES_EACH))
445
+ @items << v
446
+ }
447
+ end
448
+ def size
449
+ @items.size * BYTES_EACH
450
+ end
451
+ end
452
+
453
+ class Vertex
454
+ FORMAT="ss"
455
+ attr_reader :location
456
+ attr_accessor :id
457
+ def initialize(location=nil)
458
+ @location = location
459
+ end
460
+ def read(bytes)
461
+ @location = Point.new(*Codec.decode(FORMAT, bytes))
462
+ end
463
+ def write
464
+ Codec.encode(FORMAT, [@location.x, @location.y])
465
+ end
466
+ def to_s
467
+ " Vertex at " + @location.to_s
468
+ end
469
+ end
470
+
471
+ class Sidedefs < DecodedLump
472
+ BYTES_EACH=30
473
+ NAME="SIDEDEFS"
474
+ def initialize
475
+ super(NAME)
476
+ end
477
+ def read(bytes)
478
+ (bytes.size / BYTES_EACH).times {|index|
479
+ s = Sidedef.new
480
+ s.read(bytes.slice(index*BYTES_EACH, BYTES_EACH))
481
+ @items << s
482
+ }
483
+ end
484
+ def size
485
+ @items.size * BYTES_EACH
486
+ end
487
+ end
488
+
489
+ class Sidedef
490
+ FORMAT="ss888s"
491
+ attr_accessor :x_offset, :y_offset, :upper_texture, :lower_texture, :middle_texture, :sector_id, :id
492
+ def initialize()
493
+ @x_offset=0
494
+ @y_offset=0
495
+ @upper_texture="-"
496
+ @lower_texture="-"
497
+ @middle_texture="BROWN96"
498
+ @sector_id=0
499
+ end
500
+ def read(bytes)
501
+ @x_offset, @y_offset, @upper_texture, @lower_texture, @middle_texture, @sector_id = Codec.decode(FORMAT, bytes)
502
+ end
503
+ def write
504
+ Codec.encode(FORMAT, [@x_offset, @y_offset, @upper_texture, @lower_texture, @middle_texture, @sector_id])
505
+ end
506
+ def to_s
507
+ " Sidedef for sector " + @sector_id.to_s + "; upper/lower/middle textures are " + @upper_texture + "/" + @lower_texture + "/" + @middle_texture + " with offsets of " + @x_offset.to_s + "/" + @y_offset.to_s
508
+ end
509
+ end
510
+
511
+ class Things < DecodedLump
512
+ BYTES_EACH=10
513
+ NAME="THINGS"
514
+ def initialize
515
+ super(NAME)
516
+ end
517
+ def read(bytes)
518
+ (bytes.size / BYTES_EACH).times {|index|
519
+ thing = Thing.new
520
+ thing.read(bytes.slice(index*BYTES_EACH, BYTES_EACH))
521
+ @items << thing
522
+ }
523
+ end
524
+ def size
525
+ @items.size * BYTES_EACH
526
+ end
527
+ def add_sergeant(p)
528
+ items << Thing.new(p, Dictionary.get.thing_for_name("Sergeant").id)
529
+ end
530
+ def add_commando(p)
531
+ items << Thing.new(p, Dictionary.get.thing_for_name("Commando").id)
532
+ end
533
+ def add_barrel(p)
534
+ items << Thing.new(p, Dictionary.get.thing_for_name("Barrel").id)
535
+ end
536
+ def add_shotgun(p)
537
+ items << Thing.new(p, Dictionary.get.thing_for_name("Shotgun").id)
538
+ end
539
+ def add_imp(p)
540
+ items << Thing.new(p, Dictionary.get.thing_for_name("Imp").id)
541
+ end
542
+ def add_player(p)
543
+ items << Thing.new(p, Dictionary.get.thing_for_name("Player 1").id)
544
+ end
545
+ def player
546
+ @items.find {|t| t.type_id == Dictionary.get.thing_for_name("Player 1").id}
547
+ end
548
+ end
549
+
550
+ class Thing
551
+ attr_reader :type_id, :location
552
+ attr_accessor :facing_angle, :id
553
+ def initialize(p=nil,type_id=0)
554
+ @location = p
555
+ @facing_angle = 0
556
+ @type_id = type_id
557
+ @flags = 7
558
+ end
559
+ def read(bytes)
560
+ x, y, @facing_angle, @type_id, @flags = Codec.decode("sssss", bytes)
561
+ @location = Point.new(x,y)
562
+ end
563
+ def write
564
+ Codec.encode("sssss", [@location.x, @location.y, @facing_angle, @type_id, @flags])
565
+ end
566
+ def to_s
567
+ Dictionary.get.thing_for_type_id(@type_id).name + " at " + @location.to_s + " facing " + Dictionary.get.direction_for_angle(@facing_angle) + "; flags = " + @flags.to_s
568
+ end
569
+ end
570
+
571
+ class Linedefs < DecodedLump
572
+ BYTES_EACH=14
573
+ NAME="LINEDEFS"
574
+ def initialize
575
+ super(NAME)
576
+ end
577
+ def read(bytes)
578
+ (bytes.size / BYTES_EACH).times {|index|
579
+ linedef = Linedef.new
580
+ linedef.read(bytes.slice(index*BYTES_EACH, BYTES_EACH))
581
+ @items << linedef
582
+ }
583
+ end
584
+ def size
585
+ @items.size * BYTES_EACH
586
+ end
587
+ end
588
+
589
+ class Linedef
590
+ FORMAT="sssssss"
591
+ attr_accessor :start_vertex, :end_vertex, :attributes, :special_effects_type, :right_sidedef, :left_sidedef, :id
592
+ def initialize(v1=0,v2=0,s=0)
593
+ @start_vertex=v1
594
+ @end_vertex=v2
595
+ @attributes=1
596
+ @special_effects_type=0
597
+ @right_sidedef=s
598
+ @left_sidedef=-1
599
+ end
600
+ def read(bytes)
601
+ @start_vertex, @end_vertex, @attributes, @special_effects_type, @tag, @right_sidedef, @left_sidedef = Codec.decode(FORMAT, bytes)
602
+ end
603
+ def write
604
+ Codec.encode(FORMAT, [@start_vertex.id, @end_vertex.id, @attributes, @special_effects_type, @tag, @right_sidedef.id, -1])
605
+ end
606
+ def to_s
607
+ "Linedef from " + @start_vertex.to_s + " to " + @end_vertex.to_s + "; attribute flag is " + @attributes.to_s + "; special fx is " + @special_effects_type.to_s + "; tag is " + @tag.to_s + "; right sidedef is " + @right_sidedef.to_s + "; left sidedef is " + @left_sidedef.to_s
608
+ end
609
+ end
610
+
611
+ class DirectoryEntry
612
+ BYTES_EACH=16
613
+ attr_accessor :offset, :size, :name
614
+ def initialize(offset=nil,size=nil,name=nil)
615
+ @offset = offset
616
+ @size = size
617
+ @name = name
618
+ end
619
+ def read(array)
620
+ @offset, @size, @name = Codec.decode("ll8", array)
621
+ end
622
+ def write
623
+ Codec.encode("ll8", [@offset, @size, @name])
624
+ end
625
+ def create_lump(bytes)
626
+ lump=nil
627
+ if @name == Things::NAME
628
+ lump=Things.new
629
+ elsif @name == Linedefs::NAME
630
+ lump=Linedefs.new
631
+ elsif @name == Sidedefs::NAME
632
+ lump=Sidedefs.new
633
+ elsif @name == Vertexes::NAME
634
+ lump=Vertexes.new
635
+ elsif @name == Sectors::NAME
636
+ lump=Sectors.new
637
+ else
638
+ lump=UndecodedLump.new(@name)
639
+ end
640
+ lump.read(bytes.slice(@offset, @size))
641
+ return lump
642
+ end
643
+ def to_s
644
+ @offset.to_s + "," + @size.to_s + "," + @name
645
+ end
646
+ end
647
+
648
+ class Header
649
+ BYTES_EACH=12
650
+ attr_reader :type
651
+ attr_accessor :directory_offset, :lump_count
652
+ def initialize(type=nil)
653
+ @type = type
654
+ end
655
+ def read(array)
656
+ @type, @lump_count, @directory_offset = Codec.decode("4ll", array)
657
+ end
658
+ def write
659
+ Codec.encode("4ll", [@type, @lump_count, @directory_offset])
660
+ end
661
+ end
662
+
663
+ class Wad
664
+ attr_reader :directory_offset, :header, :bytes, :lumps
665
+ def initialize(verbose=false)
666
+ @verbose = verbose
667
+ @bytes = []
668
+ @lumps = []
669
+ end
670
+ def read(filename)
671
+ puts "Reading WAD into memory" unless !@verbose
672
+ File.new(filename).each_byte {|b|
673
+ @bytes << b
674
+ puts "Read " + (@bytes.size/1000).to_s + " KB so far " unless (!@verbose or @bytes.size % 500000 != 0)
675
+ }
676
+ puts "Done reading, building the object model" unless !@verbose
677
+ @header = Header.new
678
+ @header.read(@bytes.slice(0,Header::BYTES_EACH))
679
+ @header.lump_count.times {|directory_entry_index|
680
+ de = DirectoryEntry.new
681
+ de.read(@bytes.slice((directory_entry_index*DirectoryEntry::BYTES_EACH)+@header.directory_offset,DirectoryEntry::BYTES_EACH))
682
+ lump = de.create_lump(@bytes)
683
+ puts "Created " + lump.name unless !@verbose
684
+ @lumps << lump
685
+ }
686
+ puts "Object model built" unless !@verbose
687
+ puts "The file " + filename + " is a " + @bytes.size.to_s + " byte " + @header.type unless !@verbose
688
+ puts "It's got " + @lumps.size.to_s + " lumps, the directory started at byte " + @header.directory_offset.to_s unless !@verbose
689
+ end
690
+ def player
691
+ @lumps.find {|x| x.name == "THINGS"}.player
692
+ end
693
+ def write(filename=nil)
694
+ puts "Writing WAD" unless !@verbose
695
+ out = []
696
+ ptr = Header::BYTES_EACH
697
+ entries = []
698
+ @lumps.each {|lump|
699
+ entries << DirectoryEntry.new(ptr, lump.size, lump.name)
700
+ out += lump.write
701
+ ptr += lump.size
702
+ }
703
+ entries.each {|e| out += e.write }
704
+ # now go back and fill in the directory offset in the header
705
+ h = Header.new("PWAD")
706
+ h.directory_offset = ptr
707
+ h.lump_count = @lumps.size
708
+ out = h.write + out
709
+ File.open(filename, "w") {|f| out.each {|b| f.putc(b) } } unless filename == nil
710
+ puts "Done" unless !@verbose
711
+ return out
712
+ end
713
+ end
714
+
715
+ class SimpleLineMap
716
+ attr_reader :path
717
+ def initialize(path)
718
+ @path = path
719
+ @things = Things.new
720
+ end
721
+ def set_player(p)
722
+ @things.add_player p
723
+ end
724
+ def add_barrel(p)
725
+ @things.add_barrel p
726
+ end
727
+ def add_sergeant(p)
728
+ @things.add_sergeant p
729
+ end
730
+ def add_commando(p)
731
+ @things.add_commando p
732
+ end
733
+ def add_imp(p)
734
+ @things.add_imp p
735
+ end
736
+ def add_shotgun(p)
737
+ @things.add_shotgun p
738
+ end
739
+ def nethack(size=Nethack::DEFAULT_SIZE)
740
+ n = Nethack.new(@path.start, size)
741
+ @path.visit(n)
742
+ @things.items.each {|t| n.thing(t) }
743
+ return n.render
744
+ end
745
+ def create_wad(filename)
746
+ w = Wad.new
747
+ w.lumps << UndecodedLump.new("MAP01")
748
+ w.lumps << @things
749
+ pc = PathCompiler.new(@path)
750
+ w.lumps.concat pc.lumps
751
+ w.write(filename)
752
+ end
753
+ end
754
+
755
+ class BMPMap
756
+ attr_accessor :scale_factor, :thinning_factor
757
+ def initialize(file)
758
+ @things = Things.new
759
+ @scale_factor = 1
760
+ @thinning_factor = 5
761
+ @bitmap_file = file
762
+ end
763
+ def set_player(p)
764
+ @things.add_player p
765
+ end
766
+ def add_sergeant(p)
767
+ @things.add_sergeant p
768
+ end
769
+ def add_commando(p)
770
+ @things.add_commando p
771
+ end
772
+ def add_imp(p)
773
+ @things.add_imp p
774
+ end
775
+ def add_shotgun(p)
776
+ @things.add_shotgun p
777
+ end
778
+ def create_wad(filename)
779
+ b = BMPDecoder.new(@bitmap_file)
780
+ b.scale_factor = @scale_factor
781
+ b.thinning_factor = @thinning_factor
782
+ w = Wad.new
783
+ w.lumps << UndecodedLump.new("MAP01")
784
+ w.lumps << @things
785
+ pc = PathCompiler.new(PointsPath.new(b.thin))
786
+ w.lumps.concat(pc.lumps)
787
+ w.write(filename)
788
+ end
789
+ end
790
+
791
+ class PathCompiler
792
+ def initialize(path)
793
+ @path = path
794
+ @sectors = Sectors.new
795
+ @sectors.add(Sector.new)
796
+ @vertexes = Vertexes.new
797
+ @vertexes.add(Vertex.new(@path.start))
798
+ @path.visit(self)
799
+ @sidedefs = Sidedefs.new
800
+ @path.segment_count.times do |v|
801
+ s = @sidedefs.add Sidedef.new
802
+ s.sector_id = @sectors.items[0].id
803
+ end
804
+ @linedefs = Linedefs.new
805
+ last = nil
806
+ @vertexes.items.each do |v|
807
+ if last.nil?
808
+ last = v
809
+ next
810
+ end
811
+ @linedefs.add Linedef.new(last, v, @sidedefs.items[last.id])
812
+ last = v
813
+ end
814
+ @linedefs.add(Linedef.new(@vertexes.items.last, @vertexes.items.first, @sidedefs.items.last))
815
+ end
816
+ def line_to(p)
817
+ @vertexes.add(Vertex.new(p)) if (@vertexes.items.find {|x| x.location == p }).nil?
818
+ end
819
+ def lumps
820
+ [@vertexes, @sectors, @linedefs, @sidedefs]
821
+ end
822
+ end
823
+
824
+ class PointsPath
825
+ def initialize(points)
826
+ @points = points
827
+ end
828
+ def segment_count
829
+ @points.size
830
+ end
831
+ def visit(visitor)
832
+ @points.each {|p| visitor.line_to(p) }
833
+ end
834
+ def start
835
+ @points[0]
836
+ end
837
+ end
838
+
839
+ class Path
840
+ attr_reader :path, :start
841
+ def initialize(startx, starty, path="")
842
+ @path = path
843
+ @start = Point.new(startx, starty)
844
+ end
845
+ def add(p,count=1)
846
+ count.times {@path += p }
847
+ end
848
+ def segments
849
+ @path.split(/\//)
850
+ end
851
+ def segment_count
852
+ segments.size
853
+ end
854
+ def visit(visitor)
855
+ cur = @start
856
+ segments.each do |x|
857
+ dir = x[0].chr
858
+ len = x.slice(1, x.length-1).to_i
859
+ if dir == "e"
860
+ cur = cur.translate(len, 0)
861
+ elsif dir == "n"
862
+ cur = cur.translate(0, len)
863
+ elsif dir == "w"
864
+ cur = cur.translate(-len, 0)
865
+ elsif dir == "s"
866
+ cur = cur.translate(0, -len)
867
+ else
868
+ raise "Unrecognized direction " + dir.to_s + " in segment " + x.to_s
869
+ end
870
+ visitor.line_to(cur)
871
+ end
872
+ end
873
+ def nethack(size=Nethack::DEFAULT_SIZE)
874
+ n = Nethack.new(@start, size)
875
+ visit(n)
876
+ return n.render
877
+ end
878
+ def to_s
879
+ @path
880
+ end
881
+ end
882
+
883
+ class Nethack
884
+ DEFAULT_SIZE=20
885
+ def initialize(start,size)
886
+ @start = start
887
+ @map = Array.new(size)
888
+ @map.each_index {|x| @map[x] = Array.new(size, ".") }
889
+ @prev = @start
890
+ end
891
+ def line_to(current)
892
+ @prev.lineto(current).each {|pt| @map[pt.y/100][pt.x/100] = "X" }
893
+ @prev = current
894
+ end
895
+ def thing(t)
896
+ puts "#{(t.location.y/100)},#{((@map.size-1) - (t.location.x/100))} -> #{Dictionary.get.thing_for_type_id(t.type_id).symbol}"
897
+ @map[t.location.y/100][(@map.size-1) - (t.location.x/100)] = Dictionary.get.thing_for_type_id(t.type_id).symbol
898
+ end
899
+ def render
900
+ res = ""
901
+ @map.each_index do |x|
902
+ @map[x].each_index do
903
+ |y| res << @map[@map.size-1-x][y-1] + " "
904
+ end
905
+ res << "\n"
906
+ end
907
+ res
908
+ end
909
+ end
910
+
911
+ if __FILE__ == $0
912
+ file = ARGV.include?("-f") ? ARGV[ARGV.index("-f") + 1] : "../test_wads/simple.wad"
913
+ w = Wad.new(ARGV.include?("-v"))
914
+ w.read(file)
915
+ w.lumps.each do |lump|
916
+ puts lump.name + " (" + lump.size.to_s + " bytes)"
917
+ lump.items.each { |t| puts " - " + t.to_s }
918
+ end
919
+ end
920
+
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-doom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Copeland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-18 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.3
24
+ version:
25
+ description: Generates DOOM maps.
26
+ email:
27
+ - tom@infoether.com
28
+ executables:
29
+ - introspector
30
+ - bsp
31
+ extensions: []
32
+
33
+ extra_rdoc_files:
34
+ - History.txt
35
+ - Manifest.txt
36
+ - README.txt
37
+ files:
38
+ - History.txt
39
+ - Manifest.txt
40
+ - README.txt
41
+ - Rakefile
42
+ - bin/introspector
43
+ - bin/bsp
44
+ - lib/ruby-doom.rb
45
+ has_rdoc: true
46
+ homepage: http://ruby-doom.rubyforge.org
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README.txt
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: ruby-doom
70
+ rubygems_version: 1.3.4
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Generates DOOM maps.
74
+ test_files: []
75
+