ruby-doom 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+