Skip to content

Commit

Permalink
Add ProtocolBlockTypePalette (cuberite#4391)
Browse files Browse the repository at this point in the history
  • Loading branch information
E14 authored and madmaxoft committed Sep 22, 2019
1 parent 70d0b46 commit d1c9574
Show file tree
Hide file tree
Showing 15 changed files with 815 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@
path = lib/fmt
url = https://github.com/fmtlib/fmt.git
ignore = dirty
[submodule "Tools/BlockTypePaletteGenerator/lib/lunajson"]
path = Tools/BlockTypePaletteGenerator/lib/lunajson
url = https://github.com/grafi-tt/lunajson.git
1 change: 1 addition & 0 deletions Server/Protocol/1.13/base.btp.json

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions Tools/BlockTypePaletteGenerator/Generator.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
-- lib/lunajson/src/ is not in default Lua package paths
package.path = 'lib/lunajson/src/?.lua;' .. package.path;


--- Prints usage instructions to stdout.
-- If the optional `message` is passed, output is prepended by message _and_
-- redirected to stderr.
function usage(message)
if message then
io.output(io.stderr);
io.write(message, "\n\n");
end
io.write(
"Usage: lua Generator.lua INPUTFILE OUTPUTFILE\n"..
"Converts the Minecraft blocks.json report format to the cuberite "..
"block type palette format.\n"..
"\n"..
"INPUTFILE and OUTPUTFILE must point to a valid path. INPUTFILE must "..
"be readable and OUTPUTFILE must be writable. Either can be replaced "..
"with `-` (dash character) to point to standard-input or -output.\n");
os.exit(message and 1 or 0);
end


-- Test whether the script is run in a path where it can load it's libraries
if not pcall(function() require("lunajson.decoder") end) then
usage("Could not load required libraries, please run `Generator.lua` "..
"within its directory and make sure to run `git submodule update`.");
end


-- Check/Prepare CLI arguments
local inpath, outpath = ...;
io.input(io.stdin);
io.output(io.stdout);

if select("#", ...) ~= 2 then
usage("Incorrect number of arguments.");
end

if inpath ~= "-" then
local handle, err = io.open(inpath, "r");
io.input(handle or usage(err));
end

if outpath ~= "-" then
local handle, err = io.open(outpath, "w");
io.output(handle or usage(err));
end


-- Main program starts here
local decode = (require("lunajson.decoder"))();
local encode = (require("lunajson.encoder"))();

local input = decode(io.input():read("*a"));
local registry = {};
local max_id = -1;


for blockname, blockdata in pairs(input) do
for i = 1, #(blockdata.states or {}) do
local state = blockdata.states[i];
assert(registry[state.id + 1] == nil, "Ensure no duplicate IDs");

-- needed in the end to verify we got no holes in the array:
max_id = math.max(max_id, state.id);

registry[state.id + 1] = {
id = assert(state.id, "id is required."),
name = assert(blockname, "Block type name is required."),
-- default = state.default or nil, -- may need this later
props = state.properties,
};
end
end


-- The following assertion is not necessary by the current spec, but is required
-- by how lunajson distinguishes objects from arrays. Also if this fails, it is
-- _very_ likely that the input file is faulty.
assert(#registry == max_id + 1, "Ensure that registry has contiguous keys");

local out = {
Metadata = {
ProtocolBlockTypePaletteVersion = 1
},
Palette = registry
};

io.write(encode(out), "\n");
63 changes: 63 additions & 0 deletions Tools/BlockTypePaletteGenerator/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
This generator crafts an intermediate index format to be read by cuberite

# Running

Run `lua ./Generator.lua`, pass `blocks.json` as first argument to the script
and the desired output location as 2nd argument.

Make sure to run the Generator from within its directory (`cd` into the path
where `Generator.lua` is.)

## Examples

```bash
SERVER=/path/to/server.jar
java -cp "$SERVER" net.minecraft.data.Main --reports &&
lua Generator.lua \
generated/reports/blocks.json \
../../Server/Protocol/1.13/ProtocolBlockTypePalette.json
```

```bash
SERVER=/path/to/server.jar
java -cp "$SERVER" net.minecraft.data.Main --reports &&
lua Generator.lua - -\
< generated/reports/blocks.json \
> ../Server/Protocol/1.13/ProtocolBlockTypePalette.json
```

## Output format

The Format is a `JSON` document containing an object with at least two keys at
the top level: `Metadata` and `Palette`.

`Metadata` contains document metadata, namely a key `"ProtocolBlockType": 1`.

`Palette` contains an array of objects. Each of these objects has at least the
keys `id`, `name` and an optional `props` key that contains the individual
properties of the current state. These properties are a KV dict of pure strings.

The order of the array or object elements is not significant. `id` is unique.

```json
{
"Metadata": {
"ProtocolBlockType": 1
},
"Palette": [{
"id": 0,
"name": "minecraft:air"
}, {
"id": 1,
"name": "minecraft:stone"
}, {
"id": 221,
"name": "minecraft:dark_oak_leaves",
"props": {
"persistent": "false",
"distance": "4"
}
}
]
}
```
1 change: 1 addition & 0 deletions Tools/BlockTypePaletteGenerator/lib/lunajson
Submodule lunajson added at 1bdc88
46 changes: 46 additions & 0 deletions src/BlockState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,52 @@ BlockState::BlockState(const BlockState & aCopyFrom, const std::map<AString, ASt



bool BlockState::operator <(const BlockState & aOther) const
{
// Fast-return this using checksum
if (mChecksum != aOther.mChecksum)
{
return (mChecksum < aOther.mChecksum);
}

// Can fast-return this due to how comparison works
if (mState.size() != aOther.mState.size())
{
return (mState.size() < aOther.mState.size());
}

auto itA = mState.begin();
auto itOther = aOther.mState.begin();

// don't need to check itOther, size checks above ensure size(A) == size(O)
while (itA != mState.end())
{
{
const auto cmp = itA->first.compare(itOther->first);
if (cmp != 0)
{
return (cmp < 0);
}
}
{
const auto cmp = itA->second.compare(itOther->second);
if (cmp != 0)
{
return (cmp < 0);
}
}

++itA;
++itOther;
}

return false;
}





bool BlockState::operator ==(const BlockState & aOther) const
{
// Fast-fail if the checksums differ or differrent counts:
Expand Down
3 changes: 3 additions & 0 deletions src/BlockState.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class BlockState
(it's possible to erase a key from aCopyFrom by setting it to empty string in aAdditionalKeysAndValues). */
BlockState(const BlockState & aCopyFrom, const std::map<AString, AString> & aAdditionalKeysAndValues);

/** Less-than comparison. */
bool operator <(const BlockState & aOther) const;

/** Fast equality check. */
bool operator ==(const BlockState & aOther) const;

Expand Down
3 changes: 3 additions & 0 deletions src/Protocol/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include_directories (SYSTEM "../../lib/jsoncpp/include")

SET (SRCS
Authenticator.cpp
Expand All @@ -12,6 +13,7 @@ SET (SRCS
Protocol_1_12.cpp
Protocol_1_13.cpp
ProtocolRecognizer.cpp
ProtocolBlockTypePalette.cpp
)

SET (HDRS
Expand All @@ -28,6 +30,7 @@ SET (HDRS
Protocol_1_12.h
Protocol_1_13.h
ProtocolRecognizer.h
ProtocolBlockTypePalette.h
)

if (NOT MSVC)
Expand Down
144 changes: 144 additions & 0 deletions src/Protocol/ProtocolBlockTypePalette.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include "Globals.h"
#include "ProtocolBlockTypePalette.h"
#include <cstdint>
#include <sstream>
#include "json/value.h"
#include "json/reader.h"





ProtocolBlockTypePalette::ProtocolBlockTypePalette()
{
// empty
}





bool ProtocolBlockTypePalette::loadFromString(const AString & aMapping)
{
std::stringstream stream;
stream << aMapping;

return loadFromStream(stream);
}





bool ProtocolBlockTypePalette::loadFromStream(std::istream & aInputStream)
{
Json::Value root;

try
{
aInputStream >> root;
}
#if defined _DEBUG
catch (const std::exception & e)
{
LOGD(e.what());
return false;
}
#else
catch (const std::exception &)
{
return false;
}
#endif

if (!root.isObject() ||
!root.isMember("Metadata") ||
!root["Metadata"].isMember("ProtocolBlockTypePaletteVersion") ||
!root.isMember("Palette") ||
!root["Palette"].isArray())
{
LOGD("Incorrect palette format.");
return false;
}

if (root["Metadata"]["ProtocolBlockTypePaletteVersion"].asUInt() != 1)
{
LOGD("Palette format version not supported.");
return false;
}

auto len = root["Palette"].size();
for (decltype(len) i = 0; i < len; ++i)
{
const auto & record = root["Palette"][i];
if (!record.isObject())
{
LOGD("Record #%u must be a JSON object.", i);
return false;
}

auto blocktype = record["name"].asString();
auto id = std::stoul(record["id"].asString());
std::map<AString, AString> state;

if (id >= NOT_FOUND)
{
LOGD("`id` must be less than ProtocolBlockTypePalette::NOT_FOUND, but is %lu", id);
return false;
}

if (record.isMember("props"))
{
const auto & props = record["props"];
if (!props.isObject())
{
LOGD("`props` key must be a JSON object.");
return false;
}
for (const auto & key: props.getMemberNames())
{
state[key] = props[key].asString();
}
}

// Block type map entry already exists?
if (mIndex.count(blocktype) == 0)
{
mIndex.insert({blocktype, std::map<BlockState, UInt32>()});
}

const auto & result = mIndex[blocktype].insert({BlockState(state), id});
if (result.second == false)
{
LOGINFO("Duplicate block state encountered (Current ID: %lu, other: %lu)", result.first->second, id);
}
}
return true;
}





UInt32 ProtocolBlockTypePalette::index(const AString & aBlockTypeName, const BlockState & aBlockState) const
{
auto a = mIndex.find(aBlockTypeName);
if (a != mIndex.end())
{
auto b = a->second.find(aBlockState);
if (b != a->second.end())
{
return b->second;
}
}
return NOT_FOUND;
}





void ProtocolBlockTypePalette::clear()
{
return mIndex.clear();
}
Loading

0 comments on commit d1c9574

Please sign in to comment.