27 uv_lock = json[
"uvlock"].
get<
bool>();
47 std::set<AABB> new_colliders;
48 for (
const auto& collider : colliders)
51 switch (rotation_x / 90)
54 new_colliders.insert(collider);
57 new_center = collider.GetCenter() - 0.5;
59 new_center = new_center + 0.5;
60 new_colliders.insert(
AABB(new_center,
Vector3<double>(collider.GetHalfSize().x, collider.GetHalfSize().z, collider.GetHalfSize().y)));
63 new_center = collider.GetCenter() - 0.5;
65 new_center = new_center + 0.5;
66 new_colliders.insert(
AABB(new_center,
Vector3<double>(collider.GetHalfSize().x, collider.GetHalfSize().y, collider.GetHalfSize().z)));
69 new_center = collider.GetCenter() - 0.5;
71 new_center = new_center + 0.5;
72 new_colliders.insert(
AABB(new_center,
Vector3<double>(collider.GetHalfSize().x, collider.GetHalfSize().z, collider.GetHalfSize().y)));
75 LOG_ERROR(
"Blockstate X rotation should be in 90 degrees steps");
85 std::set<AABB> new_colliders;
86 for (
const auto& collider : colliders)
89 switch (rotation_y / 90)
92 new_colliders.insert(collider);
95 new_center = collider.GetCenter() - 0.5;
97 new_center = new_center + 0.5;
98 new_colliders.insert(
AABB(new_center,
Vector3<double>(collider.GetHalfSize().z, collider.GetHalfSize().y, collider.GetHalfSize().x)));
101 new_center = collider.GetCenter() - 0.5;
103 new_center = new_center + 0.5;
104 new_colliders.insert(
AABB(new_center,
Vector3<double>(collider.GetHalfSize().x, collider.GetHalfSize().y, collider.GetHalfSize().z)));
107 new_center = collider.GetCenter() - 0.5;
109 new_center = new_center + 0.5;
110 new_colliders.insert(
AABB(new_center,
Vector3<double>(collider.GetHalfSize().z, collider.GetHalfSize().y, collider.GetHalfSize().x)));
113 LOG_ERROR(
"Blockstate Y rotation should be in 90 degrees steps");
125 std::vector<FaceDescriptor>& faces = output.
GetFaces();
129 for (
int f = 0; f < faces.size(); ++f)
135 const auto it_cullface = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].cullface_direction);
137 if (it_cullface != rotated_orientations.end())
139 faces[f].cullface_direction = rotated_orientations[((it_cullface - rotated_orientations.begin()) + rotation_x / 90) % rotated_orientations.size()];
144 const auto it_orientation = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].orientation);
145 if (it_orientation == rotated_orientations.end())
147 faces[f].transformations.rotation -= rotation_x / 90;
150 faces[f].face =
Renderer::Face(faces[f].transformations, faces[f].orientation);
156 std::vector<FaceDescriptor>& faces = output.
GetFaces();
160 for (
int f = 0; f < faces.size(); ++f)
166 const auto it_cullface = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].cullface_direction);
168 if (it_cullface != rotated_orientations.end())
170 faces[f].cullface_direction = rotated_orientations[((it_cullface - rotated_orientations.begin()) + rotation_y / 90) % rotated_orientations.size()];
175 const auto it_orientation = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].orientation);
176 if (it_orientation == rotated_orientations.end())
179 faces[f].transformations.rotation += 2;
182 faces[f].face =
Renderer::Face(faces[f].transformations, faces[f].orientation);
192 const std::string& model_path = json[
"model"].
get_string();
193#if PROTOCOL_VERSION < 347
194 return "block/" + model_path;
195#elif PROTOCOL_VERSION > 578
197 return model_path.substr(10);
215 bool CheckCondition(
const std::string& name,
const std::string& value,
const std::vector<std::string>& variables)
221 for (
int i = 0; i < variables.size(); ++i)
225 for (
int j = 0; j < possible_values.size(); ++j)
254 if (properties.
path ==
"none")
260 if (properties.
path.empty())
266 std::string full_filepath;
270 full_filepath = ASSETS_PATH + std::string(
"/custom/blockstates/") + properties.
path +
".json";
274 full_filepath = ASSETS_PATH + std::string(
"/minecraft/blockstates/") + properties.
path +
".json";
282 std::ifstream file(full_filepath);
287 catch (
const std::runtime_error& e)
291 LOG_ERROR(
"Missing custom definition for " << full_filepath <<
'\n' << e.what());
295 LOG_ERROR(
"Error reading blockstate file at " << full_filepath <<
'\n' << e.what());
305 std::deque<std::pair<Model, int>> weighted_models;
313 if (json[
"variants"].contains(
""))
315 variant_value = json[
"variants"][
""];
318 if (json[
"variants"].contains(
"normal"))
320 variant_value = json[
"variants"][
"normal"];
329 for (
const auto& [key, val] : json[
"variants"].
get_object())
334 for (
int i = 0; i < properties.
variables.size(); ++i)
336 for (
int j = 0; j < variables_values.size(); ++j)
338 if (properties.
variables[i] == variables_values[j])
344 if (num_match > max_match)
347 max_match = num_match;
357 for (
const auto& model : variant_value.
get_array())
369 LOG_ERROR(
"Error reading " << full_filepath);
378 weighted_models.push_back({
Model(), 1 });
380 for (
const auto& part : json[
"multipart"].
get_array())
383 if (!part.contains(
"when"))
386 if (part[
"apply"].is_array())
388 size_t num_models = weighted_models.size();
389 for (
const auto& m : part[
"apply"].get_array())
392 for (
int k = 0; k < num_models; ++k)
397 weighted_models.erase(weighted_models.begin(), weighted_models.begin() + num_models);
402 for (
int k = 0; k < weighted_models.size(); ++k)
413 bool condition =
false;
415 if (part[
"when"].contains(
"OR"))
417 for (
const auto& current_condition : part[
"when"][
"OR"].get_array())
419 for (
const auto& [key, val] : current_condition.get_object())
421 const std::string condition_name = key;
422 std::string condition_value =
"";
426 condition_value = val.get_string();
428 else if (val.is_bool())
430 condition_value = val.get<
bool>() ?
"true" :
"false";
432 else if (val.is_number())
434 condition_value = std::to_string(val.get_number<
double>());
454 for (
const auto& [key, val] : part[
"when"].get_object())
456 std::string condition_value =
"";
460 condition_value = val.get_string();
462 else if (val.is_bool())
464 condition_value = val.get<
bool>() ?
"true" :
"false";
466 else if (val.is_number())
468 condition_value = std::to_string(val.get<
double>());
485 if (part[
"apply"].is_array())
487 size_t num_models = weighted_models.size();
488 for (
const auto& m : part[
"apply"].get_array())
492 for (
int k = 0; k < num_models; ++k)
497 weighted_models.erase(weighted_models.begin(), weighted_models.begin() + num_models);
503 for (
int k = 0; k < weighted_models.size(); ++k)
506 weighted_models[k].second *= model_weight;
517 std::set<AABB> colliders;
523 c[
"from"][0].get_number(),
524 c[
"from"][1].get_number(),
525 c[
"from"][2].get_number()
528 c[
"to"][0].get_number(),
529 c[
"to"][1].get_number(),
530 c[
"to"][2].get_number()
532 colliders.insert(
AABB((from + to) / 2.0 / 16.0, (to - from) / 2.0 / 16.0));
541 for (
const auto& c : v.get_array())
544 c[
"from"][0].get_number(),
545 c[
"from"][1].get_number(),
546 c[
"from"][2].get_number()
549 c[
"to"][0].get_number(),
550 c[
"to"][1].get_number(),
551 c[
"to"][2].get_number()
553 colliders.insert(
AABB((from + to) / 2.0 / 16.0, (to - from) / 2.0 / 16.0));
559 if (!colliders.empty())
561 for (
auto& [m, i] : weighted_models)
563 m.SetColliders(colliders);
598 size_t random_value = std::hash<Position>{}(pos) %
weights_sum;
599 for (
int i = 0; i < num_models; ++i)
603 return static_cast<unsigned char>(i);
623 const double max_horizontal_offset =
626 if (max_horizontal_offset == 0.0)
632 long long int pos_seed =
static_cast<long long int>(pos.
x * 3129871) ^
static_cast<long long int>(pos.
z) * 116129781LL ^ 0LL;
633 pos_seed = pos_seed * pos_seed * 42317861LL + pos_seed * 11LL;
634 pos_seed = pos_seed >> 16;
636 std::clamp((
static_cast<double>(
static_cast<float>(pos_seed & 0xFLL) / 15.0f) - 0.5) * 0.5, -max_horizontal_offset, max_horizontal_offset) + pos.
x,
638 std::clamp((
static_cast<double>(
static_cast<float>(pos_seed >> 8 & 0xFLL) / 15.0f) - 0.5) * 0.5, -max_horizontal_offset, max_horizontal_offset) + pos.
z
644 std::set<AABB> output;
650 for (
const auto& c : colliders)
652 output.insert(c + offset);
660 double distance = std::numeric_limits<double>::max();
662 for (
const auto& c : colliders)
664 const Vector3<double> closest_on_current_collider = c.GetClosestPoint(pos);
665 const double current_distance = closest_on_current_collider.
SqrDist(pos);
666 if (current_distance < distance)
668 distance = current_distance;
669 closest_point = closest_on_current_collider;
672 return closest_point;
718#if PROTOCOL_VERSION < 347
758#if PROTOCOL_VERSION < 573
767#if PROTOCOL_VERSION < 477
781#if PROTOCOL_VERSION < 347
791#if PROTOCOL_VERSION < 347
806#if PROTOCOL_VERSION < 477
815#if PROTOCOL_VERSION < 755
856 return 1.0f -
static_cast<float>(level + 1) / 9.0f;
860 const float tool_efficiency_additional_speed,
const unsigned char haste,
const unsigned char fatigue,
861 const bool on_ground,
const float speed_factor)
const
875#if PROTOCOL_VERSION > 578
880 float speed_multiplier = 1.0f;
886 if (t.tool_type != tool_type)
895 speed_multiplier = t.multiplier;
902 speed_multiplier = t.multiplier * material_multipliers[
static_cast<int>(tool_material)];
903 can_harvest |= tool_material >= t.min_material;
906 LOG_WARNING(
"Unknown tool type: " <<
static_cast<int>(tool_type));
913 if (speed_multiplier > 1.0f)
915 speed_multiplier += tool_efficiency_additional_speed;
917 speed_multiplier *= 1.0f + 0.2f * haste;
918 speed_multiplier *=
static_cast<float>(std::pow(0.3f, std::min(
static_cast<int>(fatigue), 4)));
919 speed_multiplier *= speed_factor;
920 speed_multiplier *= on_ground ? 1.0f : 0.2f;
922 const float damage_per_ticks = speed_multiplier / std::max(
hardness, 0.0000001f) / (can_harvest ? 30.0f : 100.0f);
924 if (damage_per_ticks > 1.0f)
929 return std::ceil(1.0f / damage_per_ticks) / 20.0f;
932#if PROTOCOL_VERSION < 347
933 unsigned int Blockstate::IdMetadataToId(
const int id_,
const unsigned char metadata_)
935 return id_ << 4 | metadata_;
938 void Blockstate::IdToIdMetadata(
const unsigned int input_id,
int& output_id,
unsigned char& output_metadata)
940 output_id = input_id >> 4;
941 output_metadata = input_id & 0x0F;
956 for (
auto& f : m.GetFaces())
959 std::array<unsigned short, 4> texture_pos = { 0, 0, 0, 0 };
960 std::array<unsigned short, 4> texture_size = { 0, 0, 0, 0 };
964 for (
int i = 0; i < std::min(2, static_cast<int>(f.texture_names.size())); ++i)
967 std::tie(texture_pos[2 * i + 0], texture_pos[2 * i + 1]) = texture_data.
position;
968 std::tie(texture_size[2 * i + 0], texture_size[2 * i + 1]) = texture_data.
size;
974 std::array<float, 4> coords = f.face.GetTextureCoords(
false);
976 coords[0] = (texture_pos[0] + coords[0] / 16.0f * texture_size[0]) / atlas->
GetWidth();
977 coords[1] = (texture_pos[1] + coords[1] / 16.0f * height_normalizer) / atlas->
GetHeight();
978 coords[2] = (texture_pos[0] + coords[2] / 16.0f * texture_size[0]) / atlas->
GetWidth();
979 coords[3] = (texture_pos[1] + coords[3] / 16.0f * height_normalizer) / atlas->
GetHeight();
980 f.face.SetTextureCoords(coords,
false);
983 if (f.texture_names.size() > 1)
985 coords = f.face.GetTextureCoords(
true);
987 coords[0] = (texture_pos[2] + coords[0] / 16.0f * texture_size[2]) / atlas->
GetWidth();
988 coords[1] = (texture_pos[3] + coords[1] / 16.0f * height_normalizer) / atlas->
GetHeight();
989 coords[2] = (texture_pos[2] + coords[2] / 16.0f * texture_size[2]) / atlas->
GetWidth();
990 coords[3] = (texture_pos[3] + coords[3] / 16.0f * height_normalizer) / atlas->
GetHeight();
991 f.face.SetTextureCoords(coords,
true);
994 f.face.SetTransparencyData(transparencies[0]);
1013 for (
int i = 0; i < properties.
variables.size(); ++i)
1019 if ((properties.
lava || properties.
water) && splitted[0] ==
"level")
1021 const int level = std::stoi(splitted[1]);
1028#if PROTOCOL_VERSION < 347
1066 for (
const auto& [m, w] : models_to_load)
1068 bool already_present =
false;
1073 already_present =
true;
1079 if (already_present)
1097 return condition.
get<
bool>();
1103 throw std::runtime_error(
"Unknown JSON type in GetBoolFromCondition for block " +
GetName());
1109 for (
const std::string& c : and_conditions)
1111 bool current_result =
false;
1114 const std::string& variable = splitted[0];
1115 const std::string& value = splitted[1];
1120 current_result = is_negative ? *v != value : *v == value;
1124 if (!current_result)
#define LOG_ERROR(osstream)
#define LOG_WARNING(osstream)
Vector3< double > GetClosestPoint(const Position &block_pos, const Vector3< double > &pos) const
Get the closest point on this blockstate placed at block_pos from a reference pos.
static const std::string * GetUniqueStringPtr(const std::string &s)
bool IsWaterOrWaterlogged() const
const std::string & GetName() const
BlockstateId GetId() const
bool IsDownBubbleColumn() const
bool IsTransparent() const
bool IsFluidOrWaterlogged() const
static std::set< std::string > unique_strings
bool MatchCondition(const std::string &condition) const
Check if a given string condition match this blockstate variables.
std::set< AABB > GetCollidersAtPos(const Position &pos) const
std::vector< size_t > models_indices
unsigned char GetModelId(const Position &pos) const
std::map< const std::string *, const std::string *, string_ptr_compare > variables
void LoadWeightedModels(const std::deque< std::pair< Model, int > > &models_to_load)
float GetHardness() const
bool IsFluidFalling() const
bool IsBubbleColumn() const
TintType GetTintType() const
bool IsPowderSnow() const
Vector3< double > GetHorizontalOffsetAtPos(const Position &pos) const
size_t GetNumModels() const
Blockstate(const BlockstateProperties &properties)
Create a blockstate reading files from properties path.
void LoadProperties(const BlockstateProperties &properties)
static void UpdateModelsWithAtlasData(const Renderer::Atlas *atlas)
std::vector< int > models_weights
std::vector< BestTool > best_tools
bool IsUpBubbleColumn() const
static std::deque< Model > unique_models
bool IsWaterlogged() const
std::bitset< static_cast< size_t >(BlockstateFlags::NUM_FLAGS)> flags
BlockstateId blockstate_id
float GetFriction() const
const std::string * m_name
static size_t GetUniqueModelIndex(const Model &model)
float GetFluidHeight() const
Get fluid height for this block.
const std::string & GetVariableValue(const std::string &variable) const
static std::map< std::string, ProtocolCraft::Json::Value > cached_jsons
bool IsScaffolding() const
const Model & GetModel(const unsigned short index) const
float GetMiningTimeSeconds(const ToolType tool_type, const ToolMaterial tool_material, const float tool_efficiency_additional_speed=0.0f, const unsigned char haste=0, const unsigned char fatigue=0, const bool on_ground=true, const float speed_factor=1.0f) const
Compute the amount of time (in s) required to mine this block.
bool GetBoolFromCondition(const ProtocolCraft::Json::Value &condition) const
const std::set< AABB > & GetColliders() const
void SetColliders(const std::set< AABB > &colliders_)
const std::vector< FaceDescriptor > & GetFaces() const
static const Model & GetModel(const std::string &filepath, const bool custom)
bool IsSame(const Model &other) const
Compare two models.
const TextureData & GetData(const std::string &name) const
Main class, basically a JsonVariant with extra utility functions it doesn't inherit JsonVariant direc...
void push_back(const Value &value)
bool contains(const std::string &s) const
std::string & get_string()
std::shared_ptr< Transformation > TransformationPtr
bool Contains(const std::string &mainStr, const std::string &toFind)
bool StartsWith(const std::string &mainStr, const std::string &toMatch)
std::vector< std::string > SplitString(const std::string &s, const char delimiter)
bool EndsWith(const std::string &mainStr, const std::string &toMatch)
bool CheckCondition(const std::string &name, const std::string &value, const std::vector< std::string > &variables)
int WeightFromJson(const Json::Value &json)
std::string ModelNameFromJson(const Json::Value &json)
Model ModelModificationFromJson(const Model &model, const Json::Value &json)
unsigned int BlockstateId
bool climbable
True if can be used as a ladder.
bool powder_snow
True if this block is powder_snow.
ProtocolCraft::Json::Value down_bubble_column
True if this block is a bubble column going down.
bool cobweb
True if this block is cobweb.
bool water
True for water.
std::vector< std::string > variables
bool honey
True if this block is honey.
float friction
Slipperiness coefficient.
std::vector< BestTool > best_tools
bool slime
True if this block is slime.
float horizontal_offset
Max horizontal offset value of the colliders (for bamboo and pointed dripstone)
float hardness
Digging hardness.
bool berry_bush
True if this block is sweet_berry_bush.
ProtocolCraft::Json::Value colliders
bool transparent
True if not a full 1x1x1 block OR at least one face texture has transparency.
ProtocolCraft::Json::Value solid
True if can't go through it.
bool soul_sand
True if this block is soul_sand.
bool custom
True if the model is a custom one (chests/banners etc...)
ProtocolCraft::Json::Value waterlogged
True for blocks that are always waterlogged (kelp, seagrass...)
bool any_tool_harvest
True if this block drops item when broken with no tool.
bool air
True if the block is air (air, cave_air, void and structure_void are counted as air)
bool hazardous
True if block can hurt when walking in/on it.
ProtocolCraft::Json::Value up_bubble_column
True if this block is a bubble column going up.
bool scaffolding
True if this block is scaffolding.
bool bed
True if this block has the BEDS tag.
std::pair< int, int > position
<Col, Row>
Transparency transparency
std::pair< int, int > size
<Width, Height>
double SqrDist(const Vector3 &v) const