#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 const EntityBoundModeNames = { {EntityBoundMode::MetaBoundBox, "MetaBoundBox"}, {EntityBoundMode::CollisionArea, "CollisionArea"}, {EntityBoundMode::Position, "Position"} }; template using Selector = function const&)>; template LuaTable entityQueryImpl(World* world, LuaEngine& engine, LuaTable const& options, Selector selector) { Maybe withoutEntityId = options.get>("withoutEntityId"); Maybe> includedTypes; if (auto types = options.get>("includedTypes")) { includedTypes = Set(); 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 callScript = options.get>("callScript"); List callScriptArgs = options.get>>("callScriptArgs").value(); LuaValue callScriptResult = options.get>("callScriptResult").value(LuaBoolean(true)); Maybe lineQuery = options.get>("line"); Maybe polyQuery = options.get>("poly"); Maybe rectQuery = options.get>("rect"); Maybe> radiusQuery; if (auto radius = options.get>("radius")) radiusQuery = make_pair(options.get("center"), *radius); EntityBoundMode boundMode = EntityBoundModeNames.getLeft(options.get>("boundMode").value("CollisionArea")); Maybe order = options.get>("order"); auto geometry = world->geometry(); auto innerSelector = [=](shared_ptr 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(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> entities; if (lineQuery) { entities = world->lineQuery(lineQuery->min(), lineQuery->max(), innerSelector); } else if (polyQuery) { entities = world->query(polyQuery->boundBox(), innerSelector); } else if (rectQuery) { entities = world->query(*rectQuery, innerSelector); } else if (radiusQuery) { RectF region(radiusQuery->first - Vec2F::filled(radiusQuery->second), radiusQuery->first + Vec2F::filled(radiusQuery->second)); entities = world->query(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 const& entity) { return world->geometry().diff(entity->position(), nearestPosition).magnitude(); }); } else if (*order == "random") { Random::shuffle(entities); } else { throw StarException(strf("Unsupported query order {}", order->ptr())); } } LuaTable entityIds = engine.createTable(); int entityIdsIndex = 1; for (auto entity : entities) entityIds.set(entityIdsIndex++, entity->entityId()); return entityIds; } template LuaTable entityQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options, Selector selector = {}) { if (!options) options = engine.createTable(); if (auto radius = engine.luaMaybeTo(pos2)) { Vec2F center = pos1; options->set("center", center); options->set("radius", *radius); return entityQueryImpl(world, engine, *options, selector); } else { RectF rect(pos1, engine.luaTo(pos2)); options->set("rect", rect); return entityQueryImpl(world, engine, *options, selector); } } template LuaTable entityLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe options, Selector selector = {}) { Line2F line(point1, point2); if (!options) options = engine.createTable(); options->set("line", line); return entityQueryImpl(world, engine, *options, selector); } LuaCallbacks makeWorldCallbacks(World* world) { LuaCallbacks callbacks; addWorldDebugCallbacks(callbacks); addWorldEnvironmentCallbacks(callbacks, world); addWorldEntityCallbacks(callbacks, world); callbacks.registerCallbackWithSignature>("magnitude", bind(&WorldCallbacks::magnitude, world, _1, _2)); callbacks.registerCallbackWithSignature("distance", bind(WorldCallbacks::distance, world, _1, _2)); callbacks.registerCallbackWithSignature("polyContains", bind(WorldCallbacks::polyContains, world, _1, _2)); callbacks.registerCallbackWithSignature("xwrap", bind(WorldCallbacks::xwrap, world, _1, _2)); callbacks.registerCallbackWithSignature, Variant>("nearestTo", bind(WorldCallbacks::nearestTo, world, _1, _2, _3)); callbacks.registerCallbackWithSignature>("rectCollision", bind(WorldCallbacks::rectCollision, world, _1, _2)); callbacks.registerCallbackWithSignature>("pointTileCollision", bind(WorldCallbacks::pointTileCollision, world, _1, _2)); callbacks.registerCallbackWithSignature>("lineTileCollision", bind(WorldCallbacks::lineTileCollision, world, _1, _2, _3)); callbacks.registerCallbackWithSignature>, Vec2F, Vec2F, Maybe>("lineTileCollisionPoint", bind(WorldCallbacks::lineTileCollisionPoint, world, _1, _2, _3)); callbacks.registerCallbackWithSignature>("rectTileCollision", bind(WorldCallbacks::rectTileCollision, world, _1, _2)); callbacks.registerCallbackWithSignature>("pointCollision", bind(WorldCallbacks::pointCollision, world, _1, _2)); callbacks.registerCallbackWithSignature, Maybe>, Vec2F, Vec2F, Maybe>("lineCollision", bind(WorldCallbacks::lineCollision, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, Maybe>("polyCollision", bind(WorldCallbacks::polyCollision, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, Vec2F, Vec2F, Maybe, Maybe>("collisionBlocksAlongLine", bind(WorldCallbacks::collisionBlocksAlongLine, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>, Vec2F, Vec2F>("liquidAlongLine", bind(WorldCallbacks::liquidAlongLine, world, _1, _2)); callbacks.registerCallbackWithSignature, PolyF, Vec2F, float, Maybe>("resolvePolyCollision", bind(WorldCallbacks::resolvePolyCollision, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature, Maybe>("tileIsOccupied", bind(WorldCallbacks::tileIsOccupied, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, Json>("placeObject", bind(WorldCallbacks::placeObject, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature, Json, Vec2F, Maybe, Json, Maybe, Maybe>("spawnItem", bind(WorldCallbacks::spawnItem, world, _1, _2, _3, _4, _5, _6)); callbacks.registerCallbackWithSignature, Vec2F, String, float, Maybe>("spawnTreasure", bind(WorldCallbacks::spawnTreasure, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature, String, Vec2F, Maybe>("spawnMonster", bind(WorldCallbacks::spawnMonster, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, Vec2F, String, String, float, Maybe, Json>("spawnNpc", bind(WorldCallbacks::spawnNpc, world, _1, _2, _3, _4, _5, _6)); callbacks.registerCallbackWithSignature, Vec2F, String, Json>("spawnStagehand", bind(WorldCallbacks::spawnStagehand, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, String, Vec2F, Maybe, Maybe, bool, Json>("spawnProjectile", bind(WorldCallbacks::spawnProjectile, world, _1, _2, _3, _4, _5, _6)); callbacks.registerCallbackWithSignature, String, Vec2F, Json>("spawnVehicle", bind(WorldCallbacks::spawnVehicle, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("threatLevel", bind(&World::threatLevel, world)); callbacks.registerCallbackWithSignature("time", bind(WorldCallbacks::time, world)); callbacks.registerCallbackWithSignature("day", bind(WorldCallbacks::day, world)); callbacks.registerCallbackWithSignature("timeOfDay", bind(WorldCallbacks::timeOfDay, world)); callbacks.registerCallbackWithSignature("dayLength", bind(WorldCallbacks::dayLength, world)); callbacks.registerCallbackWithSignature("getProperty", bind(WorldCallbacks::getProperty, world, _1, _2)); callbacks.registerCallbackWithSignature("setProperty", bind(WorldCallbacks::setProperty, world, _1, _2)); callbacks.registerCallbackWithSignature, Variant>("liquidAt", bind(WorldCallbacks::liquidAt, world, _1)); callbacks.registerCallbackWithSignature("gravity", bind(WorldCallbacks::gravity, world, _1)); callbacks.registerCallbackWithSignature("spawnLiquid", bind(WorldCallbacks::spawnLiquid, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, Vec2F>("destroyLiquid", bind(WorldCallbacks::destroyLiquid, world, _1)); callbacks.registerCallbackWithSignature("isTileProtected", bind(WorldCallbacks::isTileProtected, world, _1)); callbacks.registerCallbackWithSignature, Vec2F, Vec2F, ActorMovementParameters, PlatformerAStar::Parameters>("findPlatformerPath", bind(WorldCallbacks::findPlatformerPath, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature("platformerPathStart", bind(WorldCallbacks::platformerPathStart, world, _1, _2, _3, _4)); callbacks.registerCallback("type", [world](LuaEngine& engine) -> LuaString { if (auto serverWorld = as(world)) { if (auto worldParameters = serverWorld->worldTemplate()->worldParameters()) return engine.createString(worldParameters->typeName); } else if (auto clientWorld = as(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(world)) return (Vec2I)serverWorld->worldTemplate()->size(); else if (auto clientWorld = as(world)) return (Vec2I)clientWorld->currentTemplate()->size(); return Vec2I(); }); callbacks.registerCallback("inSurfaceLayer", [world](Vec2I const& position) -> bool { if (auto serverWorld = as(world)) return serverWorld->worldTemplate()->inSurfaceLayer(position); else if (auto clientWorld = as(world)) return clientWorld->currentTemplate()->inSurfaceLayer(position); return false; }); callbacks.registerCallback("surfaceLevel", [world]() -> float { if (auto serverWorld = as(world)) return serverWorld->worldTemplate()->surfaceLevel(); else if (auto clientWorld = as(world)) return clientWorld->currentTemplate()->surfaceLevel(); else return world->geometry().size()[1] / 2.0f; }); callbacks.registerCallback("terrestrial", [world]() -> bool { if (auto serverWorld = as(world)) { if (auto worldParameters = serverWorld->worldTemplate()->worldParameters()) return worldParameters->type() == WorldParametersType::TerrestrialWorldParameters; } else if (auto clientWorld = as(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(entityId)) return itemDrop->item()->descriptor().toJson(); return {}; }); callbacks.registerCallback("biomeBlocksAt", [world](Vec2I position) -> Maybe> { WorldTemplateConstPtr worldTemplate; if (auto worldClient = as(world)) worldTemplate = worldClient->currentTemplate(); else if (auto worldServer = as(world)) worldTemplate = worldServer->worldTemplate(); if (worldTemplate) { WorldTemplate::BlockInfo block = worldTemplate->blockInfo(position[0], position[1]); if (auto biome = worldTemplate->biome(block.blockBiomeIndex)) { List blocks = {biome->mainBlock}; blocks.appendAll(biome->subBlocks); return blocks; } } return {}; }); callbacks.registerCallback("dungeonId", [world](Vec2I position) -> DungeonId { if (auto serverWorld = as(world)) { return serverWorld->dungeonId(position); } else { return as(world)->dungeonId(position); } }); if (auto clientWorld = as(world)) { callbacks.registerCallbackWithSignature("clientWindow", bind(ClientWorldCallbacks::clientWindow, clientWorld)); callbacks.registerCallback("players", [clientWorld]() { List playerIds; clientWorld->forAllEntities([&](EntityPtr const& entity) { if (entity->entityType() == EntityType::Player) playerIds.emplace_back(entity->entityId()); }); return playerIds; }); } if (auto serverWorld = as(world)) { callbacks.registerCallbackWithSignature("breakObject", bind(ServerWorldCallbacks::breakObject, serverWorld, _1, _2)); callbacks.registerCallbackWithSignature("isVisibleToPlayer", bind(ServerWorldCallbacks::isVisibleToPlayer, serverWorld, _1)); callbacks.registerCallbackWithSignature("loadRegion", bind(ServerWorldCallbacks::loadRegion, serverWorld, _1)); callbacks.registerCallbackWithSignature("regionActive", bind(ServerWorldCallbacks::regionActive, serverWorld, _1)); callbacks.registerCallbackWithSignature("setTileProtection", bind(ServerWorldCallbacks::setTileProtection, serverWorld, _1, _2)); callbacks.registerCallbackWithSignature("isPlayerModified", bind(ServerWorldCallbacks::isPlayerModified, serverWorld, _1)); callbacks.registerCallbackWithSignature, Vec2F>("forceDestroyLiquid", bind(ServerWorldCallbacks::forceDestroyLiquid, serverWorld, _1)); callbacks.registerCallbackWithSignature("loadUniqueEntity", bind(ServerWorldCallbacks::loadUniqueEntity, serverWorld, _1)); callbacks.registerCallbackWithSignature("setUniqueId", bind(ServerWorldCallbacks::setUniqueId, serverWorld, _1, _2)); callbacks.registerCallbackWithSignature>("takeItemDrop", bind(ServerWorldCallbacks::takeItemDrop, world, _1, _2)); callbacks.registerCallbackWithSignature>("setPlayerStart", bind(ServerWorldCallbacks::setPlayerStart, world, _1, _2)); callbacks.registerCallbackWithSignature>("players", bind(ServerWorldCallbacks::players, world)); callbacks.registerCallbackWithSignature("fidelity", bind(ServerWorldCallbacks::fidelity, world, _1)); callbacks.registerCallbackWithSignature, String, String, LuaVariadic>("callScriptContext", bind(ServerWorldCallbacks::callScriptContext, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("skyTime", [serverWorld]() { return serverWorld->sky()->epochTime(); }); callbacks.registerCallbackWithSignature("setSkyTime", [serverWorld](double skyTime) { return serverWorld->sky()->setEpochTime(skyTime); }); callbacks.registerCallback("setExpiryTime", [serverWorld](float expiryTime) { serverWorld->setExpiryTime(expiryTime); }); 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) -> bool { return serverWorld->placeDungeon(dungeonName, position, dungeonId); }); callbacks.registerCallback("tryPlaceDungeon", [serverWorld](String dungeonName, Vec2I position, Maybe 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 gravity) { serverWorld->setDungeonGravity(dungeonId, gravity); }); callbacks.registerCallback("setDungeonBreathable", [serverWorld](DungeonId dungeonId, Maybe breathable) { serverWorld->setDungeonBreathable(dungeonId, breathable); }); callbacks.registerCallback("setDungeonId", [serverWorld](RectI tileRegion, DungeonId dungeonId) { serverWorld->setDungeonId(tileRegion, dungeonId); }); callbacks.registerCallback("enqueuePlacement", [serverWorld](List distributionConfigs, Maybe 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>("entityQuery", bind(WorldEntityCallbacks::entityQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("monsterQuery", bind(WorldEntityCallbacks::monsterQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("npcQuery", bind(WorldEntityCallbacks::npcQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("objectQuery", bind(WorldEntityCallbacks::objectQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("itemDropQuery", bind(WorldEntityCallbacks::itemDropQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("playerQuery", bind(WorldEntityCallbacks::playerQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("loungeableQuery", bind(WorldEntityCallbacks::loungeableQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("entityLineQuery", bind(WorldEntityCallbacks::entityLineQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("objectLineQuery", bind(WorldEntityCallbacks::objectLineQuery, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature>("npcLineQuery", bind(WorldEntityCallbacks::npcLineQuery, world, _1, _2, _3, _4)); callbacks.registerCallback("objectAt", [world](Vec2I const& tilePosition) -> Maybe { if (auto object = world->findEntityAtTile(tilePosition, [](TileEntityPtr const& entity) { return is(entity); })) return object->entityId(); else return {}; }); callbacks.registerCallbackWithSignature("entityExists", bind(WorldEntityCallbacks::entityExists, world, _1)); callbacks.registerCallbackWithSignature("entityCanDamage", bind(WorldEntityCallbacks::entityCanDamage, world, _1, _2)); callbacks.registerCallbackWithSignature("entityDamageTeam", bind(WorldEntityCallbacks::entityDamageTeam, world, _1)); callbacks.registerCallbackWithSignature("entityAggressive", bind(WorldEntityCallbacks::entityAggressive, world, _1)); callbacks.registerCallbackWithSignature, LuaEngine&, int>("entityType", bind(WorldEntityCallbacks::entityType, world, _1, _2)); callbacks.registerCallbackWithSignature, int>("entityPosition", bind(WorldEntityCallbacks::entityPosition, world, _1)); callbacks.registerCallbackWithSignature, int>("entityVelocity", bind(WorldEntityCallbacks::entityVelocity, world, _1)); callbacks.registerCallbackWithSignature, int>("entityMetaBoundBox", bind(WorldEntityCallbacks::entityMetaBoundBox, world, _1)); callbacks.registerCallbackWithSignature, EntityId, String>("entityCurrency", bind(WorldEntityCallbacks::entityCurrency, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId, Json, Maybe>("entityHasCountOfItem", bind(WorldEntityCallbacks::entityHasCountOfItem, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, EntityId>("entityHealth", bind(WorldEntityCallbacks::entityHealth, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("entitySpecies", bind(WorldEntityCallbacks::entitySpecies, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("entityGender", bind(WorldEntityCallbacks::entityGender, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("entityName", bind(WorldEntityCallbacks::entityName, world, _1)); callbacks.registerCallbackWithSignature, EntityId, Maybe>("entityDescription", bind(WorldEntityCallbacks::entityDescription, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId, String>("entityPortrait", bind(WorldEntityCallbacks::entityPortrait, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId, String>("entityHandItem", bind(WorldEntityCallbacks::entityHandItem, world, _1, _2)); callbacks.registerCallbackWithSignature("entityHandItemDescriptor", bind(WorldEntityCallbacks::entityHandItemDescriptor, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId>("entityUniqueId", bind(WorldEntityCallbacks::entityUniqueId, world, _1)); callbacks.registerCallbackWithSignature>("getObjectParameter", bind(WorldEntityCallbacks::getObjectParameter, world, _1, _2, _3)); callbacks.registerCallbackWithSignature>("getNpcScriptParameter", bind(WorldEntityCallbacks::getNpcScriptParameter, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, EntityId>("objectSpaces", bind(WorldEntityCallbacks::objectSpaces, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("farmableStage", bind(WorldEntityCallbacks::farmableStage, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("containerSize", bind(WorldEntityCallbacks::containerSize, world, _1)); callbacks.registerCallbackWithSignature("containerClose", bind(WorldEntityCallbacks::containerClose, world, _1)); callbacks.registerCallbackWithSignature("containerOpen", bind(WorldEntityCallbacks::containerOpen, world, _1)); callbacks.registerCallbackWithSignature("containerItems", bind(WorldEntityCallbacks::containerItems, world, _1)); callbacks.registerCallbackWithSignature("containerItemAt", bind(WorldEntityCallbacks::containerItemAt, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId, Json>("containerConsume", bind(WorldEntityCallbacks::containerConsume, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId, size_t, int>("containerConsumeAt", bind(WorldEntityCallbacks::containerConsumeAt, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, EntityId, Json>("containerAvailable", bind(WorldEntityCallbacks::containerAvailable, world, _1, _2)); callbacks.registerCallbackWithSignature("containerTakeAll", bind(WorldEntityCallbacks::containerTakeAll, world, _1)); callbacks.registerCallbackWithSignature("containerTakeAt", bind(WorldEntityCallbacks::containerTakeAt, world, _1, _2)); callbacks.registerCallbackWithSignature("containerTakeNumItemsAt", bind(WorldEntityCallbacks::containerTakeNumItemsAt, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, EntityId, Json>("containerItemsCanFit", bind(WorldEntityCallbacks::containerItemsCanFit, world, _1, _2)); callbacks.registerCallbackWithSignature("containerItemsFitWhere", bind(WorldEntityCallbacks::containerItemsFitWhere, world, _1, _2)); callbacks.registerCallbackWithSignature("containerAddItems", bind(WorldEntityCallbacks::containerAddItems, world, _1, _2)); callbacks.registerCallbackWithSignature("containerStackItems", bind(WorldEntityCallbacks::containerStackItems, world, _1, _2)); callbacks.registerCallbackWithSignature("containerPutItemsAt", bind(WorldEntityCallbacks::containerPutItemsAt, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("containerSwapItems", bind(WorldEntityCallbacks::containerSwapItems, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("containerSwapItemsNoCombine", bind(WorldEntityCallbacks::containerSwapItemsNoCombine, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("containerItemApply", bind(WorldEntityCallbacks::containerItemApply, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, EntityId, String, LuaVariadic>("callScriptedEntity", bind(WorldEntityCallbacks::callScriptedEntity, world, _1, _2, _3)); callbacks.registerCallbackWithSignature, String>("findUniqueEntity", bind(WorldEntityCallbacks::findUniqueEntity, world, _1)); callbacks.registerCallbackWithSignature, LuaEngine&, LuaValue, String, LuaVariadic>("sendEntityMessage", bind(WorldEntityCallbacks::sendEntityMessage, world, _1, _2, _3, _4)); callbacks.registerCallbackWithSignature, EntityId>("loungeableOccupied", bind(WorldEntityCallbacks::loungeableOccupied, world, _1)); callbacks.registerCallbackWithSignature>("isMonster", bind(WorldEntityCallbacks::isMonster, world, _1, _2)); callbacks.registerCallbackWithSignature, EntityId>("monsterType", bind(WorldEntityCallbacks::monsterType, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("npcType", bind(WorldEntityCallbacks::npcType, world, _1)); callbacks.registerCallbackWithSignature, EntityId>("stagehandType", bind(WorldEntityCallbacks::stagehandType, world, _1)); callbacks.registerCallbackWithSignature>("isNpc", bind(WorldEntityCallbacks::isNpc, world, _1, _2)); callbacks.registerCallback("isEntityInteractive", [world](EntityId entityId) -> Maybe { if (auto entity = world->get(entityId)) return entity->isInteractive(); return {}; }); callbacks.registerCallback("entityMouthPosition", [world](EntityId entityId) -> Maybe { if (auto entity = world->get(entityId)) return entity->mouthPosition(); return {}; }); callbacks.registerCallback("entityTypeName", [world](EntityId entityId) -> Maybe { auto entity = world->entity(entityId); if (auto monster = as(entity)) return monster->typeName(); if (auto npc = as(entity)) return npc->npcType(); if (auto vehicle = as(entity)) return vehicle->name(); if (auto object = as(entity)) return object->name(); if (auto itemDrop = as(entity)) { if (itemDrop->item()) return itemDrop->item()->name(); } return {}; }); } void addWorldEnvironmentCallbacks(LuaCallbacks& callbacks, World* world) { callbacks.registerCallbackWithSignature("lightLevel", bind(WorldEnvironmentCallbacks::lightLevel, world, _1)); callbacks.registerCallbackWithSignature("windLevel", bind(WorldEnvironmentCallbacks::windLevel, world, _1)); callbacks.registerCallbackWithSignature("breathable", bind(WorldEnvironmentCallbacks::breathable, world, _1)); callbacks.registerCallbackWithSignature("underground", bind(WorldEnvironmentCallbacks::underground, world, _1)); callbacks.registerCallbackWithSignature("material", bind(WorldEnvironmentCallbacks::material, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("mod", bind(WorldEnvironmentCallbacks::mod, world, _1, _2, _3)); callbacks.registerCallbackWithSignature("materialHueShift", bind(WorldEnvironmentCallbacks::materialHueShift, world, _1, _2)); callbacks.registerCallbackWithSignature("modHueShift", bind(WorldEnvironmentCallbacks::modHueShift, world, _1, _2)); callbacks.registerCallbackWithSignature("materialColor", bind(WorldEnvironmentCallbacks::materialColor, world, _1, _2)); callbacks.registerCallbackWithSignature("setMaterialColor", bind(WorldEnvironmentCallbacks::setMaterialColor, world, _1, _2, _3)); callbacks.registerCallback("oceanLevel", [world](Vec2I position) -> int { if (auto serverWorld = as(world)) { return serverWorld->worldTemplate()->blockInfo(position[0], position[1]).oceanLiquidLevel; } else { auto clientWorld = as(world); return clientWorld->currentTemplate()->blockInfo(position[0], position[1]).oceanLiquidLevel; } }); callbacks.registerCallback("environmentStatusEffects", [world](Vec2F const& position) { return world->environmentStatusEffects(position); }); callbacks.registerCallbackWithSignature, String, Vec2F, String, float, Maybe, Maybe>("damageTiles", bind(WorldEnvironmentCallbacks::damageTiles, world, _1, _2, _3, _4, _5, _6, _7)); callbacks.registerCallbackWithSignature, Maybe>("damageTileArea", bind(WorldEnvironmentCallbacks::damageTileArea, world, _1, _2, _3, _4, _5, _6, _7, _8)); callbacks.registerCallbackWithSignature, bool>("placeMaterial", bind(WorldEnvironmentCallbacks::placeMaterial, world, _1, _2, _3, _4, _5)); callbacks.registerCallbackWithSignature, bool>("placeMod", bind(WorldEnvironmentCallbacks::placeMod, world, _1, _2, _3, _4, _5)); callbacks.registerCallback("radialTileQuery", [world](Vec2F center, float radius, String layerName) -> List { 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 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(positionOrX)) return LuaFloat(world->geometry().xwrap(*x)); return engine.luaFrom(world->geometry().xwrap(engine.luaTo(positionOrX))); } LuaValue WorldCallbacks::nearestTo(World* world, LuaEngine& engine, Variant const& sourcePositionOrX, Variant const& targetPositionOrX) { if (targetPositionOrX.is()) { Vec2F targetPosition = targetPositionOrX.get(); Vec2F sourcePosition; if (sourcePositionOrX.is()) sourcePosition = sourcePositionOrX.get(); else sourcePosition[0] = sourcePositionOrX.get(); return engine.luaFrom(world->geometry().nearestTo(sourcePosition, targetPosition)); } else { float targetX = targetPositionOrX.get(); float sourceX; if (sourcePositionOrX.is()) sourceX = sourcePositionOrX.get()[0]; else sourceX = sourcePositionOrX.get(); return LuaFloat(world->geometry().nearestTo(sourceX, targetX)); } } bool WorldCallbacks::rectCollision(World* world, RectF const& arg1, Maybe 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 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 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> WorldCallbacks::lineTileCollisionPoint(World* world, Vec2F const& begin, Vec2F const& end, Maybe 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 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 const& collisionSet) { return world->pointCollision(point, collisionSet.value(DefaultCollisionSet)); } LuaTupleReturn, Maybe> WorldCallbacks::lineCollision(World* world, Vec2F const& start, Vec2F const& end, Maybe const& collisionSet) { Maybe point; Maybe 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 const& arg2, Maybe 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 WorldCallbacks::collisionBlocksAlongLine( World* world, Vec2F const& arg1, Vec2F const& arg2, Maybe const& arg3, Maybe 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> WorldCallbacks::liquidAlongLine(World* world, Vec2F const& start, Vec2F const& end) { List> levels; forBlocksAlongLine(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(x, y), liquidLevel)); return true; }); return levels; } Maybe WorldCallbacks::resolvePolyCollision( World* world, PolyF poly, Vec2F const& position, float maximumCorrection, Maybe const& maybeCollisionSet) { struct CollisionPoly { PolyF poly; Vec2F center; float sortingDistance; }; poly.translate(position); List 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 direction, float maximumDistance, int loops) -> Maybe { 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 const& arg2, Maybe 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 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 '{}', exception caught: {}", objectType, outputException(exception, false)); } return false; } Maybe WorldCallbacks::spawnItem(World* world, Json const& itemType, Vec2F const& worldPosition, Maybe const& inputCount, Json const& inputParameters, Maybe const& initialVelocity, Maybe 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(); } Logger::warn("Could not spawn item, item empty in WorldCallbacks::spawnItem"); } catch (StarException const& exception) { Logger::warn("Could not spawn Item of kind '{}', exception caught: {}", itemType, outputException(exception, false)); } return {}; } List WorldCallbacks::spawnTreasure( World* world, Vec2F const& position, String const& pool, float level, Maybe seed) { List 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 '{}', exception caught: {}", pool, outputException(exception, false)); } return entities; } Maybe WorldCallbacks::spawnMonster( World* world, String const& arg1, Vec2F const& arg2, Maybe 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(); } catch (StarException const& exception) { Logger::warn( "Could not spawn Monster of type '{}', exception caught: {}", arg1, outputException(exception, false)); return {}; } } Maybe WorldCallbacks::spawnNpc(World* world, Vec2F const& arg1, String const& arg2, String const& arg3, float arg4, Maybe 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(); } catch (StarException const& exception) { Logger::warn("Could not spawn NPC of species '{}' and type '{}', exception caught: {}", arg2, typeName, outputException(exception, false)); return {}; } } Maybe 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(); } catch (StarException const& exception) { Logger::warn( "Could not spawn Stagehand of type '{}', exception caught: {}", typeName, outputException(exception, false)); return {}; } } Maybe WorldCallbacks::spawnProjectile(World* world, String const& projectileType, Vec2F const& spawnPosition, Maybe const& sourceEntityId, Maybe 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(); } catch (StarException const& exception) { Logger::warn( "Could not spawn Projectile of type '{}', exception caught: {}", projectileType, outputException(exception, false)); return {}; } } Maybe 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 WorldCallbacks::liquidAt(World* world, Variant 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 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 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(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 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 const& uniqueId) { auto entity = world->entity(entityId); if (auto npc = as(entity.get())) npc->setUniqueId(uniqueId); else if (auto monster = as(entity.get())) monster->setUniqueId(uniqueId); else if (auto object = as(entity.get())) object->setUniqueId(uniqueId); else if (auto stagehand = as(entity.get())) stagehand->setUniqueId(uniqueId); else if (entity) throw StarException::format("Cannot set unique id on entity of type {}", EntityTypeNames.getRight(entity->entityType())); else throw StarException::format("No such entity with id {}", entityId); } Json ServerWorldCallbacks::takeItemDrop(World* world, EntityId entityId, Maybe const& takenBy) { auto itemDrop = world->get(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 respawnInWorld) { as(world)->setPlayerStart(playerStart, respawnInWorld.isValid() && respawnInWorld.value()); } List ServerWorldCallbacks::players(World* world) { return as(world)->players(); } LuaString ServerWorldCallbacks::fidelity(World* world, LuaEngine& engine) { return engine.createString(WorldServerFidelityNames.getRight(as(world)->fidelity())); } Maybe ServerWorldCallbacks::callScriptContext(World* world, String const& contextName, String const& function, LuaVariadic const& args) { auto context = as(world)->scriptContext(contextName); if (!context) throw StarException::format("Context {} does not exist", contextName); return context->invoke(function, args); } 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 const& args) { if (args.size() < 3) throw StarException(strf("Too few arguments to debugText: {}", args.size())); Vec2F const position = engine.luaTo(args.at(args.size() - 2)); Vec4B const color = engine.luaTo(args.at(args.size() - 1)).toRgba(); String text = formatLua(engine.luaTo(args.at(0)), slice>(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 options) { return LuaBindings::entityQuery(world, engine, pos1, pos2, move(options)); } LuaTable WorldEntityCallbacks::monsterQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options) { return LuaBindings::entityQuery(world, engine, pos1, pos2, move(options)); } LuaTable WorldEntityCallbacks::npcQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options) { return LuaBindings::entityQuery(world, engine, pos1, pos2, move(options)); } LuaTable WorldEntityCallbacks::objectQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options) { String objectName; if (options) objectName = options->get>("name").value(); return LuaBindings::entityQuery(world, engine, pos1, pos2, move(options), [&objectName](shared_ptr const& entity) -> bool { return objectName.empty() || entity->name() == objectName; }); } LuaTable WorldEntityCallbacks::itemDropQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options) { return LuaBindings::entityQuery(world, engine, pos1, pos2, move(options)); } LuaTable WorldEntityCallbacks::playerQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options) { return LuaBindings::entityQuery(world, engine, pos1, pos2, move(options)); } LuaTable WorldEntityCallbacks::loungeableQuery(World* world, LuaEngine& engine, Vec2F const& pos1, LuaValue const& pos2, Maybe options) { String orientationName; if (options) orientationName = options->get>("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 {}", orientationName)); auto filter = [orientation](shared_ptr const& entity) -> bool { auto loungeable = as(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(world, engine, pos1, pos2, move(options), filter); } LuaTable WorldEntityCallbacks::entityLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe options) { return LuaBindings::entityLineQuery(world, engine, point1, point2, move(options)); } LuaTable WorldEntityCallbacks::objectLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe options) { return LuaBindings::entityLineQuery(world, engine, point1, point2, move(options)); } LuaTable WorldEntityCallbacks::npcLineQuery(World* world, LuaEngine& engine, Vec2F const& point1, Vec2F const& point2, Maybe options) { return LuaBindings::entityLineQuery(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(entity)) return monster->aggressive(); if (auto npc = as(entity)) return npc->aggressive(); return false; } Maybe WorldEntityCallbacks::entityType(World* world, LuaEngine& engine, EntityId entityId) { if (auto entity = world->entity(entityId)) return engine.createString(EntityTypeNames.getRight(entity->entityType())); return {}; } Maybe WorldEntityCallbacks::entityPosition(World* world, EntityId entityId) { if (auto entity = world->entity(entityId)) { return entity->position(); } else { return {}; } } Maybe WorldEntityCallbacks::entityMetaBoundBox(World* world, EntityId entityId) { if (auto entity = world->entity(entityId)) { return entity->metaBoundBox(); } else { return {}; } } Maybe WorldEntityCallbacks::entityVelocity(World* world, EntityId entityId) { auto entity = world->entity(entityId); if (auto monsterEntity = as(entity)) return monsterEntity->velocity(); else if (auto npcEntity = as(entity)) return npcEntity->velocity(); else if (auto playerEntity = as(entity)) return playerEntity->velocity(); else if (auto vehicleEntity = as(entity)) return vehicleEntity->velocity(); return {}; } Maybe WorldEntityCallbacks::entityCurrency(World* world, EntityId entityId, String const& currencyType) { if (auto player = world->get(entityId)) { return player->currency(currencyType); } else { return {}; } } Maybe WorldEntityCallbacks::entityHasCountOfItem(World* world, EntityId entityId, Json descriptor, Maybe exactMatch) { if (auto player = world->get(entityId)) { return player->inventory()->hasCountOfItem(ItemDescriptor(descriptor), exactMatch.value(false)); } else { return {}; } } Maybe WorldEntityCallbacks::entityHealth(World* world, EntityId entityId) { if (auto entity = world->get(entityId)) { return Vec2F(entity->health(), entity->maxHealth()); } else { return {}; } } Maybe WorldEntityCallbacks::entitySpecies(World* world, EntityId entityId) { if (auto player = world->get(entityId)) { return player->species(); } else if (auto npc = world->get(entityId)) { return npc->species(); } else { return {}; } } Maybe WorldEntityCallbacks::entityGender(World* world, EntityId entityId) { if (auto player = world->get(entityId)) { return GenderNames.getRight(player->gender()); } else if (auto npc = world->get(entityId)) { return GenderNames.getRight(npc->gender()); } else { return {}; } } Maybe WorldEntityCallbacks::entityName(World* world, EntityId entityId) { auto entity = world->entity(entityId); if (auto portraitEntity = as(entity)) { return portraitEntity->name(); } else if (auto objectEntity = as(entity)) { return objectEntity->name(); } else if (auto itemDropEntity = as(entity)) { if (itemDropEntity->item()) return itemDropEntity->item()->name(); } else if (auto vehicleEntity = as(entity)) { return vehicleEntity->name(); } else if (auto stagehandEntity = as(entity)) { return stagehandEntity->typeName(); } else if (auto projectileEntity = as(entity)) { return projectileEntity->typeName(); } return {}; } Maybe WorldEntityCallbacks::entityDescription(World* world, EntityId entityId, Maybe const& species) { if (auto entity = world->entity(entityId)) { if (auto inspectableEntity = as(entity)) { if (species) return inspectableEntity->inspectionDescription(*species); } return entity->description(); } return {}; } Maybe WorldEntityCallbacks::entityPortrait(World* world, EntityId entityId, String const& portraitMode) { auto entity = world->entity(entityId); if (auto portraitEntity = as(entity)) { PortraitMode mode = PortraitModeNames.getLeft(portraitMode); auto drawables = portraitEntity->portrait(mode); return drawables.transformed(mem_fn(&Drawable::toJson)); } return {}; } Maybe 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 {}", handName)); } if (auto entity = world->get(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 {}", handName)); } if (auto entity = world->get(entityId)) { if (auto item = entity->handItem(toolHand)) { return item->descriptor().toJson(); } } return Json(); } Maybe 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 const& defaultValue) { Json val = Json(); if (auto objectEntity = as(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 const& defaultValue) { Json val = Json(); if (auto npcEntity = as(world->entity(entityId))) { val = npcEntity->scriptConfigParameter(parameterName); if (!val && defaultValue) val = *defaultValue; } return val; } List WorldEntityCallbacks::objectSpaces(World* world, EntityId entityId) { if (auto tileEntity = as(world->entity(entityId))) return tileEntity->spaces(); return {}; } Maybe WorldEntityCallbacks::farmableStage(World* world, EntityId entityId) { if (auto farmable = world->get(entityId)) { return farmable->stage(); } return {}; } Maybe WorldEntityCallbacks::containerSize(World* world, EntityId entityId) { if (auto container = world->get(entityId)) return container->containerSize(); return {}; } bool WorldEntityCallbacks::containerClose(World* world, EntityId entityId) { if (auto container = world->get(entityId)) { container->containerClose(); return true; } return false; } bool WorldEntityCallbacks::containerOpen(World* world, EntityId entityId) { if (auto container = world->get(entityId)) { container->containerOpen(); return true; } return false; } Json WorldEntityCallbacks::containerItems(World* world, EntityId entityId) { if (auto container = world->get(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(entityId)) { auto itemDb = Root::singleton().itemDatabase(); auto items = container->itemBag()->items(); if (offset < items.size()) { return itemDb->toJson(items.at(offset)); } } return Json(); } Maybe WorldEntityCallbacks::containerConsume(World* world, EntityId entityId, Json const& items) { if (auto container = world->get(entityId)) { auto toConsume = ItemDescriptor(items); return container->consumeItems(toConsume).result(); } return {}; } Maybe WorldEntityCallbacks::containerConsumeAt(World* world, EntityId entityId, size_t offset, int count) { if (auto container = world->get(entityId)) { if (offset < container->containerSize()) { return container->consumeItems(offset, count).result(); } } return {}; } Maybe WorldEntityCallbacks::containerAvailable(World* world, EntityId entityId, Json const& items) { if (auto container = world->get(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(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(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(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 WorldEntityCallbacks::containerItemsCanFit(World* world, EntityId entityId, Json const& items) { if (auto container = world->get(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(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(res.slots)} }; } return Json(); } Json WorldEntityCallbacks::containerAddItems(World* world, EntityId entityId, Json const& items) { if (auto container = world->get(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(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(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(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(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(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 WorldEntityCallbacks::callScriptedEntity(World* world, EntityId entityId, String const& function, LuaVariadic const& args) { auto entity = as(world->entity(entityId)); if (!entity || !entity->isMaster()) throw StarException::format("Entity {} does not exist or is not a local master scripted entity", entityId); return entity->callScript(function, args); } RpcPromise WorldEntityCallbacks::findUniqueEntity(World* world, String const& uniqueId) { return world->findUniqueEntity(uniqueId); } RpcPromise WorldEntityCallbacks::sendEntityMessage(World* world, LuaEngine& engine, LuaValue entityId, String const& message, LuaVariadic args) { if (entityId.is()) return world->sendEntityMessage(engine.luaTo(entityId), message, JsonArray::from(move(args))); else return world->sendEntityMessage(engine.luaTo(entityId), message, JsonArray::from(move(args))); } Maybe WorldEntityCallbacks::loungeableOccupied(World* world, EntityId entityId) { auto entity = world->get(entityId); if (entity && entity->anchorCount() > 0) return !entity->entitiesLoungingIn(0).empty(); return {}; } bool WorldEntityCallbacks::isMonster(World* world, EntityId entityId, Maybe const& aggressive) { if (auto entity = world->get(entityId)) return !aggressive || *aggressive == entity->aggressive(); return false; } Maybe WorldEntityCallbacks::monsterType(World* world, EntityId entityId) { if (auto monster = world->get(entityId)) return monster->typeName(); return {}; } Maybe WorldEntityCallbacks::npcType(World* world, EntityId entityId) { if (auto npc = world->get(entityId)) return npc->npcType(); return {}; } Maybe WorldEntityCallbacks::stagehandType(World* world, EntityId entityId) { if (auto stagehand = world->get(entityId)) return stagehand->typeName(); return {}; } bool WorldEntityCallbacks::isNpc(World* world, EntityId entityId, Maybe const& damageTeam) { if (auto entity = world->get(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 {}", 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 {}", 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 {}", 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 {}", 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 {}", 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 {}", layerName)); } world->modifyTile(Vec2I::floor(position), PlaceMaterialColor{layer, color}, true); } bool WorldEnvironmentCallbacks::damageTiles(World* world, List const& arg1, String const& arg2, Vec2F const& arg3, String const& arg4, float arg5, Maybe const& arg6, Maybe sourceEntity) { List 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 {}", 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 const& harvestLevel, Maybe 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 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 {}", layerName)); } auto materialName = arg3; auto materialDatabase = Root::singleton().materialDatabase(); if (!materialDatabase->materialNames().contains(materialName)) throw StarException(strf("Unknown material name {}", 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 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 {}", layerName)); } auto modName = arg3; auto materialDatabase = Root::singleton().materialDatabase(); if (!materialDatabase->modNames().contains(modName)) throw StarException(strf("Unknown mod name {}", modName)); placeMod.mod = materialDatabase->modId(modName); if (arg4) placeMod.modHueShift = (MaterialHue)*arg4; bool allowOverlap = arg5; return world->modifyTile(tilePosition, placeMod, allowOverlap); } } }