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/StarVehicle.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/StarVehicle.cpp')
-rw-r--r-- | source/game/StarVehicle.cpp | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/source/game/StarVehicle.cpp b/source/game/StarVehicle.cpp new file mode 100644 index 0000000..38755d5 --- /dev/null +++ b/source/game/StarVehicle.cpp @@ -0,0 +1,648 @@ +#include "StarVehicle.hpp" +#include "StarDataStreamExtra.hpp" +#include "StarJsonExtra.hpp" +#include "StarConfigLuaBindings.hpp" +#include "StarEntityLuaBindings.hpp" +#include "StarLuaGameConverters.hpp" +#include "StarEntityRendering.hpp" +#include "StarMovementControllerLuaBindings.hpp" +#include "StarNetworkedAnimatorLuaBindings.hpp" +#include "StarAssets.hpp" +#include "StarScriptedAnimatorLuaBindings.hpp" + +namespace Star { + +Vehicle::Vehicle(Json baseConfig, String path, Json dynamicConfig) + : m_baseConfig(move(baseConfig)), m_path(move(path)), m_dynamicConfig(move(dynamicConfig)) { + + m_typeName = m_baseConfig.getString("name"); + + setPersistent(configValue("persistent", false).toBool()); + m_clientEntityMode = ClientEntityModeNames.getLeft(configValue("clientEntityMode", "ClientSlaveOnly").toString()); + + m_scriptComponent.setScript(AssetPath::relativeTo(m_path, configValue("script").toString())); + m_scriptComponent.setUpdateDelta(configValue("scriptDelta", 1).toUInt()); + m_boundBox = jsonToRectF(configValue("boundBox")); + m_slaveControlTimeout = configValue("slaveControlTimeout").toFloat(); + m_slaveHeartbeatTimer = GameTimer(configValue("slaveControlHeartbeat").toFloat()); + m_damageTeam.set(configValue("damageTeam").opt().apply(construct<EntityDamageTeam>()).value()); + m_interactive.set(configValue("interactive", true).toBool()); + + if (auto animationScript = configValue("animationScript").optString()) + m_scriptedAnimator.setScript(*animationScript); + + for (auto const& pair : configValue("loungePositions", JsonObject()).iterateObject()) { + auto& loungePosition = m_loungePositions[pair.first]; + loungePosition.part = pair.second.getString("part"); + loungePosition.partAnchor = pair.second.getString("partAnchor"); + loungePosition.exitBottomOffset = pair.second.opt("exitBottomOffset").apply(jsonToVec2F); + loungePosition.armorCosmeticOverrides = pair.second.getObject("armorCosmeticOverrides", JsonObject()); + loungePosition.cursorOverride = pair.second.optString("cursorOverride"); + loungePosition.cameraFocus = pair.second.getBool("cameraFocus", false); + loungePosition.enabled.set(pair.second.getBool("enabled", true)); + if (auto orientation = pair.second.optString("orientation")) + loungePosition.orientation.set(LoungeOrientationNames.getLeft(*orientation)); + loungePosition.emote.set(pair.second.optString("emote")); + loungePosition.dance.set(pair.second.optString("dance")); + loungePosition.directives.set(pair.second.optString("directives")); + loungePosition.statusEffects.set(pair.second.getArray("statusEffects", {}).transformed(jsonToPersistentStatusEffect)); + } + + for (auto const& pair : configValue("physicsCollisions", JsonObject()).iterateObject()) { + auto& collisionConfig = m_movingCollisions[pair.first]; + collisionConfig.movingCollision = PhysicsMovingCollision::fromJson(pair.second); + collisionConfig.attachToPart = pair.second.optString("attachToPart"); + collisionConfig.enabled.set(pair.second.getBool("enabled", true)); + } + + for (auto const& pair : configValue("physicsForces", JsonObject()).iterateObject()) { + auto& forceRegionConfig = m_forceRegions[pair.first]; + forceRegionConfig.forceRegion = jsonToPhysicsForceRegion(pair.second); + forceRegionConfig.attachToPart = pair.second.optString("attachToPart"); + forceRegionConfig.enabled.set(pair.second.getBool("enabled", true)); + } + + for (auto const& pair : configValue("damageSources", JsonObject()).iterateObject()) { + auto& damageSourceConfig = m_damageSources[pair.first]; + damageSourceConfig.damageSource = DamageSource(pair.second); + damageSourceConfig.attachToPart = pair.second.optString("attachToPart"); + damageSourceConfig.enabled.set(pair.second.getBool("enabled", true)); + } + + auto assets = Root::singleton().assets(); + auto animationConfig = assets->fetchJson(configValue("animation"), m_path); + if (auto customConfig = configValue("animationCustom")) + animationConfig = jsonMerge(animationConfig, customConfig); + + m_networkedAnimator = NetworkedAnimator(animationConfig, m_path); + + for (auto const& p : configValue("animationGlobalTags", JsonObject()).iterateObject()) + m_networkedAnimator.setGlobalTag(p.first, p.second.toString()); + for (auto const& partPair : configValue("animationPartTags", JsonObject()).iterateObject()) { + for (auto const& tagPair : partPair.second.iterateObject()) + m_networkedAnimator.setPartTag(partPair.first, tagPair.first, tagPair.second.toString()); + } + + auto movementParameters = MovementParameters(configValue("movementSettings")); + if (!movementParameters.physicsEffectCategories) + movementParameters.physicsEffectCategories = StringSet({"vehicle"}); + m_movementController.resetParameters(movementParameters); + + m_netGroup.addNetElement(&m_interactive); + m_netGroup.addNetElement(&m_movementController); + m_netGroup.addNetElement(&m_networkedAnimator); + m_netGroup.addNetElement(&m_damageTeam); + + m_loungePositions.sortByKey(); + for (auto& p : m_loungePositions) { + m_netGroup.addNetElement(&p.second.enabled); + m_netGroup.addNetElement(&p.second.orientation); + m_netGroup.addNetElement(&p.second.emote); + m_netGroup.addNetElement(&p.second.dance); + m_netGroup.addNetElement(&p.second.directives); + m_netGroup.addNetElement(&p.second.statusEffects); + } + + m_movingCollisions.sortByKey(); + for (auto& p : m_movingCollisions) + m_netGroup.addNetElement(&p.second.enabled); + + m_forceRegions.sortByKey(); + for (auto& p : m_forceRegions) + m_netGroup.addNetElement(&p.second.enabled); + + m_damageSources.sortByKey(); + for (auto& p : m_damageSources) + m_netGroup.addNetElement(&p.second.enabled); + + // don't interpolate scripted animation parameters + m_netGroup.addNetElement(&m_scriptedAnimationParameters, false); +} + +String Vehicle::name() const { + return m_typeName; +} + +Json Vehicle::baseConfig() const { + return m_baseConfig; +} + +Json Vehicle::dynamicConfig() const { + return m_dynamicConfig; +} + +Json Vehicle::diskStore() const { + return JsonObject{ + {"movement", m_movementController.storeState()}, + {"damageTeam", m_damageTeam.get().toJson()}, + {"persistent", persistent()}, + {"scriptStorage", m_scriptComponent.getScriptStorage()} + }; +} + +void Vehicle::diskLoad(Json diskStore) { + m_movementController.loadState(diskStore.get("movement")); + m_damageTeam.set(EntityDamageTeam(diskStore.get("damageTeam"))); + setPersistent(diskStore.getBool("persistent")); + m_scriptComponent.setScriptStorage(diskStore.getObject("scriptStorage")); +} + +EntityType Vehicle::entityType() const { + return EntityType::Vehicle; +} + +ClientEntityMode Vehicle::clientEntityMode() const { + return m_clientEntityMode; +} + +Maybe<HitType> Vehicle::queryHit(DamageSource const& source) const { + if (source.intersectsWithPoly(world()->geometry(), m_movementController.collisionBody())) + return HitType::Hit; + + return {}; +} + +Maybe<PolyF> Vehicle::hitPoly() const { + return m_movementController.collisionBody(); +} + +List<DamageNotification> Vehicle::applyDamage(DamageRequest const& damage) { + if (!inWorld()) + return {}; + + return m_scriptComponent.invoke<List<DamageNotification>>("applyDamage", damage).value(); +} + +List<DamageNotification> Vehicle::selfDamageNotifications() { + return m_scriptComponent.invoke<List<DamageNotification>>("selfDamageNotifications").value(); +} + +void Vehicle::init(World* world, EntityId entityId, EntityMode mode) { + Entity::init(world, entityId, mode); + m_movementController.init(world); + m_movementController.setIgnorePhysicsEntities({entityId}); + if (isMaster()) { + m_scriptComponent.addCallbacks("vehicle", makeVehicleCallbacks()); + m_scriptComponent.addCallbacks( + "config", LuaBindings::makeConfigCallbacks(bind(&Vehicle::configValue, this, _1, _2))); + m_scriptComponent.addCallbacks("entity", LuaBindings::makeEntityCallbacks(this)); + m_scriptComponent.addCallbacks("mcontroller", LuaBindings::makeMovementControllerCallbacks(&m_movementController)); + m_scriptComponent.addCallbacks("animator", LuaBindings::makeNetworkedAnimatorCallbacks(&m_networkedAnimator)); + m_scriptComponent.init(world); + } else { + m_slaveHeartbeatTimer.reset(); + } + + if (world->isClient()) { + m_scriptedAnimator.addCallbacks("animationConfig", LuaBindings::makeScriptedAnimatorCallbacks(&m_networkedAnimator, + [this](String const& name, Json const& defaultValue) -> Json { + return m_scriptedAnimationParameters.value(name, defaultValue); + })); + m_scriptedAnimator.addCallbacks("config", LuaBindings::makeConfigCallbacks([this](String const& name, Json const& def) { + return configValue(name, def); + })); + m_scriptedAnimator.addCallbacks("entity", LuaBindings::makeEntityCallbacks(this)); + + m_scriptedAnimator.init(world); + } +} + +void Vehicle::uninit() { + m_scriptComponent.uninit(); + m_scriptComponent.removeCallbacks("vehicle"); + m_scriptComponent.removeCallbacks("config"); + m_scriptComponent.removeCallbacks("entity"); + m_scriptComponent.removeCallbacks("mcontroller"); + m_scriptComponent.removeCallbacks("animator"); + m_movementController.uninit(); + + if (world()->isClient()) { + m_scriptedAnimator.removeCallbacks("animationConfig"); + m_scriptedAnimator.removeCallbacks("config"); + m_scriptedAnimator.removeCallbacks("entity"); + } + + Entity::uninit(); +} + +Vec2F Vehicle::position() const { + return m_movementController.position(); +} + +RectF Vehicle::metaBoundBox() const { + return m_boundBox; +} + +RectF Vehicle::collisionArea() const { + return m_movementController.collisionPoly().boundBox(); +} + +Vec2F Vehicle::velocity() const { + return m_movementController.velocity(); +} + +pair<ByteArray, uint64_t> Vehicle::writeNetState(uint64_t fromVersion) { + return m_netGroup.writeNetState(fromVersion); +} + +void Vehicle::readNetState(ByteArray data, float interpolationTime) { + m_netGroup.readNetState(move(data), interpolationTime); +} + +void Vehicle::enableInterpolation(float extrapolationHint) { + m_netGroup.enableNetInterpolation(extrapolationHint); +} + +void Vehicle::disableInterpolation() { + m_netGroup.disableNetInterpolation(); +} + +void Vehicle::update(uint64_t) { + setTeam(m_damageTeam.get()); + + if (world()->isClient()) { + m_networkedAnimator.update(WorldTimestep, &m_networkedAnimatorDynamicTarget); + m_networkedAnimatorDynamicTarget.updatePosition(position()); + } else { + m_networkedAnimator.update(WorldTimestep, nullptr); + } + + if (isMaster()) { + m_movementController.tickMaster(); + m_scriptComponent.update(m_scriptComponent.updateDt()); + + eraseWhere(m_aliveMasterConnections, [](auto& p) { + return p.second.tick(WorldTimestep); + }); + + for (auto& loungePositionPair : m_loungePositions) { + for (auto& p : loungePositionPair.second.masterControlState) { + p.second.masterHeld = false; + filter(p.second.slavesHeld, [this](ConnectionId id) { + return m_aliveMasterConnections.contains(id); + }); + } + } + } else { + m_netGroup.tickNetInterpolation(WorldTimestep); + + m_movementController.tickSlave(); + + bool heartbeat = m_slaveHeartbeatTimer.wrapTick(); + + for (auto& p : m_loungePositions) { + if (heartbeat) { + JsonArray allControlsHeld = transform<JsonArray>(p.second.slaveNewControls, [](LoungeControl control) { + return LoungeControlNames.getRight(control); + }); + world()->sendEntityMessage(entityId(), "control_all", {*m_loungePositions.indexOf(p.first), move(allControlsHeld)}); + } else { + for (auto control : p.second.slaveNewControls.difference(p.second.slaveOldControls)) + world()->sendEntityMessage(entityId(), "control_on", {*m_loungePositions.indexOf(p.first), LoungeControlNames.getRight(control)}); + + for (auto control : p.second.slaveOldControls.difference(p.second.slaveNewControls)) + world()->sendEntityMessage(entityId(), "control_off", {*m_loungePositions.indexOf(p.first), LoungeControlNames.getRight(control)}); + } + + if (p.second.slaveOldAimPosition != p.second.slaveNewAimPosition) + world()->sendEntityMessage(entityId(), "aim", {*m_loungePositions.indexOf(p.first), p.second.slaveNewAimPosition[0], p.second.slaveNewAimPosition[1]}); + + p.second.slaveOldControls = take(p.second.slaveNewControls); + p.second.slaveOldAimPosition = p.second.slaveNewAimPosition; + } + } + + if (world()->isClient()) + m_scriptedAnimator.update(); + + SpatialLogger::logPoly("world", m_movementController.collisionBody(), {255, 255, 0, 255}); +} + +void Vehicle::render(RenderCallback* renderer) { + for (auto& drawable : m_networkedAnimator.drawablesWithZLevel(position())) { + if (drawable.second < 0.0f) + renderer->addDrawable(move(drawable.first), renderLayer(VehicleLayer::Back)); + else + renderer->addDrawable(move(drawable.first), renderLayer(VehicleLayer::Front)); + } + + renderer->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios()); + renderer->addParticles(m_networkedAnimatorDynamicTarget.pullNewParticles()); + renderer->addLightSources(m_networkedAnimator.lightSources(position())); + + for (auto drawablePair : m_scriptedAnimator.drawables()) + renderer->addDrawable(drawablePair.first, drawablePair.second.value(renderLayer(VehicleLayer::Front))); + renderer->addLightSources(m_scriptedAnimator.lightSources()); + renderer->addAudios(m_scriptedAnimator.pullNewAudios()); + renderer->addParticles(m_scriptedAnimator.pullNewParticles()); +} + +List<LightSource> Vehicle::lightSources() const { + auto lightSources = m_networkedAnimator.lightSources(position()); + return lightSources; +} + +bool Vehicle::shouldDestroy() const { + return m_shouldDestroy; +} + +void Vehicle::destroy(RenderCallback* renderCallback) { + if (renderCallback) { + m_networkedAnimator.update(0.0, &m_networkedAnimatorDynamicTarget); + + renderCallback->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios()); + renderCallback->addParticles(m_networkedAnimatorDynamicTarget.pullNewParticles()); + } +} + +Maybe<Json> Vehicle::receiveMessage(ConnectionId connectionId, String const& message, JsonArray const& args) { + m_aliveMasterConnections[connectionId] = GameTimer(m_slaveControlTimeout); + if (message.equalsIgnoreCase("control_on")) { + auto& loungePosition = m_loungePositions.valueAt(args.at(0).toUInt()); + loungePosition.masterControlState[LoungeControlNames.getLeft(args.at(1).toString())].slavesHeld.add(connectionId); + return Json(); + } else if (message.equalsIgnoreCase("control_off")) { + auto& loungePosition = m_loungePositions.valueAt(args.at(0).toUInt()); + loungePosition.masterControlState[LoungeControlNames.getLeft(args.at(1).toString())].slavesHeld.remove(connectionId); + return Json(); + } else if (message.equalsIgnoreCase("control_all")) { + auto& loungePosition = m_loungePositions.valueAt(args.at(0).toUInt()); + Set<LoungeControl> allControlsHeld; + for (auto const& s : args.at(1).iterateArray()) + allControlsHeld.add(LoungeControlNames.getLeft(s.toString())); + for (auto& p : loungePosition.masterControlState) { + if (allControlsHeld.contains(p.first)) + p.second.slavesHeld.add(connectionId); + else + p.second.slavesHeld.remove(connectionId); + } + return Json(); + } else if (message.equalsIgnoreCase("aim")) { + auto& loungePosition = m_loungePositions.valueAt(args.at(0).toUInt()); + loungePosition.masterAimPosition = {args.at(1).toFloat(), args.at(2).toFloat()}; + return Json(); + } else { + return m_scriptComponent.handleMessage(message, connectionId == world()->connection(), args); + } +} + +RectF Vehicle::interactiveBoundBox() const { + return collisionArea(); +} + +bool Vehicle::isInteractive() const { + return m_interactive.get(); +} + +InteractAction Vehicle::interact(InteractRequest const& request) { + auto result = m_scriptComponent.invoke<Json>("onInteraction", JsonObject{ + {"sourceId", request.sourceId}, + {"sourcePosition", jsonFromVec2F(request.sourcePosition)}, + {"interactPosition", jsonFromVec2F(request.interactPosition)} + }).value(); + + if (result.isType(Json::Type::String)) + return InteractAction(result.toString(), entityId(), Json()); + else if (!result.isNull()) + return InteractAction(result.getString(0), entityId(), result.get(1)); + + Maybe<size_t> index; + for (size_t i = 0; i < m_loungePositions.size(); ++i) { + if (!index) { + index = i; + } else { + auto const& thisLounge = m_loungePositions.valueAt(i); + if (!thisLounge.enabled.get()) + continue; + + Vec2F thisLoungePosition = *m_networkedAnimator.partPoint(thisLounge.part, thisLounge.partAnchor) + position(); + + auto const& selectedLounge = m_loungePositions.valueAt(*index); + Vec2F selectedLoungePosition = *m_networkedAnimator.partPoint(selectedLounge.part, selectedLounge.partAnchor) + position(); + + if (vmagSquared(thisLoungePosition - request.interactPosition) < vmagSquared(selectedLoungePosition - request.interactPosition)) + index = i; + } + } + + if (index) + return InteractAction(InteractActionType::SitDown, entityId(), *index); + + return InteractAction(); +} + +size_t Vehicle::anchorCount() const { + return m_loungePositions.size(); +} + +LoungeAnchorConstPtr Vehicle::loungeAnchor(size_t positionIndex) const { + auto const& positionConfig = m_loungePositions.valueAt(positionIndex); + if (!positionConfig.enabled.get()) + return {}; + + Mat3F partTransformation = m_networkedAnimator.finalPartTransformation(positionConfig.part); + Vec2F partAnchor = jsonToVec2F(m_networkedAnimator.partProperty(positionConfig.part, positionConfig.partAnchor)); + + auto loungePosition = make_shared<LoungeAnchor>(); + loungePosition->position = partTransformation.transformVec2(partAnchor) + position(); + if (positionConfig.exitBottomOffset) + loungePosition->exitBottomPosition = partTransformation.transformVec2(partAnchor + positionConfig.exitBottomOffset.value()) + position(); + loungePosition->direction = partTransformation.determinant() > 0 ? Direction::Right : Direction::Left; + loungePosition->angle = partTransformation.transformAngle(0.0f); + if (loungePosition->direction == Direction::Left) + loungePosition->angle += Constants::pi; + loungePosition->controllable = true; + loungePosition->loungeRenderLayer = renderLayer(VehicleLayer::Passenger); + loungePosition->orientation = positionConfig.orientation.get(); + loungePosition->emote = positionConfig.emote.get(); + loungePosition->dance = positionConfig.dance.get(); + loungePosition->directives = positionConfig.directives.get(); + loungePosition->statusEffects = positionConfig.statusEffects.get(); + loungePosition->armorCosmeticOverrides = positionConfig.armorCosmeticOverrides; + loungePosition->cursorOverride = positionConfig.cursorOverride; + loungePosition->cameraFocus = positionConfig.cameraFocus; + return loungePosition; +} + +void Vehicle::loungeControl(size_t index, LoungeControl loungeControl) { + auto& loungePosition = m_loungePositions.valueAt(index); + if (isSlave()) + loungePosition.slaveNewControls.add(loungeControl); + else + loungePosition.masterControlState[loungeControl].masterHeld = true; +} + +void Vehicle::loungeAim(size_t index, Vec2F const& aimPosition) { + auto& loungePosition = m_loungePositions.valueAt(index); + if (isSlave()) + loungePosition.slaveNewAimPosition = aimPosition; + else + loungePosition.masterAimPosition = aimPosition; +} + +List<PhysicsForceRegion> Vehicle::forceRegions() const { + List<PhysicsForceRegion> forces; + for (auto const& p : m_forceRegions) { + if (p.second.enabled.get()) { + PhysicsForceRegion forceRegion = p.second.forceRegion; + + Vec2F translatePos = position(); + if (p.second.attachToPart) { + Mat3F partTransformation = m_networkedAnimator.finalPartTransformation(p.second.attachToPart.get()); + Vec2F localTranslation = partTransformation.transformVec2(Vec2F()); + translatePos += localTranslation; + } + + forceRegion.call([translatePos](auto& fr) { fr.translate(translatePos); }); + forces.append(move(forceRegion)); + } + } + return forces; +} + +List<DamageSource> Vehicle::damageSources() const { + List<DamageSource> sources; + for (auto const& p : m_damageSources) { + if (p.second.enabled.get()) { + DamageSource damageSource = p.second.damageSource; + + if (p.second.attachToPart) { + Mat3F partTransformation = m_networkedAnimator.finalPartTransformation(p.second.attachToPart.get()); + damageSource.damageArea.call([partTransformation](auto& da) { da.transform(partTransformation); }); + } + + damageSource.team = m_damageTeam.get(); + damageSource.sourceEntityId = entityId(); + + sources.append(move(damageSource)); + } + } + return sources; +} + +size_t Vehicle::movingCollisionCount() const { + return m_movingCollisions.size(); +} + +Maybe<PhysicsMovingCollision> Vehicle::movingCollision(size_t positionIndex) const { + auto const& collisionConfig = m_movingCollisions.valueAt(positionIndex); + if (!collisionConfig.enabled.get()) + return {}; + + PhysicsMovingCollision collision = collisionConfig.movingCollision; + + if (collisionConfig.attachToPart) { + Mat3F partTransformation = m_networkedAnimator.finalPartTransformation(*collisionConfig.attachToPart); + + Vec2F localTranslation = partTransformation.transformVec2(Vec2F()); + collision.position += localTranslation; + + Mat3F localTransform = Mat3F::translation(-localTranslation) * partTransformation; + collision.collision.transform(localTransform); + } + + collision.position += position(); + + return collision; +} + +Maybe<LuaValue> Vehicle::callScript(String const& func, LuaVariadic<LuaValue> const& args) { + return m_scriptComponent.invoke(func, args); +} + +Maybe<LuaValue> Vehicle::evalScript(String const& code) { + return m_scriptComponent.eval(code); +} + +void Vehicle::setPosition(Vec2F const& position) { + m_movementController.setPosition(position); +} + +EntityRenderLayer Vehicle::renderLayer(VehicleLayer vehicleLayer) const { + // Z-offset based on entity id, so vehicles don't overlap strangely. + return RenderLayerVehicle + ((EntityRenderLayer)(entityId() * 4 + (unsigned)vehicleLayer) & RenderLayerLowerMask); +} + +LuaCallbacks Vehicle::makeVehicleCallbacks() { + LuaCallbacks callbacks; + + callbacks.registerCallback("controlHeld", [this](String const& loungeName, String const& controlName) { + auto const& mc = m_loungePositions.get(loungeName).masterControlState[LoungeControlNames.getLeft(controlName)]; + return mc.masterHeld || !mc.slavesHeld.empty(); + }); + + callbacks.registerCallback( "aimPosition", [this](String const& loungeName) { + return m_loungePositions.get(loungeName).masterAimPosition; + }); + + callbacks.registerCallback("entityLoungingIn", [this](String const& name) -> LuaValue { + auto entitiesIn = entitiesLoungingIn(*m_loungePositions.indexOf(name)); + if (entitiesIn.empty()) + return LuaNil; + return LuaInt(entitiesIn.first()); + }); + + callbacks.registerCallback("setLoungeEnabled", [this](String const& name, bool enabled) { + m_loungePositions.get(name).enabled.set(enabled); + }); + + callbacks.registerCallback("setLoungeOrientation", [this](String const& name, String const& orientation) { + m_loungePositions.get(name).orientation.set(LoungeOrientationNames.getLeft(orientation)); + }); + + callbacks.registerCallback("setLoungeEmote", [this](String const& name, Maybe<String> emote) { + m_loungePositions.get(name).emote.set(move(emote)); + }); + + callbacks.registerCallback("setLoungeDance", [this](String const& name, Maybe<String> dance) { + m_loungePositions.get(name).dance.set(move(dance)); + }); + + callbacks.registerCallback("setLoungeDirectives", [this](String const& name, Maybe<String> directives) { + m_loungePositions.get(name).directives.set(move(directives)); + }); + + callbacks.registerCallback("setLoungeStatusEffects", [this](String const& name, JsonArray const& statusEffects) { + m_loungePositions.get(name).statusEffects.set(statusEffects.transformed(jsonToPersistentStatusEffect)); + }); + + callbacks.registerCallback("setPersistent", [this](bool persistent) { + setPersistent(persistent); + }); + + callbacks.registerCallback("setInteractive", [this](bool interactive) { + m_interactive.set(interactive); + }); + + callbacks.registerCallback("setDamageTeam", [this](Json damageTeam) { + m_damageTeam.set(EntityDamageTeam(damageTeam)); + }); + + callbacks.registerCallback("setDamageSourceEnabled", [this](String const& name, bool enabled) { + m_damageSources.get(name).enabled.set(enabled); + }); + + callbacks.registerCallback("setMovingCollisionEnabled", [this](String const& name, bool enabled) { + m_movingCollisions.get(name).enabled.set(enabled); + }); + + callbacks.registerCallback("setForceRegionEnabled", [this](String const& name, bool enabled) { + m_forceRegions.get(name).enabled.set(enabled); + }); + + callbacks.registerCallback("destroy", [this]() { + m_shouldDestroy = true; + }); + + callbacks.registerCallback("setAnimationParameter", [this](String name, Json value) { + m_scriptedAnimationParameters.set(move(name), move(value)); + }); + + return callbacks; +} + +Json Vehicle::configValue(String const& name, Json def) const { + return jsonMergeQueryDef(name, move(def), m_baseConfig, m_dynamicConfig); +} + +} |