rubycraft 0.1.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.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ RubyCraft
2
+ ==============
3
+
4
+ RubyCraft is a simple library for manipulating [Minecraft](http://www.minecraft.net/)
5
+ region files.
6
+
7
+ Region files are files with the mcr extension on the region folder of a save folder. The
8
+ save folders are located below the saves folder of the minecraft data folder (for
9
+ instance, on Linux it is ~/.minecraft/saves/$SAVENAME/region, and on mac it is
10
+ ~/Library/Application Support/minecraft/$SAVENAME/region). More about save folders
11
+ [here](http://www.minecraftwiki.net/wiki/Tutorials/Minecraft_Help_FAQ#Common_fixes).
12
+
13
+
14
+ Quick Example
15
+ --------
16
+ filename = " ~/.minecraft/saves/$SAVENAME/region/r.0.0.mcr"
17
+ r = Region.fromFile(filename)
18
+ r.chunk(0, 0).block_map { :gold }
19
+ r.exportToFile filename
20
+
21
+ For more examples, check the
22
+ [examples](https://github.com/danielribeiro/RubyCraft/tree/master/examples) folder.
23
+
24
+ Regions
25
+ ---------
26
+ When getting many chunks from Region#chunk method, don't forget to invoke Region#unloadChunk(z,
27
+ x). This way the chunk will not occupy memory.
28
+
29
+ Alternatively you can use the Region#cube method. Example
30
+
31
+ r = Region.fromFile(filename)
32
+ c = r.cube(0, 0, 0, :width => 50, :length => 50, :height => 128)
33
+ c.each do |block, z, x, y|
34
+ block.name = :wool
35
+ block.color = :orange
36
+ end
37
+
38
+ It receives the z, x, y of the origin point of the cube, and its respective width, length
39
+ and height. The chunk load/unload is abstracted way on this interface. The cube can
40
+ receive a block, or it will return an Enumerable that iterates over the blocks of the
41
+ cube. The proc receives four arguments: the block, and its relative coordinates to the
42
+ cube's origin point.
43
+
44
+ Chunks
45
+ ---------
46
+ Chunks are both enumerable and indexable:
47
+
48
+ chunk[0, 0, 0].name = :gold
49
+ chunk.each { |block| block.name = :gold }
50
+
51
+
52
+ Note that chunks have size 16x16x128 (width, length, height). Usually you don't create
53
+ chunks directly, but get them through Region#chunk method.
54
+
55
+ Blocks
56
+ ---------
57
+ Blocks have 3 attributes: block_type, pos and data. [Block type](https://github.com/danielribeiro/RubyCraft/blob/master/lib/block_type.rb) tells the name, id and
58
+ transparency (boolean) of the block. The pos attribute indicates the position of the block
59
+ inside its chunk, and data is the integer [data
60
+ value](http://www.minecraftwiki.net/wiki/Data_values).
61
+
62
+ Id is not usually accessed directly, as the name attribute provides a more friendly
63
+ interface. For wool blocks, changing the color directly is also possible in a more
64
+ friendly way.
65
+
66
+ block.name = :wool
67
+ block.color = :purple
68
+ p block.color
69
+
70
+
71
+ Dependencies:
72
+ ---------
73
+ [Nbtfile](http://github.com/mental/nbtfile)
74
+
75
+ Meta
76
+ ----
77
+
78
+ Created by Daniel Ribeiro
79
+
80
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
81
+
82
+ http://github.com/danielribeiro/RubyCraft
83
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+ require 'spec/rake/spectask'
8
+
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gem|
12
+ gem.name = "rubycraft"
13
+ gem.summary = %Q{Lib for manipualting Minecraft world files}
14
+ gem.description = %Q{It allows you to change all the
15
+ blocks in region files in whatever way you see fit. Example: http://bit.ly/r62qGo}
16
+ gem.email = "danrbr@gmail.com"
17
+ gem.homepage = "http://github.com/danielribeiro/RubyCraft"
18
+ gem.authors = ["Daniel Ribeiro"]
19
+ gem.add_dependency 'nbtfile', '>=0.2.0'
20
+ gem.files = FileList["[A-Z]*", "{bin,lib}/**/*"]
21
+ # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
22
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
23
+ end
24
+ Jeweler::GemcutterTasks.new
25
+ rescue LoadError
26
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
27
+ end
28
+
29
+
30
+ Rake::RDocTask.new do |rdoc|
31
+ files =['README', 'LICENSE', 'lib/**/*.rb']
32
+ rdoc.rdoc_files.add(files)
33
+ rdoc.main = "README" # page to start on
34
+ rdoc.title = "RubyCraft Docs"
35
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
36
+ rdoc.options << '--line-numbers'
37
+ end
38
+
39
+ Spec::Rake::SpecTask.new do |t|
40
+ t.spec_files = FileList['spec/**/*spec.rb']
41
+ t.libs << Dir["lib"]
42
+ end
43
+
44
+ desc "Acceptance test"
45
+ task :atest do
46
+ require 'spec/acceptanceEdit'
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/block.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'block_type'
2
+
3
+ # A minecraft block. Its position is given by a coord[x, z, y]
4
+ class Block
5
+
6
+ attr_accessor :block_type, :pos, :data
7
+ def initialize(blockType, data = 0)
8
+ @blockType = blockType
9
+ @data = 0
10
+ end
11
+
12
+ def self.get(key)
13
+ new BlockType.get key
14
+ end
15
+
16
+ def self.of(key)
17
+ self[key]
18
+ end
19
+
20
+ def self.[](key)
21
+ new BlockType[key]
22
+ end
23
+
24
+
25
+ def color=(color)
26
+ @data = BlockColor::InvertedColor[color]
27
+ end
28
+
29
+ def color
30
+ BlockColor.typeColor[@data].name
31
+ end
32
+
33
+ def blockColor
34
+ BlockColor.typeColor[@data]
35
+ end
36
+
37
+ def is(name)
38
+ self.name == name.to_s
39
+ end
40
+
41
+ def name
42
+ @blockType.name
43
+ end
44
+
45
+ def id
46
+ @blockType.id
47
+ end
48
+
49
+ def transparent
50
+ @blockType.transparent
51
+ end
52
+
53
+ #sets block type by name
54
+ def name=(newName)
55
+ return if name == newName.to_s
56
+ @blockType = BlockType[newName]
57
+ end
58
+
59
+ #sets block type by id
60
+ def id=(id)
61
+ return if id == id
62
+ @blockType = BlockType.get id
63
+ end
64
+
65
+ def y
66
+ pos[2]
67
+ end
68
+
69
+ def z
70
+ pos[1]
71
+ end
72
+
73
+ def x
74
+ pos[0]
75
+ end
76
+ end
77
+
data/lib/block_type.rb ADDED
@@ -0,0 +1,191 @@
1
+ BlockTypeDSL = proc do
2
+ transparent_block 0, :air
3
+ block 1, :stone
4
+ block 2, :grass
5
+ block 3, :dirt
6
+ block 4, :cobblestone
7
+ block 5, :planks
8
+ transparent_block 6, :sapling
9
+ block 7, :bedrock
10
+ block 8, :watersource
11
+ block 9, :water
12
+ block 10, :lavasource
13
+ block 11, :lava
14
+ block 12, :sand
15
+ block 13, :gravel
16
+ block 14, :goldore
17
+ block 15, :ironore
18
+ block 16, :coal
19
+ block 17, :log
20
+ block 18, :leaves
21
+ block 19, :sponge
22
+ transparent_block 20, :glass
23
+ block 21, :lapisore
24
+ block 22, :lapis
25
+ block 23, :dispenser
26
+ block 24, :sandstone
27
+ block 25, :note
28
+ block 26, :bed
29
+ transparent_block 27, :powered_rail
30
+ transparent_block 28, :detector_rail
31
+ block 29, :sticky_piston
32
+ transparent_block 30, :cobweb
33
+ transparent_block 31, :tall_grass
34
+ transparent_block 32, :dead_shrubs
35
+ block 33, :piston
36
+ block 34, :piston_extension
37
+ block 35, :wool
38
+ transparent_block 37, :dandelion
39
+ transparent_block 38, :rose
40
+ transparent_block 39, :brown_mushroom
41
+ transparent_block 40, :red_mushroom
42
+ block 41, :gold
43
+ block 42, :iron
44
+ block 43, :slabs
45
+ block 44, :slab
46
+ block 45, :brick
47
+ block 46, :tnt
48
+ block 47, :bookshelf
49
+ block 48, :mossy
50
+ block 49, :obsidian
51
+ transparent_block 50, :torch
52
+ transparent_block 51, :fire
53
+ block 52, :spawner
54
+ block 53, :stairs
55
+ block 54, :chest
56
+ transparent_block 55, :redstone_wire
57
+ block 56, :diamond_ore
58
+ block 57, :diamond_block
59
+ block 58, :crafting_table
60
+ block 59, :seeds
61
+ block 60, :farmland
62
+ block 61, :furnace
63
+ block 62, :burning_furnace
64
+ transparent_block 63, :signpost
65
+ transparent_block 64, :door
66
+ transparent_block 65, :ladder
67
+ transparent_block 66, :rails
68
+ block 67, :cobblestone_stairs
69
+ transparent_block 68, :wall_sign
70
+ transparent_block 69, :lever
71
+ transparent_block 70, :stone_pressure_plate
72
+ transparent_block 71, :iron_door
73
+ transparent_block 72, :wooden_pressure_plate
74
+ block 73, :redstone_ore
75
+ block 74, :glowing_redstone_ore
76
+ transparent_block 75, :redstone_torch_off
77
+ transparent_block 76, :redstone_torch_on
78
+ transparent_block 77, :stone_button
79
+ block 78, :snow
80
+ block 79, :ice
81
+ block 80, :snow_block
82
+ transparent_block 81, :cactus
83
+ block 82, :clay
84
+ block 83, :sugar_cane
85
+ block 84, :jukebox
86
+ transparent_block 85, :fence
87
+ block 86, :pumpkin
88
+ block 87, :netherrack
89
+ block 88, :soulsand
90
+ block 89, :glowstone
91
+ transparent_block 90, :portal
92
+ block 91, :jock_o_lantern
93
+ transparent_block 92, :cake
94
+ transparent_block 93, :repeater_off
95
+ transparent_block 94, :repeater_on
96
+ block 95, :locked_chest
97
+ transparent_block 96, :trapdoor
98
+ end
99
+
100
+
101
+ # DSL: color name r, g, b
102
+ BlockColorDSL = proc do
103
+ white 221, 221, 221
104
+ orange 233, 126, 55
105
+ magenta 179, 75, 200
106
+ light_blue 103, 137, 211
107
+ yellow 192, 179, 28
108
+ light_green 59, 187, 47
109
+ pink 217, 132, 153
110
+ dark_gray 66, 67, 67
111
+ gray 157, 164, 165
112
+ cyan 39, 116, 148
113
+ purple 128, 53, 195
114
+ blue 39, 51, 153
115
+ brown 85, 51, 27
116
+ dark_green 55, 76, 24
117
+ red 162, 44, 42
118
+ black 26, 23, 23
119
+ end
120
+
121
+ class BlockColor
122
+ @typeColor = []
123
+
124
+ def self.method_missing(name, *args)
125
+ args << @typeColor.size
126
+ @typeColor << new(name, *args)
127
+ end
128
+
129
+ def self.typeColor
130
+ @typeColor
131
+ end
132
+
133
+ attr_reader :name, :r, :g, :b, :data
134
+ def initialize(name, r, g, b, data)
135
+ @name = name
136
+ @r = r
137
+ @g = g
138
+ @b = b
139
+ @data = data
140
+ end
141
+
142
+ def rgb
143
+ [r, g, b]
144
+ end
145
+
146
+ class_eval &BlockColorDSL
147
+ InvertedColor = Hash[typeColor.each_with_index.map { |obj, i| [obj.name, i] }]
148
+ end
149
+
150
+ # class methods and dsl for block
151
+ class BlockType
152
+ @blocks = {}
153
+ @blocks_by_name = {}
154
+ attr_reader :id, :name, :transparent
155
+
156
+ def initialize(id, name, transparent)
157
+ @id = id
158
+ @name = name.to_s
159
+ @transparent = transparent
160
+ end
161
+
162
+ def self.block(id, name, transparent = false)
163
+ block = new id, name, transparent
164
+ @blocks[id] = block
165
+ @blocks_by_name[name.to_s] = block
166
+
167
+ end
168
+
169
+ def self.transparent_block(id, name)
170
+ block id, name, true
171
+ end
172
+
173
+ def self.get(key)
174
+ if @blocks.has_key?(key)
175
+ return @blocks[key].clone
176
+ end
177
+ new(key, "unknown(#{key})", false)
178
+ end
179
+
180
+ def self.of(key)
181
+ self[key]
182
+ end
183
+
184
+ def self.[](key)
185
+ key = key.to_s
186
+ return @blocks_by_name[key] if @blocks_by_name.has_key?(key)
187
+ raise "no such name: #{key}"
188
+ end
189
+
190
+ class_eval &BlockTypeDSL
191
+ end
@@ -0,0 +1,46 @@
1
+ require 'stringio'
2
+
3
+ # Utils for manipulating bytes back and forth as strings, strings and numbers
4
+ module ByteConverter
5
+ def toByteString(array)
6
+ array.pack('C*')
7
+ end
8
+
9
+ def intBytes(i)
10
+ [i >> 24, (i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF]
11
+ end
12
+
13
+ def stringToByteArray(str)
14
+ str.bytes.to_a
15
+ end
16
+
17
+ def arrayToIO(arr)
18
+ io = StringIO.new
19
+ io.write toByteString arr
20
+ io.rewind
21
+ io
22
+ end
23
+
24
+ def stringToIo(str)
25
+ arrayToIO stringToByteArray(str)
26
+ end
27
+
28
+ def concat(array, enum)
29
+ for i in enum
30
+ array << i
31
+ end
32
+ end
33
+
34
+ def bytesToInt(array)
35
+ array.pack('C*').unpack("N").first
36
+ end
37
+
38
+ def pad(array, count, value = 0)
39
+ count.times do
40
+ array << value
41
+ end
42
+ array
43
+ end
44
+
45
+ extend self
46
+ end
data/lib/chunk.rb ADDED
@@ -0,0 +1,118 @@
1
+ # Represents a chunk data
2
+ require 'nbt_helper'
3
+ require 'byte_converter'
4
+ require 'block'
5
+ require 'matrix3d'
6
+
7
+ # Chunks are enumerable over blocks
8
+ class Chunk
9
+ include Enumerable
10
+ include ZlibHelper
11
+
12
+ Width = 16
13
+ Length = 16
14
+ Height = 128
15
+
16
+ def self.fromNbt(bytes)
17
+ new NbtHelper.fromNbt bytes
18
+ end
19
+
20
+ def initialize(nbtData)
21
+ name, @nbtBody = nbtData
22
+ bytes = level["Blocks"].value.bytes
23
+ @blocks = matrixfromBytes bytes
24
+ @blocks.each_triple_index do |b, z, x, y|
25
+ b.pos = [z, x, y]
26
+ end
27
+ data = level["Data"].value.bytes.to_a
28
+ @blocks.each_with_index do |b, index|
29
+ v = data[index / 2]
30
+ if index % 2 == 0
31
+ b.data = v & 0xF
32
+ else
33
+ b.data = v >> 4
34
+ end
35
+ end
36
+ end
37
+
38
+ # Iterates over the blocks
39
+ def each(&block)
40
+ @blocks.each &block
41
+ end
42
+
43
+
44
+ # Converts all blocks on data do another type. Gives the block and sets
45
+ # the received name
46
+ def block_map(&block)
47
+ each { |b| b.name = yield b }
48
+ end
49
+
50
+ # Converts all blocks on data do another type. Gives the block name sets
51
+ # the received name
52
+ def block_type_map(&block)
53
+ each { |b| b.name = yield b.name.to_sym }
54
+ end
55
+
56
+ def [](z, x, y)
57
+ @blocks[z, x, y]
58
+ end
59
+
60
+ def []=(z, x, y, value)
61
+ @blocks[z, x, y] = value
62
+ end
63
+
64
+ def export
65
+ level["Data"] = byteArray exportLevelData
66
+ level["Blocks"] = byteArray @blocks.map { |b| b.id }
67
+ level["HeightMap"] = byteArray exportHeightMap
68
+ ["", @nbtBody]
69
+ end
70
+
71
+ def toNbt
72
+ NbtHelper.toBytes export
73
+ end
74
+
75
+ protected
76
+ def exportHeightMap
77
+ zwidth, xwidth, ywidth = @blocks.bounds
78
+ matrix = Array.new(zwidth) { Array.new(xwidth) { 1 }}
79
+ @blocks.each_triple_index do |b, z, x, y|
80
+ unless b.transparent
81
+ matrix[z][x] = [matrix[z][x], y + 1].max
82
+ end
83
+ end
84
+ ret = []
85
+ matrix.each do |line|
86
+ line.each do |height|
87
+ ret << height
88
+ end
89
+ end
90
+ ret
91
+ end
92
+
93
+ def level
94
+ @nbtBody["Level"]
95
+ end
96
+
97
+ def exportLevelData
98
+ data = []
99
+ @blocks.each_with_index do |b, i|
100
+ if i % 2 == 0
101
+ data << b.data
102
+ else
103
+ data[i / 2] += (b.data << 4)
104
+ end
105
+ end
106
+ data
107
+ end
108
+
109
+ def byteArray(data)
110
+ NBTFile::Types::ByteArray.new ByteConverter.toByteString(data)
111
+ end
112
+
113
+ def matrixfromBytes(bytes)
114
+ Matrix3d.new(Width, Length, Height).fromArray bytes.map {|byte| Block.get(byte) }
115
+ end
116
+
117
+
118
+ end
data/lib/matrix3d.rb ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ class IndexOutOfBoundsError < StandardError
3
+
4
+ end
5
+
6
+ class Matrix3d
7
+ include Enumerable
8
+
9
+ def bounds
10
+ [@xlimit, @ylimit, @zlimit]
11
+ end
12
+
13
+ def initialize(d1,d2,d3)
14
+ @xlimit = d1
15
+ @ylimit = d2
16
+ @zlimit = d3
17
+ @data = Array.new(d1) { Array.new(d2) { Array.new(d3) } }
18
+ end
19
+
20
+ def [](x, y, z)
21
+ @data[x][y][z]
22
+ end
23
+
24
+ def []=(x, y, z, value)
25
+ @data[x][y][z] = value
26
+ end
27
+
28
+ def put(index, value)
29
+ ar = indexToArray(index)
30
+ self[*ar] = value
31
+ end
32
+
33
+ def get(index)
34
+ ar = indexToArray(index)
35
+ self[*ar]
36
+ end
37
+
38
+ def each(&block)
39
+ for z in @data
40
+ for y in z
41
+ for x in y
42
+ yield x
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def each_triple_index(&block)
49
+ return enum_for:each_triple_index unless block_given?
50
+ @data.each_with_index do |plane, x|
51
+ plane.each_with_index do |column, y|
52
+ column.each_with_index do |value, z|
53
+ yield value, x ,y ,z
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ #Actually from any iterable
60
+ def fromArray(ar)
61
+ ar.each_with_index { |obj,i| put i, obj }
62
+ return self
63
+ end
64
+
65
+
66
+ def to_a(default = nil)
67
+ map do |x|
68
+ if x.nil?
69
+ default
70
+ else
71
+ x
72
+ end
73
+ end
74
+ end
75
+
76
+ protected
77
+ def indexToArray(index)
78
+ x = index / (@zlimit * @ylimit)
79
+ index -= x * (@zlimit * @ylimit)
80
+ y = index / @zlimit
81
+ z = index % @zlimit
82
+ return x, y, z
83
+ end
84
+
85
+
86
+ end
data/lib/nbt_helper.rb ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ require 'nbtfile'
3
+ require 'zlib'
4
+ require 'byte_converter'
5
+ # Patching nbtfile clases so that they don't gzip/ungzip incorrectly the zlib bytes from
6
+ #mcr files. Use the methods from ZlibHelper
7
+ class NBTFile::Private::Tokenizer
8
+ def initialize(io)
9
+ @gz = io
10
+ @state = NBTFile::Private::TopTokenizerState.new
11
+ end
12
+ end
13
+
14
+ class NBTFile::Emitter
15
+ def initialize(stream)
16
+ @gz = stream
17
+ @state = NBTFile::Private::TopEmitterState.new
18
+ end
19
+ end
20
+
21
+ module ZlibHelper
22
+ def compress(str)
23
+ Zlib::Deflate.deflate(str)
24
+ end
25
+
26
+ def decompress(str)
27
+ Zlib::Inflate.inflate(str)
28
+ end
29
+ extend self
30
+ end
31
+
32
+ # Handles converting bytes to/from nbt regions, which are compressesed/decompress
33
+ module NbtHelper
34
+ extend ByteConverter
35
+ extend ZlibHelper
36
+
37
+ module_function
38
+ def fromNbt(bytes)
39
+ NBTFile.read stringToIo decompress toByteString bytes
40
+ end
41
+
42
+ def toBytes(nbt)
43
+ output = StringIO.new
44
+ name, body = nbt
45
+ NBTFile.write(output, name, body)
46
+ stringToByteArray compress output.string
47
+ end
48
+ end
data/lib/region.rb ADDED
@@ -0,0 +1,257 @@
1
+ require 'chunk'
2
+
3
+ class LazyChunkDelegate
4
+ include ByteConverter
5
+ include ZlibHelper
6
+
7
+ def initialize(bytes)
8
+ @bytes = bytes
9
+ @chunk = nil
10
+ end
11
+
12
+ def each(&block)
13
+ _getchunk.each &block
14
+ end
15
+
16
+ def block_map(&block)
17
+ _getchunk.block_map &block
18
+ end
19
+ def block_type_map(&block)
20
+ _getchunk.block_type_map &block
21
+ end
22
+
23
+ def [](z, x, y)
24
+ _getchunk[z, x, y]
25
+ end
26
+
27
+ def []=(z, x, y, value)
28
+ _getchunk[z, x, y] = value
29
+ end
30
+
31
+ def export
32
+ _getchunk.export
33
+ end
34
+
35
+
36
+ def toNbt
37
+ return @bytes if @chunk.nil?
38
+ @chunk.toNbt
39
+ end
40
+
41
+
42
+ # unloacs the loaded chunk. Needed for memory optmization
43
+ def _unload
44
+ return if @chunk.nil?
45
+ @bytes = @chunk.toNbt
46
+ @chunk = nil
47
+ end
48
+
49
+ protected
50
+ def _getchunk
51
+ if @chunk.nil?
52
+ @chunk = Chunk.fromNbt @bytes
53
+ end
54
+ @chunk
55
+ end
56
+
57
+ end
58
+
59
+ # Enumerable over chunks
60
+ class Region
61
+ include Enumerable
62
+ include ByteConverter
63
+ include ZlibHelper
64
+
65
+ class RegionWritter
66
+ def initialize(io)
67
+ @io = io
68
+ end
69
+
70
+ def pad(count, value = 0)
71
+ self << Array.new(count) { value }
72
+ end
73
+
74
+ def <<(o)
75
+ input = o.kind_of?(Array) ? o : [o]
76
+ @io << ByteConverter.toByteString(input)
77
+ end
78
+
79
+ def close
80
+ @io.close
81
+ end
82
+ end
83
+
84
+ def self.fromFile(filename)
85
+ new ByteConverter.stringToByteArray IO.read filename
86
+ end
87
+
88
+ def initialize(bytes)
89
+ raise "Must be an io" if bytes.kind_of?(String)
90
+ @bytes = bytes
91
+ @chunks = Array.new(32) { Array.new(32) }
92
+ readChunks bytes
93
+ end
94
+
95
+ def chunk(z, x)
96
+ @chunks[z][x]
97
+ end
98
+
99
+ def unloadChunk(z, x)
100
+ @chunks[z][x]._unload
101
+ end
102
+
103
+ def each(&block)
104
+ @chunks.each do |line|
105
+ line.each do |chunk|
106
+ yield chunk
107
+ end
108
+ end
109
+ end
110
+
111
+ def cube(z, y, x, opts = {}, &block)
112
+ c = ChunkCube.new(self, [z, y, x], opts[:width], opts[:length], opts[:height])
113
+ return c unless block_given?
114
+ c.each &block
115
+ end
116
+
117
+ def exportTo(io)
118
+ output = RegionWritter.new io
119
+ chunks = getChunks
120
+ writeChunkOffsets output, chunks
121
+ output.pad blockSize, dummytimestamp
122
+ writeChunks output, chunks
123
+ output.close
124
+ end
125
+
126
+ def exportToFile(filename)
127
+ File.open(filename, "wb") { |f| exportTo f }
128
+ end
129
+
130
+
131
+ protected
132
+ def readChunks(bytes)
133
+ bytes[0..(blockSize - 1)].each_slice(4).each_with_index do |ar, i|
134
+ offset = bytesToInt [0] + ar[0..-2]
135
+ count = ar.last
136
+ if count > 0
137
+ @chunks[i / 32][i % 32 ] = readChunk(offset, bytes)
138
+ end
139
+ end
140
+ end
141
+
142
+ def readChunk(offset, bytes)
143
+ o = offset * blockSize
144
+ bytecount = bytesToInt bytes[o..(o + 4)]
145
+ o += 5
146
+ nbtBytes = bytes[o..(o + bytecount - 2)]
147
+ LazyChunkDelegate.new nbtBytes
148
+ end
149
+
150
+ def chunkSize(chunk)
151
+ chunk.size + chunkMetaDataSize
152
+ end
153
+
154
+ def chunkBlocks(chunk)
155
+ ((chunkSize chunk).to_f / blockSize).ceil
156
+ end
157
+
158
+ def writeChunks(output, chunks)
159
+ for chunk in chunks
160
+ next if chunk.nil?
161
+ output << intBytes(chunk.size + 1)
162
+ output << defaultCompressionType
163
+ output << chunk
164
+ remaining = blockSize - chunkSize(chunk)
165
+ output.pad remaining % blockSize
166
+ end
167
+ end
168
+
169
+ def writeChunkOffsets(output, chunks)
170
+ lastVacantPosition = 2
171
+ for chunk in chunks
172
+ if chunk
173
+ sizeCount = chunkBlocks chunk
174
+ output << intBytes(lastVacantPosition)[1..3]
175
+ output << sizeCount
176
+ lastVacantPosition += sizeCount
177
+ else
178
+ output.pad 4
179
+ end
180
+ end
181
+ end
182
+
183
+ def getChunks
184
+ map do |chunk|
185
+ if chunk.nil?
186
+ nil
187
+ else
188
+ chunk.toNbt
189
+ end
190
+ end
191
+ end
192
+
193
+ def chunkMetaDataSize
194
+ 5
195
+ end
196
+
197
+ def defaultCompressionType
198
+ 2
199
+ end
200
+
201
+ def dummytimestamp
202
+ 0
203
+ end
204
+
205
+ def blockSize
206
+ 4096
207
+ end
208
+
209
+ end
210
+
211
+
212
+ class ChunkCube
213
+ include Enumerable
214
+
215
+ # width corresponds do z, length to x, and height to y.
216
+ def initialize(region, initialPos, width, length, height)
217
+ @region = region
218
+ @initialPos = initialPos
219
+ @width = width || 1
220
+ @length = length || 1
221
+ @height = height || 1
222
+ end
223
+
224
+ def each(&block)
225
+ z, x, y = @initialPos
226
+ firstChunkX = x / chunkSide
227
+ firstChunkZ = z / chunkSide
228
+ lastChunkX = (x + @length - 1) / chunkSide
229
+ lastChunkZ = (z + @width - 1) / chunkSide
230
+ for j in firstChunkZ..lastChunkZ
231
+ for i in firstChunkX..lastChunkX
232
+ iterateOverChunk j, i, &block
233
+ end
234
+ end
235
+ end
236
+
237
+ protected
238
+ def iterateOverChunk(j, i, &block)
239
+ chunk = @region.chunk(j, i)
240
+ return if chunk.nil?
241
+ z, x, y = @initialPos
242
+ chunk.each do |b|
243
+ globalZ = b.z + (j * chunkSide)
244
+ globalX = b.x + (i * chunkSide)
245
+ if globalZ.between?(z, z + @width - 1) and
246
+ globalX.between?(x, x + @length - 1) and
247
+ b.y.between?(y, y + @height - 1)
248
+ yield b, globalZ - z, globalX - x , b.y - y
249
+ end
250
+ end
251
+ @region.unloadChunk(j, i)
252
+ end
253
+
254
+ def chunkSide
255
+ 16
256
+ end
257
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubycraft
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Daniel Ribeiro
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-03 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: nbtfile
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 0
32
+ - 2
33
+ - 0
34
+ version: 0.2.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: |-
38
+ It allows you to change all the
39
+ blocks in region files in whatever way you see fit. Example: http://bit.ly/r62qGo
40
+ email: danrbr@gmail.com
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files:
46
+ - README.md
47
+ files:
48
+ - README.md
49
+ - Rakefile
50
+ - VERSION
51
+ - lib/block.rb
52
+ - lib/block_type.rb
53
+ - lib/byte_converter.rb
54
+ - lib/chunk.rb
55
+ - lib/matrix3d.rb
56
+ - lib/nbt_helper.rb
57
+ - lib/region.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/danielribeiro/RubyCraft
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options: []
64
+
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.6.2
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Lib for manipualting Minecraft world files
92
+ test_files: []
93
+