diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/scripting/StarWorldLuaBindings.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/scripting/StarWorldLuaBindings.cpp')
-rw-r--r-- | source/game/scripting/StarWorldLuaBindings.cpp | 1981 |
1 files changed, 1981 insertions, 0 deletions
diff --git a/source/game/scripting/StarWorldLuaBindings.cpp b/source/game/scripting/StarWorldLuaBindings.cpp new file mode 100644 index 0000000..612ba7e --- /dev/null +++ b/source/game/scripting/StarWorldLuaBindings.cpp @@ -0,0 +1,1981 @@ +#include "StarWorldLuaBindings.hpp" +#include "StarJsonExtra.hpp" +#include "StarWorld.hpp" +#include "StarBlocksAlongLine.hpp" +#include "StarSky.hpp" +#include "StarPlayer.hpp" +#include "StarPlayerInventory.hpp" +#include "StarMonster.hpp" +#include "StarNpc.hpp" +#include "StarStagehand.hpp" +#include "StarLoungeableObject.hpp" +#include "StarProjectileDatabase.hpp" +#include "StarProjectile.hpp" +#include "StarRoot.hpp" +#include "StarWorldServer.hpp" +#include "StarWorldClient.hpp" +#include "StarWorldTemplate.hpp" +#include "StarWorldParameters.hpp" +#include "StarItemDrop.hpp" +#include "StarMaterialDatabase.hpp" +#include "StarLogging.hpp" +#include "StarObjectDatabase.hpp" +#include "StarItemDatabase.hpp" +#include "StarItem.hpp" +#include "StarTreasure.hpp" +#include "StarContainerObject.hpp" +#include "StarFarmableObject.hpp" +#include "StarImageMetadataDatabase.hpp" +#include "StarLuaGameConverters.hpp" +#include "StarVehicleDatabase.hpp" +#include "StarUtilityLuaBindings.hpp" +#include "StarUniverseSettings.hpp" +#include "StarBiome.hpp" + +namespace Star { +namespace LuaBindings { + + enum class EntityBoundMode { MetaBoundBox, CollisionArea, Position }; + + EnumMap<EntityBoundMode> const EntityBoundModeNames = { + {EntityBoundMode::MetaBoundBox, "MetaBoundBox"}, + {EntityBoundMode::CollisionArea, "CollisionArea"}, + {EntityBoundMode::Position, "Position"} + }; + + template <typename EntityT> + using Selector = function<bool(shared_ptr<EntityT> const&)>; + + template <typename EntityT> + LuaTable entityQueryImpl(World* world, LuaEngine& engine, LuaTable const& options, Selector<EntityT> selector) { + Maybe<EntityId> withoutEntityId = options.get<Maybe<EntityId>>("withoutEntityId"); + Maybe<Set<EntityType>> includedTypes; + if (auto types = options.get<Maybe<LuaTable>>("includedTypes")) { + includedTypes = Set<EntityType>(); + types->iterate([&includedTypes](LuaValue const&, LuaString const& type) { + if (type == "mobile") { + includedTypes->add(EntityType::Player); + includedTypes->add(EntityType::Monster); + includedTypes->add(EntityType::Npc); + includedTypes->add(EntityType::Projectile); + includedTypes->add(EntityType::ItemDrop); + includedTypes->add(EntityType::Vehicle); + } else if (type == "creature") { + includedTypes->add(EntityType::Player); + includedTypes->add(EntityType::Monster); + includedTypes->add(EntityType::Npc); + } else { + includedTypes->add(EntityTypeNames.getLeft(type.ptr())); + } + }); + } + + Maybe<String> callScript = options.get<Maybe<String>>("callScript"); + List<LuaValue> callScriptArgs = options.get<Maybe<List<LuaValue>>>("callScriptArgs").value(); + LuaValue callScriptResult = options.get<Maybe<LuaValue>>("callScriptResult").value(LuaBoolean(true)); + + Maybe<Line2F> lineQuery = options.get<Maybe<Line2F>>("line"); + Maybe<PolyF> polyQuery = options.get<Maybe<PolyF>>("poly"); + Maybe<RectF> rectQuery = options.get<Maybe<RectF>>("rect"); + Maybe<pair<Vec2F, float>> radiusQuery; + if (auto radius = options.get<Maybe<float>>("radius")) + radiusQuery = make_pair(options.get<Vec2F>("center"), *radius); + + EntityBoundMode boundMode = EntityBoundModeNames.getLeft(options.get<Maybe<String>>("boundMode").value("CollisionArea")); + Maybe<LuaString> order = options.get<Maybe<LuaString>>("order"); + + auto geometry = world->geometry(); + + auto innerSelector = [=](shared_ptr<EntityT> const& entity) -> bool { + if (selector && !selector(entity)) + return false; + + if (includedTypes && !includedTypes->contains(entity->entityType())) + return false; + + if (withoutEntityId && entity->entityId() == *withoutEntityId) + return false; + + if (callScript) { + auto scriptedEntity = as<ScriptedEntity>(entity); + if (!scriptedEntity || !scriptedEntity->isMaster()) + return false; + + auto res = scriptedEntity->callScript(*callScript, luaUnpack(callScriptArgs)); + if (!res || *res != callScriptResult) + return false; + } + + auto position = entity->position(); + if (boundMode == EntityBoundMode::MetaBoundBox) { + // If using MetaBoundBox, the regular line / box query methods already + // enforce collision with MetaBoundBox + if (radiusQuery) + return geometry.rectIntersectsCircle( + entity->metaBoundBox().translated(position), radiusQuery->first, radiusQuery->second); + } else if (boundMode == EntityBoundMode::CollisionArea) { + // Collision area queries either query based on the collision area if + // that's given, or as a fallback the regular bound box. + auto collisionArea = entity->collisionArea(); + if (collisionArea.isNull()) + collisionArea = entity->metaBoundBox(); + collisionArea.translate(position); + + if (lineQuery) + return geometry.lineIntersectsRect(*lineQuery, collisionArea); + if (polyQuery) + return geometry.polyIntersectsPoly(*polyQuery, PolyF(collisionArea)); + if (rectQuery) + return geometry.rectIntersectsRect(*rectQuery, collisionArea); + if (radiusQuery) + return geometry.rectIntersectsCircle(collisionArea, radiusQuery->first, radiusQuery->second); + } else if (boundMode == EntityBoundMode::Position) { + if (lineQuery) + return geometry.lineIntersectsRect(*lineQuery, RectF(position, position)); + if (polyQuery) + return geometry.polyContains(*polyQuery, position); + if (rectQuery) + return geometry.rectContains(*rectQuery, position); + if (radiusQuery) + return geometry.diff(radiusQuery->first, position).magnitude() <= radiusQuery->second; + } + + return true; + }; + + List<shared_ptr<EntityT>> entities; + + if (lineQuery) { + entities = world->lineQuery<EntityT>(lineQuery->min(), lineQuery->max(), innerSelector); + } else if (polyQuery) { + entities = world->query<EntityT>(polyQuery->boundBox(), innerSelector); + } else if (rectQuery) { + entities = world->query<EntityT>(*rectQuery, innerSelector); + } else if (radiusQuery) { + RectF region(radiusQuery->first - Vec2F::filled(radiusQuery->second), + radiusQuery->first + Vec2F::filled(radiusQuery->second)); + entities = world->query<EntityT>(region, innerSelector); + } + + if (order) { + if (*order == "nearest") { + Vec2F nearestPosition; + if (lineQuery) + nearestPosition = lineQuery->min(); + else if (polyQuery) + nearestPosition = polyQuery->center(); + else if (rectQuery) + nearestPosition = rectQuery->center(); + else if (radiusQuery) + nearestPosition = radiusQuery->first; + sortByComputedValue(entities, + [world, nearestPosition](shared_ptr<EntityT> const& entity) { + return world->geometry().diff(entity->position(), nearestPosition).magnitude(); + }); + } else if (*order == "random") { + Random::shuffle(entities); + } else { + throw StarException(strf("Unsupported query order %s", *order)); + } + } + + LuaTable entityIds = engine.createTable(); + int entityIdsIndex = 1; + for (auto entity : entities) + entityIds.set(entityIdsIndex++, entity->entityId()); + + return entityIds; + } + + template <typename EntityT> + LuaTable entityQuery(World* world, + LuaEngine& engine, + Vec2F const& pos1, + LuaValue const& pos2, + Maybe<LuaTable> options, + Selector<EntityT> selector = {}) { + if (!options) + options = engine.createTable(); + + if (auto radius = engine.luaMaybeTo<float>(pos2)) { + Vec2F center = pos1; + options->set("center", center); + options->set("radius", *radius); + return entityQueryImpl<EntityT>(world, engine, *options, selector); + } else { + RectF rect(pos1, engine.luaTo<Vec2F>(pos2)); + options->set("rect", rect); + return entityQueryImpl<EntityT>(world, engine, *options, selector); + } + } + + template <typename EntityT> + LuaTable entityLineQuery(World* world, + LuaEngine& engine, + Vec2F const& point1, + Vec2F const& point2, + Maybe<LuaTable> options, + Selector<EntityT> selector = {}) { + Line2F line(point1, point2); + + if (!options) + options = engine.createTable(); + + options->set("line", line); + + return entityQueryImpl<EntityT>(world, engine, *options, selector); + } + + LuaCallbacks makeWorldCallbacks(World* world) { + LuaCallbacks callbacks; + + addWorldDebugCallbacks(callbacks); + addWorldEnvironmentCallbacks(callbacks, world); + addWorldEntityCallbacks(callbacks, world); + + callbacks.registerCallbackWithSignature<float, Vec2F, Maybe<Vec2F>>("magnitude", bind(&WorldCallbacks::magnitude, world, _1, _2)); + callbacks.registerCallbackWithSignature<Vec2F, Vec2F, Vec2F>("distance", bind(WorldCallbacks::distance, world, _1, _2)); + callbacks.registerCallbackWithSignature<bool, PolyF, Vec2F>("polyContains", bind(WorldCallbacks::polyContains, world, _1, _2)); + callbacks.registerCallbackWithSignature<LuaValue, LuaEngine&, LuaValue>("xwrap", bind(WorldCallbacks::xwrap, world, _1, _2)); + callbacks.registerCallbackWithSignature<LuaValue, LuaEngine&, Variant<Vec2F, float>, Variant<Vec2F, float>>("nearestTo", bind(WorldCallbacks::nearestTo, world, _1, _2, _3)); + + callbacks.registerCallbackWithSignature<bool, RectF, Maybe<CollisionSet>>("rectCollision", bind(WorldCallbacks::rectCollision, world, _1, _2)); + callbacks.registerCallbackWithSignature<bool, Vec2F, Maybe<CollisionSet>>("pointTileCollision", bind(WorldCallbacks::pointTileCollision, world, _1, _2)); + callbacks.registerCallbackWithSignature<bool, Vec2F, Vec2F, Maybe<CollisionSet>>("lineTileCollision", bind(WorldCallbacks::lineTileCollision, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<pair<Vec2F, Vec2I>>, Vec2F, Vec2F, Maybe<CollisionSet>>("lineTileCollisionPoint", bind(WorldCallbacks::lineTileCollisionPoint, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<bool, RectF, Maybe<CollisionSet>>("rectTileCollision", bind(WorldCallbacks::rectTileCollision, world, _1, _2)); + callbacks.registerCallbackWithSignature<bool, Vec2F, Maybe<CollisionSet>>("pointCollision", bind(WorldCallbacks::pointCollision, world, _1, _2)); + callbacks.registerCallbackWithSignature<LuaTupleReturn<Maybe<Vec2F>, Maybe<Vec2F>>, Vec2F, Vec2F, Maybe<CollisionSet>>("lineCollision", bind(WorldCallbacks::lineCollision, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<bool, PolyF, Maybe<Vec2F>, Maybe<CollisionSet>>("polyCollision", bind(WorldCallbacks::polyCollision, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<List<Vec2I>, Vec2F, Vec2F, Maybe<CollisionSet>, Maybe<int>>("collisionBlocksAlongLine", bind(WorldCallbacks::collisionBlocksAlongLine, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<List<pair<Vec2I, LiquidLevel>>, Vec2F, Vec2F>("liquidAlongLine", bind(WorldCallbacks::liquidAlongLine, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<Vec2F>, PolyF, Vec2F, float, Maybe<CollisionSet>>("resolvePolyCollision", bind(WorldCallbacks::resolvePolyCollision, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<bool, Vec2I, Maybe<bool>, Maybe<bool>>("tileIsOccupied", bind(WorldCallbacks::tileIsOccupied, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<bool, String, Vec2I, Maybe<int>, Json>("placeObject", bind(WorldCallbacks::placeObject, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<Maybe<EntityId>, Json, Vec2F, Maybe<size_t>, Json, Maybe<Vec2F>, Maybe<float>>("spawnItem", bind(WorldCallbacks::spawnItem, world, _1, _2, _3, _4, _5, _6)); + callbacks.registerCallbackWithSignature<List<EntityId>, Vec2F, String, float, Maybe<uint64_t>>("spawnTreasure", bind(WorldCallbacks::spawnTreasure, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<Maybe<EntityId>, String, Vec2F, Maybe<JsonObject>>("spawnMonster", bind(WorldCallbacks::spawnMonster, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<EntityId>, Vec2F, String, String, float, Maybe<uint64_t>, Json>("spawnNpc", bind(WorldCallbacks::spawnNpc, world, _1, _2, _3, _4, _5, _6)); + callbacks.registerCallbackWithSignature<Maybe<EntityId>, Vec2F, String, Json>("spawnStagehand", bind(WorldCallbacks::spawnStagehand, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<EntityId>, String, Vec2F, Maybe<EntityId>, Maybe<Vec2F>, bool, Json>("spawnProjectile", bind(WorldCallbacks::spawnProjectile, world, _1, _2, _3, _4, _5, _6)); + callbacks.registerCallbackWithSignature<Maybe<EntityId>, String, Vec2F, Json>("spawnVehicle", bind(WorldCallbacks::spawnVehicle, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<float>("threatLevel", bind(&World::threatLevel, world)); + callbacks.registerCallbackWithSignature<double>("time", bind(WorldCallbacks::time, world)); + callbacks.registerCallbackWithSignature<uint64_t>("day", bind(WorldCallbacks::day, world)); + callbacks.registerCallbackWithSignature<double>("timeOfDay", bind(WorldCallbacks::timeOfDay, world)); + callbacks.registerCallbackWithSignature<float>("dayLength", bind(WorldCallbacks::dayLength, world)); + callbacks.registerCallbackWithSignature<Json, String, Json>("getProperty", bind(WorldCallbacks::getProperty, world, _1, _2)); + callbacks.registerCallbackWithSignature<void, String, Json>("setProperty", bind(WorldCallbacks::setProperty, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<LiquidLevel>, Variant<RectF, Vec2I>>("liquidAt", bind(WorldCallbacks::liquidAt, world, _1)); + callbacks.registerCallbackWithSignature<float, Vec2F>("gravity", bind(WorldCallbacks::gravity, world, _1)); + callbacks.registerCallbackWithSignature<bool, Vec2F, LiquidId, float>("spawnLiquid", bind(WorldCallbacks::spawnLiquid, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<LiquidLevel>, Vec2F>("destroyLiquid", bind(WorldCallbacks::destroyLiquid, world, _1)); + callbacks.registerCallbackWithSignature<bool, Vec2F>("isTileProtected", bind(WorldCallbacks::isTileProtected, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<PlatformerAStar::Path>, Vec2F, Vec2F, ActorMovementParameters, PlatformerAStar::Parameters>("findPlatformerPath", bind(WorldCallbacks::findPlatformerPath, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<PlatformerAStar::PathFinder, Vec2F, Vec2F, ActorMovementParameters, PlatformerAStar::Parameters>("platformerPathStart", bind(WorldCallbacks::platformerPathStart, world, _1, _2, _3, _4)); + + callbacks.registerCallback("type", [world](LuaEngine& engine) -> LuaString { + if (auto serverWorld = as<WorldServer>(world)) { + if (auto worldParameters = serverWorld->worldTemplate()->worldParameters()) + return engine.createString(worldParameters->typeName); + } else if (auto clientWorld = as<WorldClient>(world)) { + if (auto worldParameters = clientWorld->currentTemplate()->worldParameters()) + return engine.createString(worldParameters->typeName); + } + return engine.createString("unknown"); + }); + + callbacks.registerCallback("size", [world]() -> Vec2I { + if (auto serverWorld = as<WorldServer>(world)) + return (Vec2I)serverWorld->worldTemplate()->size(); + else if (auto clientWorld = as<WorldClient>(world)) + return (Vec2I)clientWorld->currentTemplate()->size(); + return Vec2I(); + }); + + callbacks.registerCallback("inSurfaceLayer", [world](Vec2I const& position) -> bool { + if (auto serverWorld = as<WorldServer>(world)) + return serverWorld->worldTemplate()->inSurfaceLayer(position); + else if (auto clientWorld = as<WorldClient>(world)) + return clientWorld->currentTemplate()->inSurfaceLayer(position); + return false; + }); + + callbacks.registerCallback("surfaceLevel", [world]() -> float { + if (auto serverWorld = as<WorldServer>(world)) + return serverWorld->worldTemplate()->surfaceLevel(); + else if (auto clientWorld = as<WorldClient>(world)) + return clientWorld->currentTemplate()->surfaceLevel(); + else + return world->geometry().size()[1] / 2.0f; + }); + + callbacks.registerCallback("terrestrial", [world]() -> bool { + if (auto serverWorld = as<WorldServer>(world)) { + if (auto worldParameters = serverWorld->worldTemplate()->worldParameters()) + return worldParameters->type() == WorldParametersType::TerrestrialWorldParameters; + } else if (auto clientWorld = as<WorldClient>(world)) { + if (auto worldParameters = clientWorld->currentTemplate()->worldParameters()) + return worldParameters->type() == WorldParametersType::TerrestrialWorldParameters; + } + return false; + }); + + callbacks.registerCallback("itemDropItem", [world](EntityId const& entityId) -> Json { + if (auto itemDrop = world->get<ItemDrop>(entityId)) + return itemDrop->item()->descriptor().toJson(); + return {}; + }); + + callbacks.registerCallback("biomeBlocksAt", [world](Vec2I position) -> Maybe<List<MaterialId>> { + WorldTemplateConstPtr worldTemplate; + if (auto worldClient = as<WorldClient>(world)) + worldTemplate = worldClient->currentTemplate(); + else if (auto worldServer = as<WorldServer>(world)) + worldTemplate = worldServer->worldTemplate(); + + if (worldTemplate) { + WorldTemplate::BlockInfo block = worldTemplate->blockInfo(position[0], position[1]); + if (auto biome = worldTemplate->biome(block.blockBiomeIndex)) { + List<MaterialId> blocks = {biome->mainBlock}; + blocks.appendAll(biome->subBlocks); + return blocks; + } + } + + return {}; + }); + + + callbacks.registerCallback("dungeonId", [world](Vec2I position) -> DungeonId { + if (auto serverWorld = as<WorldServer>(world)) { + return serverWorld->dungeonId(position); + } else { + return as<WorldClient>(world)->dungeonId(position); + } + }); + + if (auto clientWorld = as<WorldClient>(world)) { + callbacks.registerCallbackWithSignature<RectI>("clientWindow", bind(ClientWorldCallbacks::clientWindow, clientWorld)); + } + + if (auto serverWorld = as<WorldServer>(world)) { + callbacks.registerCallbackWithSignature<bool, EntityId, bool>("breakObject", bind(ServerWorldCallbacks::breakObject, serverWorld, _1, _2)); + callbacks.registerCallbackWithSignature<bool, RectF>("isVisibleToPlayer", bind(ServerWorldCallbacks::isVisibleToPlayer, serverWorld, _1)); + callbacks.registerCallbackWithSignature<bool, RectF>("loadRegion", bind(ServerWorldCallbacks::loadRegion, serverWorld, _1)); + callbacks.registerCallbackWithSignature<bool, RectF>("regionActive", bind(ServerWorldCallbacks::regionActive, serverWorld, _1)); + callbacks.registerCallbackWithSignature<void, DungeonId, bool>("setTileProtection", bind(ServerWorldCallbacks::setTileProtection, serverWorld, _1, _2)); + callbacks.registerCallbackWithSignature<bool, RectI>("isPlayerModified", bind(ServerWorldCallbacks::isPlayerModified, serverWorld, _1)); + callbacks.registerCallbackWithSignature<Maybe<LiquidLevel>, Vec2F>("forceDestroyLiquid", bind(ServerWorldCallbacks::forceDestroyLiquid, serverWorld, _1)); + callbacks.registerCallbackWithSignature<EntityId, String>("loadUniqueEntity", bind(ServerWorldCallbacks::loadUniqueEntity, serverWorld, _1)); + callbacks.registerCallbackWithSignature<void, EntityId, String>("setUniqueId", bind(ServerWorldCallbacks::setUniqueId, serverWorld, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, Maybe<EntityId>>("takeItemDrop", bind(ServerWorldCallbacks::takeItemDrop, world, _1, _2)); + callbacks.registerCallbackWithSignature<void, Vec2F, Maybe<bool>>("setPlayerStart", bind(ServerWorldCallbacks::setPlayerStart, world, _1, _2)); + callbacks.registerCallbackWithSignature<List<EntityId>>("players", bind(ServerWorldCallbacks::players, world)); + callbacks.registerCallbackWithSignature<LuaString, LuaEngine&>("fidelity", bind(ServerWorldCallbacks::fidelity, world, _1)); + + callbacks.registerCallbackWithSignature<double>("skyTime", [serverWorld]() { + return serverWorld->sky()->epochTime(); + }); + callbacks.registerCallbackWithSignature<void, double>("setSkyTime", [serverWorld](double skyTime) { + return serverWorld->sky()->setEpochTime(skyTime); + }); + callbacks.registerCallback("flyingType", [serverWorld]() -> String { return FlyingTypeNames.getRight(serverWorld->sky()->flyingType()); }); + callbacks.registerCallback("warpPhase", [serverWorld]() -> String { return WarpPhaseNames.getRight(serverWorld->sky()->warpPhase()); }); + callbacks.registerCallback("setUniverseFlag", [serverWorld](String flagName) { return serverWorld->universeSettings()->setFlag(flagName); }); + callbacks.registerCallback("universeFlags", [serverWorld]() { return serverWorld->universeSettings()->flags(); }); + callbacks.registerCallback("universeFlagSet", [serverWorld](String const& flagName) { return serverWorld->universeSettings()->flags().contains(flagName); }); + callbacks.registerCallback("placeDungeon", [serverWorld](String dungeonName, Vec2I position, Maybe<DungeonId> dungeonId) -> bool { + return serverWorld->placeDungeon(dungeonName, position, dungeonId); + }); + callbacks.registerCallback("tryPlaceDungeon", [serverWorld](String dungeonName, Vec2I position, Maybe<DungeonId> dungeonId) -> bool { + return serverWorld->placeDungeon(dungeonName, position, dungeonId, false); + }); + + callbacks.registerCallback("addBiomeRegion", [serverWorld](Vec2I position, String biomeName, String subBlockSelector, int width) { + serverWorld->addBiomeRegion(position, biomeName, subBlockSelector, width); + }); + callbacks.registerCallback("expandBiomeRegion", [serverWorld](Vec2I position, int width) { + serverWorld->expandBiomeRegion(position, width); + }); + + callbacks.registerCallback("pregenerateAddBiome", [serverWorld](Vec2I position, int width) { + return serverWorld->pregenerateAddBiome(position, width); + }); + callbacks.registerCallback("pregenerateExpandBiome", [serverWorld](Vec2I position, int width) { + return serverWorld->pregenerateExpandBiome(position, width); + }); + + callbacks.registerCallback("setLayerEnvironmentBiome", [serverWorld](Vec2I position) { + serverWorld->setLayerEnvironmentBiome(position); + }); + + callbacks.registerCallback("setPlanetType", [serverWorld](String planetType, String primaryBiomeName) { + serverWorld->setPlanetType(planetType, primaryBiomeName); + }); + + callbacks.registerCallback("setDungeonGravity", [serverWorld](DungeonId dungeonId, Maybe<float> gravity) { + serverWorld->setDungeonGravity(dungeonId, gravity); + }); + + callbacks.registerCallback("setDungeonBreathable", [serverWorld](DungeonId dungeonId, Maybe<bool> breathable) { + serverWorld->setDungeonBreathable(dungeonId, breathable); + }); + + callbacks.registerCallback("setDungeonId", [serverWorld](RectI tileRegion, DungeonId dungeonId) { + serverWorld->setDungeonId(tileRegion, dungeonId); + }); + + callbacks.registerCallback("enqueuePlacement", [serverWorld](List<Json> distributionConfigs, Maybe<DungeonId> id) { + auto distributions = distributionConfigs.transformed([](Json const& config) { + return BiomeItemDistribution(config, Random::randu64()); + }); + return serverWorld->enqueuePlacement(move(distributions), id); + }); + } + + return callbacks; + } + + void addWorldDebugCallbacks(LuaCallbacks& callbacks) { + callbacks.registerCallback("debugPoint", WorldDebugCallbacks::debugPoint); + callbacks.registerCallback("debugLine", WorldDebugCallbacks::debugLine); + callbacks.registerCallback("debugPoly", WorldDebugCallbacks::debugPoly); + callbacks.registerCallback("debugText", WorldDebugCallbacks::debugText); + } + + void addWorldEntityCallbacks(LuaCallbacks& callbacks, World* world) { + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("entityQuery", bind(WorldEntityCallbacks::entityQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("monsterQuery", bind(WorldEntityCallbacks::monsterQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("npcQuery", bind(WorldEntityCallbacks::npcQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("objectQuery", bind(WorldEntityCallbacks::objectQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("itemDropQuery", bind(WorldEntityCallbacks::itemDropQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("playerQuery", bind(WorldEntityCallbacks::playerQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, LuaValue, Maybe<LuaTable>>("loungeableQuery", bind(WorldEntityCallbacks::loungeableQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, Vec2F, Maybe<LuaTable>>("entityLineQuery", bind(WorldEntityCallbacks::entityLineQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, Vec2F, Maybe<LuaTable>>("objectLineQuery", bind(WorldEntityCallbacks::objectLineQuery, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<LuaTable, LuaEngine&, Vec2F, Vec2F, Maybe<LuaTable>>("npcLineQuery", bind(WorldEntityCallbacks::npcLineQuery, world, _1, _2, _3, _4)); + callbacks.registerCallback("objectAt", + [world](Vec2I const& tilePosition) -> Maybe<int> { + if (auto object = world->findEntityAtTile(tilePosition, [](TileEntityPtr const& entity) { return is<Object>(entity); })) + return object->entityId(); + else + return {}; + }); + callbacks.registerCallbackWithSignature<bool, int>("entityExists", bind(WorldEntityCallbacks::entityExists, world, _1)); + callbacks.registerCallbackWithSignature<bool, int, int>("entityCanDamage", bind(WorldEntityCallbacks::entityCanDamage, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId>("entityDamageTeam", bind(WorldEntityCallbacks::entityDamageTeam, world, _1)); + callbacks.registerCallbackWithSignature<Json, EntityId>("entityAggressive", bind(WorldEntityCallbacks::entityAggressive, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<LuaString>, LuaEngine&, int>("entityType", bind(WorldEntityCallbacks::entityType, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<Vec2F>, int>("entityPosition", bind(WorldEntityCallbacks::entityPosition, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<Vec2F>, int>("entityVelocity", bind(WorldEntityCallbacks::entityVelocity, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<RectF>, int>("entityMetaBoundBox", bind(WorldEntityCallbacks::entityMetaBoundBox, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<uint64_t>, EntityId, String>("entityCurrency", bind(WorldEntityCallbacks::entityCurrency, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<uint64_t>, EntityId, Json, Maybe<bool>>("entityHasCountOfItem", bind(WorldEntityCallbacks::entityHasCountOfItem, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<Vec2F>, EntityId>("entityHealth", bind(WorldEntityCallbacks::entityHealth, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("entitySpecies", bind(WorldEntityCallbacks::entitySpecies, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("entityGender", bind(WorldEntityCallbacks::entityGender, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("entityName", bind(WorldEntityCallbacks::entityName, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId, Maybe<String>>("entityDescription", bind(WorldEntityCallbacks::entityDescription, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<JsonArray>, EntityId, String>("entityPortrait", bind(WorldEntityCallbacks::entityPortrait, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId, String>("entityHandItem", bind(WorldEntityCallbacks::entityHandItem, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, String>("entityHandItemDescriptor", bind(WorldEntityCallbacks::entityHandItemDescriptor, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("entityUniqueId", bind(WorldEntityCallbacks::entityUniqueId, world, _1)); + callbacks.registerCallbackWithSignature<Json, EntityId, String, Maybe<Json>>("getObjectParameter", bind(WorldEntityCallbacks::getObjectParameter, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Json, EntityId, String, Maybe<Json>>("getNpcScriptParameter", bind(WorldEntityCallbacks::getNpcScriptParameter, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<List<Vec2I>, EntityId>("objectSpaces", bind(WorldEntityCallbacks::objectSpaces, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<int>, EntityId>("farmableStage", bind(WorldEntityCallbacks::farmableStage, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<int>, EntityId>("containerSize", bind(WorldEntityCallbacks::containerSize, world, _1)); + callbacks.registerCallbackWithSignature<bool, EntityId>("containerClose", bind(WorldEntityCallbacks::containerClose, world, _1)); + callbacks.registerCallbackWithSignature<bool, EntityId>("containerOpen", bind(WorldEntityCallbacks::containerOpen, world, _1)); + callbacks.registerCallbackWithSignature<Json, EntityId>("containerItems", bind(WorldEntityCallbacks::containerItems, world, _1)); + callbacks.registerCallbackWithSignature<Json, EntityId, size_t>("containerItemAt", bind(WorldEntityCallbacks::containerItemAt, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<bool>, EntityId, Json>("containerConsume", bind(WorldEntityCallbacks::containerConsume, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<bool>, EntityId, size_t, int>("containerConsumeAt", bind(WorldEntityCallbacks::containerConsumeAt, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<size_t>, EntityId, Json>("containerAvailable", bind(WorldEntityCallbacks::containerAvailable, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId>("containerTakeAll", bind(WorldEntityCallbacks::containerTakeAll, world, _1)); + callbacks.registerCallbackWithSignature<Json, EntityId, size_t>("containerTakeAt", bind(WorldEntityCallbacks::containerTakeAt, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, size_t, int>("containerTakeNumItemsAt", bind(WorldEntityCallbacks::containerTakeNumItemsAt, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<size_t>, EntityId, Json>("containerItemsCanFit", bind(WorldEntityCallbacks::containerItemsCanFit, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json>("containerItemsFitWhere", bind(WorldEntityCallbacks::containerItemsFitWhere, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json>("containerAddItems", bind(WorldEntityCallbacks::containerAddItems, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json>("containerStackItems", bind(WorldEntityCallbacks::containerStackItems, world, _1, _2)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json, size_t>("containerPutItemsAt", bind(WorldEntityCallbacks::containerPutItemsAt, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json, size_t>("containerSwapItems", bind(WorldEntityCallbacks::containerSwapItems, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json, size_t>("containerSwapItemsNoCombine", bind(WorldEntityCallbacks::containerSwapItemsNoCombine, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Json, EntityId, Json, size_t>("containerItemApply", bind(WorldEntityCallbacks::containerItemApply, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<Maybe<LuaValue>, EntityId, String, LuaVariadic<LuaValue>>("callScriptedEntity", bind(WorldEntityCallbacks::callScriptedEntity, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<RpcPromise<Vec2F>, String>("findUniqueEntity", bind(WorldEntityCallbacks::findUniqueEntity, world, _1)); + callbacks.registerCallbackWithSignature<RpcPromise<Json>, LuaEngine&, LuaValue, String, LuaVariadic<Json>>("sendEntityMessage", bind(WorldEntityCallbacks::sendEntityMessage, world, _1, _2, _3, _4)); + callbacks.registerCallbackWithSignature<Maybe<bool>, EntityId>("loungeableOccupied", bind(WorldEntityCallbacks::loungeableOccupied, world, _1)); + callbacks.registerCallbackWithSignature<bool, EntityId, Maybe<bool>>("isMonster", bind(WorldEntityCallbacks::isMonster, world, _1, _2)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("monsterType", bind(WorldEntityCallbacks::monsterType, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("npcType", bind(WorldEntityCallbacks::npcType, world, _1)); + callbacks.registerCallbackWithSignature<Maybe<String>, EntityId>("stagehandType", bind(WorldEntityCallbacks::stagehandType, world, _1)); + callbacks.registerCallbackWithSignature<bool, EntityId, Maybe<int>>("isNpc", bind(WorldEntityCallbacks::isNpc, world, _1, _2)); + callbacks.registerCallback("isEntityInteractive", [world](EntityId entityId) -> Maybe<bool> { + if (auto entity = world->get<InteractiveEntity>(entityId)) + return entity->isInteractive(); + return {}; + }); + callbacks.registerCallback("entityMouthPosition", [world](EntityId entityId) -> Maybe<Vec2F> { + if (auto entity = world->get<ChattyEntity>(entityId)) + return entity->mouthPosition(); + return {}; + }); + callbacks.registerCallback("entityTypeName", [world](EntityId entityId) -> Maybe<String> { + auto entity = world->entity(entityId); + if (auto monster = as<Monster>(entity)) + return monster->typeName(); + if (auto npc = as<Npc>(entity)) + return npc->npcType(); + if (auto vehicle = as<Vehicle>(entity)) + return vehicle->name(); + if (auto object = as<Object>(entity)) + return object->name(); + if (auto itemDrop = as<ItemDrop>(entity)) { + if (itemDrop->item()) + return itemDrop->item()->name(); + } + return {}; + }); + } + + void addWorldEnvironmentCallbacks(LuaCallbacks& callbacks, World* world) { + callbacks.registerCallbackWithSignature<float, Vec2F>("lightLevel", bind(WorldEnvironmentCallbacks::lightLevel, world, _1)); + callbacks.registerCallbackWithSignature<float, Vec2F>("windLevel", bind(WorldEnvironmentCallbacks::windLevel, world, _1)); + callbacks.registerCallbackWithSignature<bool, Vec2F>("breathable", bind(WorldEnvironmentCallbacks::breathable, world, _1)); + callbacks.registerCallbackWithSignature<bool, Vec2F>("underground", bind(WorldEnvironmentCallbacks::underground, world, _1)); + callbacks.registerCallbackWithSignature<LuaValue, LuaEngine&, Vec2F, String>("material", bind(WorldEnvironmentCallbacks::material, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<LuaValue, LuaEngine&, Vec2F, String>("mod", bind(WorldEnvironmentCallbacks::mod, world, _1, _2, _3)); + callbacks.registerCallbackWithSignature<float, Vec2F, String>("materialHueShift", bind(WorldEnvironmentCallbacks::materialHueShift, world, _1, _2)); + callbacks.registerCallbackWithSignature<float, Vec2F, String>("modHueShift", bind(WorldEnvironmentCallbacks::modHueShift, world, _1, _2)); + callbacks.registerCallbackWithSignature<MaterialColorVariant, Vec2F, String>("materialColor", bind(WorldEnvironmentCallbacks::materialColor, world, _1, _2)); + callbacks.registerCallbackWithSignature<void, Vec2F, String, MaterialColorVariant>("setMaterialColor", bind(WorldEnvironmentCallbacks::setMaterialColor, world, _1, _2, _3)); + + callbacks.registerCallback("oceanLevel", [world](Vec2I position) -> int { + if (auto serverWorld = as<WorldServer>(world)) { + return serverWorld->worldTemplate()->blockInfo(position[0], position[1]).oceanLiquidLevel; + } else { + auto clientWorld = as<WorldClient>(world); + return clientWorld->currentTemplate()->blockInfo(position[0], position[1]).oceanLiquidLevel; + } + }); + + callbacks.registerCallback("environmentStatusEffects", [world](Vec2F const& position) { + return world->environmentStatusEffects(position); + }); + + callbacks.registerCallbackWithSignature<bool, List<Vec2I>, String, Vec2F, String, float, Maybe<unsigned>, Maybe<EntityId>>("damageTiles", bind(WorldEnvironmentCallbacks::damageTiles, world, _1, _2, _3, _4, _5, _6, _7)); + callbacks.registerCallbackWithSignature<bool, Vec2F, float, String, Vec2F, String, float, Maybe<unsigned>, Maybe<EntityId>>("damageTileArea", bind(WorldEnvironmentCallbacks::damageTileArea, world, _1, _2, _3, _4, _5, _6, _7, _8)); + callbacks.registerCallbackWithSignature<bool, Vec2I, String, String, Maybe<int>, bool>("placeMaterial", bind(WorldEnvironmentCallbacks::placeMaterial, world, _1, _2, _3, _4, _5)); + callbacks.registerCallbackWithSignature<bool, Vec2I, String, String, Maybe<int>, bool>("placeMod", bind(WorldEnvironmentCallbacks::placeMod, world, _1, _2, _3, _4, _5)); + + callbacks.registerCallback("radialTileQuery", [world](Vec2F center, float radius, String layerName) -> List<Vec2I> { + auto layer = TileLayerNames.getLeft(layerName); + return tileAreaBrush(radius, center, false).filtered([&](Vec2I const& t) -> bool { + return world->material(t, layer) != EmptyMaterialId; + }); + }); + } + + float WorldCallbacks::magnitude(World* world, Vec2F pos1, Maybe<Vec2F> pos2) { + if (pos2) + return world->geometry().diff(pos1, *pos2).magnitude(); + else + return pos1.magnitude(); + } + + Vec2F WorldCallbacks::distance(World* world, Vec2F const& arg1, Vec2F const& arg2) { + return world->geometry().diff(arg1, arg2); + } + + bool WorldCallbacks::polyContains(World* world, PolyF const& poly, Vec2F const& pos) { + return world->geometry().polyContains(poly, pos); + } + + LuaValue WorldCallbacks::xwrap(World* world, LuaEngine& engine, LuaValue const& positionOrX) { + if (auto x = engine.luaMaybeTo<float>(positionOrX)) + return LuaFloat(world->geometry().xwrap(*x)); + return engine.luaFrom<Vec2F>(world->geometry().xwrap(engine.luaTo<Vec2F>(positionOrX))); + } + + LuaValue WorldCallbacks::nearestTo(World* world, LuaEngine& engine, Variant<Vec2F, float> const& sourcePositionOrX, Variant<Vec2F, float> const& targetPositionOrX) { + if (targetPositionOrX.is<Vec2F>()) { + Vec2F targetPosition = targetPositionOrX.get<Vec2F>(); + Vec2F sourcePosition; + if (sourcePositionOrX.is<Vec2F>()) + sourcePosition = sourcePositionOrX.get<Vec2F>(); + else + sourcePosition[0] = sourcePositionOrX.get<float>(); + + return engine.luaFrom<Vec2F>(world->geometry().nearestTo(sourcePosition, targetPosition)); + + } else { + float targetX = targetPositionOrX.get<float>(); + float sourceX; + if (sourcePositionOrX.is<Vec2F>()) + sourceX = sourcePositionOrX.get<Vec2F>()[0]; + else + sourceX = sourcePositionOrX.get<float>(); + + return LuaFloat(world->geometry().nearestTo(sourceX, targetX)); + } + } + + bool WorldCallbacks::rectCollision(World* world, RectF const& arg1, Maybe<CollisionSet> const& arg2) { + PolyF body = PolyF(arg1); + + if (arg2) + return world->polyCollision(body, *arg2); + else + return world->polyCollision(body); + } + + bool WorldCallbacks::pointTileCollision(World* world, Vec2F const& arg1, Maybe<CollisionSet> const& arg2) { + if (arg2) + return world->pointTileCollision(arg1, *arg2); + else + return world->pointTileCollision(arg1); + } + + bool WorldCallbacks::lineTileCollision( + World* world, Vec2F const& arg1, Vec2F const& arg2, Maybe<CollisionSet> const& arg3) { + Vec2F const begin = arg1; + Vec2F const end = arg2; + + if (arg3) + return world->lineTileCollision(begin, end, *arg3); + else + return world->lineTileCollision(begin, end); + } + + Maybe<pair<Vec2F, Vec2I>> WorldCallbacks::lineTileCollisionPoint(World* world, Vec2F const& begin, Vec2F const& end, Maybe<CollisionSet> const& collisionSet) { + if (collisionSet) + return world->lineTileCollisionPoint(begin, end, *collisionSet); + else + return world->lineTileCollisionPoint(begin, end); + } + + bool WorldCallbacks::rectTileCollision(World* world, RectF const& arg1, Maybe<CollisionSet> const& arg2) { + RectI const region = RectI::integral(arg1); + + if (arg2) + return world->rectTileCollision(region, *arg2); + else + return world->rectTileCollision(region); + } + + bool WorldCallbacks::pointCollision(World* world, Vec2F const& point, Maybe<CollisionSet> const& collisionSet) { + return world->pointCollision(point, collisionSet.value(DefaultCollisionSet)); + } + + LuaTupleReturn<Maybe<Vec2F>, Maybe<Vec2F>> WorldCallbacks::lineCollision(World* world, Vec2F const& start, Vec2F const& end, Maybe<CollisionSet> const& collisionSet) { + Maybe<Vec2F> point; + Maybe<Vec2F> normal; + auto collision = world->lineCollision(Line2F(start, end), collisionSet.value(DefaultCollisionSet)); + if (collision) { + point = collision->first; + normal = collision->second; + } + return luaTupleReturn(point, normal); + } + + bool WorldCallbacks::polyCollision( + World* world, PolyF const& arg1, Maybe<Vec2F> const& arg2, Maybe<CollisionSet> const& arg3) { + PolyF body = arg1; + + Vec2F center; + if (arg2) { + center = *arg2; + body.translate(center); + } + + if (arg3) + return world->polyCollision(body, *arg3); + else + return world->polyCollision(body); + } + + List<Vec2I> WorldCallbacks::collisionBlocksAlongLine( + World* world, Vec2F const& arg1, Vec2F const& arg2, Maybe<CollisionSet> const& arg3, Maybe<int> const& arg4) { + Vec2F const begin = arg1; + Vec2F const end = arg2; + + CollisionSet collisionSet = arg3.value(DefaultCollisionSet); + int const maxSize = arg4 ? *arg4 : -1; + return world->collidingTilesAlongLine(begin, end, collisionSet, maxSize); + } + + List<pair<Vec2I, LiquidLevel>> WorldCallbacks::liquidAlongLine(World* world, Vec2F const& start, Vec2F const& end) { + List<pair<Vec2I, LiquidLevel>> levels; + forBlocksAlongLine<float>(start, world->geometry().diff(end, start), [&](int x, int y) { + auto liquidLevel = world->liquidLevel(RectF::withSize(Vec2F(x, y), Vec2F(1, 1))); + if (liquidLevel.liquid != EmptyLiquidId) + levels.append(pair<Vec2I, LiquidLevel>(Vec2I(x, y), liquidLevel)); + return true; + }); + return levels; + } + + Maybe<Vec2F> WorldCallbacks::resolvePolyCollision( + World* world, PolyF poly, Vec2F const& position, float maximumCorrection, Maybe<CollisionSet> const& maybeCollisionSet) { + struct CollisionPoly { + PolyF poly; + Vec2F center; + float sortingDistance; + }; + + poly.translate(position); + List<CollisionPoly> collisions; + CollisionSet collisionSet = maybeCollisionSet.value(DefaultCollisionSet); + world->forEachCollisionBlock(RectI::integral(poly.boundBox().padded(maximumCorrection + 1.0f)), + [&](auto const& block) { + if (collisionSet.contains(block.kind)) + collisions.append({block.poly, Vec2F(block.space), 0.0f}); + }); + + auto resolveCollision = [&](Maybe<Vec2F> direction, float maximumDistance, int loops) -> Maybe<Vec2F> { + PolyF body = poly; + Vec2F correction; + for (int i = 0; i < loops; ++i) { + Vec2F bodyCenter = body.center(); + for (auto& cp : collisions) + cp.sortingDistance = vmagSquared(bodyCenter - cp.center); + sort(collisions, [](auto const& a, auto const& b) { return a.sortingDistance < b.sortingDistance; }); + + bool anyIntersects = false; + for (auto const& cp : collisions) { + PolyF::IntersectResult intersection; + if (direction) + intersection = body.directionalSatIntersection(cp.poly, *direction, false); + else + intersection = body.satIntersection(cp.poly); + + if (intersection.intersects) { + anyIntersects = true; + body.translate(intersection.overlap); + correction += intersection.overlap; + if (vmag(correction) > maximumDistance) + return {}; + } + } + + if (!anyIntersects) + return correction; + } + + for (auto const& cp : collisions) { + if (body.intersects(cp.poly)) + return {}; + } + + return correction; + }; + + // First try any-directional SAT separation for two loops + if (auto resolution = resolveCollision({}, maximumCorrection, 2)) + return position + *resolution; + + // Then, try direction-limiting SAT in cardinals, then 45 degs, then in + // between, for 16 total angles in a circle. + for (int i : {4, 8, 12, 0, 2, 6, 10, 14, 1, 3, 7, 5, 15, 13, 9, 11}) { + float angle = i * Constants::pi / 8; + Vec2F dir = Vec2F::withAngle(angle, 1.0f); + if (auto resolution = resolveCollision(dir, maximumCorrection, 1)) + return position + *resolution; + } + + return {}; + } + + bool WorldCallbacks::tileIsOccupied( + World* world, Vec2I const& arg1, Maybe<bool> const& arg2, Maybe<bool> const& arg3) { + Vec2I const tile = arg1; + bool const tileLayerBool = arg2.value(true); + bool const includeEphemeral = arg3.value(false); + + TileLayer const tileLayer = tileLayerBool ? TileLayer::Foreground : TileLayer::Background; + + return world->tileIsOccupied(tile, tileLayer, includeEphemeral); + } + + bool WorldCallbacks::placeObject(World* world, + String const& objectType, + Vec2I const& worldPosition, + Maybe<int> const& objectDirection, + Json const& objectParameters) { + auto objectDatabase = Root::singleton().objectDatabase(); + + try { + Direction direction = Direction::Right; + if (objectDirection && *objectDirection < 0) + direction = Direction::Left; + + Json parameters = objectParameters ? objectParameters : JsonObject(); + + auto placedObject = objectDatabase->createForPlacement(world, objectType, worldPosition, direction, parameters); + if (placedObject) { + world->addEntity(placedObject); + return true; + } + } catch (StarException const& exception) { + Logger::warn("Could not create placable object of kind '%s', exception caught: %s", + objectType, + outputException(exception, false)); + } + + return false; + } + + Maybe<EntityId> WorldCallbacks::spawnItem(World* world, + Json const& itemType, + Vec2F const& worldPosition, + Maybe<size_t> const& inputCount, + Json const& inputParameters, + Maybe<Vec2F> const& initialVelocity, + Maybe<float> const& intangibleTime) { + Vec2F const position = worldPosition; + + try { + ItemDescriptor descriptor; + if (itemType.isType(Json::Type::String)) { + size_t count = inputCount.value(1); + + Json parameters = inputParameters ? inputParameters : JsonObject(); + + descriptor = ItemDescriptor(itemType.toString(), count, parameters); + } else { + descriptor = ItemDescriptor(itemType); + } + + if (auto itemDrop = ItemDrop::createRandomizedDrop(descriptor, position)) { + if (initialVelocity) + itemDrop->setVelocity(*initialVelocity); + if (intangibleTime) + itemDrop->setIntangibleTime(*intangibleTime); + world->addEntity(itemDrop); + return itemDrop->inWorld() ? itemDrop->entityId() : Maybe<EntityId>(); + } + + Logger::warn("Could not spawn item, item empty in WorldCallbacks::spawnItem"); + } catch (StarException const& exception) { + Logger::warn("Could not spawn Item of kind '%s', exception caught: %s", itemType, outputException(exception, false)); + } + + return {}; + } + + List<EntityId> WorldCallbacks::spawnTreasure( + World* world, Vec2F const& position, String const& pool, float level, Maybe<uint64_t> seed) { + List<EntityId> entities; + auto treasureDatabase = Root::singleton().treasureDatabase(); + try { + for (auto const& treasureItem : treasureDatabase->createTreasure(pool, level, seed.value(Random::randu64()))) { + ItemDropPtr entity = ItemDrop::createRandomizedDrop(treasureItem, position); + entities.append(entity->entityId()); + world->addEntity(entity); + } + } catch (StarException const& exception) { + Logger::warn( + "Could not spawn treasure from pool '%s', exception caught: %s", pool, outputException(exception, false)); + } + return entities; + } + + Maybe<EntityId> WorldCallbacks::spawnMonster( + World* world, String const& arg1, Vec2F const& arg2, Maybe<JsonObject> const& arg3) { + Vec2F const spawnPosition = arg2; + auto monsterDatabase = Root::singleton().monsterDatabase(); + + try { + JsonObject parameters; + parameters["aggressive"] = Random::randb(); + if (arg3) + parameters.merge(*arg3, true); + + float level = 1; + if (parameters.contains("level")) + level = parameters.get("level").toFloat(); + auto monster = monsterDatabase->createMonster(monsterDatabase->randomMonster(arg1, parameters), level); + + monster->setPosition(spawnPosition); + world->addEntity(monster); + return monster->inWorld() ? monster->entityId() : Maybe<EntityId>(); + } catch (StarException const& exception) { + Logger::warn( + "Could not spawn Monster of type '%s', exception caught: %s", arg1, outputException(exception, false)); + return {}; + } + } + + Maybe<EntityId> WorldCallbacks::spawnNpc(World* world, + Vec2F const& arg1, + String const& arg2, + String const& arg3, + float arg4, + Maybe<uint64_t> arg5, + Json const& arg6) { + Vec2F const spawnPosition = arg1; + + String typeName = arg3; + float level = arg4; + + uint64_t seed; + if (arg5) + seed = *arg5; + else + seed = Random::randu64(); + + Json overrides = arg6 ? arg6 : JsonObject(); + + auto npcDatabase = Root::singleton().npcDatabase(); + try { + auto npc = npcDatabase->createNpc(npcDatabase->generateNpcVariant(arg2, typeName, level, seed, overrides)); + npc->setPosition(spawnPosition); + world->addEntity(npc); + return npc->inWorld() ? npc->entityId() : Maybe<EntityId>(); + } catch (StarException const& exception) { + Logger::warn("Could not spawn NPC of species '%s' and type '%s', exception caught: %s", + arg2, + typeName, + outputException(exception, false)); + return {}; + } + } + + Maybe<EntityId> WorldCallbacks::spawnStagehand( + World* world, Vec2F const& spawnPosition, String const& typeName, Json const& overrides) { + auto stagehandDatabase = Root::singleton().stagehandDatabase(); + try { + auto stagehand = stagehandDatabase->createStagehand(typeName, overrides); + stagehand->setPosition(spawnPosition); + world->addEntity(stagehand); + return stagehand->inWorld() ? stagehand->entityId() : Maybe<EntityId>(); + } catch (StarException const& exception) { + Logger::warn( + "Could not spawn Stagehand of type '%s', exception caught: %s", typeName, outputException(exception, false)); + return {}; + } + } + + Maybe<EntityId> WorldCallbacks::spawnProjectile(World* world, + String const& projectileType, + Vec2F const& spawnPosition, + Maybe<EntityId> const& sourceEntityId, + Maybe<Vec2F> const& projectileDirection, + bool trackSourceEntity, + Json const& projectileParameters) { + + try { + auto projectileDatabase = Root::singleton().projectileDatabase(); + auto projectile = projectileDatabase->createProjectile(projectileType, projectileParameters ? projectileParameters : JsonObject()); + projectile->setInitialPosition(spawnPosition); + projectile->setInitialDirection(projectileDirection.value()); + projectile->setSourceEntity(sourceEntityId.value(NullEntityId), trackSourceEntity); + world->addEntity(projectile); + return projectile->inWorld() ? projectile->entityId() : Maybe<EntityId>(); + } catch (StarException const& exception) { + Logger::warn( + "Could not spawn Projectile of type '%s', exception caught: %s", projectileType, outputException(exception, false)); + return {}; + } + } + + Maybe<EntityId> WorldCallbacks::spawnVehicle( + World* world, String const& vehicleName, Vec2F const& pos, Json const& extraConfig) { + auto vehicleDatabase = Root::singleton().vehicleDatabase(); + auto vehicle = vehicleDatabase->create(vehicleName, extraConfig); + vehicle->setPosition(pos); + world->addEntity(vehicle); + if (vehicle->inWorld()) + return vehicle->entityId(); + return {}; + } + + double WorldCallbacks::time(World* world) { + return world->epochTime(); + } + + uint64_t WorldCallbacks::day(World* world) { + return world->day(); + } + + double WorldCallbacks::timeOfDay(World* world) { + return world->timeOfDay() / world->dayLength(); + } + + float WorldCallbacks::dayLength(World* world) { + return world->dayLength(); + } + + Json WorldCallbacks::getProperty(World* world, String const& arg1, Json const& arg2) { + return world->getProperty(arg1, arg2); + } + + void WorldCallbacks::setProperty(World* world, String const& arg1, Json const& arg2) { + world->setProperty(arg1, arg2); + } + + Maybe<LiquidLevel> WorldCallbacks::liquidAt(World* world, Variant<RectF, Vec2I> boundBoxOrPoint) { + LiquidLevel liquidLevel = boundBoxOrPoint.call([world](auto const& bbop) { return world->liquidLevel(bbop); }); + if (liquidLevel.liquid != EmptyLiquidId) + return liquidLevel; + return {}; + } + + float WorldCallbacks::gravity(World* world, Vec2F const& arg1) { + return world->gravity(arg1); + } + + bool WorldCallbacks::spawnLiquid(World* world, Vec2F const& position, LiquidId liquid, float quantity) { + return world->modifyTile(Vec2I::floor(position), PlaceLiquid{liquid, quantity}, true); + } + + Maybe<LiquidLevel> WorldCallbacks::destroyLiquid(World* world, Vec2F const& position) { + auto liquidLevel = world->liquidLevel(Vec2I::floor(position)); + if (liquidLevel.liquid != EmptyLiquidId) { + if (world->modifyTile(Vec2I::floor(position), PlaceLiquid{EmptyLiquidId, 0}, true)) + return liquidLevel; + } + return {}; + } + + bool WorldCallbacks::isTileProtected(World* world, Vec2F const& position) { + return world->isTileProtected(Vec2I::floor(position)); + } + + Maybe<PlatformerAStar::Path> WorldCallbacks::findPlatformerPath(World* world, + Vec2F const& start, + Vec2F const& end, + ActorMovementParameters actorMovementParameters, + PlatformerAStar::Parameters searchParameters) { + PlatformerAStar::PathFinder pathFinder(world, start, end, move(actorMovementParameters), move(searchParameters)); + pathFinder.explore({}); + return pathFinder.result(); + } + + PlatformerAStar::PathFinder WorldCallbacks::platformerPathStart(World* world, + Vec2F const& start, + Vec2F const& end, + ActorMovementParameters actorMovementParameters, + PlatformerAStar::Parameters searchParameters) { + return PlatformerAStar::PathFinder(world, start, end, move(actorMovementParameters), move(searchParameters)); + } + + RectI ClientWorldCallbacks::clientWindow(WorldClient* world) { + return world->clientWindow(); + } + + bool ServerWorldCallbacks::breakObject(WorldServer* world, EntityId arg1, bool arg2) { + if (auto entity = world->get<Object>(arg1)) { + bool smash = arg2; + entity->breakObject(smash); + return true; + } + return false; + } + + bool ServerWorldCallbacks::isVisibleToPlayer(WorldServer* world, RectF const& arg1) { + return world->isVisibleToPlayer(arg1); + } + + bool ServerWorldCallbacks::loadRegion(WorldServer* world, RectF const& arg1) { + return world->signalRegion(RectI::integral(arg1)); + } + + bool ServerWorldCallbacks::regionActive(WorldServer* world, RectF const& arg1) { + return world->regionActive(RectI::integral(arg1)); + } + + void ServerWorldCallbacks::setTileProtection(WorldServer* world, DungeonId arg1, bool arg2) { + DungeonId dungeonId = arg1; + bool isProtected = arg2; + world->setTileProtection(dungeonId, isProtected); + } + + bool ServerWorldCallbacks::isPlayerModified(WorldServer* world, RectI const& region) { + return world->isPlayerModified(region); + } + + Maybe<LiquidLevel> ServerWorldCallbacks::forceDestroyLiquid(WorldServer* world, Vec2F const& position) { + auto liquidLevel = world->liquidLevel(Vec2I::floor(position)); + if (liquidLevel.liquid != EmptyLiquidId) { + if (world->forceModifyTile(Vec2I::floor(position), PlaceLiquid{EmptyLiquidId, 0}, true)) + return liquidLevel; + } + return {}; + } + + EntityId ServerWorldCallbacks::loadUniqueEntity(WorldServer* world, String const& uniqueId) { + return world->loadUniqueEntity(uniqueId); + } + + void ServerWorldCallbacks::setUniqueId(WorldServer* world, EntityId entityId, Maybe<String> const& uniqueId) { + auto entity = world->entity(entityId); + if (auto npc = as<Npc>(entity.get())) + npc->setUniqueId(uniqueId); + else if (auto monster = as<Monster>(entity.get())) + monster->setUniqueId(uniqueId); + else if (auto object = as<Object>(entity.get())) + object->setUniqueId(uniqueId); + else if (auto stagehand = as<Stagehand>(entity.get())) + stagehand->setUniqueId(uniqueId); + else if (entity) + throw StarException::format("Cannot set unique id on entity of type %s", EntityTypeNames.getRight(entity->entityType())); + else + throw StarException::format("No such entity with id %s", entityId); + } + + Json ServerWorldCallbacks::takeItemDrop(World* world, EntityId entityId, Maybe<EntityId> const& takenBy) { + auto itemDrop = world->get<ItemDrop>(entityId); + if (itemDrop && itemDrop->canTake() && itemDrop->isMaster()) { + ItemPtr item; + if (takenBy) + item = itemDrop->takeBy(*takenBy); + else + item = itemDrop->take(); + + if (item) + return item->descriptor().toJson(); + } + + return Json(); + } + + void ServerWorldCallbacks::setPlayerStart(World* world, Vec2F const& playerStart, Maybe<bool> respawnInWorld) { + as<WorldServer>(world)->setPlayerStart(playerStart, respawnInWorld.isValid() && respawnInWorld.value()); + } + + List<EntityId> ServerWorldCallbacks::players(World* world) { + return as<WorldServer>(world)->players(); + } + + LuaString ServerWorldCallbacks::fidelity(World* world, LuaEngine& engine) { + return engine.createString(WorldServerFidelityNames.getRight(as<WorldServer>(world)->fidelity())); + } + + void WorldDebugCallbacks::debugPoint(Vec2F const& arg1, Color const& arg2) { + SpatialLogger::logPoint("world", arg1, arg2.toRgba()); + } + + void WorldDebugCallbacks::debugLine(Vec2F const& arg1, Vec2F const& arg2, Color const& arg3) { + SpatialLogger::logLine("world", arg1, arg2, arg3.toRgba()); + } + + void WorldDebugCallbacks::debugPoly(PolyF const& poly, Color const& color) { + SpatialLogger::logPoly("world", poly, color.toRgba()); + } + + void WorldDebugCallbacks::debugText(LuaEngine& engine, LuaVariadic<LuaValue> const& args) { + if (args.size() < 3) + throw StarException(strf("Too few arguments to debugText: %s", args.size())); + + Vec2F const position = engine.luaTo<Vec2F>(args.at(args.size() - 2)); + Vec4B const color = engine.luaTo<Color>(args.at(args.size() - 1)).toRgba(); + + String text = formatLua(engine.luaTo<String>(args.at(0)), slice<List<LuaValue>>(args, 1, args.size() - 2)); + SpatialLogger::logText("world", text, position, color); + } + + LuaTable WorldEntityCallbacks::entityQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + return LuaBindings::entityQuery<Entity>(world, engine, pos1, pos2, move(options)); + } + + LuaTable WorldEntityCallbacks::monsterQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + return LuaBindings::entityQuery<Monster>(world, engine, pos1, pos2, move(options)); + } + + LuaTable WorldEntityCallbacks::npcQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + return LuaBindings::entityQuery<Npc>(world, engine, pos1, pos2, move(options)); + } + + LuaTable WorldEntityCallbacks::objectQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + String objectName; + if (options) + objectName = options->get<Maybe<String>>("name").value(); + + return LuaBindings::entityQuery<Object>(world, + engine, + pos1, + pos2, + move(options), + [&objectName](shared_ptr<Object> const& entity) -> bool { + return objectName.empty() || entity->name() == objectName; + }); + } + + LuaTable WorldEntityCallbacks::itemDropQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + return LuaBindings::entityQuery<ItemDrop>(world, engine, pos1, pos2, move(options)); + } + + LuaTable WorldEntityCallbacks::playerQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + return LuaBindings::entityQuery<Player>(world, engine, pos1, pos2, move(options)); + } + + LuaTable WorldEntityCallbacks::loungeableQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe<LuaTable> options) { + String orientationName; + if (options) + orientationName = options->get<Maybe<String>>("orientation").value(); + + LoungeOrientation orientation = LoungeOrientation::None; + if (orientationName == "sit") + orientation = LoungeOrientation::Sit; + else if (orientationName == "lay") + orientation = LoungeOrientation::Lay; + else if (orientationName == "stand") + orientation = LoungeOrientation::Stand; + else if (orientationName.empty()) + orientation = LoungeOrientation::None; + else + throw StarException(strf("Unsupported loungeableQuery orientation %s", orientationName)); + + auto filter = [orientation](shared_ptr<LoungeableObject> const& entity) -> bool { + auto loungeable = as<LoungeableEntity>(entity); + if (!loungeable || loungeable->anchorCount() == 0) + return false; + + if (orientation == LoungeOrientation::None) + return true; + auto pos = loungeable->loungeAnchor(0); + return pos && pos->orientation == orientation; + }; + + return LuaBindings::entityQuery<LoungeableObject>(world, engine, pos1, pos2, move(options), filter); + } + + LuaTable WorldEntityCallbacks::entityLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe<LuaTable> options) { + return LuaBindings::entityLineQuery<Entity>(world, engine, point1, point2, move(options)); + } + + LuaTable WorldEntityCallbacks::objectLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe<LuaTable> options) { + return LuaBindings::entityLineQuery<Object>(world, engine, point1, point2, move(options)); + } + + LuaTable WorldEntityCallbacks::npcLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe<LuaTable> options) { + return LuaBindings::entityLineQuery<Npc>(world, engine, point1, point2, move(options)); + } + + bool WorldEntityCallbacks::entityExists(World* world, EntityId entityId) { + return (bool)world->entity(entityId); + } + + bool WorldEntityCallbacks::entityCanDamage(World* world, EntityId sourceId, EntityId targetId) { + auto source = world->entity(sourceId); + auto target = world->entity(targetId); + + if (!source || !target || !source->getTeam().canDamage(target->getTeam(), false)) + return false; + + return true; + } + + Json WorldEntityCallbacks::entityDamageTeam(World* world, EntityId entityId) { + if (auto entity = world->entity(entityId)) + return entity->getTeam().toJson(); + return {}; + } + + bool WorldEntityCallbacks::entityAggressive(World* world, EntityId entityId) { + auto entity = world->entity(entityId); + if (auto monster = as<Monster>(entity)) + return monster->aggressive(); + if (auto npc = as<Npc>(entity)) + return npc->aggressive(); + return false; + } + + Maybe<LuaString> WorldEntityCallbacks::entityType(World* world, LuaEngine& engine, EntityId entityId) { + if (auto entity = world->entity(entityId)) + return engine.createString(EntityTypeNames.getRight(entity->entityType())); + return {}; + } + + Maybe<Vec2F> WorldEntityCallbacks::entityPosition(World* world, EntityId entityId) { + if (auto entity = world->entity(entityId)) { + return entity->position(); + } else { + return {}; + } + } + + Maybe<RectF> WorldEntityCallbacks::entityMetaBoundBox(World* world, EntityId entityId) { + if (auto entity = world->entity(entityId)) { + return entity->metaBoundBox(); + } else { + return {}; + } + } + + Maybe<Vec2F> WorldEntityCallbacks::entityVelocity(World* world, EntityId entityId) { + auto entity = world->entity(entityId); + + if (auto monsterEntity = as<Monster>(entity)) + return monsterEntity->velocity(); + else if (auto npcEntity = as<Npc>(entity)) + return npcEntity->velocity(); + else if (auto playerEntity = as<Player>(entity)) + return playerEntity->velocity(); + else if (auto vehicleEntity = as<Vehicle>(entity)) + return vehicleEntity->velocity(); + + return {}; + } + + Maybe<uint64_t> WorldEntityCallbacks::entityCurrency(World* world, EntityId entityId, String const& currencyType) { + if (auto player = world->get<Player>(entityId)) { + return player->currency(currencyType); + } else { + return {}; + } + } + + Maybe<uint64_t> WorldEntityCallbacks::entityHasCountOfItem(World* world, EntityId entityId, Json descriptor, Maybe<bool> exactMatch) { + if (auto player = world->get<Player>(entityId)) { + return player->inventory()->hasCountOfItem(ItemDescriptor(descriptor), exactMatch.value(false)); + } else { + return {}; + } + } + + Maybe<Vec2F> WorldEntityCallbacks::entityHealth(World* world, EntityId entityId) { + if (auto entity = world->get<DamageBarEntity>(entityId)) { + return Vec2F(entity->health(), entity->maxHealth()); + } else { + return {}; + } + } + + Maybe<String> WorldEntityCallbacks::entitySpecies(World* world, EntityId entityId) { + if (auto player = world->get<Player>(entityId)) { + return player->species(); + } else if (auto npc = world->get<Npc>(entityId)) { + return npc->species(); + } else { + return {}; + } + } + + Maybe<String> WorldEntityCallbacks::entityGender(World* world, EntityId entityId) { + if (auto player = world->get<Player>(entityId)) { + return GenderNames.getRight(player->gender()); + } else if (auto npc = world->get<Npc>(entityId)) { + return GenderNames.getRight(npc->gender()); + } else { + return {}; + } + } + + Maybe<String> WorldEntityCallbacks::entityName(World* world, EntityId entityId) { + auto entity = world->entity(entityId); + + if (auto portraitEntity = as<PortraitEntity>(entity)) { + return portraitEntity->name(); + } else if (auto objectEntity = as<Object>(entity)) { + return objectEntity->name(); + } else if (auto itemDropEntity = as<ItemDrop>(entity)) { + if (itemDropEntity->item()) + return itemDropEntity->item()->name(); + } else if (auto vehicleEntity = as<Vehicle>(entity)) { + return vehicleEntity->name(); + } else if (auto stagehandEntity = as<Stagehand>(entity)) { + return stagehandEntity->typeName(); + } else if (auto projectileEntity = as<Projectile>(entity)) { + return projectileEntity->typeName(); + } + + return {}; + } + + Maybe<String> WorldEntityCallbacks::entityDescription(World* world, EntityId entityId, Maybe<String> const& species) { + if (auto entity = world->entity(entityId)) { + if (auto inspectableEntity = as<InspectableEntity>(entity)) { + if (species) + return inspectableEntity->inspectionDescription(*species); + } + + return entity->description(); + } + + return {}; + } + + Maybe<JsonArray> WorldEntityCallbacks::entityPortrait(World* world, EntityId entityId, String const& portraitMode) { + auto entity = world->entity(entityId); + + if (auto portraitEntity = as<PortraitEntity>(entity)) { + PortraitMode mode = PortraitModeNames.getLeft(portraitMode); + auto drawables = portraitEntity->portrait(mode); + return drawables.transformed(mem_fn(&Drawable::toJson)); + } + + return {}; + } + + Maybe<String> WorldEntityCallbacks::entityHandItem(World* world, EntityId entityId, String const& handName) { + ToolHand toolHand; + if (handName == "primary") { + toolHand = ToolHand::Primary; + } else if (handName == "alt") { + toolHand = ToolHand::Alt; + } else { + throw StarException(strf("Unknown tool hand %s", handName)); + } + + if (auto entity = world->get<ToolUserEntity>(entityId)) { + if (auto item = entity->handItem(toolHand)) { + return item->name(); + } + } + + return {}; + } + + Json WorldEntityCallbacks::entityHandItemDescriptor(World* world, EntityId entityId, String const& handName) { + ToolHand toolHand; + if (handName == "primary") { + toolHand = ToolHand::Primary; + } else if (handName == "alt") { + toolHand = ToolHand::Alt; + } else { + throw StarException(strf("Unknown tool hand %s", handName)); + } + + if (auto entity = world->get<ToolUserEntity>(entityId)) { + if (auto item = entity->handItem(toolHand)) { + return item->descriptor().toJson(); + } + } + + return Json(); + } + + Maybe<String> WorldEntityCallbacks::entityUniqueId(World* world, EntityId entityId) { + if (auto entity = world->entity(entityId)) + return entity->uniqueId(); + return {}; + } + + Json WorldEntityCallbacks::getObjectParameter(World* world, EntityId entityId, String const& parameterName, Maybe<Json> const& defaultValue) { + Json val = Json(); + + if (auto objectEntity = as<Object>(world->entity(entityId))) { + val = objectEntity->configValue(parameterName); + if (!val && defaultValue) + val = *defaultValue; + } + + return val; + } + + Json WorldEntityCallbacks::getNpcScriptParameter(World* world, EntityId entityId, String const& parameterName, Maybe<Json> const& defaultValue) { + Json val = Json(); + + if (auto npcEntity = as<Npc>(world->entity(entityId))) { + val = npcEntity->scriptConfigParameter(parameterName); + if (!val && defaultValue) + val = *defaultValue; + } + + return val; + } + + List<Vec2I> WorldEntityCallbacks::objectSpaces(World* world, EntityId entityId) { + if (auto tileEntity = as<TileEntity>(world->entity(entityId))) + return tileEntity->spaces(); + return {}; + } + + Maybe<int> WorldEntityCallbacks::farmableStage(World* world, EntityId entityId) { + if (auto farmable = world->get<FarmableObject>(entityId)) { + return farmable->stage(); + } + + return {}; + } + + Maybe<int> WorldEntityCallbacks::containerSize(World* world, EntityId entityId) { + if (auto container = world->get<ContainerObject>(entityId)) + return container->containerSize(); + + return {}; + } + + bool WorldEntityCallbacks::containerClose(World* world, EntityId entityId) { + if (auto container = world->get<ContainerObject>(entityId)) { + container->containerClose(); + return true; + } + + return false; + } + + bool WorldEntityCallbacks::containerOpen(World* world, EntityId entityId) { + if (auto container = world->get<ContainerObject>(entityId)) { + container->containerOpen(); + return true; + } + + return false; + } + + Json WorldEntityCallbacks::containerItems(World* world, EntityId entityId) { + if (auto container = world->get<ContainerObject>(entityId)) { + JsonArray res; + auto itemDb = Root::singleton().itemDatabase(); + for (auto const& item : container->itemBag()->items()) + res.append(itemDb->toJson(item)); + return res; + } + + return Json(); + } + + Json WorldEntityCallbacks::containerItemAt(World* world, EntityId entityId, size_t offset) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto items = container->itemBag()->items(); + if (offset < items.size()) { + return itemDb->toJson(items.at(offset)); + } + } + + return Json(); + } + + Maybe<bool> WorldEntityCallbacks::containerConsume(World* world, EntityId entityId, Json const& items) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto toConsume = ItemDescriptor(items); + return container->consumeItems(toConsume).result(); + } + + return {}; + } + + Maybe<bool> WorldEntityCallbacks::containerConsumeAt(World* world, EntityId entityId, size_t offset, int count) { + if (auto container = world->get<ContainerObject>(entityId)) { + if (offset < container->containerSize()) { + return container->consumeItems(offset, count).result(); + } + } + + return {}; + } + + Maybe<size_t> WorldEntityCallbacks::containerAvailable(World* world, EntityId entityId, Json const& items) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemBag = container->itemBag(); + auto toCheck = ItemDescriptor(items); + return itemBag->available(toCheck); + } + + return {}; + } + + Json WorldEntityCallbacks::containerTakeAll(World* world, EntityId entityId) { + auto itemDb = Root::singleton().itemDatabase(); + if (auto container = world->get<ContainerObject>(entityId)) { + if (auto itemList = container->clearContainer().result()) { + JsonArray res; + for (auto item : *itemList) + res.append(itemDb->toJson(item)); + return res; + } + } + + return Json(); + } + + Json WorldEntityCallbacks::containerTakeAt(World* world, EntityId entityId, size_t offset) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + if (offset < container->containerSize()) { + if (auto res = container->takeItems(offset).result()) + return itemDb->toJson(*res); + } + } + + return Json(); + } + + Json WorldEntityCallbacks::containerTakeNumItemsAt(World* world, EntityId entityId, size_t offset, int const& count) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + if (offset < container->containerSize()) { + if (auto res = container->takeItems(offset, count).result()) + return itemDb->toJson(*res); + } + } + + return Json(); + } + + Maybe<size_t> WorldEntityCallbacks::containerItemsCanFit(World* world, EntityId entityId, Json const& items) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto itemBag = container->itemBag(); + auto toSearch = itemDb->fromJson(items); + return itemBag->itemsCanFit(toSearch); + } + + return {}; + } + + Json WorldEntityCallbacks::containerItemsFitWhere(World* world, EntityId entityId, Json const& items) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto itemBag = container->itemBag(); + auto toSearch = itemDb->fromJson(items); + auto res = itemBag->itemsFitWhere(toSearch); + return JsonObject{ + {"leftover", res.leftover}, + {"slots", jsonFromList<size_t>(res.slots)} + }; + } + + return Json(); + } + + Json WorldEntityCallbacks::containerAddItems(World* world, EntityId entityId, Json const& items) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto toInsert = itemDb->fromJson(items); + if (auto res = container->addItems(toInsert).result()) + return itemDb->toJson(*res); + } + + return items; + } + + Json WorldEntityCallbacks::containerStackItems(World* world, EntityId entityId, Json const& items) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto toInsert = itemDb->fromJson(items); + if (auto res = container->addItems(toInsert).result()) + return itemDb->toJson(*res); + } + + return items; + } + + Json WorldEntityCallbacks::containerPutItemsAt(World* world, EntityId entityId, Json const& items, size_t offset) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto toInsert = itemDb->fromJson(items); + if (offset < container->containerSize()) { + if (auto res = container->putItems(offset, toInsert).result()) + return itemDb->toJson(*res); + } + } + + return items; + } + + Json WorldEntityCallbacks::containerSwapItems(World* world, EntityId entityId, Json const& items, size_t offset) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto toSwap = itemDb->fromJson(items); + if (offset < container->containerSize()) { + if (auto res = container->swapItems(offset, toSwap, true).result()) + return itemDb->toJson(*res); + } + } + + return items; + } + + Json WorldEntityCallbacks::containerSwapItemsNoCombine(World* world, EntityId entityId, Json const& items, size_t offset) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto toSwap = itemDb->fromJson(items); + if (offset < container->containerSize()) { + if (auto res = container->swapItems(offset, toSwap, false).result()) + return itemDb->toJson(*res); + } + } + + return items; + } + + Json WorldEntityCallbacks::containerItemApply(World* world, EntityId entityId, Json const& items, size_t offset) { + if (auto container = world->get<ContainerObject>(entityId)) { + auto itemDb = Root::singleton().itemDatabase(); + auto toSwap = itemDb->fromJson(items); + if (offset < container->containerSize()) { + if (auto res = container->swapItems(offset, toSwap, false).result()) + return itemDb->toJson(*res); + } + } + + return items; + } + + Maybe<LuaValue> WorldEntityCallbacks::callScriptedEntity(World* world, EntityId entityId, String const& function, LuaVariadic<LuaValue> const& args) { + auto entity = as<ScriptedEntity>(world->entity(entityId)); + if (!entity || !entity->isMaster()) + throw StarException::format("Entity %s does not exist or is not a local master scripted entity", entityId); + return entity->callScript(function, args); + } + + RpcPromise<Vec2F> WorldEntityCallbacks::findUniqueEntity(World* world, String const& uniqueId) { + return world->findUniqueEntity(uniqueId); + } + + RpcPromise<Json> WorldEntityCallbacks::sendEntityMessage(World* world, LuaEngine& engine, LuaValue entityId, String const& message, LuaVariadic<Json> args) { + if (entityId.is<LuaString>()) + return world->sendEntityMessage(engine.luaTo<String>(entityId), message, JsonArray::from(move(args))); + else + return world->sendEntityMessage(engine.luaTo<EntityId>(entityId), message, JsonArray::from(move(args))); + } + + Maybe<bool> WorldEntityCallbacks::loungeableOccupied(World* world, EntityId entityId) { + auto entity = world->get<LoungeableEntity>(entityId); + if (entity && entity->anchorCount() > 0) + return !entity->entitiesLoungingIn(0).empty(); + return {}; + } + + bool WorldEntityCallbacks::isMonster(World* world, EntityId entityId, Maybe<bool> const& aggressive) { + if (auto entity = world->get<Monster>(entityId)) + return !aggressive || *aggressive == entity->aggressive(); + + return false; + } + + Maybe<String> WorldEntityCallbacks::monsterType(World* world, EntityId entityId) { + if (auto monster = world->get<Monster>(entityId)) + return monster->typeName(); + + return {}; + } + + Maybe<String> WorldEntityCallbacks::npcType(World* world, EntityId entityId) { + if (auto npc = world->get<Npc>(entityId)) + return npc->npcType(); + + return {}; + } + + Maybe<String> WorldEntityCallbacks::stagehandType(World* world, EntityId entityId) { + if (auto stagehand = world->get<Stagehand>(entityId)) + return stagehand->typeName(); + + return {}; + } + + bool WorldEntityCallbacks::isNpc(World* world, EntityId entityId, Maybe<int> const& damageTeam) { + if (auto entity = world->get<Npc>(entityId)) { + return !damageTeam || *damageTeam == entity->getTeam().team; + } + + return false; + } + + float WorldEnvironmentCallbacks::lightLevel(World* world, Vec2F const& position) { + return world->lightLevel(position); + } + + float WorldEnvironmentCallbacks::windLevel(World* world, Vec2F const& position) { + return world->windLevel(position); + } + + bool WorldEnvironmentCallbacks::breathable(World* world, Vec2F const& position) { + return world->breathable(position); + } + + bool WorldEnvironmentCallbacks::underground(World* world, Vec2F const& position) { + return world->isUnderground(position); + } + + LuaValue WorldEnvironmentCallbacks::material(World* world, LuaEngine& engine, Vec2F const& position, String const& layerName) { + TileLayer layer; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported material layer %s", layerName)); + } + + auto materialId = world->material(Vec2I::floor(position), layer); + if (materialId == NullMaterialId) { + return LuaNil; + } else if (materialId == EmptyMaterialId) { + return false; + } else { + auto materialDatabase = Root::singleton().materialDatabase(); + return engine.createString(materialDatabase->materialName(materialId)); + } + } + + LuaValue WorldEnvironmentCallbacks::mod(World* world, LuaEngine& engine, Vec2F const& position, String const& layerName) { + TileLayer layer; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported mod layer %s", layerName)); + } + + auto modId = world->mod(Vec2I::floor(position), layer); + if (isRealMod(modId)) { + auto materialDatabase = Root::singleton().materialDatabase(); + return engine.createString(materialDatabase->modName(modId)); + } + + return LuaNil; + } + + float WorldEnvironmentCallbacks::materialHueShift(World* world, Vec2F const& position, String const& layerName) { + TileLayer layer; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported material layer %s", layerName)); + } + + return world->materialHueShift(Vec2I::floor(position), layer); + } + + float WorldEnvironmentCallbacks::modHueShift(World* world, Vec2F const& position, String const& layerName) { + TileLayer layer; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported material layer %s", layerName)); + } + + return world->modHueShift(Vec2I::floor(position), layer); + } + + MaterialColorVariant WorldEnvironmentCallbacks::materialColor(World* world, Vec2F const& position, String const& layerName) { + TileLayer layer; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported material layer %s", layerName)); + } + + return world->colorVariant(Vec2I::floor(position), layer); + } + + void WorldEnvironmentCallbacks::setMaterialColor(World* world, Vec2F const& position, String const& layerName, MaterialColorVariant color) { + TileLayer layer; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported material layer %s", layerName)); + } + + world->modifyTile(Vec2I::floor(position), PlaceMaterialColor{layer, color}, true); + } + + bool WorldEnvironmentCallbacks::damageTiles(World* world, + List<Vec2I> const& arg1, + String const& arg2, + Vec2F const& arg3, + String const& arg4, + float arg5, + Maybe<unsigned> const& arg6, + Maybe<EntityId> sourceEntity) { + List<Vec2I> tilePositions = arg1; + + TileLayer layer; + auto layerName = arg2; + if (layerName == "foreground") { + layer = TileLayer::Foreground; + } else if (layerName == "background") { + layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported damageTile layer %s", layerName)); + } + + unsigned harvestLevel = 999; + if (arg6) + harvestLevel = *arg6; + + auto tileDamage = TileDamage(TileDamageTypeNames.getLeft(arg4), arg5, harvestLevel); + auto res = world->damageTiles(tilePositions, layer, arg3, tileDamage, sourceEntity); + return res != TileDamageResult::None; + } + + bool WorldEnvironmentCallbacks::damageTileArea(World* world, + Vec2F center, + float radius, + String layer, + Vec2F sourcePosition, + String damageType, + float damage, + Maybe<unsigned> const& harvestLevel, + Maybe<EntityId> sourceEntity) { + auto tiles = tileAreaBrush(radius, center, false); + return damageTiles(world, tiles, layer, sourcePosition, damageType, damage, harvestLevel, sourceEntity); + } + + bool WorldEnvironmentCallbacks::placeMaterial(World* world, Vec2I const& arg1, String const& arg2, String const& arg3, Maybe<int> const& arg4, bool arg5) { + auto tilePosition = arg1; + + PlaceMaterial placeMaterial; + + auto layerName = arg2; + if (layerName == "foreground") { + placeMaterial.layer = TileLayer::Foreground; + } else if (layerName == "background") { + placeMaterial.layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported damageTile layer %s", layerName)); + } + + auto materialName = arg3; + auto materialDatabase = Root::singleton().materialDatabase(); + if (!materialDatabase->materialNames().contains(materialName)) + throw StarException(strf("Unknown material name %s", materialName)); + placeMaterial.material = materialDatabase->materialId(materialName); + + if (arg4) + placeMaterial.materialHueShift = (MaterialHue)*arg4; + + bool allowOverlap = arg5; + + return world->modifyTile(tilePosition, placeMaterial, allowOverlap); + } + + bool WorldEnvironmentCallbacks::placeMod(World* world, Vec2I const& arg1, String const& arg2, String const& arg3, Maybe<int> const& arg4, bool arg5) { + auto tilePosition = arg1; + + PlaceMod placeMod; + + auto layerName = arg2; + if (layerName == "foreground") { + placeMod.layer = TileLayer::Foreground; + } else if (layerName == "background") { + placeMod.layer = TileLayer::Background; + } else { + throw StarException(strf("Unsupported damageTile layer %s", layerName)); + } + + auto modName = arg3; + auto materialDatabase = Root::singleton().materialDatabase(); + if (!materialDatabase->modNames().contains(modName)) + throw StarException(strf("Unknown mod name %s", modName)); + placeMod.mod = materialDatabase->modId(modName); + + if (arg4) + placeMod.modHueShift = (MaterialHue)*arg4; + + bool allowOverlap = arg5; + + return world->modifyTile(tilePosition, placeMod, allowOverlap); + } +} + +} |