rubycraft 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +83 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/block.rb +77 -0
- data/lib/block_type.rb +191 -0
- data/lib/byte_converter.rb +46 -0
- data/lib/chunk.rb +118 -0
- data/lib/matrix3d.rb +86 -0
- data/lib/nbt_helper.rb +48 -0
- data/lib/region.rb +257 -0
- metadata +93 -0
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
|
+
|