v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
557
source/game/objects/StarContainerObject.cpp
Normal file
557
source/game/objects/StarContainerObject.cpp
Normal file
|
@ -0,0 +1,557 @@
|
|||
#include "StarContainerObject.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
#include "StarTreasure.hpp"
|
||||
#include "StarItemDatabase.hpp"
|
||||
#include "StarItemDrop.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
#include "StarEntityRendering.hpp"
|
||||
#include "StarMixer.hpp"
|
||||
#include "StarObjectDatabase.hpp"
|
||||
#include "StarAugmentItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ContainerObject::ContainerObject(ObjectConfigConstPtr config, Json const& parameters) : Object(config, parameters) {
|
||||
m_opened.set(0);
|
||||
m_count = 0;
|
||||
m_currentState = 0;
|
||||
m_animationFrameCooldown = 0;
|
||||
m_autoCloseCooldown = 0;
|
||||
|
||||
m_crafting.set(false);
|
||||
m_craftingProgress.set(0);
|
||||
|
||||
m_initialized = false;
|
||||
m_itemsUpdated = true;
|
||||
m_runUpdatedCallback = true;
|
||||
|
||||
m_items = make_shared<ItemBag>(configValue("slotCount").toInt());
|
||||
|
||||
m_netGroup.addNetElement(&m_opened);
|
||||
m_netGroup.addNetElement(&m_crafting);
|
||||
m_netGroup.addNetElement(&m_craftingProgress);
|
||||
m_netGroup.addNetElement(&m_itemsNetState);
|
||||
|
||||
m_craftingProgress.setInterpolator(lerp<float, float>);
|
||||
}
|
||||
|
||||
void ContainerObject::init(World* world, EntityId entityId, EntityMode mode) {
|
||||
if (mode == EntityMode::Master)
|
||||
m_interactive.set(true);
|
||||
|
||||
Object::init(world, entityId, mode);
|
||||
if (mode == EntityMode::Master) {
|
||||
if (!m_initialized) {
|
||||
m_initialized = true;
|
||||
float level = world->threatLevel();
|
||||
uint64_t seed = configValue("treasureSeed", Random::randu64()).toUInt();
|
||||
level = configValue("level", level).toFloat();
|
||||
level += configValue("levelAdjustment", 0).toFloat();
|
||||
if (!configValue("initialItems").isNull()) {
|
||||
List<ItemDescriptor> items;
|
||||
for (auto const& spec : configValue("initialItems").iterateArray())
|
||||
m_items->addItems({Root::singleton().itemDatabase()->item(ItemDescriptor(spec), level, ++seed)});
|
||||
}
|
||||
if (!configValue("treasurePools").isNull()) {
|
||||
String treasurePool = Random::randValueFrom(configValue("treasurePools").toArray()).toString();
|
||||
Root::singleton().treasureDatabase()->fillWithTreasure(m_items, treasurePool, level, ++seed);
|
||||
}
|
||||
itemsUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerObject::update(uint64_t currentStep) {
|
||||
Object::update(currentStep);
|
||||
|
||||
if (isMaster()) {
|
||||
for (auto const& drop : take(m_lostItems))
|
||||
world()->addEntity(ItemDrop::createRandomizedDrop(drop, position()));
|
||||
|
||||
if (m_crafting.get())
|
||||
tickCrafting();
|
||||
|
||||
if (m_autoCloseCooldown > 0) {
|
||||
m_autoCloseCooldown -= 1;
|
||||
if (m_autoCloseCooldown <= 0) {
|
||||
--m_count;
|
||||
if (m_count <= 0) {
|
||||
m_count = 0;
|
||||
m_opened.set(0);
|
||||
} else {
|
||||
m_autoCloseCooldown = configValue("autoCloseCooldown").toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ageItemsTimer.update(world()->epochTime());
|
||||
if (m_ageItemsTimer.elapsedTime() > configValue("ageItemsEvery", 10).toDouble()) {
|
||||
double elapsedTime = m_ageItemsTimer.elapsedTime() * configValue("itemAgeMultiplier", 1.0f).toDouble();
|
||||
for (auto& item : m_items->items()) {
|
||||
if (Root::singleton().itemDatabase()->ageItem(item, elapsedTime))
|
||||
itemsUpdated();
|
||||
}
|
||||
m_ageItemsTimer.setElapsedTime(0.0);
|
||||
}
|
||||
|
||||
if (take(m_runUpdatedCallback))
|
||||
m_scriptComponent.invoke("containerCallback");
|
||||
|
||||
} else {
|
||||
setImageKey("key", toString(m_currentState));
|
||||
setImageKey("state", m_crafting.get() ? "crafting" : "idle");
|
||||
|
||||
m_animationFrameCooldown -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerObject::render(RenderCallback* renderCallback) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
if (m_animationFrameCooldown <= 0) {
|
||||
if (m_opened.get() != m_currentState) {
|
||||
if (m_currentState == 0) {
|
||||
// opening, or flipping to the other side
|
||||
if (!configValue("openSounds").isNull()) {
|
||||
auto audio = make_shared<AudioInstance>(*assets->audio(Random::randValueFrom(configValue("openSounds").toArray()).toString()));
|
||||
audio->setPosition(position());
|
||||
audio->setRangeMultiplier(config()->soundEffectRangeMultiplier);
|
||||
renderCallback->addAudio(move(audio));
|
||||
}
|
||||
}
|
||||
if (m_currentState == configValue("openFrameIndex", 2).toInt()) {
|
||||
// closing
|
||||
if (!configValue("closeSounds").isNull()) {
|
||||
auto audio = make_shared<AudioInstance>(*assets->audio(Random::randValueFrom(configValue("closeSounds").toArray()).toString()));
|
||||
audio->setPosition(position());
|
||||
audio->setRangeMultiplier(config()->soundEffectRangeMultiplier);
|
||||
renderCallback->addAudio(move(audio));
|
||||
}
|
||||
}
|
||||
if (m_opened.get() < m_currentState) {
|
||||
m_currentState -= 1;
|
||||
} else {
|
||||
m_currentState += 1;
|
||||
}
|
||||
m_animationFrameCooldown = configValue("frameCooldown").toInt();
|
||||
} else {
|
||||
m_animationFrameCooldown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Object::render(renderCallback);
|
||||
}
|
||||
|
||||
void ContainerObject::destroy(RenderCallback* renderCallback) {
|
||||
Object::destroy(renderCallback);
|
||||
if (isMaster()) {
|
||||
for (auto const& drop : m_items->items())
|
||||
world()->addEntity(ItemDrop::createRandomizedDrop(drop, position()));
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<Json> ContainerObject::receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) {
|
||||
auto itemDb = Root::singleton().itemDatabase();
|
||||
|
||||
if (message.equalsIgnoreCase("startCrafting")) {
|
||||
startCrafting();
|
||||
return Json();
|
||||
|
||||
} else if (message.equalsIgnoreCase("stopCrafting")) {
|
||||
stopCrafting();
|
||||
return Json();
|
||||
|
||||
} else if (message.equalsIgnoreCase("burnContainerContents")) {
|
||||
burnContainerContents();
|
||||
return Json();
|
||||
|
||||
} else if (message.equalsIgnoreCase("addItems")) {
|
||||
return itemSafeDescriptor(doAddItems(itemDb->fromJson(args.at(0)))).toJson();
|
||||
|
||||
} else if (message.equalsIgnoreCase("putItems")) {
|
||||
return itemSafeDescriptor(doPutItems(args.at(0).toUInt(), itemDb->fromJson(args.at(1)))).toJson();
|
||||
|
||||
} else if (message.equalsIgnoreCase("takeItems")) {
|
||||
return itemSafeDescriptor(doTakeItems(args.at(0).toUInt(), args.at(1).toUInt())).toJson();
|
||||
|
||||
} else if (message.equalsIgnoreCase("swapItems")) {
|
||||
return itemSafeDescriptor(doSwapItems(args.at(0).toUInt(), itemDb->fromJson(args.at(1)), args.get(2).optBool().value(true))).toJson();
|
||||
|
||||
} else if (message.equalsIgnoreCase("applyAugment")) {
|
||||
return itemSafeDescriptor(doApplyAugment(args.at(0).toUInt(), itemDb->fromJson(args.at(1)))).toJson();
|
||||
|
||||
} else if (message.equalsIgnoreCase("consumeItems")) {
|
||||
return Json(doConsumeItems(ItemDescriptor(args.at(0))));
|
||||
|
||||
} else if (message.equalsIgnoreCase("consumeItemsAt")) {
|
||||
return Json(doConsumeItems(args.at(0).toUInt(), args.at(1).toUInt()));
|
||||
|
||||
} else if (message.equalsIgnoreCase("clearContainer")) {
|
||||
return Json(transform<JsonArray>(doClearContainer(), [](auto const& item) {
|
||||
return itemSafeDescriptor(item).toJson();
|
||||
}));
|
||||
|
||||
} else {
|
||||
return Object::receiveMessage(sendingConnection, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
InteractAction ContainerObject::interact(InteractRequest const&) {
|
||||
return InteractAction(InteractActionType::OpenContainer, entityId(), Json());
|
||||
}
|
||||
|
||||
Json ContainerObject::containerGuiConfig() const {
|
||||
return Root::singleton().assets()->json(configValue("uiConfig").toString().replace("<slots>", strf("%s", m_items->size())));
|
||||
}
|
||||
|
||||
String ContainerObject::containerDescription() const {
|
||||
return Object::shortDescription();
|
||||
}
|
||||
|
||||
String ContainerObject::containerSubTitle() const {
|
||||
Json categories = Root::singleton().assets()->json("/items/categories.config:labels");
|
||||
return categories.getString(Object::category(), Object::category());
|
||||
}
|
||||
|
||||
ItemDescriptor ContainerObject::iconItem() const {
|
||||
if (configValue("hasWindowIcon", true).toBool())
|
||||
return ItemDescriptor(name(), 1);
|
||||
return {};
|
||||
}
|
||||
|
||||
ItemBagConstPtr ContainerObject::itemBag() const {
|
||||
return m_items;
|
||||
}
|
||||
|
||||
void ContainerObject::containerOpen() {
|
||||
m_opened.set(configValue("openFrameIndex", 2).toInt());
|
||||
m_count++;
|
||||
m_autoCloseCooldown = configValue("autoCloseCooldown").toInt();
|
||||
}
|
||||
|
||||
void ContainerObject::containerClose() {
|
||||
--m_count;
|
||||
if (m_count <= 0) {
|
||||
m_count = 0;
|
||||
m_opened.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<ItemPtr> ContainerObject::addItems(ItemPtr const& items) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "addItems", {itemSafeDescriptor(items).toJson()}).wrap([](Json res) {
|
||||
return Root::singleton().itemDatabase()->item(ItemDescriptor(res));
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<ItemPtr>::createFulfilled(doAddItems(items));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<ItemPtr> ContainerObject::putItems(size_t pos, ItemPtr const& items) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "putItems", {itemSafeDescriptor(items).toJson()}).wrap([](Json res) {
|
||||
return Root::singleton().itemDatabase()->item(ItemDescriptor(res));
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<ItemPtr>::createFulfilled(doPutItems(pos, items));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<ItemPtr> ContainerObject::takeItems(size_t slot, size_t count) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "takeItems", {slot, count}).wrap([](Json res) {
|
||||
return Root::singleton().itemDatabase()->item(ItemDescriptor(res));
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<ItemPtr>::createFulfilled(doTakeItems(slot, count));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<ItemPtr> ContainerObject::swapItems(size_t slot, ItemPtr const& items, bool tryCombine) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "swapItems", {slot, itemSafeDescriptor(items).toJson(), tryCombine}).wrap([](Json res) {
|
||||
return Root::singleton().itemDatabase()->item(ItemDescriptor(res));
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<ItemPtr>::createFulfilled(doSwapItems(slot, items, tryCombine));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<ItemPtr> ContainerObject::applyAugment(size_t slot, ItemPtr const& augment) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "applyAugment", {slot, itemSafeDescriptor(augment).toJson()}).wrap([](Json res) {
|
||||
return Root::singleton().itemDatabase()->item(ItemDescriptor(res));
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<ItemPtr>::createFulfilled(doApplyAugment(slot, augment));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<bool> ContainerObject::consumeItems(ItemDescriptor const& descriptor) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "consumeItems", {descriptor.toJson()}).wrap([](Json res) {
|
||||
return res.toBool();
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<bool>::createFulfilled(doConsumeItems(descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<bool> ContainerObject::consumeItems(size_t pos, size_t count) {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "consumeItemsAt", {pos, count}).wrap([](Json res) {
|
||||
return res.toBool();
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<bool>::createFulfilled(doConsumeItems(pos, count));
|
||||
}
|
||||
}
|
||||
|
||||
RpcPromise<List<ItemPtr>> ContainerObject::clearContainer() {
|
||||
if (isSlave()) {
|
||||
return world()->sendEntityMessage(entityId(), "clearContainer", {}).wrap([](Json res) {
|
||||
auto itemDb = Root::singleton().itemDatabase();
|
||||
return res.toArray().transformed([itemDb](Json const& item) {
|
||||
return itemDb->item(ItemDescriptor(item));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return RpcPromise<List<ItemPtr>>::createFulfilled(doClearContainer());
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerObject::isCrafting() const {
|
||||
return m_crafting.get();
|
||||
}
|
||||
|
||||
void ContainerObject::startCrafting() {
|
||||
if (isSlave()) {
|
||||
world()->sendEntityMessage(entityId(), "startCrafting");
|
||||
} else {
|
||||
if (m_crafting.get())
|
||||
return;
|
||||
auto inputItems = m_items->items();
|
||||
inputItems.removeLast();
|
||||
m_goalRecipe = recipeForMaterials(inputItems);
|
||||
m_crafting.set(true);
|
||||
itemsUpdated();
|
||||
// tickCrafting validates
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerObject::stopCrafting() {
|
||||
if (isSlave()) {
|
||||
world()->sendEntityMessage(entityId(), "stopCrafting");
|
||||
} else {
|
||||
if (!m_crafting.get())
|
||||
return;
|
||||
m_crafting.set(false);
|
||||
m_craftingProgress.set(0);
|
||||
m_goalRecipe = ItemRecipe();
|
||||
}
|
||||
}
|
||||
|
||||
float ContainerObject::craftingProgress() const {
|
||||
if (!isCrafting())
|
||||
return 1;
|
||||
return clamp(m_craftingProgress.get(), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void ContainerObject::burnContainerContents() {
|
||||
if (isSlave()) {
|
||||
world()->sendEntityMessage(entityId(), "burnContainerContents");
|
||||
} else {
|
||||
stopCrafting();
|
||||
auto level = world()->getProperty("ship.fuel", 0).toUInt();
|
||||
auto maxLevel = world()->getProperty("ship.maxFuel", 0).toUInt();
|
||||
for (auto& item : m_items->items()) {
|
||||
if (level > maxLevel)
|
||||
level = maxLevel;
|
||||
if (maxLevel == level)
|
||||
break;
|
||||
|
||||
auto leftToFill = maxLevel - level;
|
||||
|
||||
if (item) {
|
||||
auto fuelSingle = item->instanceValue("fuelAmount", 0).toUInt();
|
||||
if (fuelSingle > 0) {
|
||||
auto itemsToConsume = min<uint64_t>((leftToFill + fuelSingle - 1) / fuelSingle, item->count());
|
||||
level = min(maxLevel, level + fuelSingle * itemsToConsume);
|
||||
auto consumed = item->consume(itemsToConsume);
|
||||
starAssert(consumed);
|
||||
_unused(consumed);
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsUpdated();
|
||||
world()->setProperty("ship.fuel", level);
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerObject::getNetStates(bool initial) {
|
||||
Object::getNetStates(initial);
|
||||
if (m_itemsNetState.pullUpdated()) {
|
||||
DataStreamBuffer ds(m_itemsNetState.get());
|
||||
m_items->read(ds);
|
||||
itemsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerObject::setNetStates() {
|
||||
Object::setNetStates();
|
||||
if (take(m_itemsUpdated)) {
|
||||
DataStreamBuffer ds;
|
||||
m_items->write(ds);
|
||||
m_itemsNetState.set(ds.takeData());
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerObject::readStoredData(Json const& diskStore) {
|
||||
Object::readStoredData(diskStore);
|
||||
|
||||
m_opened.set(diskStore.getInt("opened"));
|
||||
m_currentState = diskStore.getInt("currentState");
|
||||
m_crafting.set(diskStore.getBool("crafting"));
|
||||
m_craftingProgress.set(diskStore.getFloat("craftingProgress"));
|
||||
m_initialized = diskStore.getBool("initialized");
|
||||
m_items = make_shared<ItemBag>(ItemBag::loadStore(diskStore.get("items")));
|
||||
m_ageItemsTimer = EpochTimer(diskStore.get("ageItemsTimer"));
|
||||
|
||||
m_lostItems.appendAll(m_items->resize(configValue("slotCount").toUInt()));
|
||||
}
|
||||
|
||||
Json ContainerObject::writeStoredData() const {
|
||||
return Object::writeStoredData().setAll({
|
||||
{"opened", m_opened.get()},
|
||||
{"currentState", m_currentState},
|
||||
{"crafting", m_crafting.get()},
|
||||
{"craftingProgress", m_craftingProgress.get()},
|
||||
{"initialized", m_initialized},
|
||||
{"items", m_items->diskStore()},
|
||||
{"ageItemsTimer", m_ageItemsTimer.toJson()}
|
||||
});
|
||||
}
|
||||
|
||||
ItemRecipe ContainerObject::recipeForMaterials(List<ItemPtr> const& inputItems) {
|
||||
auto& root = Root::singleton();
|
||||
auto itemDatabase = root.itemDatabase();
|
||||
|
||||
Json recipeGroup = configValue("recipeGroup");
|
||||
if (!recipeGroup.isNull())
|
||||
return itemDatabase->getPreciseRecipeForMaterials(recipeGroup.toString(), inputItems, {});
|
||||
|
||||
Maybe<Json> result = m_scriptComponent.invoke<Json>("craftingRecipe", inputItems.filtered([](ItemPtr const& item) {
|
||||
return (bool)item;
|
||||
}).transformed([](ItemPtr const& item) {
|
||||
return item->descriptor().toJson();
|
||||
}));
|
||||
if (!result || result->isNull())
|
||||
return ItemRecipe();
|
||||
return itemDatabase->parseRecipe(*result);
|
||||
}
|
||||
|
||||
void ContainerObject::tickCrafting() {
|
||||
if (!m_crafting.get())
|
||||
return;
|
||||
|
||||
auto inputItems = m_items->items();
|
||||
inputItems.removeLast();
|
||||
auto recipe = recipeForMaterials(inputItems);
|
||||
bool craftingFail = false;
|
||||
if (recipe.isNull() || m_goalRecipe != recipe)
|
||||
craftingFail = true;
|
||||
ItemPtr targetItem = m_items->at(m_items->size() - 1);
|
||||
if (targetItem) {
|
||||
if (!targetItem->matches(m_goalRecipe.output, true))
|
||||
craftingFail = true;
|
||||
else if (targetItem->count() + m_goalRecipe.output.count() > targetItem->maxStack())
|
||||
craftingFail = true;
|
||||
}
|
||||
if (craftingFail) {
|
||||
m_crafting.set(false);
|
||||
m_craftingProgress.set(0);
|
||||
m_goalRecipe = ItemRecipe();
|
||||
return;
|
||||
}
|
||||
if (m_goalRecipe.duration > 0)
|
||||
m_craftingProgress.set(m_craftingProgress.get() + WorldTimestep / m_goalRecipe.duration);
|
||||
else
|
||||
m_craftingProgress.set(1.0f);
|
||||
if (m_craftingProgress.get() >= 1.0f) {
|
||||
m_craftingProgress.set(0);
|
||||
for (auto const& input : m_goalRecipe.inputs) {
|
||||
bool consumed = m_items->consumeItems(input);
|
||||
_unused(consumed);
|
||||
starAssert(consumed);
|
||||
}
|
||||
ItemPtr overflow =
|
||||
m_items->putItems(m_items->size() - 1, Root::singleton().itemDatabase()->item(m_goalRecipe.output));
|
||||
if (overflow)
|
||||
world()->addEntity(ItemDrop::createRandomizedDrop(overflow, position()));
|
||||
itemsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
ItemPtr ContainerObject::doAddItems(ItemPtr const& items) {
|
||||
itemsUpdated();
|
||||
return m_items->addItems(items);
|
||||
}
|
||||
|
||||
ItemPtr ContainerObject::doPutItems(size_t slot, ItemPtr const& items) {
|
||||
itemsUpdated();
|
||||
return m_items->putItems(slot, items);
|
||||
}
|
||||
|
||||
ItemPtr ContainerObject::doTakeItems(size_t slot, size_t count) {
|
||||
itemsUpdated();
|
||||
return m_items->takeItems(slot, count);
|
||||
}
|
||||
|
||||
ItemPtr ContainerObject::doSwapItems(size_t slot, ItemPtr const& items, bool tryCombine) {
|
||||
itemsUpdated();
|
||||
return m_items->swapItems(slot, items, tryCombine);
|
||||
}
|
||||
|
||||
ItemPtr ContainerObject::doApplyAugment(size_t slot, ItemPtr const& item) {
|
||||
itemsUpdated();
|
||||
if (auto augment = as<AugmentItem>(item))
|
||||
if (auto slotItem = m_items->at(slot))
|
||||
m_items->setItem(slot, augment->applyTo(slotItem));
|
||||
return item;
|
||||
}
|
||||
|
||||
bool ContainerObject::doConsumeItems(ItemDescriptor const& descriptor) {
|
||||
if (m_items->consumeItems(descriptor)) {
|
||||
itemsUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ContainerObject::doConsumeItems(size_t slot, size_t count) {
|
||||
if (m_items->consumeItems(slot, count)) {
|
||||
itemsUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ItemPtr> ContainerObject::doClearContainer() {
|
||||
stopCrafting();
|
||||
List<ItemPtr> result = m_items->takeAll();
|
||||
m_items->clearItems();
|
||||
itemsUpdated();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ContainerObject::itemsUpdated() {
|
||||
m_itemsUpdated = true;
|
||||
m_runUpdatedCallback = true;
|
||||
}
|
||||
|
||||
}
|
112
source/game/objects/StarContainerObject.hpp
Normal file
112
source/game/objects/StarContainerObject.hpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#ifndef STAR_CONTAINER_OBJECT_HPP
|
||||
#define STAR_CONTAINER_OBJECT_HPP
|
||||
|
||||
#include "StarItemBag.hpp"
|
||||
#include "StarObject.hpp"
|
||||
#include "StarWeightedPool.hpp"
|
||||
#include "StarContainerEntity.hpp"
|
||||
#include "StarItemRecipe.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ContainerObject);
|
||||
|
||||
class ContainerObject : public Object, public virtual ContainerEntity {
|
||||
public:
|
||||
ContainerObject(ObjectConfigConstPtr config, Json const& parameters);
|
||||
|
||||
void init(World* world, EntityId entityId, EntityMode mode) override;
|
||||
|
||||
void update(uint64_t currentStep) override;
|
||||
void render(RenderCallback* renderCallback) override;
|
||||
|
||||
void destroy(RenderCallback* renderCallback) override;
|
||||
InteractAction interact(InteractRequest const& request) override;
|
||||
|
||||
Maybe<Json> receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) override;
|
||||
|
||||
Json containerGuiConfig() const override;
|
||||
String containerDescription() const override;
|
||||
String containerSubTitle() const override;
|
||||
ItemDescriptor iconItem() const override;
|
||||
|
||||
ItemBagConstPtr itemBag() const override;
|
||||
|
||||
void containerOpen() override;
|
||||
void containerClose() override;
|
||||
|
||||
void startCrafting() override;
|
||||
void stopCrafting() override;
|
||||
bool isCrafting() const override;
|
||||
float craftingProgress() const override;
|
||||
|
||||
void burnContainerContents() override;
|
||||
|
||||
RpcPromise<ItemPtr> addItems(ItemPtr const& items) override;
|
||||
RpcPromise<ItemPtr> putItems(size_t slot, ItemPtr const& items) override;
|
||||
RpcPromise<ItemPtr> takeItems(size_t slot, size_t count = NPos) override;
|
||||
RpcPromise<ItemPtr> swapItems(size_t slot, ItemPtr const& items, bool tryCombine = true) override;
|
||||
RpcPromise<ItemPtr> applyAugment(size_t slot, ItemPtr const& augment) override;
|
||||
RpcPromise<bool> consumeItems(ItemDescriptor const& descriptor) override;
|
||||
RpcPromise<bool> consumeItems(size_t slot, size_t count) override;
|
||||
RpcPromise<List<ItemPtr>> clearContainer() override;
|
||||
|
||||
protected:
|
||||
void getNetStates(bool initial) override;
|
||||
void setNetStates() override;
|
||||
|
||||
void readStoredData(Json const& diskStore) override;
|
||||
Json writeStoredData() const override;
|
||||
|
||||
private:
|
||||
typedef std::function<void(ContainerObject*)> ContainerCallback;
|
||||
|
||||
ItemRecipe recipeForMaterials(List<ItemPtr> const& inputItems);
|
||||
void tickCrafting();
|
||||
|
||||
ItemPtr doAddItems(ItemPtr const& items);
|
||||
ItemPtr doStackItems(ItemPtr const& items);
|
||||
ItemPtr doPutItems(size_t slot, ItemPtr const& items);
|
||||
ItemPtr doTakeItems(size_t slot, size_t count = NPos);
|
||||
ItemPtr doSwapItems(size_t slot, ItemPtr const& items, bool tryCombine = true);
|
||||
ItemPtr doApplyAugment(size_t slot, ItemPtr const& augment);
|
||||
bool doConsumeItems(ItemDescriptor const& descriptor);
|
||||
bool doConsumeItems(size_t slot, size_t count);
|
||||
List<ItemPtr> doClearContainer();
|
||||
|
||||
template<typename T>
|
||||
RpcPromise<T> addSlavePromise(String const& message, JsonArray const& args, function<T(Json)> converter);
|
||||
|
||||
void itemsUpdated();
|
||||
|
||||
NetElementInt m_opened;
|
||||
|
||||
NetElementBool m_crafting;
|
||||
NetElementFloat m_craftingProgress;
|
||||
|
||||
ItemBagPtr m_items;
|
||||
NetElementBytes m_itemsNetState;
|
||||
|
||||
// master only
|
||||
|
||||
bool m_initialized;
|
||||
int m_count;
|
||||
int m_currentState;
|
||||
int64_t m_animationFrameCooldown;
|
||||
int64_t m_autoCloseCooldown;
|
||||
|
||||
ItemRecipe m_goalRecipe;
|
||||
|
||||
bool m_itemsUpdated;
|
||||
bool m_runUpdatedCallback;
|
||||
|
||||
ContainerCallback m_containerCallback;
|
||||
|
||||
EpochTimer m_ageItemsTimer;
|
||||
|
||||
List<ItemPtr> m_lostItems;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
187
source/game/objects/StarFarmableObject.cpp
Normal file
187
source/game/objects/StarFarmableObject.cpp
Normal file
|
@ -0,0 +1,187 @@
|
|||
#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();
|
||||
}
|
||||
|
||||
void FarmableObject::update(uint64_t currentStep) {
|
||||
Object::update(currentStep);
|
||||
|
||||
if (isMaster()) {
|
||||
if (m_nextStageTime == 0) {
|
||||
m_nextStageTime = world()->epochTime();
|
||||
enterStage(m_stage);
|
||||
}
|
||||
|
||||
while (!m_finalStage && world()->epochTime() >= m_nextStageTime)
|
||||
enterStage(m_stage + 1);
|
||||
|
||||
// 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 {
|
||||
world()->timer(2, [plant](World* world) {
|
||||
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}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
44
source/game/objects/StarFarmableObject.hpp
Normal file
44
source/game/objects/StarFarmableObject.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef STAR_FARMABLE_OBJECT_HPP
|
||||
#define STAR_FARMABLE_OBJECT_HPP
|
||||
|
||||
#include "StarObject.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class FarmableObject : public Object {
|
||||
public:
|
||||
FarmableObject(ObjectConfigConstPtr config, Json const& parameters);
|
||||
|
||||
void update(uint64_t currentStep) override;
|
||||
|
||||
bool damageTiles(List<Vec2I> const& position, Vec2F const& sourcePosition, TileDamage const& tileDamage) override;
|
||||
InteractAction interact(InteractRequest const& request) override;
|
||||
|
||||
bool harvest();
|
||||
int stage() const;
|
||||
|
||||
protected:
|
||||
void readStoredData(Json const& diskStore) override;
|
||||
Json writeStoredData() const override;
|
||||
|
||||
private:
|
||||
void enterStage(int newStage);
|
||||
|
||||
int m_stage;
|
||||
int m_stageAlt;
|
||||
double m_stageEnterTime;
|
||||
double m_nextStageTime;
|
||||
|
||||
SlidingWindow m_immersion;
|
||||
float m_minImmersion;
|
||||
float m_maxImmersion;
|
||||
|
||||
bool m_consumeSoilMoisture;
|
||||
|
||||
JsonArray m_stages;
|
||||
bool m_finalStage;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
107
source/game/objects/StarLoungeableObject.cpp
Normal file
107
source/game/objects/StarLoungeableObject.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
#include "StarLoungeableObject.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarObjectDatabase.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
LoungeableObject::LoungeableObject(ObjectConfigConstPtr config, Json const& parameters) : Object(config, parameters) {
|
||||
m_interactive.set(true);
|
||||
}
|
||||
|
||||
void LoungeableObject::render(RenderCallback* renderCallback) {
|
||||
Object::render(renderCallback);
|
||||
|
||||
if (!m_sitCoverImage.empty()) {
|
||||
if (!entitiesLounging().empty()) {
|
||||
if (auto orientation = currentOrientation()) {
|
||||
Drawable drawable =
|
||||
Drawable::makeImage(m_sitCoverImage, 1.0f / TilePixels, false, position() + orientation->imagePosition);
|
||||
if (m_flipImages)
|
||||
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center());
|
||||
renderCallback->addDrawable(move(drawable), RenderLayerObject + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InteractAction LoungeableObject::interact(InteractRequest const& request) {
|
||||
auto res = Object::interact(request);
|
||||
if (res.type == InteractActionType::None && !m_sitPositions.empty()) {
|
||||
Maybe<size_t> index;
|
||||
Vec2F interactOffset =
|
||||
direction() == Direction::Right ? position() - request.interactPosition : request.interactPosition - position();
|
||||
for (size_t i = 0; i < m_sitPositions.size(); ++i) {
|
||||
if (!index || vmag(m_sitPositions[i] + interactOffset) < vmag(m_sitPositions[*index] + interactOffset))
|
||||
index = i;
|
||||
}
|
||||
return InteractAction(InteractActionType::SitDown, entityId(), *index);
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
size_t LoungeableObject::anchorCount() const {
|
||||
return m_sitPositions.size();
|
||||
}
|
||||
|
||||
LoungeAnchorConstPtr LoungeableObject::loungeAnchor(size_t positionIndex) const {
|
||||
if (positionIndex >= m_sitPositions.size())
|
||||
return {};
|
||||
|
||||
auto loungeAnchor = make_shared<LoungeAnchor>();
|
||||
|
||||
loungeAnchor->controllable = false;
|
||||
loungeAnchor->direction = m_sitFlipDirection ? -direction() : direction();
|
||||
|
||||
loungeAnchor->position = m_sitPositions.at(positionIndex);
|
||||
if (loungeAnchor->direction == Direction::Left)
|
||||
loungeAnchor->position[0] *= -1;
|
||||
loungeAnchor->position += position();
|
||||
|
||||
loungeAnchor->exitBottomPosition = Vec2F(loungeAnchor->position[0], position()[1] + volume().boundBox().min()[1]);
|
||||
|
||||
loungeAnchor->angle = m_sitAngle;
|
||||
if (loungeAnchor->direction == Direction::Left)
|
||||
loungeAnchor->angle *= -1;
|
||||
|
||||
loungeAnchor->orientation = m_sitOrientation;
|
||||
// Layer all anchored entities one above the object layer, in top to bottom
|
||||
// order based on the anchor index.
|
||||
loungeAnchor->loungeRenderLayer = RenderLayerObject + m_sitPositions.size() - positionIndex;
|
||||
|
||||
loungeAnchor->statusEffects = m_sitStatusEffects;
|
||||
loungeAnchor->effectEmitters = m_sitEffectEmitters;
|
||||
loungeAnchor->emote = m_sitEmote;
|
||||
loungeAnchor->dance = m_sitDance;
|
||||
loungeAnchor->armorCosmeticOverrides = m_sitArmorCosmeticOverrides;
|
||||
loungeAnchor->cursorOverride = m_sitCursorOverride;
|
||||
|
||||
return loungeAnchor;
|
||||
}
|
||||
|
||||
void LoungeableObject::setOrientationIndex(size_t orientationIndex) {
|
||||
Object::setOrientationIndex(orientationIndex);
|
||||
if (orientationIndex != NPos) {
|
||||
if (auto sp = configValue("sitPosition")) {
|
||||
m_sitPositions = {jsonToVec2F(configValue("sitPosition")) / TilePixels};
|
||||
} else if (auto sps = configValue("sitPositions")) {
|
||||
m_sitPositions.clear();
|
||||
for (auto const& sp : sps.toArray())
|
||||
m_sitPositions.append(jsonToVec2F(sp) / TilePixels);
|
||||
}
|
||||
m_sitFlipDirection = configValue("sitFlipDirection", false).toBool();
|
||||
m_sitOrientation = LoungeOrientationNames.getLeft(configValue("sitOrientation", "sit").toString());
|
||||
m_sitAngle = configValue("sitAngle", 0).toFloat() * Constants::pi / 180.0f;
|
||||
m_sitCoverImage = configValue("sitCoverImage", "").toString();
|
||||
m_flipImages = configValue("flipImages", false).toBool();
|
||||
m_sitStatusEffects = configValue("sitStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
|
||||
m_sitEffectEmitters = jsonToStringSet(configValue("sitEffectEmitters", JsonArray()));
|
||||
m_sitEmote = configValue("sitEmote").optString();
|
||||
m_sitDance = configValue("sitDance").optString();
|
||||
m_sitArmorCosmeticOverrides = configValue("sitArmorCosmeticOverrides", JsonObject()).toObject();
|
||||
m_sitCursorOverride = configValue("sitCursorOverride").optString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
40
source/game/objects/StarLoungeableObject.hpp
Normal file
40
source/game/objects/StarLoungeableObject.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef STAR_INTERACTABLE_OBJECT_HPP
|
||||
#define STAR_INTERACTABLE_OBJECT_HPP
|
||||
|
||||
#include "StarObject.hpp"
|
||||
#include "StarLoungingEntities.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class LoungeableObject : public Object, public virtual LoungeableEntity {
|
||||
public:
|
||||
LoungeableObject(ObjectConfigConstPtr config, Json const& parameters = Json());
|
||||
|
||||
void render(RenderCallback* renderCallback) override;
|
||||
|
||||
InteractAction interact(InteractRequest const& request) override;
|
||||
|
||||
size_t anchorCount() const override;
|
||||
LoungeAnchorConstPtr loungeAnchor(size_t positionIndex) const override;
|
||||
|
||||
protected:
|
||||
void setOrientationIndex(size_t orientationIndex) override;
|
||||
|
||||
private:
|
||||
List<Vec2F> m_sitPositions;
|
||||
bool m_sitFlipDirection;
|
||||
LoungeOrientation m_sitOrientation;
|
||||
float m_sitAngle;
|
||||
String m_sitCoverImage;
|
||||
bool m_flipImages;
|
||||
List<PersistentStatusEffect> m_sitStatusEffects;
|
||||
StringSet m_sitEffectEmitters;
|
||||
Maybe<String> m_sitEmote;
|
||||
Maybe<String> m_sitDance;
|
||||
JsonObject m_sitArmorCosmeticOverrides;
|
||||
Maybe<String> m_sitCursorOverride;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
114
source/game/objects/StarPhysicsObject.cpp
Normal file
114
source/game/objects/StarPhysicsObject.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
#include "StarPhysicsObject.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarObjectDatabase.hpp"
|
||||
#include "StarLuaConverters.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
PhysicsObject::PhysicsObject(ObjectConfigConstPtr config, Json const& parameters) : Object(move(config), parameters) {
|
||||
for (auto const& p : configValue("physicsForces", JsonObject()).iterateObject()) {
|
||||
auto& forceConfig = m_physicsForces[p.first];
|
||||
|
||||
forceConfig.forceRegion = jsonToPhysicsForceRegion(p.second);
|
||||
forceConfig.enabled.set(p.second.getBool("enabled", true));
|
||||
}
|
||||
|
||||
for (auto const& p : configValue("physicsCollisions", JsonObject()).iterateObject()) {
|
||||
auto& collisionConfig = m_physicsCollisions[p.first];
|
||||
collisionConfig.movingCollision = PhysicsMovingCollision::fromJson(p.second);
|
||||
collisionConfig.xPosition.set(take(collisionConfig.movingCollision.position[0]));
|
||||
collisionConfig.yPosition.set(take(collisionConfig.movingCollision.position[1]));
|
||||
collisionConfig.enabled.set(p.second.getBool("enabled", true));
|
||||
}
|
||||
|
||||
m_physicsForces.sortByKey();
|
||||
for (auto& p : m_physicsForces)
|
||||
m_netGroup.addNetElement(&p.second.enabled);
|
||||
|
||||
m_physicsCollisions.sortByKey();
|
||||
for (auto& p : m_physicsCollisions) {
|
||||
m_netGroup.addNetElement(&p.second.xPosition);
|
||||
m_netGroup.addNetElement(&p.second.yPosition);
|
||||
p.second.xPosition.setInterpolator(lerp<float, float>);
|
||||
p.second.yPosition.setInterpolator(lerp<float, float>);
|
||||
m_netGroup.addNetElement(&p.second.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsObject::enableInterpolation(float extrapolationHint) {
|
||||
m_netGroup.enableNetInterpolation(extrapolationHint);
|
||||
}
|
||||
|
||||
void PhysicsObject::disableInterpolation() {
|
||||
m_netGroup.disableNetInterpolation();
|
||||
}
|
||||
|
||||
void PhysicsObject::init(World* world, EntityId entityId, EntityMode mode) {
|
||||
if (mode == EntityMode::Master) {
|
||||
LuaCallbacks physicsCallbacks;
|
||||
physicsCallbacks.registerCallback("setForceEnabled", [this](String const& force, bool enabled) {
|
||||
m_physicsForces.get(force).enabled.set(enabled);
|
||||
});
|
||||
physicsCallbacks.registerCallback("setCollisionPosition", [this](String const& collision, Vec2F const& pos) {
|
||||
auto& collisionConfig = m_physicsCollisions.get(collision);
|
||||
collisionConfig.xPosition.set(pos[0]);
|
||||
collisionConfig.yPosition.set(pos[1]);
|
||||
});
|
||||
physicsCallbacks.registerCallback("setCollisionEnabled", [this](String const& collision, bool const& enabled) {
|
||||
auto& collisionConfig = m_physicsCollisions.get(collision);
|
||||
collisionConfig.enabled.set(enabled);
|
||||
});
|
||||
m_scriptComponent.addCallbacks("physics", move(physicsCallbacks));
|
||||
}
|
||||
Object::init(world, entityId, mode);
|
||||
m_metaBoundBox = Object::metaBoundBox();
|
||||
for (auto const& p : m_physicsForces) {
|
||||
PhysicsForceRegion forceRegion = p.second.forceRegion;
|
||||
forceRegion.call([pos = position()](auto& fr) { fr.translate(pos); });
|
||||
m_metaBoundBox.combine(forceRegion.call([](auto& fr) { return fr.boundBox(); }));
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsObject::uninit() {
|
||||
m_scriptComponent.removeCallbacks("physics");
|
||||
Object::uninit();
|
||||
}
|
||||
|
||||
void PhysicsObject::update(uint64_t currentStep) {
|
||||
Object::update(currentStep);
|
||||
if (isSlave())
|
||||
m_netGroup.tickNetInterpolation(WorldTimestep);
|
||||
}
|
||||
|
||||
RectF PhysicsObject::metaBoundBox() const {
|
||||
return m_metaBoundBox;
|
||||
}
|
||||
|
||||
List<PhysicsForceRegion> PhysicsObject::forceRegions() const {
|
||||
List<PhysicsForceRegion> forces;
|
||||
for (auto const& p : m_physicsForces) {
|
||||
if (p.second.enabled.get()) {
|
||||
PhysicsForceRegion forceRegion = p.second.forceRegion;
|
||||
forceRegion.call([pos = position()](auto& fr) { fr.translate(pos); });
|
||||
forces.append(move(forceRegion));
|
||||
}
|
||||
}
|
||||
return forces;
|
||||
}
|
||||
|
||||
size_t PhysicsObject::movingCollisionCount() const {
|
||||
return m_physicsCollisions.size();
|
||||
}
|
||||
|
||||
Maybe<PhysicsMovingCollision> PhysicsObject::movingCollision(size_t positionIndex) const {
|
||||
auto const& collisionConfig = m_physicsCollisions.valueAt(positionIndex);
|
||||
if (!collisionConfig.enabled.get())
|
||||
return {};
|
||||
PhysicsMovingCollision collision = collisionConfig.movingCollision;
|
||||
collision.translate(position() + Vec2F(collisionConfig.xPosition.get(), collisionConfig.yPosition.get()));
|
||||
return collision;
|
||||
}
|
||||
|
||||
}
|
49
source/game/objects/StarPhysicsObject.hpp
Normal file
49
source/game/objects/StarPhysicsObject.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef STAR_PHYSICS_OBJECT_HPP
|
||||
#define STAR_PHYSICS_OBJECT_HPP
|
||||
|
||||
#include "StarObject.hpp"
|
||||
#include "StarPhysicsEntity.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class PhysicsObject : public Object, public virtual PhysicsEntity {
|
||||
public:
|
||||
PhysicsObject(ObjectConfigConstPtr config, Json const& parameters = Json());
|
||||
|
||||
void enableInterpolation(float extrapolationHint = 0.0f) override;
|
||||
void disableInterpolation() override;
|
||||
|
||||
void init(World* world, EntityId entityId, EntityMode mode) override;
|
||||
void uninit() override;
|
||||
|
||||
void update(uint64_t currentStep) override;
|
||||
|
||||
RectF metaBoundBox() const override;
|
||||
|
||||
List<PhysicsForceRegion> forceRegions() const override;
|
||||
|
||||
size_t movingCollisionCount() const override;
|
||||
Maybe<PhysicsMovingCollision> movingCollision(size_t positionIndex) const override;
|
||||
|
||||
private:
|
||||
struct PhysicsForceConfig {
|
||||
PhysicsForceRegion forceRegion;
|
||||
NetElementBool enabled;
|
||||
};
|
||||
|
||||
struct PhysicsCollisionConfig {
|
||||
PhysicsMovingCollision movingCollision;
|
||||
NetElementFloat xPosition;
|
||||
NetElementFloat yPosition;
|
||||
NetElementBool enabled;
|
||||
};
|
||||
|
||||
OrderedHashMap<String, PhysicsForceConfig> m_physicsForces;
|
||||
OrderedHashMap<String, PhysicsCollisionConfig> m_physicsCollisions;
|
||||
|
||||
RectF m_metaBoundBox;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
16
source/game/objects/StarTeleporterObject.cpp
Normal file
16
source/game/objects/StarTeleporterObject.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "StarTeleporterObject.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
TeleporterObject::TeleporterObject(ObjectConfigConstPtr config, Json const& parameters) : Object(config, parameters) {
|
||||
setUniqueId(configValue("uniqueId", Uuid().hex()).optString());
|
||||
}
|
||||
|
||||
Vec2F TeleporterObject::footPosition() const {
|
||||
if (auto footPos = configValue("teleporterFootPosition"))
|
||||
return jsonToVec2F(footPos);
|
||||
return Vec2F();
|
||||
}
|
||||
|
||||
}
|
13
source/game/objects/StarTeleporterObject.hpp
Normal file
13
source/game/objects/StarTeleporterObject.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "StarWarpTargetEntity.hpp"
|
||||
#include "StarObject.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class TeleporterObject : public Object, public WarpTargetEntity {
|
||||
public:
|
||||
TeleporterObject(ObjectConfigConstPtr config, Json const& parameters = JsonObject());
|
||||
|
||||
Vec2F footPosition() const override;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue