2023-06-20 04:33:09 +00:00
|
|
|
#include "StarPlant.hpp"
|
|
|
|
#include "StarJsonExtra.hpp"
|
|
|
|
#include "StarWorld.hpp"
|
|
|
|
#include "StarRoot.hpp"
|
|
|
|
#include "StarObjectDatabase.hpp"
|
|
|
|
#include "StarPlantDrop.hpp"
|
|
|
|
#include "StarImageMetadataDatabase.hpp"
|
|
|
|
#include "StarAssets.hpp"
|
|
|
|
#include "StarImage.hpp"
|
|
|
|
#include "StarEntityRendering.hpp"
|
|
|
|
#include "StarParticleDatabase.hpp"
|
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
float const Plant::PlantScanThreshold = 0.1f;
|
|
|
|
|
|
|
|
EnumMap<Plant::RotationType> const Plant::RotationTypeNames{
|
|
|
|
{Plant::RotationType::DontRotate, "dontRotate"},
|
|
|
|
{Plant::RotationType::RotateBranch, "rotateBranch"},
|
|
|
|
{Plant::RotationType::RotateLeaves, "rotateLeaves"},
|
|
|
|
{Plant::RotationType::RotateCrownBranch, "rotateCrownBranch"},
|
|
|
|
{Plant::RotationType::RotateCrownLeaves, "rotateCrownLeaves"},
|
|
|
|
};
|
|
|
|
|
|
|
|
Plant::PlantPiece::PlantPiece() {
|
|
|
|
image = "";
|
|
|
|
offset = {};
|
|
|
|
segmentIdx = 0;
|
|
|
|
structuralSegment = 0;
|
|
|
|
kind = PlantPieceKind::None;
|
|
|
|
zLevel = 0;
|
|
|
|
rotationType = RotationType::DontRotate;
|
|
|
|
rotationOffset = 0;
|
|
|
|
spaces = {};
|
|
|
|
flip = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Plant::Plant(TreeVariant const& config, uint64_t seed) : Plant() {
|
|
|
|
m_broken = false;
|
|
|
|
m_tilePosition = Vec2I();
|
|
|
|
m_windTime = 0.0f;
|
|
|
|
m_windLevel = 0.0f;
|
|
|
|
m_ceiling = config.ceiling;
|
|
|
|
m_piecesScanned = false;
|
|
|
|
m_fallsWhenDead = true;
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
m_tileDamageEvent = false;
|
|
|
|
|
|
|
|
m_stemDropConfig = config.stemDropConfig;
|
|
|
|
m_foliageDropConfig = config.foliageDropConfig;
|
|
|
|
if (m_stemDropConfig.isNull())
|
|
|
|
m_stemDropConfig = JsonObject();
|
|
|
|
if (m_foliageDropConfig.isNull())
|
|
|
|
m_foliageDropConfig = JsonObject();
|
|
|
|
|
|
|
|
m_stemDropConfig = m_stemDropConfig.set("hueshift", config.stemHueShift);
|
|
|
|
m_foliageDropConfig = m_foliageDropConfig.set("hueshift", config.foliageHueShift);
|
|
|
|
|
|
|
|
JsonObject saplingDropConfig;
|
|
|
|
saplingDropConfig["stemName"] = config.stemName;
|
|
|
|
saplingDropConfig["stemHueShift"] = config.stemHueShift;
|
|
|
|
if (!m_foliageDropConfig.isNull()) {
|
|
|
|
saplingDropConfig["foliageName"] = config.foliageName;
|
|
|
|
saplingDropConfig["foliageHueShift"] = config.foliageHueShift;
|
|
|
|
}
|
|
|
|
m_saplingDropConfig = saplingDropConfig;
|
|
|
|
|
|
|
|
RandomSource rnd(seed);
|
|
|
|
|
|
|
|
float xOffset = 0;
|
|
|
|
float yOffset = 0;
|
|
|
|
|
|
|
|
float roffset = Random::randf() * 0.5f;
|
|
|
|
|
|
|
|
m_descriptions = config.descriptions;
|
|
|
|
m_ephemeral = config.ephemeral;
|
|
|
|
m_tileDamageParameters = config.tileDamageParameters;
|
|
|
|
|
|
|
|
int segment = 0;
|
|
|
|
|
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
|
|
|
|
// base
|
|
|
|
{
|
|
|
|
JsonObject bases = config.stemSettings.get("base").toObject();
|
|
|
|
String baseKey = bases.keys()[rnd.randInt(bases.size() - 1)];
|
|
|
|
JsonObject baseSettings = bases[baseKey].toObject();
|
|
|
|
|
|
|
|
JsonObject attachmentSettings = baseSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
xOffset += attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
yOffset += attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
String baseFile = AssetPath::relativeTo(config.stemDirectory, baseSettings.get("image").toString());
|
|
|
|
float baseImageHeight = assets->image(baseFile)->height();
|
|
|
|
if (config.ceiling)
|
|
|
|
yOffset = 1.0 - baseImageHeight / TilePixels;
|
|
|
|
|
|
|
|
{
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", baseFile, config.stemHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F(xOffset, yOffset);
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = true;
|
|
|
|
piece.kind = PlantPieceKind::Stem;
|
|
|
|
piece.zLevel = 0.0f;
|
|
|
|
piece.rotationType = DontRotate;
|
|
|
|
piece.rotationOffset = Random::randf() + roffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
// base leaves
|
|
|
|
JsonObject baseLeaves = config.foliageSettings.getObject("baseLeaves", {});
|
|
|
|
if (baseLeaves.contains(baseKey)) {
|
|
|
|
JsonObject baseLeavesSettings = baseLeaves.get(baseKey).toObject();
|
|
|
|
JsonObject attachmentSettings = baseLeavesSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
float xOf = xOffset + attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
float yOf = yOffset + attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
if (baseLeavesSettings.contains("image") && !baseLeavesSettings.get("image").toString().empty()) {
|
|
|
|
String baseLeavesFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, baseLeavesSettings.get("image").toString());
|
|
|
|
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", baseLeavesFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOf, yOf};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = 3.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateLeaves;
|
|
|
|
piece.rotationOffset = Random::randf() + roffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (baseLeavesSettings.contains("backimage") && !baseLeavesSettings.get("backimage").toString().empty()) {
|
|
|
|
String baseLeavesBackFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, baseLeavesSettings.get("backimage").toString());
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", baseLeavesBackFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOf, yOf};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = -1.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateLeaves;
|
|
|
|
piece.rotationOffset = Random::randf() + roffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xOffset += attachmentSettings.get("x").toDouble() / TilePixels;
|
|
|
|
yOffset += attachmentSettings.get("y").toDouble() / TilePixels; // trunk height
|
|
|
|
|
|
|
|
segment++;
|
|
|
|
}
|
|
|
|
|
|
|
|
float branchYOffset = yOffset;
|
|
|
|
|
|
|
|
// trunk
|
|
|
|
{
|
|
|
|
JsonObject middles = config.stemSettings.get("middle").toObject();
|
|
|
|
|
|
|
|
int middleHeight = config.stemSettings.getInt("middleMinSize", 1) + rnd.randInt(config.stemSettings.getInt("middleMaxSize", 6) - config.stemSettings.getInt("middleMinSize", 1));
|
|
|
|
|
|
|
|
bool hasBranches = config.stemSettings.contains("branch");
|
|
|
|
JsonObject branches;
|
|
|
|
if (hasBranches) {
|
|
|
|
branches = config.stemSettings.get("branch").toObject();
|
|
|
|
if (branches.size() == 0)
|
|
|
|
hasBranches = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < middleHeight; i++) {
|
|
|
|
String middleKey = middles.keys()[rnd.randInt(middles.size() - 1)];
|
|
|
|
JsonObject middleSettings = middles[middleKey].toObject();
|
|
|
|
JsonObject attachmentSettings = middleSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
xOffset += attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
yOffset += attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
String middleFile = AssetPath::relativeTo(config.stemDirectory, middleSettings.get("image").toString());
|
|
|
|
|
|
|
|
{
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", middleFile, config.stemHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F(xOffset, yOffset);
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = true;
|
|
|
|
piece.kind = PlantPieceKind::Stem;
|
|
|
|
piece.zLevel = 1.0f;
|
|
|
|
piece.rotationType = DontRotate;
|
|
|
|
piece.rotationOffset = Random::randf() + roffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
// trunk leaves
|
|
|
|
JsonObject trunkLeaves = config.foliageSettings.getObject("trunkLeaves", {});
|
|
|
|
if (trunkLeaves.contains(middleKey)) {
|
|
|
|
JsonObject trunkLeavesSettings = trunkLeaves.get(middleKey).toObject();
|
|
|
|
JsonObject attachmentSettings = trunkLeavesSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
float xOf = xOffset + attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
float yOf = yOffset + attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
if (trunkLeavesSettings.contains("image") && !trunkLeavesSettings.get("image").toString().empty()) {
|
|
|
|
String trunkLeavesFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, trunkLeavesSettings.get("image").toString());
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", trunkLeavesFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOf, yOf};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = 3.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateLeaves;
|
|
|
|
piece.rotationOffset = Random::randf() + roffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (trunkLeavesSettings.contains("backimage") && !trunkLeavesSettings.get("backimage").toString().empty()) {
|
|
|
|
String trunkLeavesBackFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, trunkLeavesSettings.get("backimage").toString());
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", trunkLeavesBackFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOf, yOf};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = -1.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateLeaves;
|
|
|
|
piece.rotationOffset = Random::randf() + roffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xOffset += attachmentSettings.get("x").toDouble() / TilePixels;
|
|
|
|
yOffset += attachmentSettings.get("y").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
// branch
|
|
|
|
while (hasBranches && (yOffset >= branchYOffset) && ((middleHeight - i) > 0)) {
|
|
|
|
String branchKey = branches.keys()[rnd.randInt(branches.size() - 1)];
|
|
|
|
JsonObject branchSettings = branches[branchKey].toObject();
|
|
|
|
JsonObject attachmentSettings = branchSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
float h = attachmentSettings.get("h").toDouble() / TilePixels;
|
|
|
|
if (yOffset < branchYOffset + (h / 2.0f))
|
|
|
|
break;
|
|
|
|
|
|
|
|
float xO = xOffset + attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
float yO = branchYOffset + attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
if (config.stemSettings.getBool("alwaysBranch", false) || rnd.randInt(2 + i) != 0) {
|
|
|
|
float boffset = Random::randf() + roffset;
|
|
|
|
String branchFile = AssetPath::relativeTo(config.stemDirectory, branchSettings.get("image").toString());
|
|
|
|
{
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", branchFile, config.stemHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xO, yO};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Stem;
|
|
|
|
piece.zLevel = 0.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateBranch;
|
|
|
|
piece.rotationOffset = boffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
branchYOffset += h;
|
|
|
|
|
|
|
|
// branch leaves
|
|
|
|
JsonObject branchLeaves = config.foliageSettings.getObject("branchLeaves", {});
|
|
|
|
if (branchLeaves.contains(branchKey)) {
|
|
|
|
JsonObject branchLeavesSettings = branchLeaves.get(branchKey).toObject();
|
|
|
|
JsonObject attachmentSettings = branchLeavesSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
float xOf = xO + attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
float yOf = yO + attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
if (branchLeavesSettings.contains("image") && !branchLeavesSettings.get("image").toString().empty()) {
|
|
|
|
String branchLeavesFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, branchLeavesSettings.get("image").toString());
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", branchLeavesFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOf, yOf};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = 3.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateLeaves;
|
|
|
|
piece.rotationOffset = boffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (branchLeavesSettings.contains("backimage")
|
|
|
|
&& !branchLeavesSettings.get("backimage").toString().empty()) {
|
|
|
|
String branchLeavesBackFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, branchLeavesSettings.get("backimage").toString());
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", branchLeavesBackFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOf, yOf};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = -1.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateLeaves;
|
|
|
|
piece.rotationOffset = boffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
branchYOffset += (attachmentSettings.get("h").toDouble() / TilePixels) / (float)(1 + rnd.randInt(4));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
segment++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// crown
|
|
|
|
{
|
|
|
|
JsonObject crowns = config.stemSettings.getObject("crown", {});
|
|
|
|
bool hasCrown = crowns.size() > 0;
|
|
|
|
if (hasCrown) {
|
|
|
|
String crownKey = crowns.keys()[rnd.randInt(crowns.size() - 1)];
|
|
|
|
JsonObject crownSettings = crowns[crownKey].toObject();
|
|
|
|
|
|
|
|
JsonObject attachmentSettings = crownSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
xOffset += attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
yOffset += attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
float coffset = roffset + Random::randf();
|
|
|
|
|
|
|
|
String crownFile = AssetPath::relativeTo(config.stemDirectory, crownSettings.get("image").toString());
|
|
|
|
{
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", crownFile, config.stemHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xOffset, yOffset};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Stem;
|
|
|
|
piece.zLevel = 0.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateCrownBranch;
|
|
|
|
piece.rotationOffset = coffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
// crown leaves
|
|
|
|
JsonObject crownLeaves = config.foliageSettings.getObject("crownLeaves", {});
|
|
|
|
if (crownLeaves.contains(crownKey)) {
|
|
|
|
JsonObject crownLeavesSettings = crownLeaves.get(crownKey).toObject();
|
|
|
|
|
|
|
|
JsonObject attachmentSettings = crownLeavesSettings["attachment"].toObject();
|
|
|
|
|
|
|
|
float xO = xOffset + attachmentSettings.get("bx").toDouble() / TilePixels;
|
|
|
|
float yO = yOffset + attachmentSettings.get("by").toDouble() / TilePixels;
|
|
|
|
|
|
|
|
if (crownLeavesSettings.contains("image") && !crownLeavesSettings.get("image").toString().empty()) {
|
|
|
|
String crownLeavesFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, crownLeavesSettings.get("image").toString());
|
|
|
|
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", crownLeavesFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xO, yO};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = 3.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateCrownLeaves;
|
|
|
|
piece.rotationOffset = coffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (crownLeavesSettings.contains("backimage") && !crownLeavesSettings.get("backimage").toString().empty()) {
|
|
|
|
String crownLeavesBackFile =
|
|
|
|
AssetPath::relativeTo(config.foliageDirectory, crownLeavesSettings.get("backimage").toString());
|
|
|
|
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", crownLeavesBackFile, config.foliageHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = Vec2F{xO, yO};
|
|
|
|
piece.segmentIdx = segment;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::Foliage;
|
|
|
|
piece.zLevel = -1.0f;
|
|
|
|
piece.rotationType = m_ceiling ? DontRotate : RotateCrownLeaves;
|
|
|
|
piece.rotationOffset = coffset;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort(m_pieces, [](PlantPiece const& a, PlantPiece const& b) { return a.zLevel < b.zLevel; });
|
|
|
|
validatePieces();
|
|
|
|
setupNetStates();
|
|
|
|
}
|
|
|
|
|
|
|
|
Json Plant::diskStore() const {
|
|
|
|
return JsonObject{
|
|
|
|
{"tilePosition", jsonFromVec2I(m_tilePosition)},
|
|
|
|
{"ceiling", m_ceiling},
|
|
|
|
{"stemDropConfig", m_stemDropConfig},
|
|
|
|
{"foliageDropConfig", m_foliageDropConfig},
|
|
|
|
{"saplingDropConfig", m_saplingDropConfig},
|
|
|
|
{"descriptions", m_descriptions},
|
|
|
|
{"ephemeral", m_ephemeral},
|
|
|
|
{"tileDamageParameters", m_tileDamageParameters.toJson()},
|
|
|
|
{"fallsWhenDead", m_fallsWhenDead},
|
|
|
|
{"pieces", writePiecesToJson()},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteArray Plant::netStore() const {
|
|
|
|
DataStreamBuffer ds;
|
|
|
|
ds.viwrite(m_tilePosition[0]);
|
|
|
|
ds.viwrite(m_tilePosition[1]);
|
|
|
|
ds.write(m_ceiling);
|
|
|
|
ds.write(m_stemDropConfig);
|
|
|
|
ds.write(m_foliageDropConfig);
|
|
|
|
ds.write(m_saplingDropConfig);
|
|
|
|
ds.write(m_descriptions);
|
|
|
|
ds.write(m_ephemeral);
|
|
|
|
ds.write(m_tileDamageParameters);
|
|
|
|
ds.write(m_fallsWhenDead);
|
|
|
|
m_tileDamageStatus.netStore(ds);
|
|
|
|
ds.write(writePieces());
|
|
|
|
|
|
|
|
return ds.takeData();
|
|
|
|
}
|
|
|
|
|
|
|
|
Plant::Plant(GrassVariant const& config, uint64_t seed) : Plant() {
|
|
|
|
m_broken = false;
|
|
|
|
m_tilePosition = Vec2I();
|
|
|
|
m_ceiling = false;
|
|
|
|
m_windTime = 0.0f;
|
|
|
|
m_windLevel = 0.0f;
|
|
|
|
m_piecesScanned = false;
|
|
|
|
m_fallsWhenDead = false;
|
|
|
|
m_descriptions = config.descriptions;
|
|
|
|
m_ephemeral = config.ephemeral;
|
|
|
|
m_tileDamageParameters = config.tileDamageParameters;
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
|
|
|
|
RandomSource rand(seed);
|
|
|
|
|
|
|
|
String imageName = AssetPath::relativeTo(config.directory, rand.randValueFrom(config.images));
|
|
|
|
|
|
|
|
Vec2F offset = Vec2F();
|
|
|
|
// If this is a ceiling plant, offset the image so that the [0, 0] space is
|
|
|
|
// at the top
|
|
|
|
if (config.ceiling) {
|
|
|
|
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
|
|
|
float imageHeight = imgMetadata->imageSize(imageName)[1];
|
|
|
|
offset = Vec2F(0.0f, 1.0f - imageHeight / TilePixels);
|
|
|
|
}
|
|
|
|
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", imageName, config.hueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = offset;
|
|
|
|
piece.segmentIdx = 0;
|
|
|
|
piece.structuralSegment = true;
|
|
|
|
piece.kind = PlantPieceKind::None;
|
|
|
|
m_pieces = {piece};
|
|
|
|
|
|
|
|
m_ceiling = config.ceiling;
|
|
|
|
validatePieces();
|
|
|
|
setupNetStates();
|
|
|
|
}
|
|
|
|
|
|
|
|
Plant::Plant(BushVariant const& config, uint64_t seed) : Plant() {
|
|
|
|
m_broken = false;
|
|
|
|
m_tilePosition = Vec2I();
|
|
|
|
m_ceiling = false;
|
|
|
|
m_windTime = 0.0f;
|
|
|
|
m_windLevel = 0.0f;
|
|
|
|
m_piecesScanned = false;
|
|
|
|
m_fallsWhenDead = false;
|
|
|
|
m_descriptions = config.descriptions;
|
|
|
|
m_ephemeral = config.ephemeral;
|
|
|
|
m_tileDamageParameters = config.tileDamageParameters;
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
|
|
|
|
RandomSource rand(seed);
|
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
|
|
|
|
auto shape = rand.randValueFrom(config.shapes);
|
|
|
|
String shapeImageName = AssetPath::relativeTo(config.directory, shape.image);
|
|
|
|
float shapeImageHeight = assets->image(shapeImageName)->height();
|
|
|
|
Vec2F offset = Vec2F();
|
|
|
|
// If this is a ceiling plant, offset the image so that the [0, 0] space is
|
|
|
|
// at the top
|
|
|
|
if (config.ceiling)
|
|
|
|
offset = Vec2F(0.0f, 1.0f - shapeImageHeight / TilePixels);
|
|
|
|
|
|
|
|
{
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", shapeImageName, config.baseHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = offset;
|
|
|
|
piece.segmentIdx = 0;
|
|
|
|
piece.structuralSegment = true;
|
|
|
|
piece.kind = PlantPieceKind::None;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto mod = rand.randValueFrom(shape.mods);
|
|
|
|
if (!mod.empty()) {
|
|
|
|
PlantPiece piece;
|
2023-06-27 10:23:44 +00:00
|
|
|
piece.image = strf("{}?hueshift={}", AssetPath::relativeTo(config.directory, mod), config.modHueShift);
|
2023-06-20 04:33:09 +00:00
|
|
|
piece.offset = offset;
|
|
|
|
piece.segmentIdx = 0;
|
|
|
|
piece.structuralSegment = false;
|
|
|
|
piece.kind = PlantPieceKind::None;
|
|
|
|
m_pieces.append(piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ceiling = config.ceiling;
|
|
|
|
validatePieces();
|
|
|
|
setupNetStates();
|
|
|
|
}
|
|
|
|
|
|
|
|
Plant::Plant(Json const& diskStore) : Plant() {
|
|
|
|
m_tilePosition = jsonToVec2I(diskStore.get("tilePosition"));
|
|
|
|
m_ceiling = diskStore.getBool("ceiling");
|
|
|
|
m_stemDropConfig = diskStore.get("stemDropConfig");
|
|
|
|
m_foliageDropConfig = diskStore.get("foliageDropConfig");
|
|
|
|
m_saplingDropConfig = diskStore.get("saplingDropConfig");
|
|
|
|
m_descriptions = diskStore.get("descriptions");
|
|
|
|
m_ephemeral = diskStore.getBool("ephemeral");
|
|
|
|
m_tileDamageParameters = TileDamageParameters(diskStore.get("tileDamageParameters"));
|
|
|
|
m_fallsWhenDead = diskStore.getBool("fallsWhenDead");
|
|
|
|
readPiecesFromJson(diskStore.get("pieces"));
|
|
|
|
|
|
|
|
setupNetStates();
|
|
|
|
}
|
|
|
|
|
|
|
|
Plant::Plant(ByteArray const& netStore) : Plant() {
|
|
|
|
m_broken = false;
|
|
|
|
m_tilePosition = Vec2I();
|
|
|
|
m_ceiling = false;
|
|
|
|
m_windTime = 0.0f;
|
|
|
|
m_windLevel = 0.0f;
|
|
|
|
m_piecesScanned = false;
|
|
|
|
m_fallsWhenDead = false;
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
|
|
|
|
DataStreamBuffer ds(netStore);
|
|
|
|
ds.viread(m_tilePosition[0]);
|
|
|
|
ds.viread(m_tilePosition[1]);
|
|
|
|
ds.read(m_ceiling);
|
|
|
|
ds.read(m_stemDropConfig);
|
|
|
|
ds.read(m_foliageDropConfig);
|
|
|
|
ds.read(m_saplingDropConfig);
|
|
|
|
ds.read(m_descriptions);
|
|
|
|
ds.read(m_ephemeral);
|
|
|
|
ds.read(m_tileDamageParameters);
|
|
|
|
ds.read(m_fallsWhenDead);
|
|
|
|
m_tileDamageStatus.netLoad(ds);
|
|
|
|
readPieces(ds.read<ByteArray>());
|
|
|
|
|
|
|
|
setupNetStates();
|
|
|
|
}
|
|
|
|
|
|
|
|
Plant::Plant() {
|
|
|
|
m_ephemeral = false;
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
m_ceiling = false;
|
|
|
|
m_broken = false;
|
|
|
|
m_fallsWhenDead = false;
|
|
|
|
m_windTime = 0.0f;
|
|
|
|
m_windLevel = 0.0f;
|
|
|
|
m_piecesScanned = false;
|
|
|
|
m_tileDamageX = 0.0f;
|
|
|
|
m_tileDamageY = 0.0f;
|
|
|
|
m_tileDamageEventTrigger = false;
|
|
|
|
m_tileDamageEvent = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
EntityType Plant::entityType() const {
|
|
|
|
return EntityType::Plant;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::init(World* world, EntityId entityId, EntityMode mode) {
|
|
|
|
Entity::init(world, entityId, mode);
|
|
|
|
validatePieces();
|
|
|
|
m_tilePosition = world->geometry().xwrap(m_tilePosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
pair<ByteArray, uint64_t> Plant::writeNetState(uint64_t fromVersion) {
|
|
|
|
return m_netGroup.writeNetState(fromVersion);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::readNetState(ByteArray data, float interpolationTime) {
|
|
|
|
m_netGroup.readNetState(move(data), interpolationTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::enableInterpolation(float extrapolationHint) {
|
|
|
|
// Only enable plant interpolation when it actually matters, for things that
|
|
|
|
// generate PlantDrops so that they match when the PlantDrops appear.
|
|
|
|
if (m_fallsWhenDead)
|
|
|
|
m_netGroup.enableNetInterpolation(extrapolationHint);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::disableInterpolation() {
|
|
|
|
m_netGroup.disableNetInterpolation();
|
|
|
|
}
|
|
|
|
|
|
|
|
String Plant::description() const {
|
|
|
|
return m_descriptions.getString("description");
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2F Plant::position() const {
|
|
|
|
return Vec2F(m_tilePosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
RectF Plant::metaBoundBox() const {
|
|
|
|
return m_metaBoundBox;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plant::ephemeral() const {
|
|
|
|
return m_ephemeral;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2I Plant::tilePosition() const {
|
|
|
|
return m_tilePosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::setTilePosition(Vec2I const& tilePosition) {
|
|
|
|
m_tilePosition = tilePosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Vec2I> Plant::spaces() const {
|
|
|
|
return m_spaces;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Vec2I> Plant::roots() const {
|
|
|
|
return m_roots;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2I Plant::primaryRoot() const {
|
|
|
|
return m_ceiling ? Vec2I(0, 1) : Vec2I(0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plant::ceiling() const {
|
|
|
|
return m_ceiling;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plant::shouldDestroy() const {
|
|
|
|
return m_broken || m_pieces.size() == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plant::checkBroken() {
|
|
|
|
if (!m_broken) {
|
|
|
|
if (!allSpacesOccupied(m_roots)) {
|
|
|
|
if (m_fallsWhenDead) {
|
|
|
|
breakAtPosition(m_tilePosition, Vec2F(m_tilePosition));
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
m_broken = true;
|
|
|
|
} else if (anySpacesOccupied(m_spaces))
|
|
|
|
m_broken = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_broken;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Plant::PlantPiece> Plant::pieces() const {
|
|
|
|
return m_pieces;
|
|
|
|
}
|
|
|
|
|
|
|
|
RectF Plant::interactiveBoundBox() const {
|
|
|
|
return RectF(m_boundBox);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::scanSpacesAndRoots() {
|
|
|
|
auto imageMetadataDatabase = Root::singleton().imageMetadataDatabase();
|
|
|
|
|
|
|
|
// build spaces
|
|
|
|
Set<Vec2I> spaces;
|
|
|
|
|
|
|
|
// always include the base position in spaces, it causes all kinds of problems if you don't
|
|
|
|
spaces.add({0, 0});
|
|
|
|
|
|
|
|
for (auto& piece : m_pieces) {
|
|
|
|
piece.imageSize = imageMetadataDatabase->imageSize(piece.image);
|
|
|
|
piece.spaces = Set<Vec2I>::from(
|
|
|
|
imageMetadataDatabase->imageSpaces(piece.image, piece.offset * TilePixels, PlantScanThreshold, piece.flip));
|
|
|
|
spaces.addAll(piece.spaces);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_spaces = spaces.values();
|
|
|
|
|
|
|
|
m_boundBox = RectI::boundBoxOfPoints(m_spaces);
|
|
|
|
|
|
|
|
for (auto space : m_spaces) {
|
|
|
|
if (space[1] == 0) {
|
|
|
|
if (m_ceiling)
|
|
|
|
m_roots.push_back({space[0], 1});
|
|
|
|
else
|
|
|
|
m_roots.push_back({space[0], -1});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::calcBoundBox() {
|
|
|
|
RectF boundBox = RectF::boundBoxOfPoints(m_spaces);
|
|
|
|
// Plants are allowed to visibly occupy one outside space from the spaces
|
|
|
|
// they take up.
|
|
|
|
m_metaBoundBox = RectF(boundBox.min() - Vec2F(1, 1), boundBox.max() + Vec2F(2, 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
float Plant::branchRotation(float xPos, float rotoffset) const {
|
|
|
|
if (!inWorld() || m_windLevel == 0.0f)
|
|
|
|
return 0.0f;
|
|
|
|
|
|
|
|
float intensity = fabs(m_windLevel);
|
|
|
|
|
|
|
|
return copysign(0.00117f, m_windLevel) * (std::sin(m_windTime + rotoffset + xPos / 10.0f) * intensity - intensity / 300.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::update(uint64_t) {
|
|
|
|
m_windTime += WorldTimestep;
|
|
|
|
m_windTime = std::fmod(m_windTime, 628.32f);
|
|
|
|
m_windLevel = world()->windLevel(Vec2F(m_tilePosition));
|
|
|
|
|
|
|
|
if (isMaster()) {
|
|
|
|
if (m_tileDamageStatus.damaged())
|
|
|
|
m_tileDamageStatus.recover(m_tileDamageParameters, WorldTimestep);
|
|
|
|
} else {
|
|
|
|
if (m_tileDamageStatus.damaged() && !m_tileDamageStatus.damageProtected()) {
|
|
|
|
float damageEffectPercentage = m_tileDamageStatus.damageEffectPercentage();
|
|
|
|
m_windTime += damageEffectPercentage * 10 * WorldTimestep;
|
|
|
|
m_windLevel += damageEffectPercentage * 20;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_netGroup.tickNetInterpolation(WorldTimestep);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::render(RenderCallback* renderCallback) {
|
|
|
|
float damageXOffset = Random::randf(-0.1f, 0.1f) * m_tileDamageStatus.damageEffectPercentage();
|
|
|
|
|
|
|
|
for (auto const& plantPiece : m_pieces) {
|
|
|
|
auto size = Vec2F(plantPiece.imageSize) / TilePixels;
|
|
|
|
|
|
|
|
Vec2F offset = plantPiece.offset;
|
|
|
|
if ((m_ceiling && offset[1] <= m_tileDamageY) || (!m_ceiling && offset[1] + size[1] >= m_tileDamageY))
|
|
|
|
offset[0] += damageXOffset;
|
|
|
|
|
|
|
|
auto drawable = Drawable::makeImage(plantPiece.image, 1.0f / TilePixels, false, offset);
|
|
|
|
if (plantPiece.flip)
|
|
|
|
drawable.scale(Vec2F(-1, 1));
|
|
|
|
|
|
|
|
if (plantPiece.rotationType == RotateCrownBranch || plantPiece.rotationType == RotateCrownLeaves) {
|
|
|
|
drawable.rotate(branchRotation(m_tilePosition[0], plantPiece.rotationOffset * 1.4f) * 0.7f, plantPiece.offset + Vec2F(size[0] / 2.0f, 0));
|
|
|
|
drawable.translate(Vec2F(0, -0.40f));
|
|
|
|
} else if (plantPiece.rotationType == RotateBranch || plantPiece.rotationType == RotateLeaves) {
|
|
|
|
drawable.rotate(branchRotation(m_tilePosition[0], plantPiece.rotationOffset * 1.4f), plantPiece.offset + Vec2F(size) / 2.0f);
|
|
|
|
}
|
|
|
|
drawable.translate(position());
|
|
|
|
renderCallback->addDrawable(move(drawable), RenderLayerPlant);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_tileDamageEvent) {
|
|
|
|
m_tileDamageEvent = false;
|
|
|
|
if (m_stemDropConfig.type() == Json::Type::Object) {
|
|
|
|
Json particleConfig = m_stemDropConfig.get("particles", JsonObject()).get("damageTree", JsonObject());
|
|
|
|
JsonArray particleOptions = particleConfig.getArray("options", {});
|
|
|
|
auto hueshift = m_stemDropConfig.getFloat("hueshift", 0) / 360.0f;
|
|
|
|
auto density = particleConfig.getFloat("density", 1);
|
|
|
|
while (density-- > 0) {
|
|
|
|
auto config = Random::randValueFrom(particleOptions, {});
|
|
|
|
if (config.isNull() || config.size() == 0)
|
|
|
|
continue;
|
|
|
|
auto particle = Root::singleton().particleDatabase()->particle(config);
|
|
|
|
particle.color.hueShift(hueshift);
|
2023-06-26 06:09:40 +00:00
|
|
|
if (!particle.string.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
particle.string = strf("{}?hueshift={}", particle.string, hueshift);
|
2023-06-26 06:09:40 +00:00
|
|
|
particle.image = particle.string;
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
particle.position = {m_tileDamageX + Random::randf(), m_tileDamageY + Random::randf()};
|
|
|
|
particle.translate(position());
|
|
|
|
renderCallback->addParticle(move(particle));
|
|
|
|
}
|
|
|
|
JsonArray damageTreeSoundOptions = m_stemDropConfig.get("sounds", JsonObject()).getArray("damageTree", JsonArray());
|
|
|
|
if (damageTreeSoundOptions.size()) {
|
|
|
|
auto sound = Random::randFrom(damageTreeSoundOptions);
|
|
|
|
Vec2F pos = position() + Vec2F(m_tileDamageX + Random::randf(), m_tileDamageY + Random::randf());
|
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
auto audioInstance = make_shared<AudioInstance>(*assets->audio(sound.getString("file")));
|
|
|
|
audioInstance->setPosition(pos);
|
|
|
|
audioInstance->setVolume(sound.getFloat("volume", 1.0f));
|
|
|
|
renderCallback->addAudio(move(audioInstance));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::readPieces(ByteArray pieces) {
|
|
|
|
if (!pieces.empty()) {
|
|
|
|
DataStreamBuffer ds(move(pieces));
|
|
|
|
ds.readContainer(m_pieces, [](DataStream& ds, PlantPiece& piece) {
|
|
|
|
ds.read(piece.image);
|
|
|
|
ds.read(piece.offset[0]);
|
|
|
|
ds.read(piece.offset[1]);
|
|
|
|
ds.read(piece.rotationType);
|
|
|
|
ds.read(piece.rotationOffset);
|
|
|
|
ds.read(piece.structuralSegment);
|
|
|
|
ds.read(piece.kind);
|
|
|
|
ds.read(piece.segmentIdx);
|
|
|
|
ds.read(piece.flip);
|
|
|
|
});
|
|
|
|
m_piecesScanned = false;
|
|
|
|
if (inWorld())
|
|
|
|
validatePieces();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteArray Plant::writePieces() const {
|
|
|
|
return DataStreamBuffer::serializeContainer(m_pieces, [](DataStream& ds, PlantPiece const& piece) {
|
|
|
|
ds.write(piece.image);
|
|
|
|
ds.write(piece.offset[0]);
|
|
|
|
ds.write(piece.offset[1]);
|
|
|
|
ds.write(piece.rotationType);
|
|
|
|
ds.write(piece.rotationOffset);
|
|
|
|
ds.write(piece.structuralSegment);
|
|
|
|
ds.write(piece.kind);
|
|
|
|
ds.write(piece.segmentIdx);
|
|
|
|
ds.write(piece.flip);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::readPiecesFromJson(Json const& pieces) {
|
|
|
|
m_pieces = jsonToList<PlantPiece>(pieces, [](Json const& v) -> PlantPiece {
|
|
|
|
PlantPiece res;
|
|
|
|
res.image = v.getString("image");
|
|
|
|
res.offset = jsonToVec2F(v.get("offset"));
|
|
|
|
res.rotationType = RotationTypeNames.getLeft(v.getString("rotationType"));
|
|
|
|
res.rotationOffset = v.getFloat("rotationOffset");
|
|
|
|
res.structuralSegment = v.getBool("structuralSegment");
|
|
|
|
res.kind = (PlantPieceKind)v.getInt("kind");
|
|
|
|
res.segmentIdx = v.getInt("segmentIdx");
|
|
|
|
res.flip = v.getBool("flip");
|
|
|
|
|
|
|
|
return res;
|
|
|
|
});
|
|
|
|
m_piecesScanned = false;
|
|
|
|
if (inWorld())
|
|
|
|
validatePieces();
|
|
|
|
}
|
|
|
|
|
|
|
|
Json Plant::writePiecesToJson() const {
|
|
|
|
return jsonFromList<PlantPiece>(m_pieces, [](PlantPiece const& piece) -> Json {
|
|
|
|
return JsonObject{
|
|
|
|
{"image", piece.image},
|
|
|
|
{"offset", jsonFromVec2F(piece.offset)},
|
|
|
|
{"rotationType", RotationTypeNames.getRight(piece.rotationType)},
|
|
|
|
{"rotationOffset", piece.rotationOffset},
|
|
|
|
{"structuralSegment", piece.structuralSegment},
|
|
|
|
{"kind", piece.kind},
|
|
|
|
{"segmentIdx", piece.segmentIdx},
|
|
|
|
{"flip", piece.flip},
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::validatePieces() {
|
|
|
|
if (!m_piecesScanned) {
|
|
|
|
scanSpacesAndRoots();
|
|
|
|
calcBoundBox();
|
|
|
|
m_piecesScanned = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::setupNetStates() {
|
|
|
|
m_netGroup.addNetElement(&m_tileDamageStatus);
|
|
|
|
m_netGroup.addNetElement(&m_piecesNetState);
|
|
|
|
m_netGroup.addNetElement(&m_tileDamageXNetState);
|
|
|
|
m_netGroup.addNetElement(&m_tileDamageYNetState);
|
|
|
|
m_netGroup.addNetElement(&m_tileDamageEventNetState);
|
|
|
|
|
|
|
|
m_netGroup.setNeedsStoreCallback(bind(&Plant::setNetStates, this));
|
|
|
|
m_netGroup.setNeedsLoadCallback(bind(&Plant::getNetStates, this));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::getNetStates() {
|
|
|
|
if (m_piecesNetState.pullUpdated()) {
|
|
|
|
readPieces(m_piecesNetState.get());
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_tileDamageX = m_tileDamageXNetState.get();
|
|
|
|
m_tileDamageY = m_tileDamageYNetState.get();
|
|
|
|
if (m_tileDamageEventNetState.pullOccurred()) {
|
|
|
|
m_tileDamageEvent = true;
|
|
|
|
m_tileDamageEventTrigger = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::setNetStates() {
|
|
|
|
if (m_piecesUpdated) {
|
|
|
|
m_piecesNetState.set(writePieces());
|
|
|
|
m_piecesUpdated = false;
|
|
|
|
}
|
|
|
|
m_tileDamageXNetState.set(m_tileDamageX);
|
|
|
|
m_tileDamageYNetState.set(m_tileDamageY);
|
|
|
|
if (m_tileDamageEventTrigger) {
|
|
|
|
m_tileDamageEventTrigger = false;
|
|
|
|
m_tileDamageEventNetState.trigger();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plant::damageTiles(List<Vec2I> const& positions, Vec2F const& sourcePosition, TileDamage const& tileDamage) {
|
|
|
|
auto position = baseDamagePosition(positions);
|
|
|
|
|
|
|
|
auto geometry = world()->geometry();
|
|
|
|
|
|
|
|
m_tileDamageStatus.damage(m_tileDamageParameters, tileDamage);
|
|
|
|
m_tileDamageX = geometry.diff(position[0], tilePosition()[0]);
|
|
|
|
m_tileDamageY = position[1] - tilePosition()[1];
|
|
|
|
m_tileDamageEvent = true;
|
|
|
|
m_tileDamageEventTrigger = true;
|
|
|
|
|
|
|
|
bool breaking = false;
|
|
|
|
if (m_tileDamageStatus.dead()) {
|
|
|
|
breaking = true;
|
|
|
|
if (m_fallsWhenDead) {
|
|
|
|
m_tileDamageStatus.reset();
|
|
|
|
breakAtPosition(position, sourcePosition);
|
|
|
|
} else {
|
|
|
|
m_broken = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return breaking;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plant::breakAtPosition(Vec2I const& position, Vec2F const& sourcePosition) {
|
|
|
|
auto geometry = world()->geometry();
|
|
|
|
Vec2I internalPos = geometry.diff(position, tilePosition());
|
|
|
|
size_t idx = highest<size_t>();
|
|
|
|
int segmentIdx = highest<int>();
|
|
|
|
for (size_t i = 0; i < m_pieces.size(); ++i) {
|
|
|
|
auto& piece = m_pieces[i];
|
|
|
|
if (piece.structuralSegment && piece.spaces.contains(internalPos)) {
|
|
|
|
if (piece.segmentIdx < segmentIdx) {
|
|
|
|
segmentIdx = piece.segmentIdx;
|
|
|
|
idx = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// default to highest structural piece
|
|
|
|
if (idx >= m_pieces.size()) {
|
|
|
|
for (size_t i = m_pieces.size(); i > 0; --i) {
|
|
|
|
auto& piece = m_pieces[i - 1];
|
|
|
|
if (piece.structuralSegment) {
|
|
|
|
segmentIdx = piece.segmentIdx;
|
|
|
|
idx = i - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// plant has no structural segments? this is a terrible fallback because it
|
|
|
|
// prevents destruction
|
|
|
|
if (idx >= m_pieces.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
PlantPiece breakPiece = m_pieces[idx];
|
|
|
|
Vec2F breakPoint = Vec2F(position) - Vec2F(tilePosition());
|
|
|
|
if (breakPiece.spaces.size()) {
|
|
|
|
RectF bounds = RectF::null();
|
|
|
|
for (auto space : breakPiece.spaces) {
|
|
|
|
bounds.combine(Vec2F(space));
|
|
|
|
bounds.combine(Vec2F(space) + Vec2F(1, 1));
|
|
|
|
}
|
|
|
|
breakPoint[0] = (bounds.max()[0] + bounds.min()[0]) / 2.0f;
|
|
|
|
if (!m_ceiling)
|
|
|
|
breakPoint[1] = bounds.min()[1];
|
|
|
|
else
|
|
|
|
breakPoint[1] = bounds.max()[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
List<PlantPiece> droppedPieces;
|
|
|
|
if (m_pieces[idx].structuralSegment) {
|
|
|
|
idx = 0;
|
|
|
|
while (idx < m_pieces.size()) {
|
|
|
|
if (m_pieces[idx].segmentIdx >= segmentIdx) {
|
|
|
|
droppedPieces.append(m_pieces.takeAt(idx));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
droppedPieces.append(m_pieces.takeAt(idx));
|
|
|
|
}
|
|
|
|
m_piecesUpdated = true;
|
|
|
|
|
|
|
|
Vec2I breakPointI = Vec2I(round(breakPoint[0]), round(breakPoint[1]));
|
|
|
|
|
|
|
|
// Calculate a new origin for the droppedPieces
|
|
|
|
for (auto& piece : droppedPieces) {
|
|
|
|
piece.offset -= breakPoint;
|
|
|
|
Set<Vec2I> spaces = piece.spaces;
|
|
|
|
piece.spaces.clear();
|
|
|
|
for (auto const& space : spaces) {
|
|
|
|
piece.spaces.add(space - breakPointI);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2F worldSpaceBreakPoint = breakPoint + Vec2F(tilePosition());
|
|
|
|
|
|
|
|
List<int> segmentOrder;
|
|
|
|
Map<int, List<PlantPiece>> segments;
|
|
|
|
for (auto& piece : droppedPieces) {
|
|
|
|
if (!segments.contains(piece.segmentIdx))
|
|
|
|
segmentOrder.append(piece.segmentIdx);
|
|
|
|
segments[piece.segmentIdx].append(piece);
|
|
|
|
}
|
|
|
|
reverse(segmentOrder);
|
|
|
|
float random = Random::randf(-0.3f, 0.3f);
|
|
|
|
auto fallVector = (worldSpaceBreakPoint - sourcePosition).normalized();
|
|
|
|
bool first = true;
|
|
|
|
for (auto segmentIdx : segmentOrder) {
|
|
|
|
auto segment = segments[segmentIdx];
|
|
|
|
world()->addEntity(make_shared<PlantDrop>(segment,
|
|
|
|
worldSpaceBreakPoint,
|
|
|
|
fallVector,
|
|
|
|
description(),
|
|
|
|
m_ceiling,
|
|
|
|
m_stemDropConfig,
|
|
|
|
m_foliageDropConfig,
|
|
|
|
m_saplingDropConfig,
|
|
|
|
first,
|
|
|
|
random));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_piecesScanned = false;
|
|
|
|
|
|
|
|
validatePieces();
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2I Plant::baseDamagePosition(List<Vec2I> const& positions) const {
|
|
|
|
starAssert(positions.size());
|
|
|
|
auto res = positions.at(0);
|
|
|
|
|
|
|
|
for (auto const& piece : m_pieces) {
|
|
|
|
if (piece.structuralSegment) {
|
|
|
|
for (auto space : piece.spaces) {
|
|
|
|
for (auto position : positions) {
|
|
|
|
if (world()->geometry().equal(m_tilePosition + space, position)) {
|
|
|
|
// if this space is a "better match" for the root of the plant
|
|
|
|
if ((res[1] < position[1]) == m_ceiling) {
|
|
|
|
res = position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plant::damagable() const {
|
|
|
|
if (m_stemDropConfig.type() != Json::Type::Object)
|
|
|
|
return true;
|
|
|
|
if (!m_stemDropConfig.getBool("destructable", true))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|