2023-06-20 04:33:09 +00:00
|
|
|
#include "StarFarmableObject.hpp"
|
|
|
|
#include "StarLexicalCast.hpp"
|
|
|
|
#include "StarJsonExtra.hpp"
|
|
|
|
#include "StarRoot.hpp"
|
|
|
|
#include "StarAssets.hpp"
|
|
|
|
#include "StarRandom.hpp"
|
|
|
|
#include "StarPlantDatabase.hpp"
|
|
|
|
#include "StarPlant.hpp"
|
|
|
|
#include "StarWorldServer.hpp"
|
|
|
|
#include "StarTreasure.hpp"
|
|
|
|
#include "StarItemDrop.hpp"
|
|
|
|
#include "StarLogging.hpp"
|
|
|
|
#include "StarObjectDatabase.hpp"
|
|
|
|
#include "StarMaterialDatabase.hpp"
|
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
FarmableObject::FarmableObject(ObjectConfigConstPtr config, Json const& parameters) : Object(config, parameters) {
|
|
|
|
m_stages = configValue("stages", JsonArray({JsonObject()})).toArray();
|
|
|
|
m_stage = configValue("startingStage", 0).toInt();
|
|
|
|
|
|
|
|
m_stageAlt = -1;
|
|
|
|
m_stageEnterTime = 0.0;
|
|
|
|
m_nextStageTime = 0.0;
|
|
|
|
m_finalStage = false;
|
|
|
|
|
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
m_minImmersion = configValue("minImmersion", 0).toFloat();
|
|
|
|
m_maxImmersion = configValue("maxImmersion", 2).toFloat();
|
|
|
|
m_immersion = SlidingWindow(assets->json("/farming.config:immersionWindow").toFloat(),
|
|
|
|
assets->json("/farming.config:immersionResolution").toUInt(), (m_minImmersion + m_maxImmersion) / 2);
|
|
|
|
|
|
|
|
m_consumeSoilMoisture = configValue("consumeSoilMoisture", true).toBool();
|
|
|
|
}
|
|
|
|
|
2023-07-20 14:58:49 +00:00
|
|
|
void FarmableObject::update(float dt, uint64_t currentStep) {
|
|
|
|
Object::update(dt, currentStep);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
if (isMaster()) {
|
|
|
|
if (m_nextStageTime == 0) {
|
|
|
|
m_nextStageTime = world()->epochTime();
|
|
|
|
enterStage(m_stage);
|
|
|
|
}
|
|
|
|
|
2024-08-25 10:29:22 +00:00
|
|
|
|
|
|
|
while (!m_finalStage && world()->epochTime() >= m_nextStageTime) {
|
|
|
|
int lastStage = m_stage;
|
2023-06-20 04:33:09 +00:00
|
|
|
enterStage(m_stage + 1);
|
2024-08-25 10:29:22 +00:00
|
|
|
if (m_stage == lastStage)
|
|
|
|
break;
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
// update immersion and check whether farmable should break
|
|
|
|
m_immersion.update(bind(&Object::liquidFillLevel, this));
|
|
|
|
if (m_immersion.average() > m_maxImmersion || m_immersion.average() < m_minImmersion)
|
|
|
|
breakObject(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FarmableObject::damageTiles(List<Vec2I> const& position, Vec2F const& sourcePosition, TileDamage const& tileDamage) {
|
|
|
|
if ((tileDamage.type != TileDamageType::Beamish && tileDamage.type != TileDamageType::Blockish && tileDamage.type != TileDamageType::Plantish) || !harvest())
|
|
|
|
return Object::damageTiles(position, sourcePosition, tileDamage);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
InteractAction FarmableObject::interact(InteractRequest const&) {
|
|
|
|
harvest();
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FarmableObject::harvest() {
|
|
|
|
if (isMaster() && m_stages.get(m_stage).contains("harvestPool")) {
|
|
|
|
for (auto const& treasureItem : Root::singleton().treasureDatabase()->createTreasure(m_stages.get(m_stage).getString("harvestPool"), world()->threatLevel()))
|
|
|
|
world()->addEntity(ItemDrop::createRandomizedDrop(treasureItem, position()));
|
|
|
|
|
|
|
|
if (m_stages.get(m_stage).contains("resetToStage")) {
|
|
|
|
m_nextStageTime = world()->epochTime();
|
|
|
|
enterStage(m_stages.get(m_stage).getInt("resetToStage"));
|
|
|
|
} else
|
|
|
|
breakObject(true);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FarmableObject::stage() const {
|
|
|
|
return m_stage;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FarmableObject::enterStage(int newStage) {
|
|
|
|
newStage = clamp<int>(newStage, 0, m_stages.size() - 1);
|
|
|
|
|
|
|
|
// attempt to consume water from the soil if needed
|
|
|
|
if (m_consumeSoilMoisture && newStage > m_stage) {
|
|
|
|
if (auto orientation = currentOrientation()) {
|
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
auto materialDatabase = Root::singleton().materialDatabase();
|
|
|
|
auto wetToDryMods = assets->json("/farming.config:wetToDryMods");
|
|
|
|
|
|
|
|
// try to transform all anchor spaces, back out and reset stage time if
|
|
|
|
// they're not wet
|
|
|
|
for (auto anchor : orientation->anchors) {
|
|
|
|
auto pos = tilePosition() + anchor.position;
|
|
|
|
if (auto newMod = wetToDryMods.optString(materialDatabase->modName(world()->mod(pos, anchor.layer)))) {
|
|
|
|
world()->modifyTile(pos, PlaceMod{anchor.layer, materialDatabase->modId(*newMod), MaterialHue()}, true);
|
|
|
|
} else {
|
|
|
|
Vec2F durationRange = jsonToVec2F(m_stages.get(m_stage).get("duration", JsonArray({0, 0})));
|
|
|
|
m_nextStageTime = world()->epochTime() + Random::randf(durationRange[0], durationRange[1]);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: remove this hacky tree stuff and make plants handle it
|
|
|
|
if (m_stages.get(newStage).getBool("tree", false)) {
|
|
|
|
String stemName = configValue("stemName").toString();
|
|
|
|
float stemHueShift = configValue("stemHueShift", 0).toFloat();
|
|
|
|
String foliageName = configValue("foliageName", "").toString();
|
|
|
|
float foliageHueShift = configValue("foliageHueShift", 0).toFloat();
|
|
|
|
Vec2I position = tilePosition();
|
|
|
|
|
|
|
|
TreeVariant tv;
|
|
|
|
auto plantDatabase = Root::singleton().plantDatabase();
|
|
|
|
if (!foliageName.empty())
|
|
|
|
tv = plantDatabase->buildTreeVariant(stemName, stemHueShift, foliageName, foliageHueShift);
|
|
|
|
else
|
|
|
|
tv = plantDatabase->buildTreeVariant(stemName, stemHueShift);
|
|
|
|
|
|
|
|
auto plant = plantDatabase->createPlant(tv, Random::randi64());
|
|
|
|
plant->setTilePosition(position);
|
|
|
|
|
|
|
|
if (anySpacesOccupied(plant->spaces()) || !allSpacesOccupied(plant->roots())) {
|
|
|
|
newStage = 0;
|
|
|
|
} else {
|
2024-03-17 06:33:31 +00:00
|
|
|
world()->timer(2.f / 60.f, [plant](World* world) {
|
2023-06-20 04:33:09 +00:00
|
|
|
world->addEntity(plant);
|
|
|
|
});
|
|
|
|
|
|
|
|
m_finalStage = true;
|
|
|
|
breakObject(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newStage == (int)m_stages.size() - 1) {
|
|
|
|
m_finalStage = true;
|
|
|
|
} else {
|
|
|
|
m_finalStage = false;
|
|
|
|
m_stageEnterTime = m_nextStageTime;
|
|
|
|
Vec2F durationRange = jsonToVec2F(m_stages.get(newStage).get("duration", JsonArray({0, 0})));
|
|
|
|
m_nextStageTime += Random::randf(durationRange[0], durationRange[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_interactive.set(m_stages.get(newStage).contains("harvestPool"));
|
|
|
|
|
|
|
|
// keep the same variant if stages have same number of alts
|
|
|
|
if (m_stageAlt == -1 || m_stages.get(newStage).getInt("alts", 1) != m_stages.get(m_stage).getInt("alts", 1))
|
|
|
|
m_stageAlt = Random::randInt(m_stages.get(newStage).getInt("alts", 1) - 1);
|
|
|
|
|
|
|
|
m_stage = newStage;
|
|
|
|
|
|
|
|
setImageKey("stage", toString(m_stage));
|
|
|
|
setImageKey("alt", toString(m_stageAlt));
|
|
|
|
}
|
|
|
|
|
|
|
|
void FarmableObject::readStoredData(Json const& diskStore) {
|
|
|
|
Object::readStoredData(diskStore);
|
|
|
|
|
|
|
|
m_stage = diskStore.getInt("stage");
|
|
|
|
m_stageAlt = diskStore.getInt("stageAlt");
|
|
|
|
m_stageEnterTime = diskStore.getDouble("stageEnterTime");
|
|
|
|
m_nextStageTime = diskStore.getDouble("nextStageTime");
|
|
|
|
|
|
|
|
m_finalStage = (m_stage == (int)m_stages.size() - 1);
|
|
|
|
setImageKey("stage", toString(m_stage));
|
|
|
|
setImageKey("alt", toString(m_stageAlt));
|
|
|
|
}
|
|
|
|
|
|
|
|
Json FarmableObject::writeStoredData() const {
|
|
|
|
return Object::writeStoredData().setAll({
|
|
|
|
{"stage", m_stage},
|
|
|
|
{"stageAlt", m_stageAlt},
|
|
|
|
{"stageEnterTime", m_stageEnterTime},
|
|
|
|
{"nextStageTime", m_nextStageTime}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|