//// EntityScriptingInterface.h// libraries/entities/src//// Created by Brad Hefta-Gaub on 12/6/13.// Copyright 2013 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0//// TODO: How will we handle collision callbacks with Entities// #ifndef hifi_EntityScriptingInterface_h#define hifi_EntityScriptingInterface_h #include <memory> #include <QtCore/QObject>#include <QtCore/QStringList>#include <QtCore/QSharedPointer>#include <QtQml/QJSValue>#include <QtQml/QJSValueList>#include <QtCore/QSharedPointer> #include <DependencyManager.h>#include <Octree.h>#include <OctreeScriptingInterface.h>#include <RegisteredMetaTypes.h>#include "PointerEvent.h"#include <PickFilter.h>#include <ScriptManager.h>#include <ScriptValue.h>#include <CanvasCommand.h> #include "PolyVoxEntityItem.h"#include "LineEntityItem.h"#include "PolyLineEntityItem.h"#include "EntityTree.h" #include "EntityEditPacketSender.h"#include "EntitiesScriptEngineProvider.h"#include "EntityItemProperties.h" class EntityTree;class MeshProxy;class ScriptContext;class ScriptEngine; extern const QString GRABBABLE_USER_DATA;extern const QString NOT_GRABBABLE_USER_DATA; // helper factory to compose standardized, async metadata queries for "magic" Entity properties// like .script and .serverScripts. This is used for automated testing of core scripting features// as well as to provide early adopters a self-discoverable, consistent way to diagnose common// problems with their own Entity scripts.class EntityPropertyMetadataRequest {public: EntityPropertyMetadataRequest(ScriptManager* manager) : _scriptManager(manager){}; bool script(EntityItemID entityID, const ScriptValue& handler); bool serverScripts(EntityItemID entityID, const ScriptValue& handler);private: QPointer<ScriptManager> _scriptManager;}; /*@jsdoc * The result of a {@link Entities.findRayIntersection|findRayIntersection} search using a {@link PickRay}. * @typedef {object} Entities.RayToEntityIntersectionResult * @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected an entity, <code>false</code> if it * didn't. * @property {boolean} accurate - Is always <code>true</code>. * @property {Uuid} entityID - The ID if the entity intersected, if any, otherwise <code>null</code>. * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. * @property {Vec3} intersection - The intersection point. * @property {Vec3} surfaceNormal - The surface normal of the entity at the intersection point. * @property {BoxFace} face - The face of the entity's axis-aligned box that the ray intersects. * @property {object} extraInfo - Extra information depending on the entity intersected. Currently, only <code>Model</code> * entities provide extra information, and the information provided depends on the <code>precisionPicking</code> parameter * value that the search function was called with. */// "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock.class RayToEntityIntersectionResult {public: bool intersects { false }; bool accurate { true }; QUuid entityID; float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 intersection { glm::vec3(0.0f, 0.0f, 0.0f) }; glm::vec3 surfaceNormal { glm::vec3(0.0f, 1.0f, 0.0f) }; QVariantMap extraInfo;};Q_DECLARE_METATYPE(RayToEntityIntersectionResult)ScriptValue RayToEntityIntersectionResultToScriptValue(ScriptEngine* engine, const RayToEntityIntersectionResult& results);bool RayToEntityIntersectionResultFromScriptValue(const ScriptValue& object, RayToEntityIntersectionResult& results); class ParabolaToEntityIntersectionResult {public: bool intersects { false }; bool accurate { true }; QUuid entityID; float distance { 0.0f }; float parabolicDistance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 intersection; glm::vec3 surfaceNormal; QVariantMap extraInfo;}; /*@jsdoc * The <code>Entities</code> API provides facilities to create and interact with entities. Entities are 2D or 3D objects * displayed in-world. Depending on their {@link Entities.EntityHostType|EntityHostType}, they may persist in the domain as * "domain" entities, travel to different domains with a user as "avatar" entities, or be visible only to an individual user as * "local" entities (a.k.a. "overlays"). * * <p>Note: For Interface, avatar, and client entity scripts, the entities available to scripts are those that Interface has * displayed and so knows about. For assignment client scripts, the entities available are those that are "seen" by the * {@link EntityViewer}. For entity server scripts, all entities are available.</p> * * <h3>Entity Types & Properties</h3> * * <p>For a list of the entity types that you can use, see {@link Entities.EntityType|Entity Types}.</p> * * <p>For the properties of the different entity types, see {@link Entities.EntityProperties|Entity Properties}. Some properties are universal to all entity types, and some are specific to particular entity types.</p> * * <h3>Entity Methods</h3> * * <p>Some of the API's signals correspond to entity methods that are called, if present, in the entity being interacted with. * The client or server entity script must expose them as a property. However, unlike {@link Entities.callEntityMethod}, server * entity scripts do not need to list them in an <code>remotelyCallable</code> property. The entity methods are called with * parameters per their corresponding signal.</p> * <table> * <thead> * <tr><th>Method Name</th><th>Corresponding Signal</th></tr> * </thead> * <tbody> * <tr><td><code>clickDownOnEntity</code></td><td>{@link Entities.clickDownOnEntity}</td></tr> * <tr><td><code>clickReleaseOnEntity</code></td><td>{@link Entities.clickReleaseOnEntity}</td></tr> * <tr><td><code>collisionWithEntity</code></td><td>{@link Entities.collisionWithEntity}</td></tr> * <tr><td><code>enterEntity</code></td><td>{@link Entities.enterEntity}</td></tr> * <tr><td><code>holdingClickOnEntity</code></td><td>{@link Entities.holdingClickOnEntity}</td></tr> * <tr><td><code>hoverEnterEntity</code></td><td>{@link Entities.hoverEnterEntity}</td></tr> * <tr><td><code>hoverLeaveEntity</code></td><td>{@link Entities.hoverLeaveEntity}</td></tr> * <tr><td><code>hoverOverEntity</code></td><td>{@link Entities.hoverOverEntity}</td></tr> * <tr><td><code>leaveEntity</code></td><td>{@link Entities.leaveEntity}</td></tr> * <tr><td><code>mouseDoublePressOnEntity</code></td><td>{@link Entities.mouseDoublePressOnEntity}</td></tr> * <tr><td><code>mouseMoveOnEntity</code></td><td>{@link Entities.mouseMoveOnEntity}</td></tr> * <tr><td><code>mouseMoveEvent</code></td><td><span class="important">Deprecated: Use <code>mouseMoveOnEntity</code> * instead.</span></td></tr> * <tr><td><code>mousePressOnEntity</code></td><td>{@link Entities.mousePressOnEntity}</td></tr> * <tr><td><code>mouseReleaseOnEntity</code></td><td>{@link Entities.mouseReleaseOnEntity}</td></tr> * </tbody> * </table> * <p>See {@link Entities.clickDownOnEntity} for an example.</p> * * @namespace Entities * * @hifi-interface * @hifi-client-entity * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * * @property {Uuid} keyboardFocusEntity - The {@link Entities.EntityProperties-Web|Web} entity that has keyboard focus. If no * Web entity has keyboard focus, returns <code>null</code>; set to <code>null</code> or {@link Uuid(0)|Uuid.NONE} to clear * keyboard focus. *//// handles scripting of Entity commands from JS passed to assigned clientsclass EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity) friend EntityPropertyMetadataRequest;public: EntityScriptingInterface(bool bidOnSimulationOwnership); class ActivityTracking { public: int addedEntityCount { 0 }; int deletedEntityCount { 0 }; int editedEntityCount { 0 }; }; EntityEditPacketSender* getEntityPacketSender() const { return (EntityEditPacketSender*)getPacketSender(); } virtual NodeType_t getServerNodeType() const override { return NodeType::EntityServer; } virtual OctreeEditPacketSender* createPacketSender() override { return new EntityEditPacketSender(); } void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setPersistentEntitiesScriptEngine(std::shared_ptr<EntitiesScriptEngineProvider> manager); void setNonPersistentEntitiesScriptEngine(std::shared_ptr<EntitiesScriptEngineProvider> manager); void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } RayToEntityIntersectionResult evalRayIntersectionVector(const PickRay& ray, PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard); ParabolaToEntityIntersectionResult evalParabolaIntersectionVector(const PickParabola& parabola, PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard); /*@jsdoc * Gets the properties of multiple entities. * @function Entities.getMultipleEntityProperties * @param {Uuid[]} entityIDs - The IDs of the entities to get the properties of. * @param {string[]|string} [desiredProperties=[]] - The name or names of the properties to get. For properties that are * objects (e.g., the <code>"keyLight"</code> property), use the property and subproperty names in dot notation (e.g., * <code>"keyLight.color"</code>). Getting all subproperties with the name of an object is currently not supported (e.g., * passing the <code>"keyLight"</code> property only). * @returns {Entities.EntityProperties[]} The specified properties of each entity for each entity that can be found. If * none of the entities can be found, then an empty array is returned. If no properties are specified, then all * properties are returned. * @example <caption>Retrieve the names of the nearby entities</caption> * var SEARCH_RADIUS = 50; // meters * var entityIDs = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); * var propertySets = Entities.getMultipleEntityProperties(entityIDs, "name"); * print("Nearby entity names: " + JSON.stringify(propertySets)); */ static ScriptValue getMultipleEntityProperties(ScriptContext* context, ScriptEngine* engine); ScriptValue getMultipleEntityPropertiesInternal(ScriptEngine* engine, QVector<QUuid> entityIDs, const ScriptValue& extendedDesiredProperties); QUuid addEntityInternal(const EntityItemProperties& properties, entity::HostType entityHostType); public slots: /*@jsdoc * Checks whether or not the script can change the <code>locked</code> property of entities. Locked entities have their * <code>locked</code> property set to <code>true</code> and cannot be edited or deleted. * @function Entities.canAdjustLocks * @returns {boolean} <code>true</code> if the domain server will allow the script to change the <code>locked</code> * property of entities, otherwise <code>false</code>. * @example <caption>Lock an entity if you can.</caption> * if (Entities.canAdjustLocks()) { * Entities.editEntity(entityID, { locked: true }); * } else { * Window.alert("You do not have the permissions to set an entity locked!"); * } */ Q_INVOKABLE bool canAdjustLocks(); /*@jsdoc * Checks whether or not the script can rez (create) new entities in the domain. * @function Entities.canRez * @returns {boolean} <code>true</code> if the domain server will allow the script to rez (create) new entities, otherwise * <code>false</code>. */ Q_INVOKABLE bool canRez(); /*@jsdoc * Checks whether or not the script can rez (create) new temporary entities in the domain. Temporary entities are entities * with a finite <code>lifetime</code> property value set. * @function Entities.canRezTmp * @returns {boolean} <code>true</code> if the domain server will allow the script to rez (create) new temporary entities, * otherwise <code>false</code>. */ Q_INVOKABLE bool canRezTmp(); /*@jsdoc * Checks whether or not the script can make changes to the asset server's assets. * @function Entities.canWriteAssets * @returns {boolean} <code>true</code> if the domain server will allow the script to make changes to the asset server's * assets, otherwise <code>false</code>. */ Q_INVOKABLE bool canWriteAssets(); /*@jsdoc * Checks whether or not the script can replace the domain's content set. * @function Entities.canReplaceContent * @returns {boolean} <code>true</code> if the domain server will allow the script to replace the domain's content set, * otherwise <code>false</code>. */ Q_INVOKABLE bool canReplaceContent(); /*@jsdoc * Checks whether or not the script can get and set the <code>privateUserData</code> property of entities. * @function Entities.canGetAndSetPrivateUserData * @returns {boolean} <code>true</code> if the domain server will allow the script to get and set the * <code>privateUserData</code> property of entities, otherwise <code>false</code>. */ Q_INVOKABLE bool canGetAndSetPrivateUserData(); /*@jsdoc * Checks whether or not the script can rez avatar entities. * @function Entities.canRezAvatarEntities * @returns {boolean} <code>true</code> if the domain server will allow the script to rez avatar entities, * otherwise <code>false</code>. */ Q_INVOKABLE bool canRezAvatarEntities(); /*@jsdoc * Checks whether or not the script can view asset URLs * @function Entities.canViewAssetURLs * @returns {boolean} <code>true</code> if the domain server will allow the script to view asset URLs, * otherwise <code>false</code>. */ Q_INVOKABLE bool canViewAssetURLs(); /*@jsdoc * <p>How an entity is hosted and sent to others for display.</p> * <table> * <thead> * <tr><th>Value</th><th>Description</th></tr> * </thead> * <tbody> * <tr><td><code>"domain"</code></td><td>Domain entities are stored on the domain, are visible to everyone, and are * sent to everyone by the entity server.</td></tr> * <tr><td><code>"avatar"</code></td><td>Avatar entities are stored on an Interface client, are visible to everyone, * and are sent to everyone by the avatar mixer. They follow the client to each domain visited, displaying at the * same domain coordinates unless parented to the client's avatar.</td></tr> * <tr><td><code>"local"</code></td><td>Local entities are ephemeral — they aren't stored anywhere — and * are visible only to the client. They follow the client to each domain visited, displaying at the same domain * coordinates unless parented to the client's avatar. Additionally, local entities are always * collisionless.</td></tr> * </tbody> * </table> * @typedef {string} Entities.EntityHostType */ /*@jsdoc * Adds a new domain, avatar, or local entity. * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. * @param {Entities.EntityHostType} [entityHostType="domain"] - The type of entity to create. * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NONE}. * @example <caption>Create a box domain entity in front of your avatar.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * print("Entity created: " + entityID); */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString) { entity::HostType entityHostType; if (entityHostTypeString == "local") { entityHostType = entity::HostType::LOCAL; } else if (entityHostTypeString == "avatar") { entityHostType = entity::HostType::AVATAR; } else { entityHostType = entity::HostType::DOMAIN; } return addEntityInternal(properties, entityHostType); } /*@jsdoc * Adds a new avatar entity (<code>{@link Entities.EntityProperties|entityHostType}</code> property is * <code>"avatar"</code>) or domain entity (<code>{@link Entities.EntityProperties|entityHostType}</code> property is * <code>"domain"</code>). * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. * @param {boolean} [avatarEntity=false] - <code>true</code> to create an avatar entity, <code>false</code> to create a * domain entity. * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NONE}. */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool avatarEntity = false) { entity::HostType entityHostType = avatarEntity ? entity::HostType::AVATAR : entity::HostType::DOMAIN; return addEntityInternal(properties, entityHostType); } /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. // FIXME: Deprecate and remove from the API. Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity); /*@jsdoc * Creates a clone of an entity. The clone has the same properties as the original except that: it has a modified * <code>name</code> property, clone-related properties are set per the original entity's clone-related * {@link Entities.EntityProperties|properties} (e.g., <code>cloneLifetime</code>), and its clone-related properties are * set to their defaults. * <p>Domain entities must have their <code>cloneable</code> property value be <code>true</code> in order to be cloned. A * domain entity can be cloned by a client that doesn't have rez permissions in the domain.</p> * <p>Avatar entities must have their <code>cloneable</code> and <code>cloneAvatarEntity</code> property values be * <code>true</code> in order to be cloned.</p> * @function Entities.cloneEntity * @param {Uuid} entityID - The ID of the entity to clone. * @returns {Uuid} The ID of the new entity if successfully cloned, otherwise {@link Uuid(0)|Uuid.NONE}. */ Q_INVOKABLE QUuid cloneEntity(const QUuid& entityID); /*@jsdoc * Gets an entity's property values. * @function Entities.getEntityProperties * @param {Uuid} entityID - The ID of the entity to get the properties of. * @param {string|string[]} [desiredProperties=[]] - The name or names of the properties to get. For properties that are * objects (e.g., the <code>"keyLight"</code> property), use the property and subproperty names in dot notation (e.g., * <code>"keyLight.color"</code>). * @returns {Entities.EntityProperties} The specified properties of the entity if the entity can be found, otherwise an * empty object. If no properties are specified, then all properties are returned. * @example <caption>Report the color of a new box entity.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID); Q_INVOKABLE ScriptValue getEntityProperties(const QUuid& entityID, const ScriptValue &desiredProperties); /** * @brief Internal function to get entity properties. * * It's being called by EntityScriptingInterface::getEntityProperties * and also from C++ side in several places in the source code. * * @param entityID The ID of the entity to get the properties of. * @param desiredProperties Flags representing requested entity properties * @param returnNothingOnEmptyPropertyFlags If this parameter is false and property flags are empty, then all possible * properties will get returned. This is needed because we divide properties requested through getEntityProperties into * real properties and pseudo properties. Only real properties are passed here as desiredProperties, so if user requests * only pseudo properties, then desiredProperties will be empty. In such case we need to pass true * as returnNothingOnEmptyPropertyFlags to avoid mistakenly returning all the properties. * @return EntityItemProperties Requested properties of the entity if it can be found. */ Q_INVOKABLE EntityItemProperties getEntityPropertiesInternal(const QUuid& entityID, EntityPropertyFlags desiredProperties, bool returnNothingOnEmptyPropertyFlags); //Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties); /*@jsdoc * Edits an entity, changing one or more of its property values. * @function Entities.editEntity * @param {Uuid} entityID - The ID of the entity to edit. * @param {Entities.EntityProperties} properties - The new property values. * @returns {Uuid} The ID of the entity if the edit was successful, otherwise <code>null</code> or {@link Uuid|Uuid.NONE}. * @example <caption>Change the color of an entity.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); * * Script.setTimeout(function () { // Wait for the entity to be created before editing. * Entities.editEntity(entityID, { * color: { red: 255, green: 0, blue: 0 } * }); * properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); * }, 50); */ Q_INVOKABLE QUuid editEntity(const QUuid& entityID, const EntityItemProperties& properties); /*@jsdoc * Deletes an entity. * @function Entities.deleteEntity * @param {Uuid} entityID - The ID of the entity to delete. * @example <caption>Delete an entity a few seconds after creating it.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 } * }); * * Script.setTimeout(function () { * Entities.deleteEntity(entityID); * }, 3000); */ Q_INVOKABLE void deleteEntity(const QUuid& entityID); /*@jsdoc * Gets an entity's type. * @function Entities.getEntityType * @param {Uuid} id - The ID of the entity to get the type of. * @returns {Entities.EntityType} The type of the entity. */ Q_INVOKABLE QString getEntityType(const QUuid& entityID); /*@jsdoc * Gets an entity's script object. In particular, this is useful for accessing a {@link Entities.EntityProperties-Web|Web} * entity's HTML <code>EventBridge</code> script object to exchange messages with the web page script. * <p>To send a message from an Interface script to a Web entity over its event bridge:</p> * <pre class="prettyprint"><code>var entityObject = Entities.getEntityObject(entityID); * entityObject.emitScriptEvent(message);</code></pre> * <p>To receive a message from a Web entity over its event bridge in an Interface script:</p> * <pre class="prettyprint"><code>var entityObject = Entities.getentityObject(entityID); * entityObject.webEventReceived.connect(function(message) { * ... * };</code></pre> * <p>Alternatively, you can use {@link Entities.emitScriptEvent} and {@link Entities.webEventReceived} to exchange * messages with a Web entity over its event bridge.</p> * @function Entities.getEntityObject * @param {Uuid} id - The ID of the entity to get the script object for. * @returns {object} The script object for the entity if found. * @example <caption>Exchange messages with a Web entity with an HTML source.</caption> * // HTML file, name: "webEntity.html". * <!DOCTYPE html> * <html>Lines 1–500 of 2781. Load more
// // EntityScriptingInterface.h // libraries/entities/src // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // // TODO: How will we handle collision callbacks with Entities // #ifndef hifi_EntityScriptingInterface_h #define hifi_EntityScriptingInterface_h #include <memory> #include <QtCore/QObject> #include <QtCore/QStringList> #include <QtCore/QSharedPointer> #include <QtQml/QJSValue> #include <QtQml/QJSValueList> #include <QtCore/QSharedPointer> #include <DependencyManager.h> #include <Octree.h> #include <OctreeScriptingInterface.h> #include <RegisteredMetaTypes.h> #include "PointerEvent.h" #include <PickFilter.h> #include <ScriptManager.h> #include <ScriptValue.h> #include <CanvasCommand.h> #include "PolyVoxEntityItem.h" #include "LineEntityItem.h" #include "PolyLineEntityItem.h" #include "EntityTree.h" #include "EntityEditPacketSender.h" #include "EntitiesScriptEngineProvider.h" #include "EntityItemProperties.h" class EntityTree; class MeshProxy; class ScriptContext; class ScriptEngine; extern const QString GRABBABLE_USER_DATA; extern const QString NOT_GRABBABLE_USER_DATA; // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features // as well as to provide early adopters a self-discoverable, consistent way to diagnose common // problems with their own Entity scripts. class EntityPropertyMetadataRequest { public: EntityPropertyMetadataRequest(ScriptManager* manager) : _scriptManager(manager){}; bool script(EntityItemID entityID, const ScriptValue& handler); bool serverScripts(EntityItemID entityID, const ScriptValue& handler); private: QPointer<ScriptManager> _scriptManager; }; /*@jsdoc * The result of a {@link Entities.findRayIntersection|findRayIntersection} search using a {@link PickRay}. * @typedef {object} Entities.RayToEntityIntersectionResult * @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected an entity, <code>false</code> if it * didn't. * @property {boolean} accurate - Is always <code>true</code>. * @property {Uuid} entityID - The ID if the entity intersected, if any, otherwise <code>null</code>. * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. * @property {Vec3} intersection - The intersection point. * @property {Vec3} surfaceNormal - The surface normal of the entity at the intersection point. * @property {BoxFace} face - The face of the entity's axis-aligned box that the ray intersects. * @property {object} extraInfo - Extra information depending on the entity intersected. Currently, only <code>Model</code> * entities provide extra information, and the information provided depends on the <code>precisionPicking</code> parameter * value that the search function was called with. */ // "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock. class RayToEntityIntersectionResult { public: bool intersects { false }; bool accurate { true }; QUuid entityID; float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 intersection { glm::vec3(0.0f, 0.0f, 0.0f) }; glm::vec3 surfaceNormal { glm::vec3(0.0f, 1.0f, 0.0f) }; QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToEntityIntersectionResult) ScriptValue RayToEntityIntersectionResultToScriptValue(ScriptEngine* engine, const RayToEntityIntersectionResult& results); bool RayToEntityIntersectionResultFromScriptValue(const ScriptValue& object, RayToEntityIntersectionResult& results); class ParabolaToEntityIntersectionResult { public: bool intersects { false }; bool accurate { true }; QUuid entityID; float distance { 0.0f }; float parabolicDistance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 intersection; glm::vec3 surfaceNormal; QVariantMap extraInfo; }; /*@jsdoc * The <code>Entities</code> API provides facilities to create and interact with entities. Entities are 2D or 3D objects * displayed in-world. Depending on their {@link Entities.EntityHostType|EntityHostType}, they may persist in the domain as * "domain" entities, travel to different domains with a user as "avatar" entities, or be visible only to an individual user as * "local" entities (a.k.a. "overlays"). * * <p>Note: For Interface, avatar, and client entity scripts, the entities available to scripts are those that Interface has * displayed and so knows about. For assignment client scripts, the entities available are those that are "seen" by the * {@link EntityViewer}. For entity server scripts, all entities are available.</p> * * <h3>Entity Types & Properties</h3> * * <p>For a list of the entity types that you can use, see {@link Entities.EntityType|Entity Types}.</p> * * <p>For the properties of the different entity types, see {@link Entities.EntityProperties|Entity Properties}. Some properties are universal to all entity types, and some are specific to particular entity types.</p> * * <h3>Entity Methods</h3> * * <p>Some of the API's signals correspond to entity methods that are called, if present, in the entity being interacted with. * The client or server entity script must expose them as a property. However, unlike {@link Entities.callEntityMethod}, server * entity scripts do not need to list them in an <code>remotelyCallable</code> property. The entity methods are called with * parameters per their corresponding signal.</p> * <table> * <thead> * <tr><th>Method Name</th><th>Corresponding Signal</th></tr> * </thead> * <tbody> * <tr><td><code>clickDownOnEntity</code></td><td>{@link Entities.clickDownOnEntity}</td></tr> * <tr><td><code>clickReleaseOnEntity</code></td><td>{@link Entities.clickReleaseOnEntity}</td></tr> * <tr><td><code>collisionWithEntity</code></td><td>{@link Entities.collisionWithEntity}</td></tr> * <tr><td><code>enterEntity</code></td><td>{@link Entities.enterEntity}</td></tr> * <tr><td><code>holdingClickOnEntity</code></td><td>{@link Entities.holdingClickOnEntity}</td></tr> * <tr><td><code>hoverEnterEntity</code></td><td>{@link Entities.hoverEnterEntity}</td></tr> * <tr><td><code>hoverLeaveEntity</code></td><td>{@link Entities.hoverLeaveEntity}</td></tr> * <tr><td><code>hoverOverEntity</code></td><td>{@link Entities.hoverOverEntity}</td></tr> * <tr><td><code>leaveEntity</code></td><td>{@link Entities.leaveEntity}</td></tr> * <tr><td><code>mouseDoublePressOnEntity</code></td><td>{@link Entities.mouseDoublePressOnEntity}</td></tr> * <tr><td><code>mouseMoveOnEntity</code></td><td>{@link Entities.mouseMoveOnEntity}</td></tr> * <tr><td><code>mouseMoveEvent</code></td><td><span class="important">Deprecated: Use <code>mouseMoveOnEntity</code> * instead.</span></td></tr> * <tr><td><code>mousePressOnEntity</code></td><td>{@link Entities.mousePressOnEntity}</td></tr> * <tr><td><code>mouseReleaseOnEntity</code></td><td>{@link Entities.mouseReleaseOnEntity}</td></tr> * </tbody> * </table> * <p>See {@link Entities.clickDownOnEntity} for an example.</p> * * @namespace Entities * * @hifi-interface * @hifi-client-entity * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * * @property {Uuid} keyboardFocusEntity - The {@link Entities.EntityProperties-Web|Web} entity that has keyboard focus. If no * Web entity has keyboard focus, returns <code>null</code>; set to <code>null</code> or {@link Uuid(0)|Uuid.NONE} to clear * keyboard focus. */ /// handles scripting of Entity commands from JS passed to assigned clients class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity) friend EntityPropertyMetadataRequest; public: EntityScriptingInterface(bool bidOnSimulationOwnership); class ActivityTracking { public: int addedEntityCount { 0 }; int deletedEntityCount { 0 }; int editedEntityCount { 0 }; }; EntityEditPacketSender* getEntityPacketSender() const { return (EntityEditPacketSender*)getPacketSender(); } virtual NodeType_t getServerNodeType() const override { return NodeType::EntityServer; } virtual OctreeEditPacketSender* createPacketSender() override { return new EntityEditPacketSender(); } void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setPersistentEntitiesScriptEngine(std::shared_ptr<EntitiesScriptEngineProvider> manager); void setNonPersistentEntitiesScriptEngine(std::shared_ptr<EntitiesScriptEngineProvider> manager); void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } RayToEntityIntersectionResult evalRayIntersectionVector(const PickRay& ray, PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard); ParabolaToEntityIntersectionResult evalParabolaIntersectionVector(const PickParabola& parabola, PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard); /*@jsdoc * Gets the properties of multiple entities. * @function Entities.getMultipleEntityProperties * @param {Uuid[]} entityIDs - The IDs of the entities to get the properties of. * @param {string[]|string} [desiredProperties=[]] - The name or names of the properties to get. For properties that are * objects (e.g., the <code>"keyLight"</code> property), use the property and subproperty names in dot notation (e.g., * <code>"keyLight.color"</code>). Getting all subproperties with the name of an object is currently not supported (e.g., * passing the <code>"keyLight"</code> property only). * @returns {Entities.EntityProperties[]} The specified properties of each entity for each entity that can be found. If * none of the entities can be found, then an empty array is returned. If no properties are specified, then all * properties are returned. * @example <caption>Retrieve the names of the nearby entities</caption> * var SEARCH_RADIUS = 50; // meters * var entityIDs = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); * var propertySets = Entities.getMultipleEntityProperties(entityIDs, "name"); * print("Nearby entity names: " + JSON.stringify(propertySets)); */ static ScriptValue getMultipleEntityProperties(ScriptContext* context, ScriptEngine* engine); ScriptValue getMultipleEntityPropertiesInternal(ScriptEngine* engine, QVector<QUuid> entityIDs, const ScriptValue& extendedDesiredProperties); QUuid addEntityInternal(const EntityItemProperties& properties, entity::HostType entityHostType); public slots: /*@jsdoc * Checks whether or not the script can change the <code>locked</code> property of entities. Locked entities have their * <code>locked</code> property set to <code>true</code> and cannot be edited or deleted. * @function Entities.canAdjustLocks * @returns {boolean} <code>true</code> if the domain server will allow the script to change the <code>locked</code> * property of entities, otherwise <code>false</code>. * @example <caption>Lock an entity if you can.</caption> * if (Entities.canAdjustLocks()) { * Entities.editEntity(entityID, { locked: true }); * } else { * Window.alert("You do not have the permissions to set an entity locked!"); * } */ Q_INVOKABLE bool canAdjustLocks(); /*@jsdoc * Checks whether or not the script can rez (create) new entities in the domain. * @function Entities.canRez * @returns {boolean} <code>true</code> if the domain server will allow the script to rez (create) new entities, otherwise * <code>false</code>. */ Q_INVOKABLE bool canRez(); /*@jsdoc * Checks whether or not the script can rez (create) new temporary entities in the domain. Temporary entities are entities * with a finite <code>lifetime</code> property value set. * @function Entities.canRezTmp * @returns {boolean} <code>true</code> if the domain server will allow the script to rez (create) new temporary entities, * otherwise <code>false</code>. */ Q_INVOKABLE bool canRezTmp(); /*@jsdoc * Checks whether or not the script can make changes to the asset server's assets. * @function Entities.canWriteAssets * @returns {boolean} <code>true</code> if the domain server will allow the script to make changes to the asset server's * assets, otherwise <code>false</code>. */ Q_INVOKABLE bool canWriteAssets(); /*@jsdoc * Checks whether or not the script can replace the domain's content set. * @function Entities.canReplaceContent * @returns {boolean} <code>true</code> if the domain server will allow the script to replace the domain's content set, * otherwise <code>false</code>. */ Q_INVOKABLE bool canReplaceContent(); /*@jsdoc * Checks whether or not the script can get and set the <code>privateUserData</code> property of entities. * @function Entities.canGetAndSetPrivateUserData * @returns {boolean} <code>true</code> if the domain server will allow the script to get and set the * <code>privateUserData</code> property of entities, otherwise <code>false</code>. */ Q_INVOKABLE bool canGetAndSetPrivateUserData(); /*@jsdoc * Checks whether or not the script can rez avatar entities. * @function Entities.canRezAvatarEntities * @returns {boolean} <code>true</code> if the domain server will allow the script to rez avatar entities, * otherwise <code>false</code>. */ Q_INVOKABLE bool canRezAvatarEntities(); /*@jsdoc * Checks whether or not the script can view asset URLs * @function Entities.canViewAssetURLs * @returns {boolean} <code>true</code> if the domain server will allow the script to view asset URLs, * otherwise <code>false</code>. */ Q_INVOKABLE bool canViewAssetURLs(); /*@jsdoc * <p>How an entity is hosted and sent to others for display.</p> * <table> * <thead> * <tr><th>Value</th><th>Description</th></tr> * </thead> * <tbody> * <tr><td><code>"domain"</code></td><td>Domain entities are stored on the domain, are visible to everyone, and are * sent to everyone by the entity server.</td></tr> * <tr><td><code>"avatar"</code></td><td>Avatar entities are stored on an Interface client, are visible to everyone, * and are sent to everyone by the avatar mixer. They follow the client to each domain visited, displaying at the * same domain coordinates unless parented to the client's avatar.</td></tr> * <tr><td><code>"local"</code></td><td>Local entities are ephemeral — they aren't stored anywhere — and * are visible only to the client. They follow the client to each domain visited, displaying at the same domain * coordinates unless parented to the client's avatar. Additionally, local entities are always * collisionless.</td></tr> * </tbody> * </table> * @typedef {string} Entities.EntityHostType */ /*@jsdoc * Adds a new domain, avatar, or local entity. * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. * @param {Entities.EntityHostType} [entityHostType="domain"] - The type of entity to create. * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NONE}. * @example <caption>Create a box domain entity in front of your avatar.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * print("Entity created: " + entityID); */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString) { entity::HostType entityHostType; if (entityHostTypeString == "local") { entityHostType = entity::HostType::LOCAL; } else if (entityHostTypeString == "avatar") { entityHostType = entity::HostType::AVATAR; } else { entityHostType = entity::HostType::DOMAIN; } return addEntityInternal(properties, entityHostType); } /*@jsdoc * Adds a new avatar entity (<code>{@link Entities.EntityProperties|entityHostType}</code> property is * <code>"avatar"</code>) or domain entity (<code>{@link Entities.EntityProperties|entityHostType}</code> property is * <code>"domain"</code>). * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. * @param {boolean} [avatarEntity=false] - <code>true</code> to create an avatar entity, <code>false</code> to create a * domain entity. * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NONE}. */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool avatarEntity = false) { entity::HostType entityHostType = avatarEntity ? entity::HostType::AVATAR : entity::HostType::DOMAIN; return addEntityInternal(properties, entityHostType); } /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. // FIXME: Deprecate and remove from the API. Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity); /*@jsdoc * Creates a clone of an entity. The clone has the same properties as the original except that: it has a modified * <code>name</code> property, clone-related properties are set per the original entity's clone-related * {@link Entities.EntityProperties|properties} (e.g., <code>cloneLifetime</code>), and its clone-related properties are * set to their defaults. * <p>Domain entities must have their <code>cloneable</code> property value be <code>true</code> in order to be cloned. A * domain entity can be cloned by a client that doesn't have rez permissions in the domain.</p> * <p>Avatar entities must have their <code>cloneable</code> and <code>cloneAvatarEntity</code> property values be * <code>true</code> in order to be cloned.</p> * @function Entities.cloneEntity * @param {Uuid} entityID - The ID of the entity to clone. * @returns {Uuid} The ID of the new entity if successfully cloned, otherwise {@link Uuid(0)|Uuid.NONE}. */ Q_INVOKABLE QUuid cloneEntity(const QUuid& entityID); /*@jsdoc * Gets an entity's property values. * @function Entities.getEntityProperties * @param {Uuid} entityID - The ID of the entity to get the properties of. * @param {string|string[]} [desiredProperties=[]] - The name or names of the properties to get. For properties that are * objects (e.g., the <code>"keyLight"</code> property), use the property and subproperty names in dot notation (e.g., * <code>"keyLight.color"</code>). * @returns {Entities.EntityProperties} The specified properties of the entity if the entity can be found, otherwise an * empty object. If no properties are specified, then all properties are returned. * @example <caption>Report the color of a new box entity.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID); Q_INVOKABLE ScriptValue getEntityProperties(const QUuid& entityID, const ScriptValue &desiredProperties); /** * @brief Internal function to get entity properties. * * It's being called by EntityScriptingInterface::getEntityProperties * and also from C++ side in several places in the source code. * * @param entityID The ID of the entity to get the properties of. * @param desiredProperties Flags representing requested entity properties * @param returnNothingOnEmptyPropertyFlags If this parameter is false and property flags are empty, then all possible * properties will get returned. This is needed because we divide properties requested through getEntityProperties into * real properties and pseudo properties. Only real properties are passed here as desiredProperties, so if user requests * only pseudo properties, then desiredProperties will be empty. In such case we need to pass true * as returnNothingOnEmptyPropertyFlags to avoid mistakenly returning all the properties. * @return EntityItemProperties Requested properties of the entity if it can be found. */ Q_INVOKABLE EntityItemProperties getEntityPropertiesInternal(const QUuid& entityID, EntityPropertyFlags desiredProperties, bool returnNothingOnEmptyPropertyFlags); //Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties); /*@jsdoc * Edits an entity, changing one or more of its property values. * @function Entities.editEntity * @param {Uuid} entityID - The ID of the entity to edit. * @param {Entities.EntityProperties} properties - The new property values. * @returns {Uuid} The ID of the entity if the edit was successful, otherwise <code>null</code> or {@link Uuid|Uuid.NONE}. * @example <caption>Change the color of an entity.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); * * Script.setTimeout(function () { // Wait for the entity to be created before editing. * Entities.editEntity(entityID, { * color: { red: 255, green: 0, blue: 0 } * }); * properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); * }, 50); */ Q_INVOKABLE QUuid editEntity(const QUuid& entityID, const EntityItemProperties& properties); /*@jsdoc * Deletes an entity. * @function Entities.deleteEntity * @param {Uuid} entityID - The ID of the entity to delete. * @example <caption>Delete an entity a few seconds after creating it.</caption> * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * dimensions: { x: 0.5, y: 0.5, z: 0.5 } * }); * * Script.setTimeout(function () { * Entities.deleteEntity(entityID); * }, 3000); */ Q_INVOKABLE void deleteEntity(const QUuid& entityID); /*@jsdoc * Gets an entity's type. * @function Entities.getEntityType * @param {Uuid} id - The ID of the entity to get the type of. * @returns {Entities.EntityType} The type of the entity. */ Q_INVOKABLE QString getEntityType(const QUuid& entityID); /*@jsdoc * Gets an entity's script object. In particular, this is useful for accessing a {@link Entities.EntityProperties-Web|Web} * entity's HTML <code>EventBridge</code> script object to exchange messages with the web page script. * <p>To send a message from an Interface script to a Web entity over its event bridge:</p> * <pre class="prettyprint"><code>var entityObject = Entities.getEntityObject(entityID); * entityObject.emitScriptEvent(message);</code></pre> * <p>To receive a message from a Web entity over its event bridge in an Interface script:</p> * <pre class="prettyprint"><code>var entityObject = Entities.getentityObject(entityID); * entityObject.webEventReceived.connect(function(message) { * ... * };</code></pre> * <p>Alternatively, you can use {@link Entities.emitScriptEvent} and {@link Entities.webEventReceived} to exchange * messages with a Web entity over its event bridge.</p> * @function Entities.getEntityObject * @param {Uuid} id - The ID of the entity to get the script object for. * @returns {object} The script object for the entity if found. * @example <caption>Exchange messages with a Web entity with an HTML source.</caption> * // HTML file, name: "webEntity.html". * <!DOCTYPE html> * <html>