Botcraft 1.21.4
Loading...
Searching...
No Matches
Blockstate.cpp
Go to the documentation of this file.
1#include <deque>
2#include <fstream>
3#include <limits>
4#include <set>
5
9
10#if USE_GUI
12#endif
13
14using namespace ProtocolCraft;
15
16namespace Botcraft
17{
18 // Utilities functions
19
21 {
22 Model output = model;
23
24 bool uv_lock = false;
25 if (json.contains("uvlock"))
26 {
27 uv_lock = json["uvlock"].get<bool>();
28 }
29
30 int rotation_x = 0;
31 if (json.contains("x"))
32 {
33 rotation_x = json["x"].get_number<int>();
34 }
35
36 int rotation_y = 0;
37 if (json.contains("y"))
38 {
39 rotation_y = json["y"].get_number<int>();
40 }
41
42 //Rotate colliders
43
44 if (rotation_x != 0)
45 {
46 const std::set<AABB>& colliders = output.GetColliders();
47 std::set<AABB> new_colliders;
48 for (const auto& collider : colliders)
49 {
50 Vector3<double> new_center;
51 switch (rotation_x / 90)
52 {
53 case 0:
54 new_colliders.insert(collider);
55 break;
56 case 1:
57 new_center = collider.GetCenter() - 0.5;
58 new_center = Vector3<double>(new_center.x, new_center.z, -new_center.y);
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)));
61 break;
62 case 2:
63 new_center = collider.GetCenter() - 0.5;
64 new_center = Vector3<double>(new_center.x, -new_center.y, -new_center.z);
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)));
67 break;
68 case 3:
69 new_center = collider.GetCenter() - 0.5;
70 new_center = Vector3<double>(new_center.x, -new_center.z, new_center.y);
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)));
73 break;
74 default:
75 LOG_ERROR("Blockstate X rotation should be in 90 degrees steps");
76 break;
77 }
78 }
79 output.SetColliders(new_colliders);
80 }
81
82 if (rotation_y != 0)
83 {
84 const std::set<AABB>& colliders = output.GetColliders();
85 std::set<AABB> new_colliders;
86 for (const auto& collider : colliders)
87 {
88 Vector3<double> new_center;
89 switch (rotation_y / 90)
90 {
91 case 0:
92 new_colliders.insert(collider);
93 break;
94 case 1:
95 new_center = collider.GetCenter() - 0.5;
96 new_center = Vector3<double>(-new_center.z, new_center.y, new_center.x);
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)));
99 break;
100 case 2:
101 new_center = collider.GetCenter() - 0.5;
102 new_center = Vector3<double>(-new_center.x, new_center.y, -new_center.z);
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)));
105 break;
106 case 3:
107 new_center = collider.GetCenter() - 0.5;
108 new_center = Vector3<double>(new_center.z, new_center.y, -new_center.x);
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)));
111 break;
112 default:
113 LOG_ERROR("Blockstate Y rotation should be in 90 degrees steps");
114 break;
115 }
116 }
117 output.SetColliders(new_colliders);
118 }
119
120#ifdef USE_GUI
121 //Rotate faces
122
123 if (rotation_x != 0)
124 {
125 std::vector<FaceDescriptor>& faces = output.GetFaces();
126
127 std::vector<Orientation> rotated_orientations = { Orientation::Top, Orientation::North, Orientation::Bottom, Orientation::South };
128
129 for (int f = 0; f < faces.size(); ++f)
130 {
131 //We want to add this transformation after the local translations,
132 //Just before the global translation to final position
133 faces[f].transformations.translations.insert(faces[f].transformations.translations.begin(), Renderer::TransformationPtr(new Renderer::Rotation(1.0f, 0.0f, 0.0f, -static_cast<float>(rotation_x))));
134
135 const auto it_cullface = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].cullface_direction);
136
137 if (it_cullface != rotated_orientations.end())
138 {
139 faces[f].cullface_direction = rotated_orientations[((it_cullface - rotated_orientations.begin()) + rotation_x / 90) % rotated_orientations.size()];
140 }
141
142 if (uv_lock)
143 {
144 const auto it_orientation = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].orientation);
145 if (it_orientation == rotated_orientations.end())
146 {
147 faces[f].transformations.rotation -= rotation_x / 90;
148 }
149 }
150 faces[f].face = Renderer::Face(faces[f].transformations, faces[f].orientation);
151 }
152 }
153
154 if (rotation_y != 0)
155 {
156 std::vector<FaceDescriptor>& faces = output.GetFaces();
157
158 std::vector<Orientation> rotated_orientations = { Orientation::South, Orientation::West, Orientation::North, Orientation::East };
159
160 for (int f = 0; f < faces.size(); ++f)
161 {
162 //We want to add this transformation after the local translations,
163 //Just before the global translation to final position
164 faces[f].transformations.translations.insert(faces[f].transformations.translations.begin(), Renderer::TransformationPtr(new Renderer::Rotation(0.0f, 1.0f, 0.0f, -static_cast<float>(rotation_y))));
165
166 const auto it_cullface = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].cullface_direction);
167
168 if (it_cullface != rotated_orientations.end())
169 {
170 faces[f].cullface_direction = rotated_orientations[((it_cullface - rotated_orientations.begin()) + rotation_y / 90) % rotated_orientations.size()];
171 }
172
173 if (uv_lock)
174 {
175 const auto it_orientation = std::find(rotated_orientations.begin(), rotated_orientations.end(), faces[f].orientation);
176 if (it_orientation == rotated_orientations.end())
177 {
178 // Not sure why it's always 2 here...
179 faces[f].transformations.rotation += 2;
180 }
181 }
182 faces[f].face = Renderer::Face(faces[f].transformations, faces[f].orientation);
183 }
184 }
185#endif
186
187 return output;
188 }
189
190 std::string ModelNameFromJson(const Json::Value& json)
191 {
192 const std::string& model_path = json["model"].get_string();
193#if PROTOCOL_VERSION < 347 /* < 1.13 */
194 return "block/" + model_path;
195#elif PROTOCOL_VERSION > 578 /* > 1.15.2 */
196 // Remove the minecraft: prefix from the model path
197 return model_path.substr(10);
198#else
199 return model_path;
200#endif
201 }
202
204 {
205 int output = 1;
206
207 if (json.contains("weight"))
208 {
209 output = json["weight"].get_number<int>();
210 }
211
212 return output;
213 }
214
215 bool CheckCondition(const std::string& name, const std::string& value, const std::vector<std::string>& variables)
216 {
217 const std::vector<std::string> possible_values = Utilities::SplitString(value, '|');
218
219 bool output = false;
220
221 for (int i = 0; i < variables.size(); ++i)
222 {
223 if (Utilities::StartsWith(variables[i], name))
224 {
225 for (int j = 0; j < possible_values.size(); ++j)
226 {
227 if (Utilities::EndsWith(variables[i], possible_values[j]))
228 {
229 output = true;
230 break;
231 }
232 }
233 if (output)
234 {
235 break;
236 }
237 }
238 }
239
240 return output;
241 }
242
243 // Blockstate implementation starts here
244 std::map<std::string, Json::Value> Blockstate::cached_jsons;
245 std::set<std::string> Blockstate::unique_strings;
246 std::deque<Model> Blockstate::unique_models;
247
249 {
250 LoadProperties(properties);
251
252 weights_sum = 0;
253
254 if (properties.path == "none")
255 {
256 LoadWeightedModels({ {Model(), 1} });
257 return;
258 }
259
260 if (properties.path.empty())
261 {
262 LoadWeightedModels({ {Model::GetModel("", false), 1} });
263 return;
264 }
265
266 std::string full_filepath;
267
268 if (properties.custom)
269 {
270 full_filepath = ASSETS_PATH + std::string("/custom/blockstates/") + properties.path + ".json";
271 }
272 else
273 {
274 full_filepath = ASSETS_PATH + std::string("/minecraft/blockstates/") + properties.path + ".json";
275 }
276
277 try
278 {
279 auto it = cached_jsons.find(full_filepath);
280 if (it == cached_jsons.end())
281 {
282 std::ifstream file(full_filepath);
283 file >> cached_jsons[full_filepath];
284 file.close();
285 }
286 }
287 catch (const std::runtime_error& e)
288 {
289 if (properties.custom)
290 {
291 LOG_ERROR("Missing custom definition for " << full_filepath << '\n' << e.what());
292 }
293 else
294 {
295 LOG_ERROR("Error reading blockstate file at " << full_filepath << '\n' << e.what());
296 }
297
298 LoadWeightedModels({ {Model::GetModel("", false), 1} });
299 return;
300 }
301
302 const Json::Value& json = cached_jsons[full_filepath];
303
304 // We store the models in a deque for efficiency
305 std::deque<std::pair<Model, int>> weighted_models;
306
307 //If it's a "normal" blockstate
308 if (json.contains("variants"))
309 {
310 Json::Value null_value;
311 Json::Value& variant_value = null_value;
312
313 if (json["variants"].contains(""))
314 {
315 variant_value = json["variants"][""];
316 }
317
318 if (json["variants"].contains("normal"))
319 {
320 variant_value = json["variants"]["normal"];
321 }
322
323 //This case means we have to check the variables to find
324 //the right variant
325 if (properties.variables.size() > 0 && variant_value.is_null())
326 {
327 int max_match = 0;
328
329 for (const auto& [key, val] : json["variants"].get_object())
330 {
331 const std::vector<std::string> variables_values = Utilities::SplitString(key, ',');
332
333 int num_match = 0;
334 for (int i = 0; i < properties.variables.size(); ++i)
335 {
336 for (int j = 0; j < variables_values.size(); ++j)
337 {
338 if (properties.variables[i] == variables_values[j])
339 {
340 num_match++;
341 }
342 }
343 }
344 if (num_match > max_match)
345 {
346 variant_value = val;
347 max_match = num_match;
348 }
349 }
350 }
351
352 //If we have found a correct value
353 if (!variant_value.is_null())
354 {
355 if (variant_value.is_array())
356 {
357 for (const auto& model : variant_value.get_array())
358 {
359 weighted_models.push_back({ ModelModificationFromJson(Model::GetModel(ModelNameFromJson(model), properties.custom), model), WeightFromJson(model) });
360 }
361 }
362 else
363 {
364 weighted_models.push_back({ ModelModificationFromJson(Model::GetModel(ModelNameFromJson(variant_value), properties.custom), variant_value), WeightFromJson(variant_value) });
365 }
366 }
367 else
368 {
369 LOG_ERROR("Error reading " << full_filepath);
370 weighted_models.push_back({ Model::GetModel("", false), 1 });
371 }
372 }
373
374 //If it's a multipart blockstate
375 if (json.contains("multipart"))
376 {
377 //Start with an empty model
378 weighted_models.push_back({ Model(), 1 });
379
380 for (const auto& part : json["multipart"].get_array())
381 {
382 //If no condition
383 if (!part.contains("when"))
384 {
385 //If there are several models
386 if (part["apply"].is_array())
387 {
388 size_t num_models = weighted_models.size();
389 for (const auto& m : part["apply"].get_array())
390 {
391 const std::string model_name = ModelNameFromJson(m);
392 for (int k = 0; k < num_models; ++k)
393 {
394 weighted_models.push_back({ weighted_models[k].first + ModelModificationFromJson(Model::GetModel(model_name, properties.custom), m), weighted_models[k].second * WeightFromJson(m) });
395 }
396 }
397 weighted_models.erase(weighted_models.begin(), weighted_models.begin() + num_models);
398 }
399 else
400 {
401 const std::string model_name = ModelNameFromJson(part["apply"]);
402 for (int k = 0; k < weighted_models.size(); ++k)
403 {
404 weighted_models[k].first += ModelModificationFromJson(Model::GetModel(model_name, properties.custom), part["apply"]);
405 weighted_models[k].second *= WeightFromJson(part["apply"]);
406 }
407 }
408 }
409 //There is a condition !
410 //We check if there is a match with the variables
411 else
412 {
413 bool condition = false;
414 //If it's a OR condition
415 if (part["when"].contains("OR"))
416 {
417 for (const auto& current_condition : part["when"]["OR"].get_array())
418 {
419 for (const auto& [key, val] : current_condition.get_object())
420 {
421 const std::string condition_name = key;
422 std::string condition_value = "";
423
424 if (val.is_string())
425 {
426 condition_value = val.get_string();
427 }
428 else if (val.is_bool())
429 {
430 condition_value = val.get<bool>() ? "true" : "false";
431 }
432 else if (val.is_number())
433 {
434 condition_value = std::to_string(val.get_number<double>());
435 }
436
437 condition = CheckCondition(condition_name, condition_value, properties.variables);
438 //If one condition in the list is not verified,
439 //the whole condition is not
440 if (!condition)
441 {
442 break;
443 }
444 }
445
446 if (condition)
447 {
448 break;
449 }
450 }
451 }
452 else
453 {
454 for (const auto& [key, val] : part["when"].get_object())
455 {
456 std::string condition_value = "";
457
458 if (val.is_string())
459 {
460 condition_value = val.get_string();
461 }
462 else if (val.is_bool())
463 {
464 condition_value = val.get<bool>() ? "true" : "false";
465 }
466 else if (val.is_number())
467 {
468 condition_value = std::to_string(val.get<double>());
469 }
470
471 condition = CheckCondition(key, condition_value, properties.variables);
472 //If one condition in the list is not verified,
473 //the whole condition is not
474 if (!condition)
475 {
476 break;
477 }
478 }
479 }
480
481 //If the condition is verified, add the model
482 if (condition)
483 {
484 //If there are several models
485 if (part["apply"].is_array())
486 {
487 size_t num_models = weighted_models.size();
488 for (const auto& m : part["apply"].get_array())
489 {
490 const std::string model_name = ModelNameFromJson(m);
491 const int model_weight = WeightFromJson(m);
492 for (int k = 0; k < num_models; ++k)
493 {
494 weighted_models.push_back({ weighted_models[k].first + ModelModificationFromJson(Model::GetModel(model_name, properties.custom), m), weighted_models[k].second * model_weight });
495 }
496 }
497 weighted_models.erase(weighted_models.begin(), weighted_models.begin() + num_models);
498 }
499 else
500 {
501 const std::string model_name = ModelNameFromJson(part["apply"]);
502 const int model_weight = WeightFromJson(part["apply"]);
503 for (int k = 0; k < weighted_models.size(); ++k)
504 {
505 weighted_models[k].first += ModelModificationFromJson(Model::GetModel(model_name, properties.custom), part["apply"]);
506 weighted_models[k].second *= model_weight;
507 }
508 }
509 }
510 }
511 }
512 }
513
514 // If this block has colliders different from rendered model
515 if (!properties.colliders.is_null())
516 {
517 std::set<AABB> colliders;
518 if (properties.colliders.is_array())
519 {
520 for (const auto& c : properties.colliders.get_array())
521 {
522 Vector3<double> from(
523 c["from"][0].get_number(),
524 c["from"][1].get_number(),
525 c["from"][2].get_number()
526 );
528 c["to"][0].get_number(),
529 c["to"][1].get_number(),
530 c["to"][2].get_number()
531 );
532 colliders.insert(AABB((from + to) / 2.0 / 16.0, (to - from) / 2.0 / 16.0));
533 }
534 }
535 else if (properties.colliders.is_object())
536 {
537 for (const auto& [k, v] : properties.colliders.get_object())
538 {
539 if (MatchCondition(k))
540 {
541 for (const auto& c : v.get_array())
542 {
543 Vector3<double> from(
544 c["from"][0].get_number(),
545 c["from"][1].get_number(),
546 c["from"][2].get_number()
547 );
549 c["to"][0].get_number(),
550 c["to"][1].get_number(),
551 c["to"][2].get_number()
552 );
553 colliders.insert(AABB((from + to) / 2.0 / 16.0, (to - from) / 2.0 / 16.0));
554 }
555 break;
556 }
557 }
558 }
559 if (!colliders.empty())
560 {
561 for (auto& [m, i] : weighted_models)
562 {
563 m.SetColliders(colliders);
564 }
565 }
566 }
567
568 LoadWeightedModels(weighted_models);
569 }
570
571 Blockstate::Blockstate(const BlockstateProperties& properties, const Model& model_)
572 {
573 LoadProperties(properties);
574
575 LoadWeightedModels({ {model_, 1} });
576 }
577
579 {
580 return blockstate_id;
581 }
582
583 const Model& Blockstate::GetModel(const unsigned short index) const
584 {
585 return unique_models.at(models_indices[index]);
586 }
587
588 unsigned char Blockstate::GetModelId(const Position& pos) const
589 {
590 const size_t num_models = models_weights.size();
591
592 // If there is only one model, don't bother computing hash
593 if (num_models == 1)
594 {
595 return 0;
596 }
597
598 size_t random_value = std::hash<Position>{}(pos) % weights_sum;
599 for (int i = 0; i < num_models; ++i)
600 {
601 if (random_value < models_weights[i])
602 {
603 return static_cast<unsigned char>(i);
604 }
605 random_value -= models_weights[i];
606 }
607 // Should never be here
608 return 0;
609 }
610
611 const std::string& Blockstate::GetName() const
612 {
613 return *m_name;
614 }
615
616 const std::string& Blockstate::GetVariableValue(const std::string& variable) const
617 {
618 return *variables.at(&variable);
619 }
620
622 {
623 const double max_horizontal_offset =
624 0.125f * flags[static_cast<size_t>(BlockstateFlags::HorizontalOffset0_125)] +
625 0.25f * flags[static_cast<size_t>(BlockstateFlags::HorizontalOffset0_25)];
626 if (max_horizontal_offset == 0.0)
627 {
628 return Vector3<double>(pos.x, pos.y, pos.z);
629 }
630
631 // We need to offset the colliders based on position-based pseudo randomness
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;
635 return Vector3<double>(
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,
637 0.0 + pos.y,
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
639 );
640 }
641
642 std::set<AABB> Blockstate::GetCollidersAtPos(const Position& pos) const
643 {
644 std::set<AABB> output;
645
646 const Model& model = GetModel(GetModelId(pos));
647
648 const std::set<AABB>& colliders = model.GetColliders();
649 const Vector3<double> offset = GetHorizontalOffsetAtPos(pos);
650 for (const auto& c : colliders)
651 {
652 output.insert(c + offset);
653 }
654 return output;
655 }
656
658 {
659 const std::set<AABB> colliders = GetCollidersAtPos(block_pos);
660 double distance = std::numeric_limits<double>::max();
661 Vector3<double> closest_point;
662 for (const auto& c : colliders)
663 {
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)
667 {
668 distance = current_distance;
669 closest_point = closest_on_current_collider;
670 }
671 }
672 return closest_point;
673 }
674
675 bool Blockstate::IsAir() const
676 {
677 return flags[static_cast<size_t>(BlockstateFlags::Air)];
678 }
679
681 {
682 return flags[static_cast<size_t>(BlockstateFlags::Solid)];
683 }
684
686 {
687 return flags[static_cast<size_t>(BlockstateFlags::Transparent)];
688 }
689
691 {
692 return flags[static_cast<size_t>(BlockstateFlags::Water)] ||
693 flags[static_cast<size_t>(BlockstateFlags::Lava)];
694 }
695
697 {
698 return IsFluid() || flags[static_cast<size_t>(BlockstateFlags::WaterLogged)];
699 }
700
702 {
703 return flags[static_cast<size_t>(BlockstateFlags::Lava)];
704 }
705
707 {
708 return flags[static_cast<size_t>(BlockstateFlags::FluidFalling)];
709 }
710
712 {
713 return flags[static_cast<size_t>(BlockstateFlags::Water)];
714 }
715
717 {
718#if PROTOCOL_VERSION < 347 /* < 1.13 */
719 // No waterlogging before 1.13
720 return false;
721#else
722 return flags[static_cast<size_t>(BlockstateFlags::WaterLogged)];
723#endif
724 }
725
727 {
728 return IsWater() || IsWaterlogged();
729 }
730
732 {
733 return flags[static_cast<size_t>(BlockstateFlags::Climbable)];
734 }
735
737 {
738 return flags[static_cast<size_t>(BlockstateFlags::Hazardous)];
739 }
740
742 {
743 return flags[static_cast<size_t>(BlockstateFlags::Slime)];
744 }
745
746 bool Blockstate::IsBed() const
747 {
748 return flags[static_cast<size_t>(BlockstateFlags::Bed)];
749 }
750
752 {
753 return flags[static_cast<size_t>(BlockstateFlags::SoulSand)];
754 }
755
757 {
758#if PROTOCOL_VERSION < 573 /* < 1.15 */
759 return false;
760#else
761 return flags[static_cast<size_t>(BlockstateFlags::Honey)];
762#endif
763 }
764
766 {
767#if PROTOCOL_VERSION < 477 /* < 1.14 */
768 return false;
769#else
770 return flags[static_cast<size_t>(BlockstateFlags::Scaffolding)];
771#endif
772 }
773
775 {
777 }
778
780 {
781#if PROTOCOL_VERSION < 347 /* < 1.13 */
782 // No bubble column before 1.13
783 return false;
784#else
785 return flags[static_cast<size_t>(BlockstateFlags::UpBubbleColumn)];
786#endif
787 }
788
790 {
791#if PROTOCOL_VERSION < 347 /* < 1.13 */
792 // No bubble column before 1.13
793 return false;
794#else
795 return flags[static_cast<size_t>(BlockstateFlags::DownBubbleColumn)];
796#endif
797 }
798
800 {
801 return flags[static_cast<size_t>(BlockstateFlags::Cobweb)];
802 }
803
805 {
806#if PROTOCOL_VERSION < 477 /* < 1.14 */
807 return false;
808#else
809 return flags[static_cast<size_t>(BlockstateFlags::BerryBush)];
810#endif
811 }
812
814 {
815#if PROTOCOL_VERSION < 755 /* < 1.17 */
816 return false;
817#else
818 return flags[static_cast<size_t>(BlockstateFlags::PowderSnow)];
819#endif
820 }
821
823 {
824 return hardness;
825 }
826
828 {
829 return friction;
830 }
831
833 {
834 return tint_type;
835 }
836
838 {
840 {
841 return 0.0f;
842 }
843 if (flags[static_cast<size_t>(BlockstateFlags::FluidFalling)])
844 {
845 return 1.0f;
846 }
847 if (IsWaterlogged())
848 {
849 return 8.0f / 9.0f;
850 }
851
852 const char level = static_cast<char>(flags[static_cast<size_t>(BlockstateFlags::FluidLevelBit_0)]) |
853 static_cast<char>(flags[static_cast<size_t>(BlockstateFlags::FluidLevelBit_1)] << 1) |
854 static_cast<char>(flags[static_cast<size_t>(BlockstateFlags::FluidLevelBit_2)] << 2);
855
856 return 1.0f - static_cast<float>(level + 1) / 9.0f;
857 }
858
859 float Blockstate::GetMiningTimeSeconds(const ToolType tool_type, const ToolMaterial tool_material,
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
862 {
863 if (hardness < 0.0f || IsWater() || IsLava())
864 {
865 return -1.0f;
866 }
867
868 static const std::array<float, static_cast<int>(ToolMaterial::NUM_TOOL_MATERIAL)> material_multipliers{ {
869 1.0f,
870 2.0f,
871 12.0f,
872 4.0f,
873 6.0f,
874 8.0f,
875#if PROTOCOL_VERSION > 578 /* > 1.15.2 */
876 9.0f,
877#endif
878 } };
879
880 float speed_multiplier = 1.0f;
881 bool can_harvest = flags[static_cast<size_t>(BlockstateFlags::AnyToolHarvest)];
882
883 for (const auto& t : best_tools)
884 {
885 // If this is not the tool we use, just skip to next one
886 if (t.tool_type != tool_type)
887 {
888 continue;
889 }
890
891 switch (tool_type)
892 {
893 case ToolType::Shears:
894 case ToolType::Sword:
895 speed_multiplier = t.multiplier;
896 can_harvest = true;
897 break;
898 case ToolType::Axe:
899 case ToolType::Hoe:
901 case ToolType::Shovel:
902 speed_multiplier = t.multiplier * material_multipliers[static_cast<int>(tool_material)];
903 can_harvest |= tool_material >= t.min_material;
904 break;
905 default:
906 LOG_WARNING("Unknown tool type: " << static_cast<int>(tool_type));
907 break;
908 }
909 // If we are here we already found and processed the current tool
910 break;
911 }
912
913 if (speed_multiplier > 1.0f)
914 {
915 speed_multiplier += tool_efficiency_additional_speed;
916 }
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;
921
922 const float damage_per_ticks = speed_multiplier / std::max(hardness, 0.0000001f) / (can_harvest ? 30.0f : 100.0f);
923
924 if (damage_per_ticks > 1.0f)
925 {
926 return 0.0f;
927 }
928
929 return std::ceil(1.0f / damage_per_ticks) / 20.0f;
930 }
931
932#if PROTOCOL_VERSION < 347 /* < 1.13 */
933 unsigned int Blockstate::IdMetadataToId(const int id_, const unsigned char metadata_)
934 {
935 return id_ << 4 | metadata_;
936 }
937
938 void Blockstate::IdToIdMetadata(const unsigned int input_id, int& output_id, unsigned char& output_metadata)
939 {
940 output_id = input_id >> 4;
941 output_metadata = input_id & 0x0F;
942 }
943#endif
944
946 {
947 cached_jsons.clear();
948 unique_models.shrink_to_fit();
949 }
950
951#if USE_GUI
953 {
954 for (auto& m : unique_models)
955 {
956 for (auto& f : m.GetFaces())
957 {
958 // Extract texture info in the atlas
959 std::array<unsigned short, 4> texture_pos = { 0, 0, 0, 0 };
960 std::array<unsigned short, 4> texture_size = { 0, 0, 0, 0 };
961 std::array<Renderer::Transparency, 2> transparencies = { Renderer::Transparency::Opaque, Renderer::Transparency::Opaque };
962 std::array<Renderer::Animation, 2> animated = { Renderer::Animation::Static, Renderer::Animation::Static };
963
964 for (int i = 0; i < std::min(2, static_cast<int>(f.texture_names.size())); ++i)
965 {
966 const Renderer::TextureData& texture_data = atlas->GetData(f.texture_names[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;
969 transparencies[i] = texture_data.transparency;
970 animated[i] = texture_data.animation;
971 }
972
973 // Main texture coords in the atlas
974 std::array<float, 4> coords = f.face.GetTextureCoords(false);
975 unsigned short height_normalizer = animated[0] == Renderer::Animation::Animated ? texture_size[0] : texture_size[1];
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);
981
982 // Overlay texture coords in the atlas if existing
983 if (f.texture_names.size() > 1)
984 {
985 coords = f.face.GetTextureCoords(true);
986 height_normalizer = animated[1] == Renderer::Animation::Animated ? texture_size[2] : texture_size[3];
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);
992 }
993
994 f.face.SetTransparencyData(transparencies[0]);
995 }
996 }
997 }
998#endif
999
1001 {
1002 return models_indices.size();
1003 }
1004
1006 {
1007 m_name = GetUniqueStringPtr(properties.name);
1008 hardness = properties.hardness;
1009 friction = properties.friction;
1010 tint_type = properties.tint_type;
1011 best_tools = properties.best_tools;
1012
1013 for (int i = 0; i < properties.variables.size(); ++i)
1014 {
1015 std::vector<std::string> splitted = Utilities::SplitString(properties.variables[i], '=');
1016 variables[GetUniqueStringPtr(splitted[0])] = GetUniqueStringPtr(splitted[1]);
1017
1018 // Special case for fluids
1019 if ((properties.lava || properties.water) && splitted[0] == "level")
1020 {
1021 const int level = std::stoi(splitted[1]);
1022 flags[static_cast<size_t>(BlockstateFlags::FluidFalling)] = (level >> 3) & 0x01;
1023 flags[static_cast<size_t>(BlockstateFlags::FluidLevelBit_0)] = (level >> 0) & 0x01;
1024 flags[static_cast<size_t>(BlockstateFlags::FluidLevelBit_1)] = (level >> 1) & 0x01;
1025 flags[static_cast<size_t>(BlockstateFlags::FluidLevelBit_2)] = (level >> 2) & 0x01;
1026 }
1027 }
1028#if PROTOCOL_VERSION < 347 /* < 1.13 */
1029 blockstate_id = { properties.id, properties.metadata };
1030#else
1031 blockstate_id = properties.id;
1032#endif
1033
1034 flags[static_cast<size_t>(BlockstateFlags::Air)] = properties.air;
1035 flags[static_cast<size_t>(BlockstateFlags::Solid)] = GetBoolFromCondition(properties.solid);
1036 flags[static_cast<size_t>(BlockstateFlags::Transparent)] = properties.transparent;
1037 flags[static_cast<size_t>(BlockstateFlags::Lava)] = properties.lava;
1038 flags[static_cast<size_t>(BlockstateFlags::Water)] = properties.water;
1039 flags[static_cast<size_t>(BlockstateFlags::WaterLogged)] = GetBoolFromCondition(properties.waterlogged);
1040 // FluidFalling and FluidLevelBit_N are already set when loading variables
1041 flags[static_cast<size_t>(BlockstateFlags::Climbable)] = properties.climbable;
1042 flags[static_cast<size_t>(BlockstateFlags::Hazardous)] = properties.hazardous;
1043 flags[static_cast<size_t>(BlockstateFlags::AnyToolHarvest)] = properties.any_tool_harvest;
1044 flags[static_cast<size_t>(BlockstateFlags::Slime)] = properties.slime;
1045 flags[static_cast<size_t>(BlockstateFlags::Bed)] = properties.bed;
1046 flags[static_cast<size_t>(BlockstateFlags::SoulSand)] = properties.soul_sand;
1047 flags[static_cast<size_t>(BlockstateFlags::Honey)] = properties.honey;
1048 flags[static_cast<size_t>(BlockstateFlags::Scaffolding)] = properties.scaffolding;
1049 flags[static_cast<size_t>(BlockstateFlags::Cobweb)] = properties.cobweb;
1052 flags[static_cast<size_t>(BlockstateFlags::BerryBush)] = properties.berry_bush;
1053 flags[static_cast<size_t>(BlockstateFlags::PowderSnow)] = properties.powder_snow;
1054 flags[static_cast<size_t>(BlockstateFlags::HorizontalOffset0_25)] = std::abs(properties.horizontal_offset - 0.25f) < 0.01f;
1055 flags[static_cast<size_t>(BlockstateFlags::HorizontalOffset0_125)] = std::abs(properties.horizontal_offset - 0.125f) < 0.01f;
1056 }
1057
1058 void Blockstate::LoadWeightedModels(const std::deque<std::pair<Model, int>>& models_to_load)
1059 {
1060 models_indices.clear();
1061 models_indices.reserve(models_to_load.size());
1062 models_weights.clear();
1063 models_weights.reserve(models_to_load.size());
1064 weights_sum = 0;
1065
1066 for (const auto& [m, w] : models_to_load)
1067 {
1068 bool already_present = false;
1069 for (size_t i = 0; i < models_indices.size(); ++i)
1070 {
1071 if (unique_models[models_indices[i]].IsSame(m))
1072 {
1073 already_present = true;
1074 models_weights[i] += w;
1075 weights_sum += w;
1076 break;
1077 }
1078 }
1079 if (already_present)
1080 {
1081 continue;
1082 }
1083
1084 models_indices.push_back(GetUniqueModelIndex(m));
1085 models_weights.push_back(w);
1086 weights_sum += w;
1087 }
1088
1089 models_indices.shrink_to_fit();
1090 models_weights.shrink_to_fit();
1091 }
1092
1094 {
1095 if (condition.is_bool())
1096 {
1097 return condition.get<bool>();
1098 }
1099 else if (condition.is_string())
1100 {
1101 return MatchCondition(condition.get_string());
1102 }
1103 throw std::runtime_error("Unknown JSON type in GetBoolFromCondition for block " + GetName());
1104 }
1105
1106 bool Blockstate::MatchCondition(const std::string& condition) const
1107 {
1108 const std::vector<std::string> and_conditions = Utilities::SplitString(condition, ',');
1109 for (const std::string& c : and_conditions)
1110 {
1111 bool current_result = false;
1112 const bool is_negative = Utilities::Contains(c, "!=");
1113 const std::vector<std::string> splitted = is_negative ? Utilities::SplitString(c, "!=") : Utilities::SplitString(c, '=');
1114 const std::string& variable = splitted[0];
1115 const std::string& value = splitted[1];
1116 for (const auto [k, v] : variables)
1117 {
1118 if (*k == variable)
1119 {
1120 current_result = is_negative ? *v != value : *v == value;
1121 break;
1122 }
1123 }
1124 if (!current_result)
1125 {
1126 return false;
1127 }
1128 }
1129 return true;
1130 }
1131
1132 const std::string* Blockstate::GetUniqueStringPtr(const std::string& s)
1133 {
1134 return &*unique_strings.insert(s).first;
1135 }
1136
1138 {
1139 // Don't bother searching for a preexisting model if USE_GUI
1140 // as IsSame always returns false anyway
1141#if !USE_GUI
1142 for (size_t i = 0; i < unique_models.size(); ++i)
1143 {
1144 if (model.IsSame(unique_models[i]))
1145 {
1146 return i;
1147 }
1148 }
1149#endif
1150 unique_models.push_back(model);
1151 return unique_models.size() - 1;
1152 }
1153} //Botcraft
#define LOG_ERROR(osstream)
Definition Logger.hpp:45
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
bool IsHazardous() const
bool IsBerryBush() const
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
bool IsCobweb() const
BlockstateId GetId() const
bool IsDownBubbleColumn() const
bool IsTransparent() const
bool IsFluidOrWaterlogged() const
bool IsClimbable() 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 void ClearCache()
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
bool IsSoulSand() 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
Definition Model.cpp:585
void SetColliders(const std::set< AABB > &colliders_)
Definition Model.cpp:590
const std::vector< FaceDescriptor > & GetFaces() const
Definition Model.cpp:601
static const Model & GetModel(const std::string &filepath, const bool custom)
Definition Model.cpp:23
bool IsSame(const Model &other) const
Compare two models.
Definition Model.cpp:574
const TextureData & GetData(const std::string &name) const
Definition Atlas.cpp:237
Main class, basically a JsonVariant with extra utility functions it doesn't inherit JsonVariant direc...
Definition Json.hpp:45
bool is_string() const
Definition Json.cpp:144
bool is_array() const
Definition Json.cpp:154
void push_back(const Value &value)
Definition Json.cpp:257
bool is_object() const
Definition Json.cpp:149
bool contains(const std::string &s) const
Definition Json.cpp:232
std::string & get_string()
Definition Json.cpp:119
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>
Definition Atlas.hpp:16
std::pair< int, int > size
<Width, Height>
Definition Atlas.hpp:14
double SqrDist(const Vector3 &v) const
Definition Vector3.hpp:192