Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/interfaces
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/interfaces
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/game/interfaces')
-rw-r--r--source/game/interfaces/StarActivatableItem.hpp21
-rw-r--r--source/game/interfaces/StarAggressiveEntity.hpp17
-rw-r--r--source/game/interfaces/StarAnchorableEntity.cpp21
-rw-r--r--source/game/interfaces/StarAnchorableEntity.hpp39
-rw-r--r--source/game/interfaces/StarBeamItem.cpp268
-rw-r--r--source/game/interfaces/StarBeamItem.hpp78
-rw-r--r--source/game/interfaces/StarChattyEntity.hpp19
-rw-r--r--source/game/interfaces/StarContainerEntity.cpp15
-rw-r--r--source/game/interfaces/StarContainerEntity.hpp50
-rw-r--r--source/game/interfaces/StarDamageBarEntity.cpp11
-rw-r--r--source/game/interfaces/StarDamageBarEntity.hpp27
-rw-r--r--source/game/interfaces/StarDurabilityItem.hpp18
-rw-r--r--source/game/interfaces/StarEffectSourceItem.hpp18
-rw-r--r--source/game/interfaces/StarEmoteEntity.hpp18
-rw-r--r--source/game/interfaces/StarEntity.cpp194
-rw-r--r--source/game/interfaces/StarEntity.hpp229
-rw-r--r--source/game/interfaces/StarFireableItem.cpp300
-rw-r--r--source/game/interfaces/StarFireableItem.hpp91
-rw-r--r--source/game/interfaces/StarInspectableEntity.hpp37
-rw-r--r--source/game/interfaces/StarInteractiveEntity.cpp25
-rw-r--r--source/game/interfaces/StarInteractiveEntity.hpp34
-rw-r--r--source/game/interfaces/StarLoungingEntities.cpp63
-rw-r--r--source/game/interfaces/StarLoungingEntities.hpp71
-rw-r--r--source/game/interfaces/StarNametagEntity.hpp20
-rw-r--r--source/game/interfaces/StarNonRotatedDrawablesItem.hpp18
-rw-r--r--source/game/interfaces/StarPhysicsEntity.cpp85
-rw-r--r--source/game/interfaces/StarPhysicsEntity.hpp61
-rw-r--r--source/game/interfaces/StarPointableItem.cpp13
-rw-r--r--source/game/interfaces/StarPointableItem.hpp22
-rw-r--r--source/game/interfaces/StarPortraitEntity.hpp19
-rw-r--r--source/game/interfaces/StarPreviewTileTool.hpp20
-rw-r--r--source/game/interfaces/StarPreviewableItem.hpp19
-rw-r--r--source/game/interfaces/StarScriptedEntity.hpp25
-rw-r--r--source/game/interfaces/StarStatusEffectEntity.hpp18
-rw-r--r--source/game/interfaces/StarStatusEffectItem.hpp18
-rw-r--r--source/game/interfaces/StarSwingableItem.cpp53
-rw-r--r--source/game/interfaces/StarSwingableItem.hpp36
-rw-r--r--source/game/interfaces/StarTileEntity.cpp101
-rw-r--r--source/game/interfaces/StarTileEntity.hpp97
-rw-r--r--source/game/interfaces/StarToolUserEntity.hpp104
-rw-r--r--source/game/interfaces/StarToolUserItem.cpp59
-rw-r--r--source/game/interfaces/StarToolUserItem.hpp49
-rw-r--r--source/game/interfaces/StarWarpTargetEntity.hpp20
-rw-r--r--source/game/interfaces/StarWireEntity.hpp28
-rw-r--r--source/game/interfaces/StarWorld.cpp153
-rw-r--r--source/game/interfaces/StarWorld.hpp259
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