This commit is contained in:
Aria 2025-03-21 22:23:30 +11:00
commit 9c94d113d3
Signed by untrusted user who does not match committer: aria
GPG key ID: 19AB7AA462B8AB3B
10260 changed files with 1237388 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -0,0 +1,11 @@
#include "StarDamageBarEntity.hpp"
namespace Star {
EnumMap<DamageBarType> const DamageBarTypeNames{
{DamageBarType::Default, "Default"},
{DamageBarType::None, "None"},
{DamageBarType::Special, "Special"}
};
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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 {};
}
}

View file

@ -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

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {};
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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";
}
}

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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 {};
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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