diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/interfaces | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/interfaces')
46 files changed, 2961 insertions, 0 deletions
diff --git a/source/game/interfaces/StarActivatableItem.hpp b/source/game/interfaces/StarActivatableItem.hpp new file mode 100644 index 0000000..c8802c0 --- /dev/null +++ b/source/game/interfaces/StarActivatableItem.hpp @@ -0,0 +1,21 @@ +#ifndef STAR_ACTIVATABLE_ITEM_HPP +#define STAR_ACTIVATABLE_ITEM_HPP + +#include "StarConfig.hpp" + +namespace Star { + +STAR_CLASS(ActivatableItem); + +class ActivatableItem { +public: + virtual ~ActivatableItem() {} + virtual bool active() const = 0; + virtual void setActive(bool active) = 0; + virtual bool usable() const = 0; + virtual void activate() = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarAggressiveEntity.hpp b/source/game/interfaces/StarAggressiveEntity.hpp new file mode 100644 index 0000000..8f202dc --- /dev/null +++ b/source/game/interfaces/StarAggressiveEntity.hpp @@ -0,0 +1,17 @@ +#ifndef STAR_AGGRESSIVE_ENTITY_HPP +#define STAR_AGGRESSIVE_ENTITY_HPP + +#include "StarEntity.hpp" + +namespace Star { + +STAR_CLASS(AggressiveEntity); + +class AggressiveEntity : public virtual Entity { +public: + virtual bool aggressive() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarAnchorableEntity.cpp b/source/game/interfaces/StarAnchorableEntity.cpp new file mode 100644 index 0000000..8f15dbe --- /dev/null +++ b/source/game/interfaces/StarAnchorableEntity.cpp @@ -0,0 +1,21 @@ +#include "StarAnchorableEntity.hpp" + +namespace Star { + +bool EntityAnchorState::operator==(EntityAnchorState const& eas) const { + return tie(entityId, positionIndex) == tie(eas.entityId, eas.positionIndex); +} + +DataStream& operator>>(DataStream& ds, EntityAnchorState& anchorState) { + ds.read(anchorState.entityId); + ds.readVlqS(anchorState.positionIndex); + return ds; +} + +DataStream& operator<<(DataStream& ds, EntityAnchorState const& anchorState) { + ds.write(anchorState.entityId); + ds.writeVlqS(anchorState.positionIndex); + return ds; +} + +} diff --git a/source/game/interfaces/StarAnchorableEntity.hpp b/source/game/interfaces/StarAnchorableEntity.hpp new file mode 100644 index 0000000..4f0e5b8 --- /dev/null +++ b/source/game/interfaces/StarAnchorableEntity.hpp @@ -0,0 +1,39 @@ +#ifndef STAR_ANCHORABLE_ENTITY_HPP +#define STAR_ANCHORABLE_ENTITY_HPP + +#include "StarEntity.hpp" + +namespace Star { + +STAR_STRUCT(EntityAnchor); + +struct EntityAnchor { + virtual ~EntityAnchor() = default; + + Vec2F position; + // If set, the entity should place the bottom center of its collision poly on + // the given position at exit + Maybe<Vec2F> exitBottomPosition; + Direction direction; + float angle; +}; + +struct EntityAnchorState { + EntityId entityId; + size_t positionIndex; + + bool operator==(EntityAnchorState const& eas) const; +}; + +DataStream& operator>>(DataStream& ds, EntityAnchorState& anchorState); +DataStream& operator<<(DataStream& ds, EntityAnchorState const& anchorState); + +class AnchorableEntity : public virtual Entity { +public: + virtual size_t anchorCount() const = 0; + virtual EntityAnchorConstPtr anchor(size_t anchorPositionIndex) const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarBeamItem.cpp b/source/game/interfaces/StarBeamItem.cpp new file mode 100644 index 0000000..4a6a98b --- /dev/null +++ b/source/game/interfaces/StarBeamItem.cpp @@ -0,0 +1,268 @@ +#include "StarBeamItem.hpp" +#include "StarJsonExtra.hpp" +#include "StarImageProcessing.hpp" +#include "StarRoot.hpp" +#include "StarAssets.hpp" +#include "StarRandom.hpp" +#include "StarItem.hpp" +#include "StarToolUserEntity.hpp" +#include "StarWorld.hpp" + +namespace Star { + +BeamItem::BeamItem(Json config) { + config = Root::singleton().assets()->json("/player.config:beamGunConfig").setAll(config.toObject()); + + m_image = config.get("image").toString(); + m_endImages = jsonToStringList(config.get("endImages")); + m_endType = EndType::Invalid; + m_segmentsPerUnit = config.get("segmentsPerUnit").toFloat(); + m_nearControlPointElasticity = config.get("nearControlPointElasticity").toFloat(); + m_farControlPointElasticity = config.get("farControlPointElasticity").toFloat(); + m_nearControlPointDistance = config.get("nearControlPointDistance").toFloat(); + m_handPosition = jsonToVec2F(config.get("handPosition")); + m_firePosition = jsonToVec2F(config.get("firePosition")); + m_range = 1.0f; + m_targetSegmentRun = config.get("targetSegmentRun").toFloat(); + m_minBeamWidth = config.get("minBeamWidth").toFloat(); + m_maxBeamWidth = config.get("maxBeamWidth").toFloat(); + m_beamWidthDev = config.getFloat("beamWidthDev", (m_maxBeamWidth - m_minBeamWidth) / 3); + m_minBeamJitter = config.get("minBeamJitter").toFloat(); + m_maxBeamJitter = config.get("maxBeamJitter").toFloat(); + m_beamJitterDev = config.getFloat("beamJitterDev", (m_maxBeamJitter * 2) / 3); + m_minBeamTrans = config.get("minBeamTrans").toFloat(); + m_maxBeamTrans = config.get("maxBeamTrans").toFloat(); + m_beamTransDev = config.getFloat("beamTransDev", (m_maxBeamTrans - m_minBeamTrans) / 3); + m_minBeamLines = config.get("minBeamLines").toInt(); + m_maxBeamLines = config.get("maxBeamLines").toInt(); + m_innerBrightnessScale = config.get("innerBrightnessScale").toFloat(); + m_firstStripeThickness = config.get("firstStripeThickness").toFloat(); + m_secondStripeThickness = config.get("secondStripeThickness").toFloat(); + m_color = {255, 255, 255, 255}; + m_particleGenerateCooldown = .25; + m_inRangeLastUpdate = false; +} + +void BeamItem::init(ToolUserEntity* owner, ToolHand hand) { + ToolUserItem::init(owner, hand); + + m_beamCurve = CSplineF(); + + if (initialized()) { + m_color = owner->favoriteColor(); + m_range = owner->beamGunRadius(); + return; + } + + throw ItemException("BeamItem::init: Beam Gun not init'd properly, or user not recognized as Tool User."); +} + +void BeamItem::update(FireMode, bool, HashSet<MoveControlType> const&) { + if (m_particleGenerateCooldown >= 0) + m_particleGenerateCooldown -= WorldTimestep; + + if (!initialized()) + throw ItemException("BeamItem::update: Beam Gun not init'd properly, or user not recognized as Tool User."); + + m_beamCurve.origin() = owner()->handPosition(hand(), (m_firePosition - m_handPosition) / TilePixels); + + if (m_endType == EndType::TileGroup) + m_beamCurve.dest() = world()->geometry().diff(owner()->aimPosition().round(), owner()->position()); + else if (m_endType == EndType::Wire) + m_beamCurve.dest() = world()->geometry().diff(owner()->aimPosition(), owner()->position()); + else + m_beamCurve.dest() = world()->geometry().diff(centerOfTile(owner()->aimPosition()), owner()->position()); + + if (m_beamCurve.dest().magnitudeSquared() < m_beamCurve.origin().magnitudeSquared()) + m_beamCurve[2] = m_beamCurve.dest(); + else + m_beamCurve[2] = m_beamCurve[2] + (m_beamCurve.dest() - m_beamCurve[2]) * m_farControlPointElasticity; + + Vec2F desiredNearControlPoint = (m_beamCurve.dest() - m_beamCurve.origin()) * m_nearControlPointDistance; + + if (m_beamCurve.dest().magnitudeSquared() < m_beamCurve.origin().magnitudeSquared()) + m_beamCurve[1] = m_beamCurve.origin(); + else if (owner()->facingDirection() != getAngleSide(m_beamCurve[1].angle()).second) + m_beamCurve[1] = desiredNearControlPoint; + else + m_beamCurve[1] = m_beamCurve[1] + (desiredNearControlPoint - m_beamCurve[1]) * m_nearControlPointElasticity; +} + +List<Drawable> BeamItem::nonRotatedDrawables() const { + return beamDrawables(); +} + +float BeamItem::getAngle(float angle) { + if (m_beamCurve.dest().magnitudeSquared() < m_beamCurve.origin().magnitudeSquared() + || m_beamCurve.origin() == m_beamCurve[1]) + return angle; + return getAngleSide(m_beamCurve[1].angle()).first; +} + +List<Drawable> BeamItem::drawables() const { + return {Drawable::makeImage(m_image, 1.0f / TilePixels, true, -handPosition() / TilePixels)}; +} + +Vec2F BeamItem::handPosition() const { + return m_handPosition; +} + +Vec2F BeamItem::firePosition() const { + return m_firePosition; +} + +void BeamItem::setRange(float range) { + m_range = range; +} + +float BeamItem::getAppropriateOpacity() const { + float curveLen = m_beamCurve.length(); + const float rangeEffect = (m_range - curveLen) / m_range; + + auto projectOntoRange = [&](float min, float max) { return rangeEffect * (max - min) + min; }; + auto rangeRand = [&](float dev, float min, float max) { + return clamp<float>(Random::nrandf(dev, projectOntoRange(min, max)), min, max); + }; + + int numLines = projectOntoRange(m_minBeamLines, m_maxBeamLines); + float res = (1 - rangeRand(m_beamTransDev, m_minBeamTrans, m_maxBeamTrans)); + if (numLines > 0) { + for (auto line = 0; line < numLines - 1; line++) + res *= (1 - rangeRand(m_beamTransDev, m_minBeamTrans, m_maxBeamTrans)); + } + return 1 - res; +} + +void BeamItem::setEnd(EndType type) { + m_endType = type; +} + +List<Drawable> BeamItem::beamDrawables(bool canPlace) const { + List<Drawable> res; + + float curveLen = m_beamCurve.length(); + const float rangeEffect = (m_range - curveLen) / m_range; + + auto projectOntoRange = [&](float min, float max) { return rangeEffect * (max - min) + min; }; + auto rangeRand = [&](float dev, float min, float max) { + return clamp<float>(Random::nrandf(dev, projectOntoRange(min, max)), min, max); + }; + + if (initialized()) { + Vec2F endPoint; + if (m_endType == EndType::TileGroup) + endPoint = owner()->aimPosition().round(); + else if (m_endType == EndType::Wire) + endPoint = owner()->aimPosition(); + else + endPoint = centerOfTile(owner()->aimPosition()); + + if ((endPoint - owner()->position()).magnitude() <= m_range && curveLen <= m_range) { + m_inRangeLastUpdate = true; + int numLines = projectOntoRange(m_minBeamLines, m_maxBeamLines); + Vec4B mainColor = m_color; + if (!canPlace) { + Color temp = Color::rgba(m_color); + temp.setHue(temp.hue() + 120); + mainColor = temp.toRgba(); + } + m_lastUpdateColor = mainColor; + + String endImage = ""; + if (m_endType != EndType::Invalid) { + endImage = m_endImages[(unsigned)m_endType]; + } + + if (!endImage.empty()) { + if (!canPlace) { + ImageOperation op = HueShiftImageOperation::hueShiftDegrees(120); + endImage = strf("%s?%s", endImage, imageOperationToString(op)); + } + + Drawable ball = Drawable::makeImage(endImage, 1.0f / TilePixels, true, m_beamCurve.dest()); + Color ballColor = Color::White; + ballColor.setAlphaF(getAppropriateOpacity()); + ball.color = ballColor; + res.push_back(ball); + } + + for (auto line = 0; line < numLines; line++) { + float lineThickness = rangeRand(m_beamWidthDev, m_minBeamWidth, m_maxBeamWidth); + float beamTransparency = rangeRand(m_beamTransDev, m_minBeamTrans, m_maxBeamTrans); + mainColor[3] = mainColor[3] * beamTransparency; + Vec2F previousLoc = m_beamCurve.origin(); // lines meet at origin and dest. + Color innerStripe = Color::rgba(mainColor); + innerStripe.setValue(1 - (1 - innerStripe.value()) / m_innerBrightnessScale); + innerStripe.setSaturation(innerStripe.saturation() / m_innerBrightnessScale); + Vec4B firstStripe = innerStripe.toRgba(); + innerStripe.setValue(1 - (1 - innerStripe.value()) / m_innerBrightnessScale); + innerStripe.setSaturation(innerStripe.saturation() / m_innerBrightnessScale); + Vec4B secondStripe = innerStripe.toRgba(); + + for (auto i = 1; i < (int)(curveLen * m_targetSegmentRun - .5); i++) { // one less than full length + float pos = (float)i / (float)(int)(curveLen * m_targetSegmentRun + .5); // project the discrete steps evenly + + Vec2F currentLoc = + m_beamCurve.pointAt(pos) + Vec2F(rangeRand(m_beamJitterDev, -m_maxBeamJitter, m_maxBeamJitter), + rangeRand(m_beamJitterDev, -m_maxBeamJitter, m_maxBeamJitter)); + res.push_back( + Drawable::makeLine(Line2F(previousLoc, currentLoc), lineThickness, Color::rgba(mainColor), Vec2F())); + res.push_back(Drawable::makeLine(Line2F(previousLoc, currentLoc), + lineThickness * m_firstStripeThickness, + Color::rgba(firstStripe), + Vec2F())); + res.push_back(Drawable::makeLine(Line2F(previousLoc, currentLoc), + lineThickness * m_secondStripeThickness, + Color::rgba(secondStripe), + Vec2F())); + previousLoc = std::move(currentLoc); + } + res.push_back(Drawable::makeLine( + Line2F(previousLoc, m_beamCurve.dest()), lineThickness, Color::rgba(mainColor), Vec2F())); + res.push_back(Drawable::makeLine(Line2F(previousLoc, m_beamCurve.dest()), + lineThickness * m_firstStripeThickness, + Color::rgba(firstStripe), + Vec2F())); + res.push_back(Drawable::makeLine(Line2F(previousLoc, m_beamCurve.dest()), + lineThickness * m_secondStripeThickness, + Color::rgba(secondStripe), + Vec2F())); + } + } else { + if (m_inRangeLastUpdate) { + m_inRangeLastUpdate = false; + m_particleGenerateCooldown = .25; // TODO, expose to json + List<Particle> beamLeftovers; + for (auto i = 1; i < (int)(curveLen * m_targetSegmentRun * 2 - .5); i++) { // one less than full length + float pos = + (float)i / (float)(int)(curveLen * m_targetSegmentRun * 2 + .5); // project the discrete steps evenly + float curveLoc = m_beamCurve.arcLenPara(pos); + + Particle beamParticle; + beamParticle.type = Particle::Type::Ember; + beamParticle.position = m_beamCurve.pointAt(curveLoc); + beamParticle.size = 1.0f; + + Color randomColor = Color::rgba(m_lastUpdateColor); + randomColor.setValue(1 - (1 - randomColor.value()) / Random::randf(1, 4)); + randomColor.setSaturation(randomColor.saturation() / Random::randf(1, 4)); + + beamParticle.color = randomColor; + beamParticle.velocity = Vec2F::filled(Random::randf()); + beamParticle.finalVelocity = Vec2F(0.0f, -20.0f); + beamParticle.approach = Vec2F(0.0f, 5.0f); + beamParticle.timeToLive = 0.25f; + beamParticle.destructionAction = Particle::DestructionAction::Shrink; + beamParticle.destructionTime = 0.2f; + beamLeftovers.append(beamParticle); + } + + owner()->addParticles(beamLeftovers); + } + } + } + + return res; +} + +} diff --git a/source/game/interfaces/StarBeamItem.hpp b/source/game/interfaces/StarBeamItem.hpp new file mode 100644 index 0000000..aeb5d93 --- /dev/null +++ b/source/game/interfaces/StarBeamItem.hpp @@ -0,0 +1,78 @@ +#ifndef STAR_BEAM_ITEM_HPP +#define STAR_BEAM_ITEM_HPP + +#include "StarSpline.hpp" +#include "StarGameTypes.hpp" +#include "StarNonRotatedDrawablesItem.hpp" +#include "StarToolUserItem.hpp" + +namespace Star { + +STAR_CLASS(Item); +STAR_CLASS(ToolUserEntity); +STAR_CLASS(World); + +STAR_CLASS(BeamItem); + +class BeamItem : public virtual NonRotatedDrawablesItem, public virtual ToolUserItem { +public: + enum class EndType { Invalid = -1, Object, Tile, TileGroup, Wire }; + + BeamItem(Json config); + virtual ~BeamItem() = default; + + virtual void init(ToolUserEntity* owner, ToolHand hand) override; + virtual void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override; + + virtual List<Drawable> nonRotatedDrawables() const override; + + virtual float getAngle(float angle); + virtual List<Drawable> drawables() const; + virtual Vec2F handPosition() const; + virtual Vec2F firePosition() const; + virtual void setRange(float range); + virtual float getAppropriateOpacity() const; + virtual void setEnd(EndType type); + +protected: + List<Drawable> beamDrawables(bool canPlace = true) const; + + String m_image; + StringList m_endImages; + EndType m_endType; + + float m_segmentsPerUnit; + float m_nearControlPointElasticity; + float m_farControlPointElasticity; + float m_nearControlPointDistance; + Vec2F m_handPosition; + Vec2F m_firePosition; + float m_range; + + float m_targetSegmentRun; + float m_minBeamWidth; + float m_maxBeamWidth; + float m_beamWidthDev; + float m_minBeamJitter; + float m_maxBeamJitter; + float m_beamJitterDev; + float m_minBeamTrans; + float m_maxBeamTrans; + float m_beamTransDev; + int m_minBeamLines; + int m_maxBeamLines; + float m_innerBrightnessScale; + float m_firstStripeThickness; + float m_secondStripeThickness; + Vec4B m_color; + + mutable bool m_inRangeLastUpdate; + mutable Vec4B m_lastUpdateColor; + mutable float m_particleGenerateCooldown; + + CSplineF m_beamCurve; +}; + +} + +#endif diff --git a/source/game/interfaces/StarChattyEntity.hpp b/source/game/interfaces/StarChattyEntity.hpp new file mode 100644 index 0000000..57c590a --- /dev/null +++ b/source/game/interfaces/StarChattyEntity.hpp @@ -0,0 +1,19 @@ +#ifndef STAR_CHATTY_ENTITY_HPP +#define STAR_CHATTY_ENTITY_HPP + +#include "StarChatAction.hpp" +#include "StarEntity.hpp" + +namespace Star { + +STAR_CLASS(ChattyEntity); + +class ChattyEntity : public virtual Entity { +public: + virtual Vec2F mouthPosition() const = 0; + virtual List<ChatAction> pullPendingChatActions() = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarContainerEntity.cpp b/source/game/interfaces/StarContainerEntity.cpp new file mode 100644 index 0000000..05de6f8 --- /dev/null +++ b/source/game/interfaces/StarContainerEntity.cpp @@ -0,0 +1,15 @@ +#include "StarContainerEntity.hpp" +#include "StarItemBag.hpp" + +namespace Star { + +size_t ContainerEntity::containerSize() const { + return itemBag()->size(); +} + +List<ItemPtr> ContainerEntity::containerItems() const { + return itemBag()->items(); +} + +} + diff --git a/source/game/interfaces/StarContainerEntity.hpp b/source/game/interfaces/StarContainerEntity.hpp new file mode 100644 index 0000000..6a123ee --- /dev/null +++ b/source/game/interfaces/StarContainerEntity.hpp @@ -0,0 +1,50 @@ +#ifndef STAR_CONTAINER_ENTITY_HPP +#define STAR_CONTAINER_ENTITY_HPP + +#include "StarGameTypes.hpp" +#include "StarTileEntity.hpp" +#include "StarItemDescriptor.hpp" +#include "StarRpcPromise.hpp" + +namespace Star { + +STAR_CLASS(Item); +STAR_CLASS(ItemBag); +STAR_CLASS(ContainerEntity); + +// All container methods may be called on both master and slave entities. +class ContainerEntity : public virtual TileEntity { +public: + size_t containerSize() const; + List<ItemPtr> containerItems() const; + + virtual Json containerGuiConfig() const = 0; + virtual String containerDescription() const = 0; + virtual String containerSubTitle() const = 0; + virtual ItemDescriptor iconItem() const = 0; + + virtual ItemBagConstPtr itemBag() const = 0; + + virtual void containerOpen() = 0; + virtual void containerClose() = 0; + + virtual void startCrafting() = 0; + virtual void stopCrafting() = 0; + virtual bool isCrafting() const = 0; + virtual float craftingProgress() const = 0; + + virtual void burnContainerContents() = 0; + + virtual RpcPromise<ItemPtr> addItems(ItemPtr const& items) = 0; + virtual RpcPromise<ItemPtr> putItems(size_t slot, ItemPtr const& items) = 0; + virtual RpcPromise<ItemPtr> takeItems(size_t slot, size_t count = NPos) = 0; + virtual RpcPromise<ItemPtr> swapItems(size_t slot, ItemPtr const& items, bool tryCombine = true) = 0; + virtual RpcPromise<ItemPtr> applyAugment(size_t slot, ItemPtr const& augment) = 0; + virtual RpcPromise<bool> consumeItems(ItemDescriptor const& descriptor) = 0; + virtual RpcPromise<bool> consumeItems(size_t slot, size_t count) = 0; + virtual RpcPromise<List<ItemPtr>> clearContainer() = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarDamageBarEntity.cpp b/source/game/interfaces/StarDamageBarEntity.cpp new file mode 100644 index 0000000..d070c6e --- /dev/null +++ b/source/game/interfaces/StarDamageBarEntity.cpp @@ -0,0 +1,11 @@ +#include "StarDamageBarEntity.hpp" + +namespace Star { + +EnumMap<DamageBarType> const DamageBarTypeNames{ + {DamageBarType::Default, "Default"}, + {DamageBarType::None, "None"}, + {DamageBarType::Special, "Special"} +}; + +} diff --git a/source/game/interfaces/StarDamageBarEntity.hpp b/source/game/interfaces/StarDamageBarEntity.hpp new file mode 100644 index 0000000..758ccab --- /dev/null +++ b/source/game/interfaces/StarDamageBarEntity.hpp @@ -0,0 +1,27 @@ +#ifndef STAR_DAMAGE_BAR_ENTITY_HPP +#define STAR_DAMAGE_BAR_ENTITY_HPP + +#include "StarPortraitEntity.hpp" + +namespace Star { + +STAR_CLASS(DamageBarEntity); + +enum class DamageBarType : uint8_t { + Default, + None, + Special +}; +extern EnumMap<DamageBarType> const DamageBarTypeNames; + +class DamageBarEntity : public virtual PortraitEntity { +public: + virtual float health() const = 0; + virtual float maxHealth() const = 0; + virtual String name() const = 0; + virtual DamageBarType damageBar() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarDurabilityItem.hpp b/source/game/interfaces/StarDurabilityItem.hpp new file mode 100644 index 0000000..9743a67 --- /dev/null +++ b/source/game/interfaces/StarDurabilityItem.hpp @@ -0,0 +1,18 @@ +#ifndef STAR_DURABILITY_ITEM_HPP +#define STAR_DURABILITY_ITEM_HPP + +#include "StarConfig.hpp" + +namespace Star { + +STAR_CLASS(DurabilityItem); + +class DurabilityItem { +public: + virtual ~DurabilityItem() {} + virtual float durabilityStatus() = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarEffectSourceItem.hpp b/source/game/interfaces/StarEffectSourceItem.hpp new file mode 100644 index 0000000..9890885 --- /dev/null +++ b/source/game/interfaces/StarEffectSourceItem.hpp @@ -0,0 +1,18 @@ +#ifndef STAR_EFFECT_SOURCE_ITEM_HPP +#define STAR_EFFECT_SOURCE_ITEM_HPP + +#include "StarString.hpp" + +namespace Star { + +STAR_CLASS(EffectSourceItem); + +class EffectSourceItem { +public: + virtual ~EffectSourceItem() {} + virtual StringSet effectSources() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarEmoteEntity.hpp b/source/game/interfaces/StarEmoteEntity.hpp new file mode 100644 index 0000000..b66da52 --- /dev/null +++ b/source/game/interfaces/StarEmoteEntity.hpp @@ -0,0 +1,18 @@ +#ifndef STAR_EMOTE_ENTITY_HPP +#define STAR_EMOTE_ENTITY_HPP + +#include "StarHumanoid.hpp" +#include "StarEntity.hpp" + +namespace Star { + +STAR_CLASS(EmoteEntity); + +class EmoteEntity : public virtual Entity { +public: + virtual void playEmote(HumanoidEmote emote) = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarEntity.cpp b/source/game/interfaces/StarEntity.cpp new file mode 100644 index 0000000..405fdde --- /dev/null +++ b/source/game/interfaces/StarEntity.cpp @@ -0,0 +1,194 @@ +#include "StarEntity.hpp" +#include "StarDamageManager.hpp" + +namespace Star { + +EnumMap<ClientEntityMode> const ClientEntityModeNames{ + {ClientEntityMode::ClientSlaveOnly, "ClientSlaveOnly"}, + {ClientEntityMode::ClientMasterAllowed, "ClientMasterAllowed"}, + {ClientEntityMode::ClientPresenceMaster, "ClientPresenceMaster"} +}; + +EnumMap<EntityType> const EntityTypeNames{ + {EntityType::Plant, "plant"}, + {EntityType::Object, "object"}, + {EntityType::Vehicle, "vehicle"}, + {EntityType::ItemDrop, "itemDrop"}, + {EntityType::PlantDrop, "plantDrop"}, + {EntityType::Projectile, "projectile"}, + {EntityType::Stagehand, "stagehand"}, + {EntityType::Monster, "monster"}, + {EntityType::Npc, "npc"}, + {EntityType::Player, "player"} +}; + +Entity::~Entity() {} + +void Entity::init(World* world, EntityId entityId, EntityMode mode) { + if (!world) + throw EntityException("Entity::init called with null world pointer"); + if (entityId == NullEntityId) + throw EntityException("Entity::init called with null entity id"); + if (m_world) + throw EntityException("Entity::init called when already initialized"); + + m_world = world; + m_entityMode = mode; + m_entityId = entityId; +} + +void Entity::uninit() { + m_world = nullptr; + m_entityMode = {}; + m_entityId = NullEntityId; +} + +pair<ByteArray, uint64_t> Entity::writeNetState(uint64_t) { + return {ByteArray(), 0}; +} + +void Entity::readNetState(ByteArray, float) {} + +void Entity::enableInterpolation(float) {} + +void Entity::disableInterpolation() {} + +RectF Entity::collisionArea() const { + return RectF::null(); +} + +bool Entity::ephemeral() const { + return false; +} + +ClientEntityMode Entity::clientEntityMode() const { + return ClientEntityMode::ClientSlaveOnly; +} + +bool Entity::masterOnly() const { + return false; +} + +String Entity::description() const { + return ""; +} + +List<LightSource> Entity::lightSources() const { + return {}; +} + +List<DamageSource> Entity::damageSources() const { + return {}; +} + +void Entity::hitOther(EntityId, DamageRequest const&) {} + +void Entity::damagedOther(DamageNotification const&) {} + +Maybe<HitType> Entity::queryHit(DamageSource const&) const { + return {}; +} + +Maybe<PolyF> Entity::hitPoly() const { + return {}; +} + +List<DamageNotification> Entity::applyDamage(DamageRequest const&) { + return {}; +} + +List<DamageNotification> Entity::selfDamageNotifications() { + return {}; +} + +bool Entity::shouldDestroy() const { + return false; +} + +void Entity::destroy(RenderCallback*) {} + +Maybe<Json> Entity::receiveMessage(ConnectionId, String const&, JsonArray const&) { + return {}; +} + +void Entity::update(uint64_t) {} + +void Entity::render(RenderCallback*) {} + +EntityId Entity::entityId() const { + return m_entityId; +} + +EntityDamageTeam Entity::getTeam() const { + return m_team; +} + +bool Entity::inWorld() const { + if (m_world) { + starAssert(m_world && m_entityId != NullEntityId && m_entityMode); + return true; + } else { + starAssert(!m_world && m_entityId == NullEntityId && !m_entityMode); + return false; + } +} + +World* Entity::world() const { + if (!m_world) + throw EntityException("world() called while uninitialized"); + + return m_world; +} + +World* Entity::worldPtr() const { + return m_world; +} + +bool Entity::persistent() const { + return m_persistent; +} + +bool Entity::keepAlive() const { + return m_keepAlive; +} + +Maybe<String> Entity::uniqueId() const { + return m_uniqueId; +} + +Maybe<EntityMode> Entity::entityMode() const { + return m_entityMode; +} + +bool Entity::isMaster() const { + return m_entityMode == EntityMode::Master; +} + +bool Entity::isSlave() const { + return m_entityMode == EntityMode::Slave; +} + +Entity::Entity() { + m_world = nullptr; + m_entityId = NullEntityId; + m_persistent = false; + m_keepAlive = false; +} + +void Entity::setPersistent(bool persistent) { + m_persistent = persistent; +} + +void Entity::setKeepAlive(bool keepAlive) { + m_keepAlive = keepAlive; +} + +void Entity::setUniqueId(Maybe<String> uniqueId) { + m_uniqueId = uniqueId; +} + +void Entity::setTeam(EntityDamageTeam newTeam) { + m_team = newTeam; +} + +} diff --git a/source/game/interfaces/StarEntity.hpp b/source/game/interfaces/StarEntity.hpp new file mode 100644 index 0000000..3452654 --- /dev/null +++ b/source/game/interfaces/StarEntity.hpp @@ -0,0 +1,229 @@ +#ifndef STAR_ENTITY_HPP +#define STAR_ENTITY_HPP + +#include "StarCasting.hpp" +#include "StarDamage.hpp" +#include "StarLightSource.hpp" + +namespace Star { + +STAR_CLASS(RenderCallback); +STAR_CLASS(World); +STAR_STRUCT(DamageNotification); +STAR_CLASS(Entity); + +STAR_EXCEPTION(EntityException, StarException); + +// Specifies how the client should treat an entity created on the client, +// whether it should always be sent to the server and be a slave on the client, +// whether it is allowed to be master on the client, and whether client master +// entities should contribute to client presence. +enum class ClientEntityMode { + // Always a slave on the client + ClientSlaveOnly, + // Can be a master on the client + ClientMasterAllowed, + // Can be a master on the client, and when it is contributes to client + // presence. + ClientPresenceMaster +}; +extern EnumMap<ClientEntityMode> const ClientEntityModeNames; + +// The top-level entity type. The enum order is intended to be in the order in +// which entities should be updated every tick +enum class EntityType : uint8_t { + Plant, + Object, + Vehicle, + ItemDrop, + PlantDrop, + Projectile, + Stagehand, + Monster, + Npc, + Player +}; +extern EnumMap<EntityType> const EntityTypeNames; + +class Entity { +public: + virtual ~Entity(); + + virtual EntityType entityType() const = 0; + + // Called when an entity is first inserted into a World. Calling base class + // init sets the world pointer, entityId, and entityMode. + virtual void init(World* world, EntityId entityId, EntityMode mode); + + // Should do whatever steps necessary to take an entity out of a world, + // default implementation clears the world pointer, entityMode, and entityId. + virtual void uninit(); + + // Write state data that changes over time, and is used to keep slaves in + // sync. Can return empty and this is the default. May be called + // uninitalized. Should return the delta to be written to the slave, along + // with the version to pass into writeDeltaState on the next call. The first + // delta written to a slave entity will always be the delta starting with 0. + virtual pair<ByteArray, uint64_t> writeNetState(uint64_t fromVersion = 0); + // Will be called with deltas written by writeDeltaState, including if the + // delta is empty. interpolationTime will be provided if interpolation is + // enabled. + virtual void readNetState(ByteArray data, float interpolationTime = 0.0); + + virtual void enableInterpolation(float extrapolationHint); + virtual void disableInterpolation(); + + // Base position of this entity, bound boxes, drawables, and other entity + // positions are relative to this. + virtual Vec2F position() const = 0; + + // Largest bounding-box of this entity. Any damage boxes / drawables / light + // or sound *sources* must be contained within this bounding box. Used for + // all top-level spatial queries. + virtual RectF metaBoundBox() const = 0; + + // By default returns a null rect, if non-null, it defines the area around + // this entity where it is likely for the entity to physically collide with + // collision geometry. + virtual RectF collisionArea() const; + + // Should this entity allow object / block placement over it, and can the + // entity immediately be despawned without terribly bad effects? + virtual bool ephemeral() const; + + // How should this entity be treated if created on the client? Defaults to + // ClientSlave. + virtual ClientEntityMode clientEntityMode() const; + // Should this entity only exist on the master side? + virtual bool masterOnly() const; + + virtual String description() const; + + // Gameplay affecting light sources (separate from light sources added during + // rendering) + virtual List<LightSource> lightSources() const; + + // All damage sources for this frame. + virtual List<DamageSource> damageSources() const; + + // Return the damage that would result from being hit by the given damage + // source. Will be called on master and slave entities. Culling based on + // team damage and self damage will be done outside of this query. + virtual Maybe<HitType> queryHit(DamageSource const& source) const; + + // Return the polygonal area in which the entity can be hit. Not used for + // actual hit computation, only for determining more precisely where a + // hit intersection occurred (e.g. by projectiles) + virtual Maybe<PolyF> hitPoly() const; + + // Apply a request to damage this entity. Will only be called on Master + // entities. DamageRequest might be adjusted based on protection and other + // effects + virtual List<DamageNotification> applyDamage(DamageRequest const& damage); + + // Pull any pending damage notifications applied internally, only called on + // Master entities. + virtual List<DamageNotification> selfDamageNotifications(); + + // Called on master entities when a DamageRequest has been generated due to a + // DamageSource from this entity being applied to another entity. Will be + // called on the *causing* entity of the damage. + virtual void hitOther(EntityId targetEntityId, DamageRequest const& damageRequest); + + // Called on master entities when this entity has damaged another entity. + // Only called on the *source entity* of the damage, which may be different + // than the causing entity. + virtual void damagedOther(DamageNotification const& damage); + + // Returning true here indicates that this entity should be removed from the + // world, default returns false. + virtual bool shouldDestroy() const; + // Will be called once before removing the entity from the World on both + // master and slave entities. + virtual void destroy(RenderCallback* renderCallback); + + // Entities can send other entities potentially remote messages and get + // responses back from them, and should implement this to receive and respond + // to messages. If the message is NOT handled, should return Nothing, + // otherwise should return some Json value. + // This will only ever be called on master entities. + virtual Maybe<Json> receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args); + + virtual void update(uint64_t currentStep); + + virtual void render(RenderCallback* renderer); + + EntityId entityId() const; + + EntityDamageTeam getTeam() const; + + // Returns true if an entity is initialized in a world, and thus has a valid + // world pointer, entity id, and entity mode. + bool inWorld() const; + + // Throws an exception if not currently in a world. + World* world() const; + // Returns nullptr if not currently in a world. + World* worldPtr() const; + + // Specifies if the entity is to be saved to disk alongside the sector or + // despawned. + bool persistent() const; + + // Entity should keep any sector it is in alive. Default implementation + // returns false. + bool keepAlive() const; + + // If set, then the entity will be discoverable by its unique id and will be + // indexed in the stored world. Unique ids must be different across all + // entities in a single world. + Maybe<String> uniqueId() const; + + // EntityMode will only be set if the entity is initialized, if the entity is + // uninitialized then isMaster and isSlave will both return false. + Maybe<EntityMode> entityMode() const; + bool isMaster() const; + bool isSlave() const; + +protected: + Entity(); + + void setPersistent(bool persistent); + void setKeepAlive(bool keepAlive); + void setUniqueId(Maybe<String> uniqueId); + void setTeam(EntityDamageTeam newTeam); + +private: + EntityId m_entityId; + Maybe<EntityMode> m_entityMode; + bool m_persistent; + bool m_keepAlive; + Maybe<String> m_uniqueId; + World* m_world; + EntityDamageTeam m_team; +}; + +template <typename EntityT> +using EntityCallbackOf = function<void(shared_ptr<EntityT> const&)>; + +template <typename EntityT> +using EntityFilterOf = function<bool(shared_ptr<EntityT> const&)>; + +typedef EntityCallbackOf<Entity> EntityCallback; +typedef EntityFilterOf<Entity> EntityFilter; + +// Filters based first on dynamic casting to the given type, then optionally on +// the given derived type filter. +template <typename EntityT> +EntityFilter entityTypeFilter(function<bool(shared_ptr<EntityT> const&)> filter = {}) { + return [filter](EntityPtr const& e) -> bool { + if (auto entity = as<EntityT>(e)) { + return !filter || filter(entity); + } else { + return false; + } + }; +} +} + +#endif diff --git a/source/game/interfaces/StarFireableItem.cpp b/source/game/interfaces/StarFireableItem.cpp new file mode 100644 index 0000000..e39d77e --- /dev/null +++ b/source/game/interfaces/StarFireableItem.cpp @@ -0,0 +1,300 @@ +#include "StarFireableItem.hpp" +#include "StarJsonExtra.hpp" +#include "StarWorldLuaBindings.hpp" +#include "StarConfigLuaBindings.hpp" +#include "StarItemLuaBindings.hpp" +#include "StarFireableItemLuaBindings.hpp" +#include "StarItem.hpp" +#include "StarWorld.hpp" + +namespace Star { + +FireableItem::FireableItem() + : m_fireTimer(0), + m_cooldownTime(10), + m_windupTime(0), + m_fireWhenReady(false), + m_startWhenReady(false), + m_cooldown(false), + m_alreadyInit(false), + m_requireEdgeTrigger(false), + m_attemptedFire(false), + m_fireOnRelease(false), + m_timeFiring(0.0f), + m_startTimingFire(false), + m_inUse(false), + m_walkWhileFiring(false), + m_stopWhileFiring(false), + m_mode(FireMode::None) {} + +FireableItem::FireableItem(Json const& params) : FireableItem() { + setParams(params); + m_fireableParams = params; +} + +FireableItem::FireableItem(FireableItem const& rhs) : ToolUserItem(rhs), StatusEffectItem(rhs) { + m_fireTimer = rhs.m_fireTimer; + m_cooldownTime = rhs.m_cooldownTime; + m_windupTime = rhs.m_windupTime; + m_fireWhenReady = rhs.m_fireWhenReady; + m_startWhenReady = rhs.m_startWhenReady; + m_cooldown = rhs.m_cooldown; + m_alreadyInit = rhs.m_alreadyInit; + m_requireEdgeTrigger = rhs.m_requireEdgeTrigger; + m_attemptedFire = rhs.m_attemptedFire; + m_fireOnRelease = rhs.m_fireOnRelease; + m_timeFiring = rhs.m_timeFiring; + m_startTimingFire = rhs.m_startTimingFire; + m_inUse = rhs.m_inUse; + m_walkWhileFiring = rhs.m_walkWhileFiring; + m_stopWhileFiring = rhs.m_stopWhileFiring; + m_fireableParams = rhs.m_fireableParams; + m_handPosition = rhs.m_handPosition; + m_mode = rhs.m_mode; +} + +void FireableItem::init(ToolUserEntity* owner, ToolHand hand) { + ToolUserItem::init(owner, hand); + + m_fireWhenReady = false; + m_startWhenReady = false; + + auto scripts = m_fireableParams.opt("scripts").apply(jsonToStringList); + if (entityMode() == EntityMode::Master && scripts) { + if (!m_scriptComponent) { + m_scriptComponent.emplace(); + m_scriptComponent->setScripts(*scripts); + } + m_scriptComponent->addCallbacks( + "config", LuaBindings::makeConfigCallbacks(bind(&Item::instanceValue, as<Item>(this), _1, _2))); + m_scriptComponent->addCallbacks("fireableItem", LuaBindings::makeFireableItemCallbacks(this)); + m_scriptComponent->addCallbacks("item", LuaBindings::makeItemCallbacks(as<Item>(this))); + m_scriptComponent->init(world()); + } +} + +void FireableItem::uninit() { + if (m_scriptComponent) { + m_scriptComponent->uninit(); + m_scriptComponent->removeCallbacks("config"); + m_scriptComponent->removeCallbacks("fireableItem"); + m_scriptComponent->removeCallbacks("item"); + } + + ToolUserItem::uninit(); +} + +void FireableItem::fire(FireMode mode, bool, bool edgeTriggered) { + m_attemptedFire = true; + if (ready()) { + m_inUse = true; + m_startTimingFire = true; + m_mode = mode; + if (!m_requireEdgeTrigger || edgeTriggered) { + setFireTimer(windupTime() + cooldownTime()); + if (!m_fireOnRelease) { + m_fireWhenReady = true; + m_startWhenReady = true; + } + } + } + + if (m_scriptComponent) + m_scriptComponent->invoke("attemptedFire"); +} + +void FireableItem::endFire(FireMode mode, bool) { + if (m_scriptComponent) + m_scriptComponent->invoke("endFire"); + + m_attemptedFire = false; + if (m_fireOnRelease && m_timeFiring) { + m_mode = mode; + triggerCooldown(); + fireTriggered(); + } +} + +FireMode FireableItem::fireMode() const { + return m_mode; +} + +float FireableItem::cooldownTime() const { + return m_cooldownTime; +} + +void FireableItem::setCooldownTime(float cooldownTime) { + m_cooldownTime = cooldownTime; +} + +float FireableItem::fireTimer() const { + return m_fireTimer; +} + +void FireableItem::setFireTimer(float fireTimer) { + m_fireTimer = fireTimer; +} + +bool FireableItem::ready() const { + return fireTimer() <= 0; +} + +bool FireableItem::firing() const { + return m_timeFiring > 0; +} + +bool FireableItem::inUse() const { + return m_inUse; +} + +bool FireableItem::walkWhileFiring() const { + return m_walkWhileFiring; +} + +bool FireableItem::stopWhileFiring() const { + return m_stopWhileFiring; +} + +bool FireableItem::windup() const { + if (ready()) + return false; + + if (m_scriptComponent) + m_scriptComponent->invoke("triggerWindup"); + + return fireTimer() > cooldownTime(); +} + +void FireableItem::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const&) { + if (m_scriptComponent) + m_scriptComponent->invoke("update", WorldTimestep, FireModeNames.getRight(fireMode), shifting); + + if (m_attemptedFire) { + if (m_startTimingFire) { + m_timeFiring += WorldTimestep; + if (m_scriptComponent) + m_scriptComponent->invoke("continueFire", WorldTimestep); + } + } else { + m_timeFiring = 0.0f; + m_startTimingFire = false; + } + m_attemptedFire = false; + + if (entityMode() == EntityMode::Master) { + if (fireTimer() > 0.0f) { + setFireTimer(fireTimer() - WorldTimestep); + if (fireTimer() < 0.0f) { + setFireTimer(0.0f); + m_inUse = false; + } + } + if (fireTimer() <= 0) { + m_cooldown = false; + } + if (m_startWhenReady) { + m_startWhenReady = false; + startTriggered(); + } + if (m_fireWhenReady) { + if (fireTimer() <= cooldownTime()) { + m_fireWhenReady = false; + fireTriggered(); + } + } + } +} + +void FireableItem::triggerCooldown() { + setFireTimer(cooldownTime()); + m_cooldown = true; + if (m_scriptComponent) + m_scriptComponent->invoke("triggerCooldown"); +} + +bool FireableItem::coolingDown() const { + return m_cooldown; +} + +void FireableItem::setCoolingDown(bool coolingdown) { + m_cooldown = coolingdown; +} + +float FireableItem::timeFiring() const { + return m_timeFiring; +} + +void FireableItem::setTimeFiring(float timeFiring) { + m_timeFiring = timeFiring; +} + +Vec2F FireableItem::handPosition() const { + return m_handPosition; +} + +Vec2F FireableItem::firePosition() const { + return Vec2F(); +} + +Json FireableItem::fireableParam(String const& key) const { + return m_fireableParams.get(key); +} + +Json FireableItem::fireableParam(String const& key, Json const& defaultVal) const { + return m_fireableParams.get(key, defaultVal); +} + +bool FireableItem::validAimPos(Vec2F const&) { + return true; +} + +void FireableItem::setParams(Json const& params) { + if (!m_alreadyInit) { + // cannot use setWindupTime or setCooldownTime here, because object is not fully constructed + m_windupTime = params.getFloat("windupTime", 0.0f); + m_cooldownTime = params.getFloat("cooldown", params.getFloat("fireTime", 0.15f) - m_windupTime); + if (params.contains("handPosition")) { + m_handPosition = jsonToVec2F(params.get("handPosition")); + } + m_requireEdgeTrigger = params.getBool("edgeTrigger", false); + m_fireOnRelease = params.getBool("fireOnRelease", false); + m_walkWhileFiring = params.getBool("walkWhileFiring", false); + m_stopWhileFiring = params.getBool("stopWhileFiring", false); + m_alreadyInit = true; + } +} + +void FireableItem::setFireableParam(String const& key, Json const& value) { + m_fireableParams = m_fireableParams.set(key, value); +} + +void FireableItem::startTriggered() { + if (m_scriptComponent) + m_scriptComponent->invoke("startTriggered"); +} + +void FireableItem::fireTriggered() { + if (m_scriptComponent) + m_scriptComponent->invoke("fireTriggered"); +} + +Vec2F FireableItem::ownerFirePosition() const { + if (!initialized()) + throw StarException("FireableItem uninitialized in ownerFirePosition"); + + return owner()->handPosition(hand(), (this->firePosition() - handPosition()) / TilePixels); +} + +float FireableItem::windupTime() const { + return m_windupTime; +} + +void FireableItem::setWindupTime(float time) { + m_windupTime = time; +} + +List<PersistentStatusEffect> FireableItem::statusEffects() const { + return {}; +} + +} diff --git a/source/game/interfaces/StarFireableItem.hpp b/source/game/interfaces/StarFireableItem.hpp new file mode 100644 index 0000000..1b2c058 --- /dev/null +++ b/source/game/interfaces/StarFireableItem.hpp @@ -0,0 +1,91 @@ +#ifndef STAR_FIREABLE_ITEM_HPP +#define STAR_FIREABLE_ITEM_HPP + +#include "StarToolUserItem.hpp" +#include "StarStatusEffectItem.hpp" +#include "StarLuaComponents.hpp" + +namespace Star { + +STAR_CLASS(FireableItem); + +class FireableItem : public virtual ToolUserItem, public virtual StatusEffectItem { +public: + FireableItem(); + FireableItem(Json const& params); + virtual ~FireableItem() {} + + FireableItem(FireableItem const& fireableItem); + + virtual void fire(FireMode mode, bool shifting, bool edgeTriggered); + virtual void endFire(FireMode mode, bool shifting); + virtual FireMode fireMode() const; + virtual float fireTimer() const; + virtual void setFireTimer(float fireTimer); + virtual float cooldownTime() const; + virtual void setCooldownTime(float cooldownTime); + virtual float windupTime() const; + virtual void setWindupTime(float time); + virtual bool ready() const; + virtual bool firing() const; + virtual bool inUse() const; + virtual bool walkWhileFiring() const; + virtual bool stopWhileFiring() const; + virtual bool windup() const; + virtual void triggerCooldown(); + virtual bool coolingDown() const; + virtual void setCoolingDown(bool coolingdown); + virtual float timeFiring() const; + virtual void setTimeFiring(float timeFiring); + virtual Vec2F firePosition() const; + virtual Vec2F handPosition() const; + + virtual void init(ToolUserEntity* owner, ToolHand hand) override; + virtual void uninit() override; + virtual void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override; + + virtual List<PersistentStatusEffect> statusEffects() const override; + + virtual bool validAimPos(Vec2F const& aimPos); + + Json fireableParam(String const& key) const; + Json fireableParam(String const& key, Json const& defaultVal) const; + +protected: + void setParams(Json const& params); + void setFireableParam(String const& key, Json const& value); + virtual void startTriggered(); + virtual void fireTriggered(); + + // firePosition translated by the hand in the owner's space + Vec2F ownerFirePosition() const; + + float m_fireTimer; + float m_cooldownTime; + float m_windupTime; + bool m_fireWhenReady; + bool m_startWhenReady; + bool m_cooldown; + bool m_alreadyInit; + bool m_requireEdgeTrigger; + + bool m_attemptedFire; + bool m_fireOnRelease; + float m_timeFiring; + bool m_startTimingFire; + bool m_inUse; + bool m_walkWhileFiring; + bool m_stopWhileFiring; + + mutable Maybe<LuaWorldComponent<LuaBaseComponent>> m_scriptComponent; + + Json m_fireableParams; + + Vec2F m_handPosition; + + FireMode m_mode; +}; + +} + +#endif diff --git a/source/game/interfaces/StarInspectableEntity.hpp b/source/game/interfaces/StarInspectableEntity.hpp new file mode 100644 index 0000000..c5d175b --- /dev/null +++ b/source/game/interfaces/StarInspectableEntity.hpp @@ -0,0 +1,37 @@ +#ifndef STAR_INSPECTABLE_ENTITY_HPP +#define STAR_INSPECTABLE_ENTITY_HPP + +#include "StarTileEntity.hpp" + +namespace Star { + +STAR_CLASS(InspectableEntity); + +class InspectableEntity : public virtual TileEntity { +public: + // Default implementation returns true + virtual bool inspectable() const; + + // If this entity can be entered into the player log, will return the log + // identifier. + virtual Maybe<String> inspectionLogName() const; + + // Long description to display when inspected, if any + virtual Maybe<String> inspectionDescription(String const& species) const; +}; + +inline bool InspectableEntity::inspectable() const { + return true; +} + +inline Maybe<String> InspectableEntity::inspectionLogName() const { + return {}; +} + +inline Maybe<String> InspectableEntity::inspectionDescription(String const&) const { + return {}; +} + +} + +#endif diff --git a/source/game/interfaces/StarInteractiveEntity.cpp b/source/game/interfaces/StarInteractiveEntity.cpp new file mode 100644 index 0000000..5ad93ff --- /dev/null +++ b/source/game/interfaces/StarInteractiveEntity.cpp @@ -0,0 +1,25 @@ +#include "StarInteractiveEntity.hpp" + +namespace Star { + +RectF InteractiveEntity::interactiveBoundBox() const { + return metaBoundBox(); +} + +bool InteractiveEntity::isInteractive() const { + return true; +} + +List<QuestArcDescriptor> InteractiveEntity::offeredQuests() const { + return {}; +} + +StringSet InteractiveEntity::turnInQuests() const { + return {}; +} + +Vec2F InteractiveEntity::questIndicatorPosition() const { + return position(); +} + +} diff --git a/source/game/interfaces/StarInteractiveEntity.hpp b/source/game/interfaces/StarInteractiveEntity.hpp new file mode 100644 index 0000000..9e655e1 --- /dev/null +++ b/source/game/interfaces/StarInteractiveEntity.hpp @@ -0,0 +1,34 @@ +#ifndef STAR_INTERACTIVE_ENTITY_HPP +#define STAR_INTERACTIVE_ENTITY_HPP + +#include "StarInteractionTypes.hpp" +#include "StarEntity.hpp" +#include "StarQuestDescriptor.hpp" + +namespace Star { + +STAR_CLASS(InteractiveEntity); + +class InteractiveEntity : public virtual Entity { +public: + // Interaction always takes place on the *server*, whether the interactive + // entity is master or slave there. + virtual InteractAction interact(InteractRequest const& request) = 0; + + // Defaults to metaBoundBox + virtual RectF interactiveBoundBox() const; + + // Defaults to true + virtual bool isInteractive() const; + + // Defaults to empty + virtual List<QuestArcDescriptor> offeredQuests() const; + virtual StringSet turnInQuests() const; + + // Defaults to position() + virtual Vec2F questIndicatorPosition() const; +}; + +} + +#endif diff --git a/source/game/interfaces/StarLoungingEntities.cpp b/source/game/interfaces/StarLoungingEntities.cpp new file mode 100644 index 0000000..6b2910d --- /dev/null +++ b/source/game/interfaces/StarLoungingEntities.cpp @@ -0,0 +1,63 @@ +#include "StarLoungingEntities.hpp" +#include "StarWorld.hpp" + +namespace Star { + +EnumMap<LoungeOrientation> const LoungeOrientationNames{{LoungeOrientation::None, "none"}, + {LoungeOrientation::Sit, "sit"}, + {LoungeOrientation::Lay, "lay"}, + {LoungeOrientation::Stand, "stand"}}; + +EnumMap<LoungeControl> const LoungeControlNames{{LoungeControl::Left, "Left"}, + {LoungeControl::Right, "Right"}, + {LoungeControl::Down, "Down"}, + {LoungeControl::Up, "Up"}, + {LoungeControl::Jump, "Jump"}, + {LoungeControl::PrimaryFire, "PrimaryFire"}, + {LoungeControl::AltFire, "AltFire"}, + {LoungeControl::Special1, "Special1"}, + {LoungeControl::Special2, "Special2"}, + {LoungeControl::Special3, "Special3"}}; + +EntityAnchorConstPtr LoungeableEntity::anchor(size_t anchorPositionIndex) const { + return loungeAnchor(anchorPositionIndex); +} + +void LoungeableEntity::loungeControl(size_t, LoungeControl) {} + +void LoungeableEntity::loungeAim(size_t, Vec2F const&) {} + +Set<EntityId> LoungeableEntity::entitiesLoungingIn(size_t positionIndex) const { + Set<EntityId> loungingInEntities; + for (auto const& p : entitiesLounging()) { + if (p.second == positionIndex) + loungingInEntities.add(p.first); + } + return loungingInEntities; +} + +Set<pair<EntityId, size_t>> LoungeableEntity::entitiesLounging() const { + Set<pair<EntityId, size_t>> loungingInEntities; + world()->forEachEntity(metaBoundBox().translated(position()), + [&](EntityPtr const& entity) { + if (auto lounger = as<LoungingEntity>(entity)) { + if (auto anchorStatus = lounger->loungingIn()) { + if (anchorStatus->entityId == entityId()) + loungingInEntities.add({entity->entityId(), anchorStatus->positionIndex}); + } + } + }); + return loungingInEntities; +} + +bool LoungingEntity::inConflictingLoungeAnchor() const { + if (auto loungeAnchorState = loungingIn()) { + if (auto loungeableEntity = world()->get<LoungeableEntity>(loungeAnchorState->entityId)) { + auto entitiesLoungingIn = loungeableEntity->entitiesLoungingIn(loungeAnchorState->positionIndex); + return entitiesLoungingIn.size() > 1 || !entitiesLoungingIn.contains(entityId()); + } + } + return false; +} + +} diff --git a/source/game/interfaces/StarLoungingEntities.hpp b/source/game/interfaces/StarLoungingEntities.hpp new file mode 100644 index 0000000..bec553c --- /dev/null +++ b/source/game/interfaces/StarLoungingEntities.hpp @@ -0,0 +1,71 @@ +#ifndef STAR_LOUNGING_ENTITIES_HPP +#define STAR_LOUNGING_ENTITIES_HPP + +#include "StarDrawable.hpp" +#include "StarAnchorableEntity.hpp" +#include "StarStatusTypes.hpp" +#include "StarEntityRenderingTypes.hpp" + +namespace Star { + +STAR_CLASS(World); + +STAR_STRUCT(LoungeAnchor); +STAR_CLASS(LoungeableEntity); +STAR_CLASS(LoungingEntity); + +enum class LoungeOrientation { None, Sit, Lay, Stand }; +extern EnumMap<LoungeOrientation> const LoungeOrientationNames; + +enum class LoungeControl { Left, Right, Down, Up, Jump, PrimaryFire, AltFire, Special1, Special2, Special3 }; +extern EnumMap<LoungeControl> const LoungeControlNames; + +struct LoungeAnchor : EntityAnchor { + LoungeOrientation orientation; + EntityRenderLayer loungeRenderLayer; + bool controllable; + List<PersistentStatusEffect> statusEffects; + StringSet effectEmitters; + Maybe<String> emote; + Maybe<String> dance; + Maybe<String> directives; + JsonObject armorCosmeticOverrides; + Maybe<String> cursorOverride; + bool cameraFocus; +}; + +// Extends an AnchorableEntity to have more specific effects when anchoring, +// such as status effects and lounge controls. All LoungeableEntity methods +// may be called on both the master and slave. +class LoungeableEntity : public AnchorableEntity { +public: + virtual size_t anchorCount() const override = 0; + EntityAnchorConstPtr anchor(size_t anchorPositionIndex) const override; + virtual LoungeAnchorConstPtr loungeAnchor(size_t anchorPositionIndex) const = 0; + + // Default does nothing. + virtual void loungeControl(size_t anchorPositionIndex, LoungeControl loungeControl); + virtual void loungeAim(size_t anchorPositionIndex, Vec2F const& aimPosition); + + // Queries around this entity's metaBoundBox for any LoungingEntities + // reporting that they are lounging in this entity, and returns ones that are + // lounging in the given position. + Set<EntityId> entitiesLoungingIn(size_t anchorPositionIndex) const; + // Returns pairs of entity ids, and the position they are lounging in. + Set<pair<EntityId, size_t>> entitiesLounging() const; +}; + +// Any lounging entity should report the entity it is lounging in on both +// master and slave, so that lounging entities can cooperate and avoid lounging +// in the same spot. +class LoungingEntity : public virtual Entity { +public: + virtual Maybe<EntityAnchorState> loungingIn() const = 0; + // Returns true if the entity is in a lounge achor, but other entities are + // also reporting being in that lounge anchor. + bool inConflictingLoungeAnchor() const; +}; + +} + +#endif diff --git a/source/game/interfaces/StarNametagEntity.hpp b/source/game/interfaces/StarNametagEntity.hpp new file mode 100644 index 0000000..c9a9008 --- /dev/null +++ b/source/game/interfaces/StarNametagEntity.hpp @@ -0,0 +1,20 @@ +#ifndef STAR_NAMETAG_ENTITY_HPP +#define STAR_NAMETAG_ENTITY_HPP + +#include "StarEntity.hpp" + +namespace Star { + +STAR_CLASS(NametagEntity); + +class NametagEntity : public virtual Entity { +public: + virtual String name() const = 0; + virtual Maybe<String> statusText() const = 0; + virtual bool displayNametag() const = 0; + virtual Vec3B nametagColor() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarNonRotatedDrawablesItem.hpp b/source/game/interfaces/StarNonRotatedDrawablesItem.hpp new file mode 100644 index 0000000..d11eb7a --- /dev/null +++ b/source/game/interfaces/StarNonRotatedDrawablesItem.hpp @@ -0,0 +1,18 @@ +#ifndef STAR_NON_ROTATED_DRAWABLES_ITEM_HPP +#define STAR_NON_ROTATED_DRAWABLES_ITEM_HPP + +#include "StarDrawable.hpp" + +namespace Star { + +STAR_CLASS(NonRotatedDrawablesItem); + +class NonRotatedDrawablesItem { +public: + virtual ~NonRotatedDrawablesItem() {} + virtual List<Drawable> nonRotatedDrawables() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarPhysicsEntity.cpp b/source/game/interfaces/StarPhysicsEntity.cpp new file mode 100644 index 0000000..1eda4bf --- /dev/null +++ b/source/game/interfaces/StarPhysicsEntity.cpp @@ -0,0 +1,85 @@ +#include "StarPhysicsEntity.hpp" +#include "StarJsonExtra.hpp" +#include "StarDataStreamExtra.hpp" + +namespace Star { + +PhysicsMovingCollision PhysicsMovingCollision::fromJson(Json const& json) { + PhysicsMovingCollision pmc; + pmc.position = json.opt("position").apply(jsonToVec2F).value(); + pmc.collision = jsonToPolyF(json.get("collision")); + pmc.collisionKind = CollisionKindNames.getLeft(json.getString("collisionKind", "block")); + pmc.categoryFilter = jsonToPhysicsCategoryFilter(json); + return pmc; +} + +RectF PhysicsMovingCollision::boundBox() const { + return collision.boundBox().translated(position); +} + +void PhysicsMovingCollision::translate(Vec2F const& pos) { + position += pos; +} + +bool PhysicsMovingCollision::operator==(PhysicsMovingCollision const& rhs) const { + return tie(position, collision, collisionKind, categoryFilter) == tie(rhs.position, rhs.collision, rhs.collisionKind, rhs.categoryFilter); +} + +DataStream& operator>>(DataStream& ds, PhysicsMovingCollision& pmc) { + ds >> pmc.position; + ds >> pmc.collision; + ds >> pmc.collisionKind; + ds >> pmc.categoryFilter; + return ds; +} + +DataStream& operator<<(DataStream& ds, PhysicsMovingCollision const& pmc) { + ds << pmc.position; + ds << pmc.collision; + ds << pmc.collisionKind; + ds << pmc.categoryFilter; + return ds; +} + +MovingCollisionId::MovingCollisionId() : physicsEntityId(NullEntityId), collisionIndex(0) {} + +MovingCollisionId::MovingCollisionId(EntityId physicsEntityId, size_t collisionIndex) + : physicsEntityId(physicsEntityId), collisionIndex(collisionIndex) {} + +bool MovingCollisionId::operator==(MovingCollisionId const& rhs) { + return tie(physicsEntityId, collisionIndex) == tie(rhs.physicsEntityId, rhs.collisionIndex); +} + +bool MovingCollisionId::valid() const { + return physicsEntityId != NullEntityId; +} + +MovingCollisionId::operator bool() const { + return valid(); +} + +DataStream& operator>>(DataStream& ds, MovingCollisionId& mci) { + ds.read(mci.physicsEntityId); + ds.readVlqS(mci.collisionIndex); + return ds; +} + +DataStream& operator<<(DataStream& ds, MovingCollisionId const& mci) { + ds.write(mci.physicsEntityId); + ds.writeVlqS(mci.collisionIndex); + return ds; +} + +List<PhysicsForceRegion> PhysicsEntity::forceRegions() const { + return {}; +} + +size_t PhysicsEntity::movingCollisionCount() const { + return 0; +} + +Maybe<PhysicsMovingCollision> PhysicsEntity::movingCollision(size_t) const { + return {}; +} + +} diff --git a/source/game/interfaces/StarPhysicsEntity.hpp b/source/game/interfaces/StarPhysicsEntity.hpp new file mode 100644 index 0000000..b9a7611 --- /dev/null +++ b/source/game/interfaces/StarPhysicsEntity.hpp @@ -0,0 +1,61 @@ +#ifndef STAR_PHYSICS_ENTITY_HPP +#define STAR_PHYSICS_ENTITY_HPP + +#include "StarPoly.hpp" +#include "StarVariant.hpp" +#include "StarJson.hpp" +#include "StarEntity.hpp" +#include "StarForceRegions.hpp" +#include "StarCollisionBlock.hpp" + +namespace Star { + +STAR_CLASS(PhysicsEntity); + +struct PhysicsMovingCollision { + static PhysicsMovingCollision fromJson(Json const& json); + + RectF boundBox() const; + + void translate(Vec2F const& pos); + + bool operator==(PhysicsMovingCollision const& rhs) const; + + Vec2F position; + PolyF collision; + CollisionKind collisionKind; + PhysicsCategoryFilter categoryFilter; +}; + +DataStream& operator>>(DataStream& ds, PhysicsMovingCollision& pmc); +DataStream& operator<<(DataStream& ds, PhysicsMovingCollision const& pmc); + +struct MovingCollisionId { + MovingCollisionId(); + MovingCollisionId(EntityId physicsEntityId, size_t collisionIndex); + + bool operator==(MovingCollisionId const& rhs); + + // Returns true if the MovingCollisionId is not empty, i.e. default + // constructed + bool valid() const; + operator bool() const; + + EntityId physicsEntityId; + size_t collisionIndex; +}; + +DataStream& operator>>(DataStream& ds, MovingCollisionId& mci); +DataStream& operator<<(DataStream& ds, MovingCollisionId const& mci); + +class PhysicsEntity : public virtual Entity { +public: + virtual List<PhysicsForceRegion> forceRegions() const; + + virtual size_t movingCollisionCount() const; + virtual Maybe<PhysicsMovingCollision> movingCollision(size_t positionIndex) const; +}; + +} + +#endif diff --git a/source/game/interfaces/StarPointableItem.cpp b/source/game/interfaces/StarPointableItem.cpp new file mode 100644 index 0000000..b12ef3c --- /dev/null +++ b/source/game/interfaces/StarPointableItem.cpp @@ -0,0 +1,13 @@ +#include "StarPointableItem.hpp" + +namespace Star { + +float PointableItem::getAngleDir(float angle, Direction) { + return getAngle(angle); +} + +float PointableItem::getAngle(float angle) { + return angle; +} + +} diff --git a/source/game/interfaces/StarPointableItem.hpp b/source/game/interfaces/StarPointableItem.hpp new file mode 100644 index 0000000..a5bd6ea --- /dev/null +++ b/source/game/interfaces/StarPointableItem.hpp @@ -0,0 +1,22 @@ +#ifndef STAR_POINTABLE_ITEM_HPP +#define STAR_POINTABLE_ITEM_HPP + +#include "StarGameTypes.hpp" +#include "StarDrawable.hpp" + +namespace Star { + +STAR_CLASS(PointableItem); + +class PointableItem { +public: + virtual ~PointableItem() {} + + virtual float getAngleDir(float aimAngle, Direction facingDirection); + virtual float getAngle(float angle); + virtual List<Drawable> drawables() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarPortraitEntity.hpp b/source/game/interfaces/StarPortraitEntity.hpp new file mode 100644 index 0000000..4dda3e6 --- /dev/null +++ b/source/game/interfaces/StarPortraitEntity.hpp @@ -0,0 +1,19 @@ +#ifndef STAR_PORTRAIT_ENTITY_HPP +#define STAR_PORTRAIT_ENTITY_HPP + +#include "StarDrawable.hpp" +#include "StarEntity.hpp" + +namespace Star { + +STAR_CLASS(PortraitEntity); + +class PortraitEntity : public virtual Entity { +public: + virtual List<Drawable> portrait(PortraitMode mode) const = 0; + virtual String name() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarPreviewTileTool.hpp b/source/game/interfaces/StarPreviewTileTool.hpp new file mode 100644 index 0000000..dfc53bf --- /dev/null +++ b/source/game/interfaces/StarPreviewTileTool.hpp @@ -0,0 +1,20 @@ +#ifndef STAR_PREVIEW_TILE_TOOL_HPP +#define STAR_PREVIEW_TILE_TOOL_HPP + +#include "StarList.hpp" + +STAR_STRUCT(PreviewTile); + +STAR_CLASS(PreviewTileTool); + +namespace Star { + +class PreviewTileTool { +public: + virtual ~PreviewTileTool() {} + virtual List<PreviewTile> preview(bool shifting) const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarPreviewableItem.hpp b/source/game/interfaces/StarPreviewableItem.hpp new file mode 100644 index 0000000..4cfebd5 --- /dev/null +++ b/source/game/interfaces/StarPreviewableItem.hpp @@ -0,0 +1,19 @@ +#ifndef STAR_PREVIEWABLE_ITEM +#define STAR_PREVIEWABLE_ITEM + +#include "StarDrawable.hpp" + +namespace Star { + +STAR_CLASS(Player); +STAR_CLASS(PreviewableItem); + +class PreviewableItem { +public: + virtual ~PreviewableItem() {} + virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarScriptedEntity.hpp b/source/game/interfaces/StarScriptedEntity.hpp new file mode 100644 index 0000000..40aa552 --- /dev/null +++ b/source/game/interfaces/StarScriptedEntity.hpp @@ -0,0 +1,25 @@ +#ifndef STAR_SCRIPTED_ENTITY_HPP +#define STAR_SCRIPTED_ENTITY_HPP + +#include "StarEntity.hpp" +#include "StarLua.hpp" + +namespace Star { + +STAR_CLASS(ScriptedEntity); + +// All ScriptedEntity methods should only be called on master entities +class ScriptedEntity : public virtual Entity { +public: + // Call a script function directly with the given arguments, should return + // nothing only on failure. + virtual Maybe<LuaValue> callScript(String const& func, LuaVariadic<LuaValue> const& args) = 0; + + // Execute the given code directly in the underlying context, return nothing + // on failure. + virtual Maybe<LuaValue> evalScript(String const& code) = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarStatusEffectEntity.hpp b/source/game/interfaces/StarStatusEffectEntity.hpp new file mode 100644 index 0000000..d971671 --- /dev/null +++ b/source/game/interfaces/StarStatusEffectEntity.hpp @@ -0,0 +1,18 @@ +#ifndef STAR_STATUS_EFFECT_ENTITY_HPP +#define STAR_STATUS_EFFECT_ENTITY_HPP + +#include "StarEntity.hpp" + +namespace Star { + +STAR_CLASS(StatusEffectEntity); + +class StatusEffectEntity : public virtual Entity { +public: + virtual List<PersistentStatusEffect> statusEffects() const = 0; + virtual PolyF statusEffectArea() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarStatusEffectItem.hpp b/source/game/interfaces/StarStatusEffectItem.hpp new file mode 100644 index 0000000..8590e7b --- /dev/null +++ b/source/game/interfaces/StarStatusEffectItem.hpp @@ -0,0 +1,18 @@ +#ifndef STAR_STATUS_EFFECT_ITEM_HPP +#define STAR_STATUS_EFFECT_ITEM_HPP + +#include "StarStatusTypes.hpp" + +namespace Star { + +STAR_CLASS(StatusEffectItem); + +class StatusEffectItem { +public: + virtual ~StatusEffectItem() {} + virtual List<PersistentStatusEffect> statusEffects() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarSwingableItem.cpp b/source/game/interfaces/StarSwingableItem.cpp new file mode 100644 index 0000000..3a113d8 --- /dev/null +++ b/source/game/interfaces/StarSwingableItem.cpp @@ -0,0 +1,53 @@ +#include "StarSwingableItem.hpp" + +namespace Star { + +SwingableItem::SwingableItem() { + m_swingAimFactor = 0; + m_swingStart = 0; + m_swingFinish = 0; +} + +SwingableItem::SwingableItem(Json const& params) : FireableItem(params) { + setParams(params); +} + +void SwingableItem::setParams(Json const& params) { + m_swingStart = params.getFloat("swingStart", 60) * Constants::pi / 180; + m_swingFinish = params.getFloat("swingFinish", -40) * Constants::pi / 180; + m_swingAimFactor = params.getFloat("swingAimFactor", 1); + m_coolingDownAngle = params.optFloat("coolingDownAngle").apply([](float angle) { return angle * Constants::pi / 180; }); + FireableItem::setParams(params); +} + +float SwingableItem::getAngleDir(float angle, Direction) { + return getAngle(angle); +} + +float SwingableItem::getAngle(float aimAngle) { + if (!ready()) { + if (coolingDown()) { + if (m_coolingDownAngle) + return *m_coolingDownAngle + aimAngle * m_swingAimFactor; + else + return -Constants::pi / 2; + } + + if (m_timeFiring < windupTime()) + return m_swingStart + (m_swingFinish - m_swingStart) * m_timeFiring / windupTime() + aimAngle * m_swingAimFactor; + + return m_swingFinish + (m_swingStart - m_swingFinish) * fireTimer() / (cooldownTime() + windupTime()) + aimAngle * m_swingAimFactor; + } + + return -Constants::pi / 2; +} + +float SwingableItem::getItemAngle(float aimAngle) { + return getAngle(aimAngle); +} + +String SwingableItem::getArmFrame() { + return "rotation"; +} + +} diff --git a/source/game/interfaces/StarSwingableItem.hpp b/source/game/interfaces/StarSwingableItem.hpp new file mode 100644 index 0000000..2ea9cac --- /dev/null +++ b/source/game/interfaces/StarSwingableItem.hpp @@ -0,0 +1,36 @@ +#ifndef STAR_SWINGABLE_ITEM_HPP +#define STAR_SWINGABLE_ITEM_HPP + +#include "StarFireableItem.hpp" + +namespace Star { + +STAR_CLASS(SwingableItem); + +class SwingableItem : public FireableItem { +public: + SwingableItem(); + SwingableItem(Json const& params); + virtual ~SwingableItem() {} + + // These can be different + // Default implementation is the same though + virtual float getAngleDir(float aimAngle, Direction facingDirection); + virtual float getAngle(float aimAngle); + virtual float getItemAngle(float aimAngle); + virtual String getArmFrame(); + + virtual List<Drawable> drawables() const = 0; + + void setParams(Json const& params); + +protected: + float m_swingStart; + float m_swingFinish; + float m_swingAimFactor; + Maybe<float> m_coolingDownAngle; +}; + +} + +#endif diff --git a/source/game/interfaces/StarTileEntity.cpp b/source/game/interfaces/StarTileEntity.cpp new file mode 100644 index 0000000..7e80b7b --- /dev/null +++ b/source/game/interfaces/StarTileEntity.cpp @@ -0,0 +1,101 @@ +#include "StarTileEntity.hpp" +#include "StarWorld.hpp" +#include "StarRoot.hpp" +#include "StarLiquidsDatabase.hpp" +#include "StarDataStreamExtra.hpp" + +namespace Star { + +DataStream& operator<<(DataStream& ds, MaterialSpace const& materialSpace) { + ds.write(materialSpace.space); + ds.write(materialSpace.material); + return ds; +} + +DataStream& operator>>(DataStream& ds, MaterialSpace& materialSpace) { + ds.read(materialSpace.space); + ds.read(materialSpace.material); + return ds; +} + +TileEntity::TileEntity() { + setPersistent(true); +} + +Vec2F TileEntity::position() const { + return Vec2F(tilePosition()); +} + +List<Vec2I> TileEntity::spaces() const { + return {}; +} + +List<Vec2I> TileEntity::roots() const { + return {}; +} + +List<MaterialSpace> TileEntity::materialSpaces() const { + return {}; +} + +bool TileEntity::damageTiles(List<Vec2I> const&, Vec2F const&, TileDamage const&) { + return false; +} + +bool TileEntity::isInteractive() const { + return false; +} + +List<Vec2I> TileEntity::interactiveSpaces() const { + return spaces(); +} + +InteractAction TileEntity::interact(InteractRequest const& request) { + _unused(request); + return InteractAction(); +} + +List<QuestArcDescriptor> TileEntity::offeredQuests() const { + return {}; +} + +StringSet TileEntity::turnInQuests() const { + return StringSet(); +} + +Vec2F TileEntity::questIndicatorPosition() const { + return position(); +} + +bool TileEntity::anySpacesOccupied(List<Vec2I> const& spaces) const { + Vec2I tp = tilePosition(); + for (auto pos : spaces) { + pos += tp; + if (isConnectableMaterial(world()->material(pos, TileLayer::Foreground))) + return true; + } + + return false; +} + +bool TileEntity::allSpacesOccupied(List<Vec2I> const& spaces) const { + Vec2I tp = tilePosition(); + for (auto pos : spaces) { + pos += tp; + if (!isConnectableMaterial(world()->material(pos, TileLayer::Foreground))) + return false; + } + + return true; +} + +float TileEntity::spacesLiquidFillLevel(List<Vec2I> const& relativeSpaces) const { + float total = 0.0f; + for (auto pos : relativeSpaces) { + pos += tilePosition(); + total += world()->liquidLevel(pos).level; + } + return total / relativeSpaces.size(); +} + +} diff --git a/source/game/interfaces/StarTileEntity.hpp b/source/game/interfaces/StarTileEntity.hpp new file mode 100644 index 0000000..86c01b6 --- /dev/null +++ b/source/game/interfaces/StarTileEntity.hpp @@ -0,0 +1,97 @@ +#ifndef STAR_TILE_ENTITY_HPP +#define STAR_TILE_ENTITY_HPP + +#include "StarEntity.hpp" +#include "StarTileDamage.hpp" +#include "StarInteractiveEntity.hpp" + +namespace Star { + +STAR_CLASS(TileEntity); + +struct MaterialSpace { + MaterialSpace(); + MaterialSpace(Vec2I space, MaterialId material); + + bool operator==(MaterialSpace const& rhs) const; + + Vec2I space; + MaterialId material; +}; + +DataStream& operator<<(DataStream& ds, MaterialSpace const& materialSpace); +DataStream& operator>>(DataStream& ds, MaterialSpace& materialSpace); + +// Entities that derive from TileEntity are those that can be placed in the +// tile grid, and occupy tile spaces, possibly affecting collision. +class TileEntity : public virtual InteractiveEntity { +public: + TileEntity(); + + // position() here is simply the tilePosition (but Vec2F) + virtual Vec2F position() const override; + + // The base tile position of this object. + virtual Vec2I tilePosition() const = 0; + virtual void setTilePosition(Vec2I const& pos) = 0; + + // TileEntities occupy the given spaces in tile space. This is relative to + // the current base position, and may include negative positions. A 1x1 + // object would occupy just (0, 0). + virtual List<Vec2I> spaces() const; + + // Blocks that should be marked as "root", so that they are non-destroyable + // until this entity is destroyable. Should be outside of spaces(), and + // after placement should remain static for the lifetime of the entity. + virtual List<Vec2I> roots() const; + + // TileEntities may register some of their occupied spaces with metamaterials + // to generate collidable regions + virtual List<MaterialSpace> materialSpaces() const; + + // Returns whether the entity was destroyed + virtual bool damageTiles(List<Vec2I> const& positions, Vec2F const& sourcePosition, TileDamage const& tileDamage); + + // Forces the tile entity to do an immediate check if it has been invalidly + // placed in some way. The tile entity may do this check on its own, but + // less often. + virtual bool checkBroken() = 0; + + // If the entity accepts interaction through right clicking, by default, + // returns false. + virtual bool isInteractive() const override; + // By default, does nothing. Will be called only on the server. + virtual InteractAction interact(InteractRequest const& request) override; + // Specific subset spaces that are interactive, by default, just returns + // spaces() + virtual List<Vec2I> interactiveSpaces() const; + + virtual List<QuestArcDescriptor> offeredQuests() const override; + virtual StringSet turnInQuests() const override; + virtual Vec2F questIndicatorPosition() const override; + +protected: + // Checks whether any of a given spaces list (relative to current tile + // position) is occupied by a real material. (Does not include tile + // entities). + bool anySpacesOccupied(List<Vec2I> const& relativeSpaces) const; + + // Checks that *all* spaces are occupied by a real material. + bool allSpacesOccupied(List<Vec2I> const& relativeSpaces) const; + + float spacesLiquidFillLevel(List<Vec2I> const& relativeSpaces) const; +}; + +inline MaterialSpace::MaterialSpace() + : material(NullMaterialId) {} + +inline MaterialSpace::MaterialSpace(Vec2I space, MaterialId material) + : space(space), material(material) {} + +inline bool MaterialSpace::operator==(MaterialSpace const& rhs) const { + return tie(space, material) == tie(rhs.space, rhs.material); +} + +} + +#endif diff --git a/source/game/interfaces/StarToolUserEntity.hpp b/source/game/interfaces/StarToolUserEntity.hpp new file mode 100644 index 0000000..60134ef --- /dev/null +++ b/source/game/interfaces/StarToolUserEntity.hpp @@ -0,0 +1,104 @@ +#ifndef STAR_TOOL_USER_ENTITY_HPP +#define STAR_TOOL_USER_ENTITY_HPP + +#include "StarEntity.hpp" +#include "StarParticle.hpp" +#include "StarStatusTypes.hpp" +#include "StarInteractionTypes.hpp" + +namespace Star { + +STAR_CLASS(Item); +STAR_CLASS(ToolUserEntity); +STAR_CLASS(ActorMovementController); +STAR_CLASS(StatusController); + +// FIXME: This interface is a complete mess. +class ToolUserEntity : public virtual Entity { +public: + // Translates the given arm position into it's final entity space position + // based on the given facing direction, and arm angle, and an offset from the + // rotation center of the arm. + virtual Vec2F armPosition(ToolHand hand, Direction facingDirection, float armAngle, Vec2F offset = {}) const = 0; + // The offset to give to armPosition to get the position of the hand. + virtual Vec2F handOffset(ToolHand hand, Direction facingDirection) const = 0; + + // Gets the world position of the current aim point. + virtual Vec2F aimPosition() const = 0; + + virtual bool isAdmin() const = 0; + virtual Vec4B favoriteColor() const = 0; + virtual String species() const = 0; + + virtual void requestEmote(String const& emote) = 0; + + virtual ActorMovementController* movementController() = 0; + virtual StatusController* statusController() = 0; + + // FIXME: This is effectively unusable, because since tool user items control + // the angle and facing direction of the owner, and this uses the facing + // direction and angle as input, the result will always be behind. + virtual Vec2F handPosition(ToolHand hand, Vec2F const& handOffset = Vec2F()) const = 0; + + // FIXME: This was used for an Item to get an ItemPtr to itself, which was + // super bad and weird, but it COULD be used to get the item in the owner's + // other hand, which is LESS bad. + virtual ItemPtr handItem(ToolHand hand) const = 0; + + // FIXME: What is the difference between interactRadius (which defines a tool + // range) and inToolRange (which also defines a tool range indirectly). + // inToolRange() implements based on the center of the tile of the aim + // position (NOT the aim position!) but inToolRange(Vec2F) uses the given + // position, which is again redundant. Also, what is beamGunRadius and why + // is it different than interact radius? Can different tools have a + // different interact radius? + virtual float interactRadius() const = 0; + virtual bool inToolRange() const = 0; + virtual bool inToolRange(Vec2F const& position) const = 0; + virtual float beamGunRadius() const = 0; + + // FIXME: Too specific to Player, just cast to Player if you have to and do + // that, NPCs cannot possibly implement these properly (and do not implement + // them at all). + virtual void queueUIMessage(String const& message) = 0; + virtual void interact(InteractAction const& action) = 0; + + // FIXME: Ditto here, instrumentPlaying() is just an accessor to the songbook + // for when the songbook has had a song selected, and the instrument decides + // when to cancel music anyway, also instrumentEquipped(String) is a straight + // up ridiculous way of notifying the Player that the player itself is + // holding an instrument, which it already knows. + virtual bool instrumentPlaying() = 0; + virtual void instrumentEquipped(String const& instrumentKind) = 0; + + // FIXME: how is this related to the hand position and isn't it already + // included in the hand position and why is it necessary? + virtual Vec2F armAdjustment() const = 0; + + // FIXME: These were all fine, just need to be fixed because now we have the + // movement controller itself and can use that directly + virtual Vec2F position() const = 0; + virtual Vec2F velocity() const = 0; + virtual Direction facingDirection() const = 0; + virtual Direction walkingDirection() const = 0; + + // FIXME: Ditto here, except we now have the status controller directly. + virtual float powerMultiplier() const = 0; + virtual bool fullEnergy() const = 0; + virtual float energy() const = 0; + virtual bool consumeEnergy(float energy) = 0; + virtual bool energyLocked() const = 0; + virtual void addEphemeralStatusEffects(List<EphemeralStatusEffect> const& statusEffects) = 0; + virtual ActiveUniqueStatusEffectSummary activeUniqueStatusEffectSummary() const = 0; + + // FIXME: This is a dumb way of getting limited animation support + virtual void addEffectEmitters(StringSet const& emitters) = 0; + virtual void addParticles(List<Particle> const& particles) = 0; + virtual void addSound(String const& sound, float volume = 1.0f) = 0; + + virtual void setCameraFocusEntity(Maybe<EntityId> const& cameraFocusEntity) = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarToolUserItem.cpp b/source/game/interfaces/StarToolUserItem.cpp new file mode 100644 index 0000000..e9354a1 --- /dev/null +++ b/source/game/interfaces/StarToolUserItem.cpp @@ -0,0 +1,59 @@ +#include "StarToolUserItem.hpp" + +namespace Star { + +ToolUserItem::ToolUserItem() : m_owner(nullptr) {} + +void ToolUserItem::init(ToolUserEntity* owner, ToolHand hand) { + m_owner = owner; + m_hand = hand; +} + +void ToolUserItem::uninit() { + m_owner = nullptr; + m_hand = {}; +} + +void ToolUserItem::update(FireMode, bool, HashSet<MoveControlType> const&) {} + +bool ToolUserItem::initialized() const { + return (bool)m_owner; +} + +ToolUserEntity* ToolUserItem::owner() const { + if (!m_owner) + throw ToolUserItemException("Not initialized in ToolUserItem::owner"); + return m_owner; +} + +EntityMode ToolUserItem::entityMode() const { + if (!m_owner) + throw ToolUserItemException("Not initialized in ToolUserItem::entityMode"); + return *m_owner->entityMode(); +} + +ToolHand ToolUserItem::hand() const { + if (!m_owner) + throw ToolUserItemException("Not initialized in ToolUserItem::hand"); + return *m_hand; +} + +World* ToolUserItem::world() const { + if (!m_owner) + throw ToolUserItemException("Not initialized in ToolUserItem::world"); + return m_owner->world(); +} + +List<DamageSource> ToolUserItem::damageSources() const { + return {}; +} + +List<PolyF> ToolUserItem::shieldPolys() const { + return {}; +} + +List<PhysicsForceRegion> ToolUserItem::forceRegions() const { + return {}; +} + +} diff --git a/source/game/interfaces/StarToolUserItem.hpp b/source/game/interfaces/StarToolUserItem.hpp new file mode 100644 index 0000000..9113ab0 --- /dev/null +++ b/source/game/interfaces/StarToolUserItem.hpp @@ -0,0 +1,49 @@ +#ifndef STAR_TOOL_USER_ITEM_HPP +#define STAR_TOOL_USER_ITEM_HPP + +#include "StarToolUserEntity.hpp" +#include "StarPhysicsEntity.hpp" + +namespace Star { + +STAR_EXCEPTION(ToolUserItemException, StarException); + +STAR_CLASS(ToolUserItem); + +// FIXME: You know what another name for an item that a tool user uses is? A +// Tool. Three words when one will do, rename. +class ToolUserItem { +public: + ToolUserItem(); + virtual ~ToolUserItem() = default; + + // Owner must be initialized when a ToolUserItem is initialized and + // uninitialized before the owner is uninitialized. + virtual void init(ToolUserEntity* owner, ToolHand hand); + virtual void uninit(); + + // Default implementation does nothing + virtual void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves); + + // Default implementations return empty list + virtual List<DamageSource> damageSources() const; + virtual List<PolyF> shieldPolys() const; + virtual List<PhysicsForceRegion> forceRegions() const; + + bool initialized() const; + + // owner, entityMode, hand, and world throw ToolUserException if + // initialized() is false + ToolUserEntity* owner() const; + EntityMode entityMode() const; + ToolHand hand() const; + World* world() const; + +private: + ToolUserEntity* m_owner; + Maybe<ToolHand> m_hand; +}; + +} + +#endif diff --git a/source/game/interfaces/StarWarpTargetEntity.hpp b/source/game/interfaces/StarWarpTargetEntity.hpp new file mode 100644 index 0000000..cdc68f5 --- /dev/null +++ b/source/game/interfaces/StarWarpTargetEntity.hpp @@ -0,0 +1,20 @@ +#ifndef STAR_WARP_TARGET_ENTITY_HPP +#define STAR_WARP_TARGET_ENTITY_HPP + +#include "StarWarping.hpp" +#include "StarTileEntity.hpp" + +namespace Star { + +STAR_CLASS(WarpTargetEntity); + +class WarpTargetEntity : public virtual TileEntity { +public: + // Foot position for things teleporting onto this entity, relative to root + // position. + virtual Vec2F footPosition() const = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarWireEntity.hpp b/source/game/interfaces/StarWireEntity.hpp new file mode 100644 index 0000000..a848865 --- /dev/null +++ b/source/game/interfaces/StarWireEntity.hpp @@ -0,0 +1,28 @@ +#ifndef STAR_WIRE_ENTITY_HPP +#define STAR_WIRE_ENTITY_HPP + +#include "StarWiring.hpp" +#include "StarTileEntity.hpp" + +namespace Star { + +STAR_CLASS(WireEntity); + +class WireEntity : public virtual TileEntity { +public: + virtual ~WireEntity() {} + + virtual size_t nodeCount(WireDirection direction) const = 0; + virtual Vec2I nodePosition(WireNode wireNode) const = 0; + virtual List<WireConnection> connectionsForNode(WireNode wireNode) const = 0; + virtual bool nodeState(WireNode wireNode) const = 0; + + virtual void addNodeConnection(WireNode wireNode, WireConnection nodeConnection) = 0; + virtual void removeNodeConnection(WireNode wireNode, WireConnection nodeConnection) = 0; + + virtual void evaluate(WireCoordinator* coordinator) = 0; +}; + +} + +#endif diff --git a/source/game/interfaces/StarWorld.cpp b/source/game/interfaces/StarWorld.cpp new file mode 100644 index 0000000..5f416df --- /dev/null +++ b/source/game/interfaces/StarWorld.cpp @@ -0,0 +1,153 @@ +#include "StarWorld.hpp" +#include "StarScriptedEntity.hpp" + +namespace Star { + +bool World::isServer() const { + return connection() == ServerConnectionId; +} + +bool World::isClient() const { + return !isServer(); +} + +List<EntityPtr> World::entityQuery(RectF const& boundBox, EntityFilter selector) const { + List<EntityPtr> list; + forEachEntity(boundBox, [&](EntityPtr const& entity) { + if (!selector || selector(entity)) + list.append(entity); + }); + return list; +} + +List<EntityPtr> World::entityLineQuery(Vec2F const& begin, Vec2F const& end, EntityFilter selector) const { + List<EntityPtr> list; + forEachEntityLine(begin, end, [&](EntityPtr const& entity) { + if (!selector || selector(entity)) + list.append(entity); + }); + return list; +} + +List<TileEntityPtr> World::entitiesAtTile(Vec2I const& pos, EntityFilter selector) const { + List<TileEntityPtr> list; + forEachEntityAtTile(pos, [&](TileEntityPtr entity) { + if (!selector || selector(entity)) + list.append(move(entity)); + }); + return list; +} + +List<Vec2I> World::findEmptyTiles(Vec2I pos, unsigned maxDist, size_t maxAmount, bool excludeEphemeral) const { + List<Vec2I> res; + if (!tileIsOccupied(pos, TileLayer::Foreground, excludeEphemeral)) + res.append(pos); + + if (res.size() >= maxAmount) + return res; + + // searches manhattan distance counterclockwise from right + for (int distance = 1; distance <= (int)maxDist; distance++) { + const int totalSpots = 4 * distance; + int xDiff = distance; + int yDiff = 0; + int dx = -1; + int dy = 1; + for (int i = 0; i < totalSpots; i++) { + if (!tileIsOccupied(pos + Vec2I(xDiff, yDiff), TileLayer::Foreground)) { + res.append(pos + Vec2I(xDiff, yDiff)); + if (res.size() >= maxAmount) { + return res; + } + } + xDiff += dx; + yDiff += dy; + if (abs(xDiff) == distance) + dx *= -1; + if (abs(yDiff) == distance) + dy *= -1; + } + } + + return res; +} + +bool World::canModifyTile(Vec2I const& pos, TileModification const& modification, bool allowEntityOverlap) const { + return !validTileModifications({{pos, modification}}, allowEntityOverlap).empty(); +} + +bool World::modifyTile(Vec2I const& pos, TileModification const& modification, bool allowEntityOverlap) { + return applyTileModifications({{pos, modification}}, allowEntityOverlap).empty(); +} + +TileDamageResult World::damageTile(Vec2I const& tilePosition, TileLayer layer, Vec2F const& sourcePosition, TileDamage const& tileDamage, Maybe<EntityId> sourceEntity) { + return damageTiles({tilePosition}, layer, sourcePosition, tileDamage, sourceEntity); +} + +EntityPtr World::closestEntityInSight(Vec2F const& center, float radius, CollisionSet const& collisionSet, EntityFilter selector) const { + return closestEntity(center, radius, [=](EntityPtr const& entity) { + return selector(entity) && !lineTileCollision(center, entity->position(), collisionSet); + }); +} + +bool World::pointCollision(Vec2F const& point, CollisionSet const& collisionSet) const { + bool collided = false; + + forEachCollisionBlock(RectI::withCenter(Vec2I(point), {3, 3}), [&](CollisionBlock const& block) { + if (collided || !isColliding(block.kind, collisionSet)) + return; + + if (block.poly.contains(point)) + collided = true; + }); + + return collided; +} + +Maybe<pair<Vec2F, Maybe<Vec2F>>> World::lineCollision(Line2F const& line, CollisionSet const& collisionSet) const { + auto geometry = this->geometry(); + Maybe<PolyF> intersectPoly; + Maybe<PolyF::LineIntersectResult> closestIntersection; + + forEachCollisionBlock(RectI::integral(RectF::boundBoxOf(line.min(), line.max()).padded(1)), [&](CollisionBlock const& block) { + if (block.poly.isNull() || !isColliding(block.kind, collisionSet)) + return; + + Vec2F nearMin = geometry.nearestTo(block.poly.center(), line.min()); + auto intersection = block.poly.lineIntersection(Line2F(nearMin, nearMin + line.diff())); + if (intersection && (!closestIntersection || intersection->along < closestIntersection->along)) { + intersectPoly = block.poly; + closestIntersection = intersection; + } + }); + + if (closestIntersection) { + auto point = line.eval(closestIntersection->along); + auto normal = closestIntersection->intersectedSide.apply([&](uint64_t side) { return intersectPoly->normal(side); }); + return make_pair(point, normal); + } + return {}; +} + +bool World::polyCollision(PolyF const& poly, CollisionSet const& collisionSet) const { + auto geometry = this->geometry(); + Vec2F polyCenter = poly.center(); + PolyF translatedPoly; + bool collided = false; + + forEachCollisionBlock(RectI::integral(poly.boundBox()).padded(1), [&](CollisionBlock const& block) { + if (collided || !isColliding(block.kind, collisionSet)) + return; + + Vec2F center = block.poly.center(); + Vec2F newCenter = geometry.nearestTo(polyCenter, center); + translatedPoly = block.poly; + translatedPoly.translate(newCenter - center); + if (poly.intersects(translatedPoly)) + collided = true; + }); + + return collided; +} + +} diff --git a/source/game/interfaces/StarWorld.hpp b/source/game/interfaces/StarWorld.hpp new file mode 100644 index 0000000..dc673e1 --- /dev/null +++ b/source/game/interfaces/StarWorld.hpp @@ -0,0 +1,259 @@ +#ifndef STAR_WORLD_HPP +#define STAR_WORLD_HPP + +#include "StarTileEntity.hpp" +#include "StarInteractionTypes.hpp" +#include "StarCollisionBlock.hpp" +#include "StarForceRegions.hpp" +#include "StarWorldGeometry.hpp" +#include "StarTileModification.hpp" +#include "StarLuaRoot.hpp" +#include "StarRpcPromise.hpp" + +namespace Star { + +STAR_CLASS(World); +STAR_CLASS(TileEntity); +STAR_CLASS(ScriptedEntity); + +typedef function<void(World*)> WorldAction; + +class World { +public: + virtual ~World() {} + + // Will remain constant throughout the life of the world. + virtual ConnectionId connection() const = 0; + virtual WorldGeometry geometry() const = 0; + + // Update frame counter. Returns the frame that is *currently* being + // updated, not the *last* frame, so during the first call to update(), this + // would return 1 + virtual uint64_t currentStep() const = 0; + + // All methods that take int parameters wrap around or clamp so that all int + // values are valid world indexes. + + virtual MaterialId material(Vec2I const& position, TileLayer layer) const = 0; + virtual MaterialHue materialHueShift(Vec2I const& position, TileLayer layer) const = 0; + virtual ModId mod(Vec2I const& position, TileLayer layer) const = 0; + virtual MaterialHue modHueShift(Vec2I const& position, TileLayer layer) const = 0; + virtual MaterialColorVariant colorVariant(Vec2I const& position, TileLayer layer) const = 0; + virtual LiquidLevel liquidLevel(Vec2I const& pos) const = 0; + virtual LiquidLevel liquidLevel(RectF const& region) const = 0; + + // Tests a tile modification list and returns the ones that are valid. + virtual TileModificationList validTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) const = 0; + // Apply a list of tile modifications in the best order to apply as many + // possible, and returns the modifications that could not be applied. + virtual TileModificationList applyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) = 0; + + virtual bool isTileProtected(Vec2I const& pos) const = 0; + + virtual EntityPtr entity(EntityId entityId) const = 0; + // *If* the entity is initialized immediately and locally, then will use the + // passed in pointer directly and initialize it, and entity will have a valid + // id in this world and be ready for use. This is always the case on the + // server, but not *always* the case on the client. + virtual void addEntity(EntityPtr const& entity) = 0; + + virtual EntityPtr closestEntity(Vec2F const& center, float radius, EntityFilter selector = {}) const = 0; + + virtual void forAllEntities(EntityCallback entityCallback) const = 0; + + // Query here is a fuzzy query based on metaBoundBox + virtual void forEachEntity(RectF const& boundBox, EntityCallback entityCallback) const = 0; + // Fuzzy metaBoundBox query for intersecting the given line. + virtual void forEachEntityLine(Vec2F const& begin, Vec2F const& end, EntityCallback entityCallback) const = 0; + // Performs action for all entities that occupies the given tile position + // (only entity types laid out in the tile grid). + virtual void forEachEntityAtTile(Vec2I const& pos, EntityCallbackOf<TileEntity> entityCallback) const = 0; + + // Like forEachEntity, but stops scanning when entityFilter returns true, and + // returns the EntityPtr found, otherwise returns a null pointer. + virtual EntityPtr findEntity(RectF const& boundBox, EntityFilter entityFilter) const = 0; + virtual EntityPtr findEntityLine(Vec2F const& begin, Vec2F const& end, EntityFilter entityFilter) const = 0; + virtual EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const = 0; + + // Is the given tile layer and position occupied by an entity or block? + virtual bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false) const = 0; + + // Iterate over the collision block for each tile in the region. Collision + // polys for tiles can extend to a maximum of 1 tile outside of the natural + // tile bounds. + virtual void forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const = 0; + + // Is there some connectable tile / tile based entity in this position? If + // tilesOnly is true, only checks to see whether that tile is a connectable + // material. + virtual bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const = 0; + + // Returns whether or not a given point is inside any colliding tile. If + // collisionSet is Dynamic or Static, then does not intersect with platforms. + virtual bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const = 0; + + // Returns whether line intersects with any colliding tiles. + virtual bool lineTileCollision(Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet = DefaultCollisionSet) const = 0; + virtual Maybe<pair<Vec2F, Vec2I>> lineTileCollisionPoint(Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet = DefaultCollisionSet) const = 0; + + // Returns a list of all the collidable tiles along the given line. + virtual List<Vec2I> collidingTilesAlongLine(Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet = DefaultCollisionSet, int maxSize = -1, bool includeEdges = true) const = 0; + + // Returns whether the given rect contains any colliding tiles. + virtual bool rectTileCollision(RectI const& region, CollisionSet const& collisionSet = DefaultCollisionSet) const = 0; + + // Damage multiple tiles, avoiding duplication (objects or plants that occupy + // more than one tile + // position are only damaged once) + virtual TileDamageResult damageTiles(List<Vec2I> const& tilePositions, TileLayer layer, Vec2F const& sourcePosition, TileDamage const& tileDamage, Maybe<EntityId> sourceEntity = {}) = 0; + + virtual InteractiveEntityPtr getInteractiveInRange(Vec2F const& targetPosition, Vec2F const& sourcePosition, float maxRange) const = 0; + // Can the target entity be reached from the given position within the given radius? + virtual bool canReachEntity(Vec2F const& position, float radius, EntityId targetEntity, bool preferInteractive = true) const = 0; + virtual RpcPromise<InteractAction> interact(InteractRequest const& request) = 0; + + virtual float gravity(Vec2F const& pos) const = 0; + virtual float windLevel(Vec2F const& pos) const = 0; + virtual float lightLevel(Vec2F const& pos) const = 0; + virtual bool breathable(Vec2F const& pos) const = 0; + virtual float threatLevel() const = 0; + virtual StringList environmentStatusEffects(Vec2F const& pos) const = 0; + virtual StringList weatherStatusEffects(Vec2F const& pos) const = 0; + virtual bool exposedToWeather(Vec2F const& pos) const = 0; + virtual bool isUnderground(Vec2F const& pos) const = 0; + virtual bool disableDeathDrops() const = 0; + virtual List<PhysicsForceRegion> forceRegions() const = 0; + + // Gets / sets world-wide properties + virtual Json getProperty(String const& propertyName, Json const& def = {}) const = 0; + virtual void setProperty(String const& propertyName, Json const& property) = 0; + + virtual void timer(int stepsDelay, WorldAction worldAction) = 0; + virtual double epochTime() const = 0; + virtual uint32_t day() const = 0; + virtual float dayLength() const = 0; + virtual float timeOfDay() const = 0; + + virtual LuaRootPtr luaRoot() = 0; + + // Locate a unique entity, if the target is local, the promise will be + // finished before being returned. If the unique entity is not found, the + // promise will fail. + virtual RpcPromise<Vec2F> findUniqueEntity(String const& uniqueEntityId) = 0; + + // Send a message to a local or remote scripted entity. If the target is + // local, the promise will be finished before being returned. Entity id can + // either be EntityId or a uniqueId. + virtual RpcPromise<Json> sendEntityMessage(Variant<EntityId, String> const& entity, String const& message, JsonArray const& args = {}) = 0; + + // Helper non-virtual methods. + + bool isServer() const; + bool isClient() const; + + List<EntityPtr> entityQuery(RectF const& boundBox, EntityFilter selector = {}) const; + List<EntityPtr> entityLineQuery(Vec2F const& begin, Vec2F const& end, EntityFilter selector = {}) const; + + List<TileEntityPtr> entitiesAtTile(Vec2I const& pos, EntityFilter filter = EntityFilter()) const; + + // Find tiles near the given point that are not occupied (according to + // tileIsOccupied) + List<Vec2I> findEmptyTiles(Vec2I pos, unsigned maxDist = 5, size_t maxAmount = 1, bool excludeEphemeral = false) const; + + // Do tile modification that only uses a single tile. + bool canModifyTile(Vec2I const& pos, TileModification const& modification, bool allowEntityOverlap) const; + bool modifyTile(Vec2I const& pos, TileModification const& modification, bool allowEntityOverlap); + + TileDamageResult damageTile(Vec2I const& tilePosition, TileLayer layer, Vec2F const& sourcePosition, TileDamage const& tileDamage, Maybe<EntityId> sourceEntity = {}); + + // Returns closest entity for which lineCollision between the given center + // position and the entity position returns false. + EntityPtr closestEntityInSight(Vec2F const& center, float radius, CollisionSet const& collisionSet = DefaultCollisionSet, EntityFilter selector = {}) const; + + // Returns whether point collides with any collision geometry. + bool pointCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const; + + // Returns first point along line that collides with any collision geometry, along + // with the normal of the intersected line, if any. + Maybe<pair<Vec2F, Maybe<Vec2F>>> lineCollision(Line2F const& line, CollisionSet const& collisionSet = DefaultCollisionSet) const; + + // Returns whether poly collides with any collision geometry. + bool polyCollision(PolyF const& poly, CollisionSet const& collisionSet = DefaultCollisionSet) const; + + // Helper template methods. Only queries entities of the given template + // type, and casts them to the appropriate pointer type. + + template <typename EntityT> + shared_ptr<EntityT> get(EntityId entityId) const; + + template <typename EntityT> + List<shared_ptr<EntityT>> query(RectF const& boundBox, EntityFilterOf<EntityT> selector = {}) const; + + template <typename EntityT> + shared_ptr<EntityT> closest(Vec2F const& center, float radius, EntityFilterOf<EntityT> selector = {}) const; + + template <typename EntityT> + shared_ptr<EntityT> closestInSight(Vec2F const& center, float radius, CollisionSet const& collisionSet, EntityFilterOf<EntityT> selector = {}) const; + + template <typename EntityT> + List<shared_ptr<EntityT>> lineQuery(Vec2F const& begin, Vec2F const& end, EntityFilterOf<EntityT> selector = {}) const; + + template <typename EntityT> + List<shared_ptr<EntityT>> atTile(Vec2I const& pos) const; +}; + +template <typename EntityT> +shared_ptr<EntityT> World::get(EntityId entityId) const { + return as<EntityT>(entity(entityId)); +} + +template <typename EntityT> +List<shared_ptr<EntityT>> World::query(RectF const& boundBox, EntityFilterOf<EntityT> selector) const { + List<shared_ptr<EntityT>> list; + forEachEntity(boundBox, [&](EntityPtr const& entity) { + if (auto e = as<EntityT>(entity)) { + if (!selector || selector(e)) + list.append(move(e)); + } + }); + + return list; +} + +template <typename EntityT> +shared_ptr<EntityT> World::closest(Vec2F const& center, float radius, EntityFilterOf<EntityT> selector) const { + return as<EntityT>(closestEntity(center, radius, entityTypeFilter<EntityT>(selector))); +} + +template <typename EntityT> +shared_ptr<EntityT> World::closestInSight( + Vec2F const& center, float radius, CollisionSet const& collisionSet, EntityFilterOf<EntityT> selector) const { + return as<EntityT>(closestEntityInSight(center, radius, collisionSet, entityTypeFilter<EntityT>(selector))); +} + +template <typename EntityT> +List<shared_ptr<EntityT>> World::lineQuery( + Vec2F const& begin, Vec2F const& end, EntityFilterOf<EntityT> selector) const { + List<shared_ptr<EntityT>> list; + forEachEntityLine(begin, end, [&](EntityPtr entity) { + if (auto e = as<EntityT>(move(entity))) { + if (!selector || selector(e)) + list.append(move(e)); + } + }); + + return list; +} + +template <typename EntityT> +List<shared_ptr<EntityT>> World::atTile(Vec2I const& pos) const { + List<shared_ptr<EntityT>> list; + forEachEntityAtTile(pos, [&](TileEntityPtr const& entity) { + if (auto e = as<EntityT>(entity)) + list.append(move(e)); + }); + return list; +} +} + +#endif |