mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 06:44:06 +02:00
first pass cloneables WIP
This commit is contained in:
parent
b9656e5c72
commit
3241c18271
19 changed files with 410 additions and 164 deletions
|
@ -44,6 +44,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
|||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||
PacketType::EntityClone,
|
||||
PacketType::EntityEdit,
|
||||
PacketType::EntityErase,
|
||||
PacketType::EntityPhysics,
|
||||
|
|
|
@ -34,7 +34,7 @@ void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<Received
|
|||
}
|
||||
|
||||
void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) {
|
||||
if (type == PacketType::EntityAdd || type == PacketType::EntityEdit || type == PacketType::EntityPhysics) {
|
||||
if (type == PacketType::EntityAdd || type == PacketType::EntityClone || type == PacketType::EntityEdit || type == PacketType::EntityPhysics) {
|
||||
EntityItem::adjustEditPacketForClockSkew(buffer, clockSkew);
|
||||
}
|
||||
}
|
||||
|
@ -162,3 +162,15 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI
|
|||
queueOctreeEditMessage(PacketType::EntityErase, bufferOut);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityEditPacketSender::queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID) {
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityClone), 0);
|
||||
|
||||
if (EntityItemProperties::encodeCloneEntityMessage(entityIDToClone, newEntityID, bufferOut)) {
|
||||
queueOctreeEditMessage(PacketType::EntityClone, bufferOut);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
|
||||
|
||||
void queueEraseEntityMessage(const EntityItemID& entityItemID);
|
||||
void queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID);
|
||||
|
||||
// My server type is the model server
|
||||
virtual char getMyNodeType() const override { return NodeType::EntityServer; }
|
||||
|
|
|
@ -124,6 +124,11 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
|
||||
requestedProperties += PROP_LAST_EDITED_BY;
|
||||
|
||||
requestedProperties += PROP_CLONEABLE;
|
||||
requestedProperties += PROP_CLONEABLE_LIFETIME;
|
||||
requestedProperties += PROP_CLONEABLE_LIMIT;
|
||||
requestedProperties += PROP_CLONEABLE_DYNAMIC;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -288,6 +293,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy());
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, getCloneable());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIFETIME, getCloneableLifetime());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIMIT, getCloneableLimit());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_DYNAMIC, getCloneableDynamic());
|
||||
|
||||
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
|
||||
requestedProperties,
|
||||
propertyFlags,
|
||||
|
@ -848,6 +858,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
|
||||
READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy);
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_CLONEABLE, bool, setCloneable);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONEABLE_LIFETIME, float, setCloneableLifetime);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONEABLE_LIMIT, float, setCloneableLimit);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONEABLE_DYNAMIC, bool, setCloneableDynamic);
|
||||
|
||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
|
||||
propertyFlags, overwriteLocalData, somethingChanged);
|
||||
|
||||
|
@ -1275,6 +1290,11 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
|
|||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy);
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneableLifetime, getCloneableLifetime);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneableLimit, getCloneableLimit);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneableDynamic, getCloneableDynamic);
|
||||
|
||||
properties._defaultSettings = false;
|
||||
|
||||
return properties;
|
||||
|
@ -1382,6 +1402,11 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy);
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneable, setCloneable);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneableLifetime, setCloneableLifetime);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneableLimit, setCloneableLimit);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneableDynamic, setCloneableDynamic);
|
||||
|
||||
if (updateQueryAACube()) {
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
@ -2975,3 +3000,76 @@ std::unordered_map<std::string, graphics::MultiMaterial> EntityItem::getMaterial
|
|||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
bool EntityItem::getCloneable() const {
|
||||
bool result;
|
||||
withReadLock([&] {
|
||||
result = _cloneable;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneable(bool value) {
|
||||
withWriteLock([&] {
|
||||
_cloneable = value;
|
||||
});
|
||||
}
|
||||
|
||||
float EntityItem::getCloneableLifetime() const {
|
||||
float result;
|
||||
withReadLock([&] {
|
||||
result = _cloneableLifetime;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneableLifetime(float value) {
|
||||
withWriteLock([&] {
|
||||
_cloneableLifetime = value;
|
||||
});
|
||||
}
|
||||
|
||||
float EntityItem::getCloneableLimit() const {
|
||||
float result;
|
||||
withReadLock([&] {
|
||||
result = _cloneableLimit;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneableLimit(float value) {
|
||||
withWriteLock([&] {
|
||||
_cloneableLimit = value;
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityItem::getCloneableDynamic() const {
|
||||
bool result;
|
||||
withReadLock([&] {
|
||||
result = _cloneableDynamic;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneableDynamic(const bool value) {
|
||||
withWriteLock([&] {
|
||||
_cloneableDynamic = value;
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityItem::addCloneID(const QUuid& cloneID) {
|
||||
if (!_cloneIDs.contains(cloneID)) {
|
||||
_cloneIDs.append(cloneID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityItem::removeCloneID(const QUuid& cloneID) {
|
||||
int index = _cloneIDs.indexOf(cloneID);
|
||||
if (index > 0) {
|
||||
_cloneIDs.removeAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -341,6 +341,15 @@ public:
|
|||
quint32 getStaticCertificateVersion() const;
|
||||
void setStaticCertificateVersion(const quint32&);
|
||||
|
||||
bool getCloneable() const;
|
||||
void setCloneable(bool value);
|
||||
float getCloneableLifetime() const;
|
||||
void setCloneableLifetime(float value);
|
||||
float getCloneableLimit() const;
|
||||
void setCloneableLimit(float value);
|
||||
bool getCloneableDynamic() const;
|
||||
void setCloneableDynamic(const bool value);
|
||||
|
||||
// TODO: get rid of users of getRadius()...
|
||||
float getRadius() const;
|
||||
|
||||
|
@ -494,6 +503,12 @@ public:
|
|||
void setSimulationOwnershipExpiry(uint64_t expiry) { _simulationOwnershipExpiry = expiry; }
|
||||
uint64_t getSimulationOwnershipExpiry() const { return _simulationOwnershipExpiry; }
|
||||
|
||||
bool addCloneID(const QUuid& cloneID);
|
||||
bool removeCloneID(const QUuid& cloneID);
|
||||
const QList<QUuid>& getCloneIDs() const { return _cloneIDs; }
|
||||
void setCloneParent(const QUuid& cloneParentID) { _cloneParentID = cloneParentID; }
|
||||
const QUuid& getCloneParent() const { return _cloneParentID; }
|
||||
|
||||
signals:
|
||||
void requestRenderUpdate();
|
||||
|
||||
|
@ -648,6 +663,14 @@ protected:
|
|||
|
||||
bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera
|
||||
|
||||
bool _cloneable;
|
||||
float _cloneableLifetime;
|
||||
float _cloneableLimit;
|
||||
bool _cloneableDynamic;
|
||||
|
||||
QList<QUuid> _cloneIDs;
|
||||
QUuid _cloneParentID;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||
std::mutex _materialsLock;
|
||||
|
|
|
@ -436,6 +436,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
|
||||
CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
|
||||
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE, cloneable);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE_LIFETIME, cloneableLifetime);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE_LIMIT, cloneableLimit);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE_DYNAMIC, cloneableDynamic);
|
||||
|
||||
changedProperties += _animation.getChangedProperties();
|
||||
changedProperties += _keyLight.getChangedProperties();
|
||||
changedProperties += _ambientLight.getChangedProperties();
|
||||
|
@ -1430,6 +1435,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE, cloneable);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE_LIFETIME, cloneableLifetime);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE_LIMIT, cloneableLimit);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE_DYNAMIC, cloneableDynamic);
|
||||
|
||||
// Rendering info
|
||||
if (!skipDefaults && !strictSemantics) {
|
||||
QScriptValue renderInfo = engine->newObject();
|
||||
|
@ -1642,6 +1652,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneable, bool, setCloneable);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneableLifetime, float, setCloneableLifetime);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneableLimit, float, setCloneableLimit);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneableDynamic, bool, setCloneableDynamic);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -2017,6 +2032,11 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
|
||||
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE, Cloneable, cloneable, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE_LIFETIME, CloneableLifetime, cloneableLifetime, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE_LIMIT, CloneableLimit, cloneableLimit, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE_DYNAMIC, CloneableDynamic, cloneableDynamic, bool);
|
||||
|
||||
// FIXME - these are not yet handled
|
||||
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
|
||||
|
||||
|
@ -2331,6 +2351,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion());
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, properties.getCloneable());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIFETIME, properties.getCloneableLifetime());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_LIMIT, properties.getCloneableLimit());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE_DYNAMIC, properties.getCloneableDynamic());
|
||||
}
|
||||
|
||||
if (propertyCount > 0) {
|
||||
|
@ -2701,6 +2726,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion);
|
||||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE, bool, setCloneable);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE_LIFETIME, float, setCloneableLifetime);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE_LIMIT, float, setCloneableLimit);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE_DYNAMIC, bool, setCloneableDynamic);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
@ -2780,6 +2810,54 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt
|
|||
return true;
|
||||
}
|
||||
|
||||
bool EntityItemProperties::encodeCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID, QByteArray& buffer) {
|
||||
|
||||
char* copyAt = buffer.data();
|
||||
int outputLength = 0;
|
||||
|
||||
if (buffer.size() < (int)(sizeof(NUM_BYTES_RFC4122_UUID) * 2)) {
|
||||
qCDebug(entities) << "ERROR - encodeCloneEntityMessage() called with buffer that is too small!";
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(copyAt, entityIDToClone.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
|
||||
copyAt += NUM_BYTES_RFC4122_UUID;
|
||||
outputLength += NUM_BYTES_RFC4122_UUID;
|
||||
|
||||
memcpy(copyAt, newEntityID.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
|
||||
copyAt += NUM_BYTES_RFC4122_UUID;
|
||||
outputLength += NUM_BYTES_RFC4122_UUID;
|
||||
|
||||
buffer.resize(outputLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntityItemProperties::decodeCloneEntityMessage(const QByteArray& buffer, int& processedBytes, EntityItemID& entityIDToClone, EntityItemID& newEntityID) {
|
||||
|
||||
const unsigned char* packetData = (const unsigned char*)buffer.constData();
|
||||
const unsigned char* dataAt = packetData;
|
||||
size_t packetLength = buffer.size();
|
||||
processedBytes = 0;
|
||||
|
||||
if (NUM_BYTES_RFC4122_UUID * 2 > packetLength) {
|
||||
qCDebug(entities) << "EntityItemProperties::processEraseMessageDetails().... bailing because not enough bytes in buffer";
|
||||
return false; // bail to prevent buffer overflow
|
||||
}
|
||||
|
||||
QByteArray encodedID = buffer.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID);
|
||||
entityIDToClone = QUuid::fromRfc4122(encodedID);
|
||||
dataAt += encodedID.size();
|
||||
processedBytes += encodedID.size();
|
||||
|
||||
encodedID = buffer.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID);
|
||||
newEntityID = QUuid::fromRfc4122(encodedID);
|
||||
dataAt += encodedID.size();
|
||||
processedBytes += encodedID.size();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityItemProperties::markAllChanged() {
|
||||
_lastEditedByChanged = true;
|
||||
_simulationOwnerChanged = true;
|
||||
|
@ -2941,6 +3019,11 @@ void EntityItemProperties::markAllChanged() {
|
|||
|
||||
_dpiChanged = true;
|
||||
_relayParentJointsChanged = true;
|
||||
|
||||
_cloneableChanged = true;
|
||||
_cloneableLifetimeChanged = true;
|
||||
_cloneableLimitChanged = true;
|
||||
_cloneableDynamicChanged = true;
|
||||
}
|
||||
|
||||
// The minimum bounding box for the entity.
|
||||
|
@ -3373,6 +3456,20 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
out += "isUVModeStretch";
|
||||
}
|
||||
|
||||
if (cloneableChanged()) {
|
||||
out += "cloneable";
|
||||
}
|
||||
if (cloneableLifetimeChanged()) {
|
||||
out += "cloneableLifetime";
|
||||
}
|
||||
if (cloneableLimitChanged()) {
|
||||
out += "cloneableLimit";
|
||||
}
|
||||
if (cloneableDynamicChanged()) {
|
||||
out += "cloneableDynamic";
|
||||
}
|
||||
|
||||
|
||||
getAnimation().listChangedProperties(out);
|
||||
getKeyLight().listChangedProperties(out);
|
||||
getAmbientLight().listChangedProperties(out);
|
||||
|
|
|
@ -272,6 +272,11 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS);
|
||||
DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS);
|
||||
|
||||
DEFINE_PROPERTY(PROP_CLONEABLE, Cloneable, cloneable, bool, ENTITY_ITEM_CLONEABLE);
|
||||
DEFINE_PROPERTY(PROP_CLONEABLE_LIFETIME, CloneableLifetime, cloneableLifetime, float, ENTITY_ITEM_CLONEABLE_LIFETIME);
|
||||
DEFINE_PROPERTY(PROP_CLONEABLE_LIMIT, CloneableLimit, cloneableLimit, float, ENTITY_ITEM_CLONEABLE_LIMIT);
|
||||
DEFINE_PROPERTY(PROP_CLONEABLE_DYNAMIC, CloneableDynamic, cloneableDynamic, bool, ENTITY_ITEM_CLONEABLE_DYNAMIC);
|
||||
|
||||
static QString getComponentModeString(uint32_t mode);
|
||||
static QString getComponentModeAsString(uint32_t mode);
|
||||
|
||||
|
@ -294,6 +299,8 @@ public:
|
|||
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
|
||||
|
||||
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
|
||||
static bool encodeCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID, QByteArray& buffer);
|
||||
static bool decodeCloneEntityMessage(const QByteArray& buffer, int& processedBytes, EntityItemID& entityIDToClone, EntityItemID& newEntityID);
|
||||
|
||||
static bool decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes,
|
||||
EntityItemID& entityID, EntityItemProperties& properties);
|
||||
|
|
|
@ -97,4 +97,9 @@ const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid();
|
|||
|
||||
const bool ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS = false;
|
||||
|
||||
const bool ENTITY_ITEM_CLONEABLE = false;
|
||||
const float ENTITY_ITEM_CLONEABLE_LIFETIME = 300.0f;
|
||||
const int ENTITY_ITEM_CLONEABLE_LIMIT = 0;
|
||||
const bool ENTITY_ITEM_CLONEABLE_DYNAMIC = false;
|
||||
|
||||
#endif // hifi_EntityItemPropertiesDefaults_h
|
||||
|
|
|
@ -242,6 +242,11 @@ enum EntityPropertyList {
|
|||
PROP_MATERIAL_MAPPING_ROT,
|
||||
PROP_MATERIAL_DATA,
|
||||
|
||||
PROP_CLONEABLE,
|
||||
PROP_CLONEABLE_LIFETIME,
|
||||
PROP_CLONEABLE_LIMIT,
|
||||
PROP_CLONEABLE_DYNAMIC,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -258,33 +258,9 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
|
||||
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
|
||||
|
||||
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||
|
||||
EntityItemID id;
|
||||
// If we have a local entity tree set, then also update it.
|
||||
bool success = true;
|
||||
if (_entityTree) {
|
||||
_entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
|
||||
if (entity) {
|
||||
if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
propertiesWithSimID.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
propertiesWithSimID.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
bool success = addLocalEntityCopy(propertiesWithSimID, id);
|
||||
|
||||
// queue the packet
|
||||
if (success) {
|
||||
|
@ -295,6 +271,39 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
}
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properties, EntityItemID& id) {
|
||||
bool success = true;
|
||||
|
||||
id = EntityItemID(QUuid::createUuid());
|
||||
|
||||
if (_entityTree) {
|
||||
_entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = _entityTree->addEntity(id, properties);
|
||||
if (entity) {
|
||||
if (properties.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
properties.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
properties.setLastEdited(entity->getLastEdited());
|
||||
}
|
||||
else {
|
||||
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures,
|
||||
const QString& shapeType, bool dynamic, bool collisionless,
|
||||
const glm::vec3& position, const glm::vec3& gravity) {
|
||||
|
@ -320,6 +329,18 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin
|
|||
return addEntity(properties);
|
||||
}
|
||||
|
||||
QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) {
|
||||
EntityItemID newEntityID;
|
||||
EntityItemProperties properties = getEntityProperties(entityIDToClone);
|
||||
if (addLocalEntityCopy(properties, newEntityID)) {
|
||||
qCDebug(entities) << "DBACK POOPY cloneEntity addLocalEntityCopy" << newEntityID;
|
||||
getEntityPacketSender()->queueCloneEntityMessage(entityIDToClone, newEntityID);
|
||||
return newEntityID;
|
||||
} else {
|
||||
return QUuid();
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
|
||||
EntityPropertyFlags noSpecificProperties;
|
||||
return getEntityProperties(identity, noSpecificProperties);
|
||||
|
|
|
@ -224,6 +224,8 @@ public slots:
|
|||
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic,
|
||||
bool collisionless, const glm::vec3& position, const glm::vec3& gravity);
|
||||
|
||||
Q_INVOKABLE QUuid cloneEntity(QUuid entityIDToClone);
|
||||
|
||||
/**jsdoc
|
||||
* Get the properties of an entity.
|
||||
* @function Entities.getEntityProperties
|
||||
|
@ -1875,6 +1877,7 @@ private:
|
|||
bool polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
|
||||
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
|
||||
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
|
||||
bool addLocalEntityCopy(EntityItemProperties& propertiesWithSimID, EntityItemID& id);
|
||||
|
||||
EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID,
|
||||
EntityTypes::EntityType entityType = EntityTypes::Unknown);
|
||||
|
|
|
@ -228,6 +228,7 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const {
|
|||
// we handle these types of "edit" packets
|
||||
switch (packetType) {
|
||||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityClone:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityErase:
|
||||
case PacketType::EntityPhysics:
|
||||
|
@ -592,6 +593,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
|
|||
return;
|
||||
}
|
||||
|
||||
removeCloneIDFromCloneParent(entityID);
|
||||
unhookChildAvatar(entityID);
|
||||
emit deletingEntity(entityID);
|
||||
emit deletingEntityPointer(existingEntity.get());
|
||||
|
@ -625,6 +627,19 @@ void EntityTree::unhookChildAvatar(const EntityItemID entityID) {
|
|||
});
|
||||
}
|
||||
|
||||
void EntityTree::removeCloneIDFromCloneParent(const EntityItemID& entityID) {
|
||||
EntityItemPointer entity = findEntityByEntityItemID(entityID);
|
||||
if (entity) {
|
||||
const QUuid& cloneParentID = entity->getCloneParent();
|
||||
if (!cloneParentID.isNull()) {
|
||||
EntityItemPointer cloneParent = findEntityByID(cloneParentID);
|
||||
if (cloneParent) {
|
||||
cloneParent->removeCloneID(entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool ignoreWarnings) {
|
||||
// NOTE: callers must lock the tree before using this method
|
||||
DeleteEntityOperator theOperator(getThisPointer());
|
||||
|
@ -653,6 +668,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
|
|||
}
|
||||
|
||||
// tell our delete operator about this entityID
|
||||
removeCloneIDFromCloneParent(entityID);
|
||||
unhookChildAvatar(entityID);
|
||||
theOperator.addEntityIDToDeleteList(entityID);
|
||||
emit deletingEntity(entityID);
|
||||
|
@ -1392,6 +1408,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
|
||||
int processedBytes = 0;
|
||||
bool isAdd = false;
|
||||
bool isClone = false;
|
||||
// we handle these types of "edit" packets
|
||||
switch (message.getType()) {
|
||||
case PacketType::EntityErase: {
|
||||
|
@ -1400,8 +1417,10 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
break;
|
||||
}
|
||||
|
||||
case PacketType::EntityClone:
|
||||
isClone = true; // fall through to next case
|
||||
case PacketType::EntityAdd:
|
||||
isAdd = true; // fall through to next case
|
||||
isAdd = true; // fall through to next case
|
||||
// FALLTHRU
|
||||
case PacketType::EntityPhysics:
|
||||
case PacketType::EntityEdit: {
|
||||
|
@ -1422,8 +1441,21 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
EntityItemProperties properties;
|
||||
startDecode = usecTimestampNow();
|
||||
|
||||
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes,
|
||||
entityItemID, properties);
|
||||
bool validEditPacket = false;
|
||||
EntityItemID entityIDToClone;
|
||||
EntityItemPointer entityToClone;
|
||||
if (isClone) {
|
||||
QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<const char*>(editData), maxLength);
|
||||
validEditPacket = EntityItemProperties::decodeCloneEntityMessage(buffer, processedBytes, entityIDToClone, entityItemID);
|
||||
entityToClone = findEntityByEntityItemID(entityIDToClone);
|
||||
if (entityToClone) {
|
||||
properties = entityToClone->getProperties();
|
||||
}
|
||||
}
|
||||
else {
|
||||
validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties);
|
||||
}
|
||||
|
||||
endDecode = usecTimestampNow();
|
||||
|
||||
EntityItemPointer existingEntity;
|
||||
|
@ -1491,24 +1523,26 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
|
||||
}
|
||||
|
||||
if ((isAdd || properties.lifetimeChanged()) &&
|
||||
((!senderNode->getCanRez() && senderNode->getCanRezTmp()) ||
|
||||
(!senderNode->getCanRezCertified() && senderNode->getCanRezTmpCertified()))) {
|
||||
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
|
||||
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
||||
properties.getLifetime() > _maxTmpEntityLifetime) {
|
||||
properties.setLifetime(_maxTmpEntityLifetime);
|
||||
if (!isClone) {
|
||||
if ((isAdd || properties.lifetimeChanged()) &&
|
||||
((!senderNode->getCanRez() && senderNode->getCanRezTmp()) ||
|
||||
(!senderNode->getCanRezCertified() && senderNode->getCanRezTmpCertified()))) {
|
||||
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
|
||||
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
||||
properties.getLifetime() > _maxTmpEntityLifetime) {
|
||||
properties.setLifetime(_maxTmpEntityLifetime);
|
||||
bumpTimestamp(properties);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
|
||||
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
|
||||
// clear the locked property and allow the unlocked entity to be created.
|
||||
properties.setLocked(false);
|
||||
bumpTimestamp(properties);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
|
||||
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
|
||||
// clear the locked property and allow the unlocked entity to be created.
|
||||
properties.setLocked(false);
|
||||
bumpTimestamp(properties);
|
||||
}
|
||||
|
||||
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
||||
// an existing entity... handle appropriately
|
||||
if (validEditPacket) {
|
||||
|
@ -1566,17 +1600,39 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
} else if (isAdd) {
|
||||
bool failedAdd = !allowed;
|
||||
bool isCertified = !properties.getCertificateID().isEmpty();
|
||||
bool isCloneable = properties.getCloneable();
|
||||
int cloneLimit = properties.getCloneableLimit();
|
||||
if (!allowed) {
|
||||
qCDebug(entities) << "Filtered entity add. ID:" << entityItemID;
|
||||
} else if (!isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
|
||||
} else if (!isClone && !isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User without 'uncertified rez rights' [" << senderNode->getUUID()
|
||||
<< "] attempted to add an uncertified entity with ID:" << entityItemID;
|
||||
} else if (isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
|
||||
} else if (!isClone && isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
|
||||
<< "] attempted to add a certified entity with ID:" << entityItemID;
|
||||
} else if (isClone && isCertified) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User attempted to clone certified entity from entity ID:" << entityIDToClone;
|
||||
} else if (isClone && !isCloneable) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User attempted to clone non-cloneable entity from entity ID:" << entityIDToClone;
|
||||
} else if (isClone && entityToClone && entityToClone->getCloneIDs().size() >= cloneLimit) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User attempted to clone entity ID:" << entityIDToClone << " which reached it's cloneable limit.";
|
||||
} else {
|
||||
if (isClone) {
|
||||
properties.setName(properties.getName() + "-clone-" + entityIDToClone.toString());
|
||||
properties.setLocked(false);
|
||||
properties.setLifetime(properties.getCloneableLifetime());
|
||||
properties.setDynamic(properties.getCloneableDynamic());
|
||||
properties.setCloneable(ENTITY_ITEM_CLONEABLE);
|
||||
properties.setCloneableLifetime(ENTITY_ITEM_CLONEABLE_LIFETIME);
|
||||
properties.setCloneableLimit(ENTITY_ITEM_CLONEABLE_LIMIT);
|
||||
properties.setCloneableDynamic(ENTITY_ITEM_CLONEABLE_DYNAMIC);
|
||||
}
|
||||
|
||||
// this is a new entity... assign a new entityID
|
||||
properties.setCreated(properties.getLastEdited());
|
||||
properties.setLastEditedBy(senderNode->getUUID());
|
||||
|
@ -1600,6 +1656,11 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
if (newEntity) {
|
||||
newEntity->markAsChangedOnServer();
|
||||
notifyNewlyCreatedEntity(*newEntity, senderNode);
|
||||
if (isClone) {
|
||||
entityToClone->addCloneID(newEntity->getEntityItemID());
|
||||
newEntity->setCloneParent(entityIDToClone);
|
||||
qCDebug(entities) << "DBACK POOPY addedEntity clone " << newEntity->getEntityItemID();
|
||||
}
|
||||
|
||||
startLogging = usecTimestampNow();
|
||||
if (wantEditLogging()) {
|
||||
|
|
|
@ -117,6 +117,7 @@ public:
|
|||
|
||||
// check if the avatar is a child of this entity, If so set the avatar parentID to null
|
||||
void unhookChildAvatar(const EntityItemID entityID);
|
||||
void removeCloneIDFromCloneParent(const EntityItemID& entityID);
|
||||
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = true);
|
||||
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = true);
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ Buffer::Update::Update(const Buffer& parent) : buffer(parent) {
|
|||
void Buffer::Update::apply() const {
|
||||
// Make sure we're loaded in order
|
||||
buffer._applyUpdateCount++;
|
||||
assert(buffer._applyUpdateCount == updateNumber);
|
||||
//assert(buffer._applyUpdateCount == updateNumber);
|
||||
|
||||
const auto pageSize = buffer._pages._pageSize;
|
||||
buffer._renderSysmem.resize(size);
|
||||
|
|
|
@ -29,10 +29,11 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::DomainList:
|
||||
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport);
|
||||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityClone:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
case PacketType::EntityPhysics:
|
||||
return static_cast<PacketVersion>(EntityVersion::MaterialData);
|
||||
return static_cast<PacketVersion>(EntityVersion::CloneableData);
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
|
||||
case PacketType::AvatarIdentity:
|
||||
|
|
|
@ -75,6 +75,7 @@ public:
|
|||
EntityData,
|
||||
EntityQuery,
|
||||
EntityAdd,
|
||||
EntityClone,
|
||||
EntityErase,
|
||||
EntityEdit,
|
||||
DomainServerConnectionToken,
|
||||
|
@ -232,7 +233,8 @@ enum class EntityVersion : PacketVersion {
|
|||
SoftEntities,
|
||||
MaterialEntities,
|
||||
ShadowControl,
|
||||
MaterialData
|
||||
MaterialData,
|
||||
CloneableData
|
||||
};
|
||||
|
||||
enum class EntityScriptCallMethodVersion : PacketVersion {
|
||||
|
|
|
@ -1040,12 +1040,12 @@ function loaded() {
|
|||
elWantsTrigger.checked = false;
|
||||
elIgnoreIK.checked = true;
|
||||
|
||||
elCloneable.checked = false;
|
||||
elCloneableDynamic.checked = false;
|
||||
elCloneable.checked = properties.cloneable;
|
||||
elCloneableDynamic.checked = properties.cloneableDynamic;
|
||||
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
|
||||
elCloneableLimit.value = 0;
|
||||
elCloneableLifetime.value = 300;
|
||||
|
||||
elCloneableLimit.value = properties.cloneableLimit;
|
||||
elCloneableLifetime.value = properties.cloneableLifetime;
|
||||
|
||||
var grabbablesSet = false;
|
||||
var parsedUserData = {};
|
||||
try {
|
||||
|
@ -1069,27 +1069,6 @@ function loaded() {
|
|||
} else {
|
||||
elIgnoreIK.checked = true;
|
||||
}
|
||||
if ("cloneable" in grabbableData) {
|
||||
elCloneable.checked = grabbableData.cloneable;
|
||||
elCloneableGroup.style.display = elCloneable.checked ? "block" : "none";
|
||||
elCloneableDynamic.checked =
|
||||
grabbableData.cloneDynamic ? grabbableData.cloneDynamic : properties.dynamic;
|
||||
if (elCloneable.checked) {
|
||||
if ("cloneLifetime" in grabbableData) {
|
||||
elCloneableLifetime.value =
|
||||
grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300;
|
||||
}
|
||||
if ("cloneLimit" in grabbableData) {
|
||||
elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0;
|
||||
}
|
||||
if ("cloneAvatarEntity" in grabbableData) {
|
||||
elCloneableAvatarEntity.checked =
|
||||
grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
elCloneable.checked = false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: What should go here?
|
||||
|
@ -1460,45 +1439,11 @@ function loaded() {
|
|||
}
|
||||
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true);
|
||||
});
|
||||
elCloneableDynamic.addEventListener('change', function(event) {
|
||||
userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1);
|
||||
});
|
||||
|
||||
elCloneableAvatarEntity.addEventListener('change', function(event) {
|
||||
userDataChanger("grabbableKey", "cloneAvatarEntity", event.target, elUserData, -1);
|
||||
});
|
||||
|
||||
elCloneable.addEventListener('change', function (event) {
|
||||
var checked = event.target.checked;
|
||||
if (checked) {
|
||||
multiDataUpdater("grabbableKey", {
|
||||
cloneLifetime: elCloneableLifetime,
|
||||
cloneLimit: elCloneableLimit,
|
||||
cloneDynamic: elCloneableDynamic,
|
||||
cloneAvatarEntity: elCloneableAvatarEntity,
|
||||
cloneable: event.target,
|
||||
grabbable: null
|
||||
}, elUserData, {});
|
||||
elCloneableGroup.style.display = "block";
|
||||
updateProperty('dynamic', false);
|
||||
} else {
|
||||
multiDataUpdater("grabbableKey", {
|
||||
cloneLifetime: null,
|
||||
cloneLimit: null,
|
||||
cloneDynamic: null,
|
||||
cloneAvatarEntity: null,
|
||||
cloneable: false
|
||||
}, elUserData, {});
|
||||
elCloneableGroup.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
var numberListener = function (event) {
|
||||
userDataChanger("grabbableKey",
|
||||
event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false);
|
||||
};
|
||||
elCloneableLifetime.addEventListener('change', numberListener);
|
||||
elCloneableLimit.addEventListener('change', numberListener);
|
||||
|
||||
elCloneable.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneable'));
|
||||
elCloneableDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneableDynamic'));
|
||||
elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneableLifetime'));
|
||||
elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneableLimit'));
|
||||
|
||||
elWantsTrigger.addEventListener('change', function() {
|
||||
userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false);
|
||||
|
|
|
@ -33,8 +33,7 @@ if (typeof Object.assign !== 'function') {
|
|||
|
||||
entityIsCloneable = function(props) {
|
||||
if (props) {
|
||||
var grabbableData = getGrabbableData(props);
|
||||
return grabbableData.cloneable;
|
||||
return props.cloneable;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
@ -42,56 +41,19 @@ entityIsCloneable = function(props) {
|
|||
propsAreCloneDynamic = function(props) {
|
||||
var cloneable = entityIsCloneable(props);
|
||||
if (cloneable) {
|
||||
var grabInfo = getGrabbableData(props);
|
||||
if (grabInfo.cloneDynamic) {
|
||||
return true;
|
||||
}
|
||||
return props.cloneableDynamic;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
cloneEntity = function(props, worldEntityProps) {
|
||||
// we need all the properties, for this
|
||||
var cloneableProps = Entities.getEntityProperties(props.id);
|
||||
|
||||
var count = 0;
|
||||
worldEntityProps.forEach(function(itemWE) {
|
||||
if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
var grabInfo = getGrabbableData(cloneableProps);
|
||||
var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0;
|
||||
if (count >= limit && limit !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id;
|
||||
var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300;
|
||||
var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false;
|
||||
var triggerable = grabInfo.triggerable ? grabInfo.triggerable : false;
|
||||
var avatarEntity = grabInfo.cloneAvatarEntity ? grabInfo.cloneAvatarEntity : false;
|
||||
var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData));
|
||||
var cProperties = Object.assign({}, cloneableProps);
|
||||
|
||||
|
||||
delete cUserData.grabbableKey.cloneLifetime;
|
||||
delete cUserData.grabbableKey.cloneable;
|
||||
delete cUserData.grabbableKey.cloneDynamic;
|
||||
delete cUserData.grabbableKey.cloneLimit;
|
||||
delete cUserData.grabbableKey.cloneAvatarEntity;
|
||||
delete cProperties.id;
|
||||
|
||||
|
||||
cProperties.dynamic = dynamic;
|
||||
cProperties.locked = false;
|
||||
cUserData.grabbableKey.triggerable = triggerable;
|
||||
cUserData.grabbableKey.grabbable = true;
|
||||
cProperties.lifetime = lifetime;
|
||||
cProperties.userData = JSON.stringify(cUserData);
|
||||
|
||||
var cloneID = Entities.addEntity(cProperties, avatarEntity);
|
||||
return cloneID;
|
||||
var entityToClone = props.id;
|
||||
var certificateID = Entities.getEntityProperties(entityToClone, ['certificateID']).certificateID;
|
||||
// ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits
|
||||
// will now be handled by the server where the entity add will fail if limit reached
|
||||
if (entityIsCloneable(props) && (certificateID === undefined || certificateID.length === 0)) {
|
||||
var cloneID = Entities.cloneEntity(entityToClone);
|
||||
return cloneID;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -125,7 +125,8 @@ DISPATCHER_PROPERTIES = [
|
|||
"dimensions",
|
||||
"userData",
|
||||
"type",
|
||||
"href"
|
||||
"href",
|
||||
"cloneable"
|
||||
];
|
||||
|
||||
// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step
|
||||
|
|
Loading…
Reference in a new issue