#pragma once #include "StarPeriodicFunction.hpp" #include "StarTtlCache.hpp" #include "StarGameTypes.hpp" #include "StarItemDescriptor.hpp" #include "StarParticle.hpp" #include "StarSet.hpp" #include "StarTileDamage.hpp" #include "StarDamageTypes.hpp" #include "StarStatusTypes.hpp" #include "StarEntityRendering.hpp" #include "StarTileEntity.hpp" namespace Star { STAR_CLASS(World); STAR_CLASS(Image); STAR_CLASS(ItemDatabase); STAR_CLASS(RecipeDatabase); STAR_CLASS(Object); STAR_STRUCT(ObjectOrientation); STAR_STRUCT(ObjectConfig); STAR_CLASS(ObjectDatabase); STAR_EXCEPTION(ObjectException, StarException); struct ObjectOrientation { struct Anchor { TileLayer layer; Vec2I position; bool tilled; bool soil; Maybe material; }; struct ParticleEmissionEntry { float particleEmissionRate; float particleEmissionRateVariance; // Particle positions are considered relative to image pixels, and are // flipped with image flipping Particle particle; Particle particleVariance; bool placeInSpaces; }; // The JSON values that were used to configure this orientation. Json config; EntityRenderLayer renderLayer; List imageLayers; bool flipImages; // Offset of image from (0, 0) object position, in tile coordinates Vec2F imagePosition; // If an object has frames > 1, then the image name will have the marker // "{frame}" replaced with an integer in [0, frames) unsigned frames; float animationCycle; // Spaces the object occupies. By default, this is simply the single space // at the object position, but can be specified in config as either a list of // Vec2I, or by setting a threshold value using "spaceScanning", which will // scan the image (frame 1) for non-transparent pixels. List spaces; RectI boundBox; // Allow an orientation to override the metaboundbox in case you don't want to // specify spaces Maybe metaBoundBox; // Anchors of the object to place it in the world // For background tiles set in order for the object to // remain placed. Must be within 1 space of the bounding box of spaces. // For foreground tiles this cannot logically contain any position // also in spaces, as objects cannot overlap with foreground tiles. List anchors; // if true, only one anchor needs to be valid for the orientation to be valid, // otherwise all anchors must be valid bool anchorAny; Maybe directionAffinity; // Optional list of material spaces List materialSpaces; // optionally override the default spaces used for interaction Maybe> interactiveSpaces; Vec2F lightPosition; float beamAngle; List particleEmitters; Maybe statusEffectArea; Json touchDamageConfig; static ParticleEmissionEntry parseParticleEmitter(String const& path, Json const& config); bool placementValid(World const* world, Vec2I const& position) const; bool anchorsValid(World const* world, Vec2I const& position) const; }; // TODO: This is used very strangely and inconsistently. We go to all the trouble of populating // this ObjectConfig structure from the JSON, but then keep around the JSON anyway. In some // places we access the objectConfig, but in many more we use the object's configValue method // to access the raw config JSON which means it's inconsistent which parameters can be overridden // by instance values at various levels. This whole system needs reevaluation. struct ObjectConfig { // Returns the index of the best valid orientation. If no orientations are // valid, returns NPos size_t findValidOrientation(World const* world, Vec2I const& position, Maybe directionAffinity = Maybe()) const; String path; // The JSON values that were used to configure this Object Json config; String name; String type; String race; String category; StringList colonyTags; StringList scripts; StringList animationScripts; unsigned price; bool printable; bool scannable; bool interactive; StringMap lightColors; LightType lightType; float pointBeam; float beamAmbience; Maybe> lightFlickering; String soundEffect; float soundEffectRangeMultiplier; List statusEffects; Json touchDamageConfig; bool hasObjectItem; bool retainObjectParametersInItem; bool smashable; bool smashOnBreak; bool unbreakable; String smashDropPool; List> smashDropOptions; StringList smashSoundOptions; JsonArray smashParticles; String breakDropPool; List> breakDropOptions; TileDamageParameters tileDamageParameters; float damageShakeMagnitude; String damageMaterialKind; EntityDamageTeam damageTeam; Maybe minimumLiquidLevel; Maybe maximumLiquidLevel; float liquidCheckInterval; float health; Json animationConfig; List orientations; // If true, the object will root - it will prevent the blocks it is // anchored to from being destroyed directly, and damage from those // blocks will be redirected to the object bool rooting; bool biomePlaced; }; class ObjectDatabase { public: static List scanImageSpaces(ImageConstPtr const& image, Vec2F const& position, float fillLimit, bool flip = false); static Json parseTouchDamage(String const& path, Json const& touchDamage); static List parseOrientations(String const& path, Json const& configList); ObjectDatabase(); void cleanup(); StringList allObjects() const; bool isObject(String const& name) const; ObjectConfigPtr getConfig(String const& objectName) const; List const& getOrientations(String const& objectName) const; ObjectPtr createObject(String const& objectName, Json const& objectParameters = JsonObject()) const; ObjectPtr diskLoadObject(Json const& diskStore) const; ObjectPtr netLoadObject(ByteArray const& netStore) const; bool canPlaceObject(World const* world, Vec2I const& position, String const& objectName) const; // If the object is placeable in the given position, creates the given object // and sets its position and direction and returns it, otherwise returns // null. ObjectPtr createForPlacement(World const* world, String const& objectName, Vec2I const& position, Direction direction, Json const& parameters = JsonObject()) const; List cursorHintDrawables(World const* world, String const& objectName, Vec2I const& position, Direction direction, Json parameters = {}) const; private: static ObjectConfigPtr readConfig(String const& path); StringMap m_paths; mutable Mutex m_cacheMutex; mutable HashTtlCache m_configCache; }; }