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 +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
|
+
|