mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #13179 from dback2/cloneables
Server-Managed Cloneables + Cloneable Properties
This commit is contained in:
commit
3e665fb616
22 changed files with 588 additions and 176 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,
|
||||
|
|
|
@ -154,3 +154,11 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI
|
|||
queueOctreeEditMessage(PacketType::EntityErase, bufferOut);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityEditPacketSender::queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID) {
|
||||
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,13 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
|
||||
requestedProperties += PROP_LAST_EDITED_BY;
|
||||
|
||||
requestedProperties += PROP_CLONEABLE;
|
||||
requestedProperties += PROP_CLONE_LIFETIME;
|
||||
requestedProperties += PROP_CLONE_LIMIT;
|
||||
requestedProperties += PROP_CLONE_DYNAMIC;
|
||||
requestedProperties += PROP_CLONE_AVATAR_ENTITY;
|
||||
requestedProperties += PROP_CLONE_ORIGIN_ID;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -288,6 +295,13 @@ 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_CLONE_LIFETIME, getCloneLifetime());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, getCloneLimit());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, getCloneDynamic());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, getCloneAvatarEntity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, getCloneOriginID());
|
||||
|
||||
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
|
||||
requestedProperties,
|
||||
propertyFlags,
|
||||
|
@ -848,6 +862,13 @@ 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_CLONE_LIFETIME, float, setCloneLifetime);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONE_LIMIT, float, setCloneLimit);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, bool, setCloneDynamic);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity);
|
||||
READ_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, QUuid, setCloneOriginID);
|
||||
|
||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
|
||||
propertyFlags, overwriteLocalData, somethingChanged);
|
||||
|
||||
|
@ -1275,6 +1296,13 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
|
|||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy);
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLifetime, getCloneLifetime);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLimit, getCloneLimit);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneDynamic, getCloneDynamic);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneAvatarEntity, getCloneAvatarEntity);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneOriginID, getCloneOriginID);
|
||||
|
||||
properties._defaultSettings = false;
|
||||
|
||||
return properties;
|
||||
|
@ -1382,6 +1410,13 @@ 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(cloneLifetime, setCloneLifetime);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLimit, setCloneLimit);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneDynamic, setCloneDynamic);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneAvatarEntity, setCloneAvatarEntity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneOriginID, setCloneOriginID);
|
||||
|
||||
if (updateQueryAACube()) {
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
@ -2975,3 +3010,118 @@ 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::getCloneLifetime() const {
|
||||
float result;
|
||||
withReadLock([&] {
|
||||
result = _cloneLifetime;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneLifetime(float value) {
|
||||
withWriteLock([&] {
|
||||
_cloneLifetime = value;
|
||||
});
|
||||
}
|
||||
|
||||
float EntityItem::getCloneLimit() const {
|
||||
float result;
|
||||
withReadLock([&] {
|
||||
result = _cloneLimit;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneLimit(float value) {
|
||||
withWriteLock([&] {
|
||||
_cloneLimit = value;
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityItem::getCloneDynamic() const {
|
||||
bool result;
|
||||
withReadLock([&] {
|
||||
result = _cloneDynamic;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneDynamic(bool value) {
|
||||
withWriteLock([&] {
|
||||
_cloneDynamic = value;
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityItem::getCloneAvatarEntity() const {
|
||||
bool result;
|
||||
withReadLock([&] {
|
||||
result = _cloneAvatarEntity;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneAvatarEntity(bool value) {
|
||||
withWriteLock([&] {
|
||||
_cloneAvatarEntity = value;
|
||||
});
|
||||
}
|
||||
|
||||
const QUuid EntityItem::getCloneOriginID() const {
|
||||
QUuid result;
|
||||
withReadLock([&] {
|
||||
result = _cloneOriginID;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneOriginID(const QUuid& value) {
|
||||
withWriteLock([&] {
|
||||
_cloneOriginID = value;
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::addCloneID(const QUuid& cloneID) {
|
||||
withWriteLock([&] {
|
||||
if (!_cloneIDs.contains(cloneID)) {
|
||||
_cloneIDs.append(cloneID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::removeCloneID(const QUuid& cloneID) {
|
||||
withWriteLock([&] {
|
||||
int index = _cloneIDs.indexOf(cloneID);
|
||||
if (index >= 0) {
|
||||
_cloneIDs.removeAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const QVector<QUuid> EntityItem::getCloneIDs() const {
|
||||
QVector<QUuid> result;
|
||||
withReadLock([&] {
|
||||
result = _cloneIDs;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setCloneIDs(const QVector<QUuid>& cloneIDs) {
|
||||
withWriteLock([&] {
|
||||
_cloneIDs = cloneIDs;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -341,6 +341,19 @@ public:
|
|||
quint32 getStaticCertificateVersion() const;
|
||||
void setStaticCertificateVersion(const quint32&);
|
||||
|
||||
bool getCloneable() const;
|
||||
void setCloneable(bool value);
|
||||
float getCloneLifetime() const;
|
||||
void setCloneLifetime(float value);
|
||||
float getCloneLimit() const;
|
||||
void setCloneLimit(float value);
|
||||
bool getCloneDynamic() const;
|
||||
void setCloneDynamic(bool value);
|
||||
bool getCloneAvatarEntity() const;
|
||||
void setCloneAvatarEntity(bool value);
|
||||
const QUuid getCloneOriginID() const;
|
||||
void setCloneOriginID(const QUuid& value);
|
||||
|
||||
// TODO: get rid of users of getRadius()...
|
||||
float getRadius() const;
|
||||
|
||||
|
@ -494,6 +507,11 @@ public:
|
|||
void setSimulationOwnershipExpiry(uint64_t expiry) { _simulationOwnershipExpiry = expiry; }
|
||||
uint64_t getSimulationOwnershipExpiry() const { return _simulationOwnershipExpiry; }
|
||||
|
||||
void addCloneID(const QUuid& cloneID);
|
||||
void removeCloneID(const QUuid& cloneID);
|
||||
const QVector<QUuid> getCloneIDs() const;
|
||||
void setCloneIDs(const QVector<QUuid>& cloneIDs);
|
||||
|
||||
signals:
|
||||
void requestRenderUpdate();
|
||||
|
||||
|
@ -648,6 +666,14 @@ protected:
|
|||
|
||||
bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera
|
||||
|
||||
bool _cloneable { ENTITY_ITEM_DEFAULT_CLONEABLE };
|
||||
float _cloneLifetime { ENTITY_ITEM_DEFAULT_CLONE_LIFETIME };
|
||||
float _cloneLimit { ENTITY_ITEM_DEFAULT_CLONE_LIMIT };
|
||||
bool _cloneDynamic { ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC };
|
||||
bool _cloneAvatarEntity { ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY };
|
||||
QUuid _cloneOriginID;
|
||||
QVector<QUuid> _cloneIDs;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||
std::mutex _materialsLock;
|
||||
|
|
|
@ -436,6 +436,13 @@ 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_CLONE_LIFETIME, cloneLifetime);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONE_LIMIT, cloneLimit);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONE_DYNAMIC, cloneDynamic);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLONE_ORIGIN_ID, cloneOriginID);
|
||||
|
||||
changedProperties += _animation.getChangedProperties();
|
||||
changedProperties += _keyLight.getChangedProperties();
|
||||
changedProperties += _ambientLight.getChangedProperties();
|
||||
|
@ -1430,6 +1437,13 @@ 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_CLONE_LIFETIME, cloneLifetime);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIMIT, cloneLimit);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_DYNAMIC, cloneDynamic);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_ORIGIN_ID, cloneOriginID);
|
||||
|
||||
// Rendering info
|
||||
if (!skipDefaults && !strictSemantics) {
|
||||
QScriptValue renderInfo = engine->newObject();
|
||||
|
@ -1642,6 +1656,13 @@ 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(cloneLifetime, float, setCloneLifetime);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLimit, float, setCloneLimit);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneDynamic, bool, setCloneDynamic);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneAvatarEntity, bool, setCloneAvatarEntity);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneOriginID, QUuid, setCloneOriginID);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -1793,6 +1814,13 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
|
|||
|
||||
COPY_PROPERTY_IF_CHANGED(dpi);
|
||||
|
||||
COPY_PROPERTY_IF_CHANGED(cloneable);
|
||||
COPY_PROPERTY_IF_CHANGED(cloneLifetime);
|
||||
COPY_PROPERTY_IF_CHANGED(cloneLimit);
|
||||
COPY_PROPERTY_IF_CHANGED(cloneDynamic);
|
||||
COPY_PROPERTY_IF_CHANGED(cloneAvatarEntity);
|
||||
COPY_PROPERTY_IF_CHANGED(cloneOriginID);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -2017,6 +2045,13 @@ 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_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid);
|
||||
|
||||
// FIXME - these are not yet handled
|
||||
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
|
||||
|
||||
|
@ -2331,6 +2366,12 @@ 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_CLONE_LIFETIME, properties.getCloneLifetime());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, properties.getCloneLimit());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, properties.getCloneDynamic());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, properties.getCloneAvatarEntity());
|
||||
}
|
||||
|
||||
if (propertyCount > 0) {
|
||||
|
@ -2701,6 +2742,12 @@ 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_CLONE_LIFETIME, float, setCloneLifetime);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIMIT, float, setCloneLimit);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_DYNAMIC, bool, setCloneDynamic);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
@ -2780,6 +2827,52 @@ 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)(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 +3034,13 @@ void EntityItemProperties::markAllChanged() {
|
|||
|
||||
_dpiChanged = true;
|
||||
_relayParentJointsChanged = true;
|
||||
|
||||
_cloneableChanged = true;
|
||||
_cloneLifetimeChanged = true;
|
||||
_cloneLimitChanged = true;
|
||||
_cloneDynamicChanged = true;
|
||||
_cloneAvatarEntityChanged = true;
|
||||
_cloneOriginIDChanged = true;
|
||||
}
|
||||
|
||||
// The minimum bounding box for the entity.
|
||||
|
@ -3373,6 +3473,25 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
out += "isUVModeStretch";
|
||||
}
|
||||
|
||||
if (cloneableChanged()) {
|
||||
out += "cloneable";
|
||||
}
|
||||
if (cloneLifetimeChanged()) {
|
||||
out += "cloneLifetime";
|
||||
}
|
||||
if (cloneLimitChanged()) {
|
||||
out += "cloneLimit";
|
||||
}
|
||||
if (cloneDynamicChanged()) {
|
||||
out += "cloneDynamic";
|
||||
}
|
||||
if (cloneAvatarEntityChanged()) {
|
||||
out += "cloneAvatarEntity";
|
||||
}
|
||||
if (cloneOriginIDChanged()) {
|
||||
out += "cloneOriginID";
|
||||
}
|
||||
|
||||
getAnimation().listChangedProperties(out);
|
||||
getKeyLight().listChangedProperties(out);
|
||||
getAmbientLight().listChangedProperties(out);
|
||||
|
@ -3536,3 +3655,18 @@ bool EntityItemProperties::verifyStaticCertificateProperties() {
|
|||
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
|
||||
return verifySignature(EntityItem::_marketplacePublicKey, getStaticCertificateHash(), QByteArray::fromBase64(getCertificateID().toUtf8()));
|
||||
}
|
||||
|
||||
void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityIDToClone) {
|
||||
setName(getName() + "-clone-" + entityIDToClone.toString());
|
||||
setLocked(false);
|
||||
setLifetime(getCloneLifetime());
|
||||
setDynamic(getCloneDynamic());
|
||||
setClientOnly(getCloneAvatarEntity());
|
||||
setCreated(usecTimestampNow());
|
||||
setLastEdited(usecTimestampNow());
|
||||
setCloneable(ENTITY_ITEM_DEFAULT_CLONEABLE);
|
||||
setCloneLifetime(ENTITY_ITEM_DEFAULT_CLONE_LIFETIME);
|
||||
setCloneLimit(ENTITY_ITEM_DEFAULT_CLONE_LIMIT);
|
||||
setCloneDynamic(ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC);
|
||||
setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY);
|
||||
}
|
||||
|
|
|
@ -272,6 +272,13 @@ 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_DEFAULT_CLONEABLE);
|
||||
DEFINE_PROPERTY(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float, ENTITY_ITEM_DEFAULT_CLONE_LIFETIME);
|
||||
DEFINE_PROPERTY(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float, ENTITY_ITEM_DEFAULT_CLONE_LIMIT);
|
||||
DEFINE_PROPERTY(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool, ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC);
|
||||
DEFINE_PROPERTY(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool, ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY);
|
||||
DEFINE_PROPERTY_REF(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid, ENTITY_ITEM_DEFAULT_CLONE_ORIGIN_ID);
|
||||
|
||||
static QString getComponentModeString(uint32_t mode);
|
||||
static QString getComponentModeAsString(uint32_t mode);
|
||||
|
||||
|
@ -294,6 +301,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);
|
||||
|
@ -369,6 +378,8 @@ public:
|
|||
bool verifyStaticCertificateProperties();
|
||||
static bool verifySignature(const QString& key, const QByteArray& text, const QByteArray& signature);
|
||||
|
||||
void convertToCloneProperties(const EntityItemID& entityIDToClone);
|
||||
|
||||
protected:
|
||||
QString getCollisionMaskAsString() const;
|
||||
void setCollisionMaskFromString(const QString& maskString);
|
||||
|
@ -515,6 +526,13 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SkyboxMode, skyboxMode, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cloneable, cloneable, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneLifetime, cloneLifetime, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneLimit, cloneLimit, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneDynamic, cloneDynamic, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneAvatarEntity, cloneAvatarEntity, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneOriginID, cloneOriginID, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, "");
|
||||
|
|
|
@ -97,4 +97,11 @@ const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid();
|
|||
|
||||
const bool ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS = false;
|
||||
|
||||
const bool ENTITY_ITEM_DEFAULT_CLONEABLE = false;
|
||||
const float ENTITY_ITEM_DEFAULT_CLONE_LIFETIME = 300.0f;
|
||||
const int ENTITY_ITEM_DEFAULT_CLONE_LIMIT = 0;
|
||||
const bool ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC = false;
|
||||
const bool ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY = false;
|
||||
const QUuid ENTITY_ITEM_DEFAULT_CLONE_ORIGIN_ID = QUuid();
|
||||
|
||||
#endif // hifi_EntityItemPropertiesDefaults_h
|
||||
|
|
|
@ -204,6 +204,13 @@ enum EntityPropertyList {
|
|||
PROP_CERTIFICATE_ID,
|
||||
PROP_STATIC_CERTIFICATE_VERSION,
|
||||
|
||||
PROP_CLONEABLE,
|
||||
PROP_CLONE_LIFETIME,
|
||||
PROP_CLONE_LIMIT,
|
||||
PROP_CLONE_DYNAMIC,
|
||||
PROP_CLONE_AVATAR_ENTITY,
|
||||
PROP_CLONE_ORIGIN_ID,
|
||||
|
||||
PROP_HAZE_MODE,
|
||||
|
||||
PROP_KEYLIGHT_COLOR,
|
||||
|
|
|
@ -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,37 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
}
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properties, EntityItemID& id, bool isClone) {
|
||||
bool success = true;
|
||||
id = EntityItemID(QUuid::createUuid());
|
||||
|
||||
if (_entityTree) {
|
||||
_entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = _entityTree->addEntity(id, properties, isClone);
|
||||
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 +327,28 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin
|
|||
return addEntity(properties);
|
||||
}
|
||||
|
||||
QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) {
|
||||
EntityItemID newEntityID;
|
||||
EntityItemProperties properties = getEntityProperties(entityIDToClone);
|
||||
bool cloneAvatarEntity = properties.getCloneAvatarEntity();
|
||||
properties.convertToCloneProperties(entityIDToClone);
|
||||
|
||||
if (cloneAvatarEntity) {
|
||||
return addEntity(properties, true);
|
||||
} else {
|
||||
// setLastEdited timestamp to 0 to ensure this entity gets updated with the properties
|
||||
// from the server-created entity, don't change this unless you know what you are doing
|
||||
properties.setLastEdited(0);
|
||||
bool success = addLocalEntityCopy(properties, newEntityID, true);
|
||||
if (success) {
|
||||
getEntityPacketSender()->queueCloneEntityMessage(entityIDToClone, newEntityID);
|
||||
return newEntityID;
|
||||
} else {
|
||||
return QUuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
|
||||
EntityPropertyFlags noSpecificProperties;
|
||||
return getEntityProperties(identity, noSpecificProperties);
|
||||
|
|
|
@ -224,6 +224,16 @@ 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);
|
||||
|
||||
/**jsdoc
|
||||
* Request a clone of an entity. Only entities that have been marked as 'cloneable' will be able to be cloned using this method.
|
||||
* A cloned entity has most of the properties of the orignal entity, and can be requested from clients that do not have rez permissions.
|
||||
* The client requests a clone from the entity server, which returns back the entityID of a valid clone if the operation was allowed.
|
||||
* @function Entities.cloneEntity
|
||||
* @param {Uuid} entityIDToClone - the ID of the entity to clone
|
||||
* @returns {Entities.EntityID} The ID of the newly created clone
|
||||
*/
|
||||
Q_INVOKABLE QUuid cloneEntity(QUuid entityIDToClone);
|
||||
|
||||
/**jsdoc
|
||||
* Get the properties of an entity.
|
||||
* @function Entities.getEntityProperties
|
||||
|
@ -1875,6 +1885,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, bool isClone = false);
|
||||
|
||||
EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID,
|
||||
EntityTypes::EntityType entityType = EntityTypes::Unknown);
|
||||
|
|
|
@ -65,6 +65,7 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
|
|||
removeEntityInternal(entity);
|
||||
if (entity->getElement()) {
|
||||
_deadEntities.insert(entity);
|
||||
_entityTree->cleanupCloneIDs(entity->getEntityItemID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
@ -492,7 +493,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
|
|||
return true;
|
||||
}
|
||||
|
||||
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) {
|
||||
EntityItemPointer result = NULL;
|
||||
EntityItemProperties props = properties;
|
||||
|
||||
|
@ -504,7 +505,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
|||
|
||||
if (!properties.getClientOnly() && getIsClient() &&
|
||||
!nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() &&
|
||||
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain) {
|
||||
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain && !isClone) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -592,6 +593,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
|
|||
return;
|
||||
}
|
||||
|
||||
cleanupCloneIDs(entityID);
|
||||
unhookChildAvatar(entityID);
|
||||
emit deletingEntity(entityID);
|
||||
emit deletingEntityPointer(existingEntity.get());
|
||||
|
@ -625,6 +627,28 @@ void EntityTree::unhookChildAvatar(const EntityItemID entityID) {
|
|||
});
|
||||
}
|
||||
|
||||
void EntityTree::cleanupCloneIDs(const EntityItemID& entityID) {
|
||||
EntityItemPointer entity = findEntityByEntityItemID(entityID);
|
||||
if (entity) {
|
||||
// remove clone ID from it's clone origin's clone ID list if clone origin exists
|
||||
const QUuid& cloneOriginID = entity->getCloneOriginID();
|
||||
if (!cloneOriginID.isNull()) {
|
||||
EntityItemPointer cloneOrigin = findEntityByID(cloneOriginID);
|
||||
if (cloneOrigin) {
|
||||
cloneOrigin->removeCloneID(entityID);
|
||||
}
|
||||
}
|
||||
// clear the clone origin ID on any clones that this entity had
|
||||
const QVector<QUuid>& cloneIDs = entity->getCloneIDs();
|
||||
foreach(const QUuid& cloneChildID, cloneIDs) {
|
||||
EntityItemPointer cloneChild = findEntityByEntityItemID(cloneChildID);
|
||||
if (cloneChild) {
|
||||
cloneChild->setCloneOriginID(QUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 +677,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
|
|||
}
|
||||
|
||||
// tell our delete operator about this entityID
|
||||
cleanupCloneIDs(entityID);
|
||||
unhookChildAvatar(entityID);
|
||||
theOperator.addEntityIDToDeleteList(entityID);
|
||||
emit deletingEntity(entityID);
|
||||
|
@ -1392,6 +1417,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,6 +1426,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
break;
|
||||
}
|
||||
|
||||
case PacketType::EntityClone:
|
||||
isClone = true; // fall through to next case
|
||||
// FALLTHRU
|
||||
case PacketType::EntityAdd:
|
||||
isAdd = true; // fall through to next case
|
||||
// FALLTHRU
|
||||
|
@ -1422,8 +1451,22 @@ 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);
|
||||
if (validEditPacket) {
|
||||
entityToClone = findEntityByEntityItemID(entityIDToClone);
|
||||
if (entityToClone) {
|
||||
properties = entityToClone->getProperties();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties);
|
||||
}
|
||||
|
||||
endDecode = usecTimestampNow();
|
||||
|
||||
EntityItemPointer existingEntity;
|
||||
|
@ -1491,24 +1534,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 +1611,32 @@ 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.getCloneLimit();
|
||||
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 && cloneLimit != 0) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User attempted to clone entity ID:" << entityIDToClone << " which reached it's cloneable limit.";
|
||||
} else {
|
||||
if (isClone) {
|
||||
properties.convertToCloneProperties(entityIDToClone);
|
||||
}
|
||||
|
||||
// this is a new entity... assign a new entityID
|
||||
properties.setCreated(properties.getLastEdited());
|
||||
properties.setLastEditedBy(senderNode->getUUID());
|
||||
|
@ -1597,10 +1657,15 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
}
|
||||
}
|
||||
|
||||
if (newEntity && isClone) {
|
||||
entityToClone->addCloneID(newEntity->getEntityItemID());
|
||||
newEntity->setCloneOriginID(entityIDToClone);
|
||||
}
|
||||
|
||||
if (newEntity) {
|
||||
newEntity->markAsChangedOnServer();
|
||||
notifyNewlyCreatedEntity(*newEntity, senderNode);
|
||||
|
||||
|
||||
startLogging = usecTimestampNow();
|
||||
if (wantEditLogging()) {
|
||||
qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:"
|
||||
|
@ -1927,6 +1992,7 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo
|
|||
|
||||
if (shouldEraseEntity(entityID, sourceNode)) {
|
||||
entityItemIDsToDelete << entityItemID;
|
||||
cleanupCloneIDs(entityItemID);
|
||||
}
|
||||
}
|
||||
deleteEntities(entityItemIDsToDelete, true, true);
|
||||
|
@ -1976,6 +2042,7 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons
|
|||
|
||||
if (shouldEraseEntity(entityID, sourceNode)) {
|
||||
entityItemIDsToDelete << entityItemID;
|
||||
cleanupCloneIDs(entityItemID);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2322,6 +2389,8 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
|||
return false;
|
||||
}
|
||||
|
||||
QMap<QUuid, QVector<QUuid>> cloneIDs;
|
||||
|
||||
bool success = true;
|
||||
foreach (QVariant entityVariant, entitiesQList) {
|
||||
// QVariantMap --> QScriptValue --> EntityItemProperties --> Entity
|
||||
|
@ -2409,11 +2478,43 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
|||
}
|
||||
}
|
||||
|
||||
// Convert old cloneable entities so they use cloneableData instead of userData
|
||||
if (contentVersion < (int)EntityVersion::CloneableData) {
|
||||
QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object();
|
||||
QJsonObject grabbableKey = userData["grabbableKey"].toObject();
|
||||
QJsonValue cloneable = grabbableKey["cloneable"];
|
||||
if (cloneable.isBool() && cloneable.toBool()) {
|
||||
QJsonValue cloneLifetime = grabbableKey["cloneLifetime"];
|
||||
QJsonValue cloneLimit = grabbableKey["cloneLimit"];
|
||||
QJsonValue cloneDynamic = grabbableKey["cloneDynamic"];
|
||||
QJsonValue cloneAvatarEntity = grabbableKey["cloneAvatarEntity"];
|
||||
|
||||
// This is cloneable, we need to convert the properties
|
||||
properties.setCloneable(true);
|
||||
properties.setCloneLifetime(cloneLifetime.toInt());
|
||||
properties.setCloneLimit(cloneLimit.toInt());
|
||||
properties.setCloneDynamic(cloneDynamic.toBool());
|
||||
properties.setCloneAvatarEntity(cloneAvatarEntity.toBool());
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemPointer entity = addEntity(entityItemID, properties);
|
||||
if (!entity) {
|
||||
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
|
||||
success = false;
|
||||
}
|
||||
|
||||
const QUuid& cloneOriginID = entity->getCloneOriginID();
|
||||
if (!cloneOriginID.isNull()) {
|
||||
cloneIDs[cloneOriginID].push_back(entity->getEntityItemID());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entityID : cloneIDs.keys()) {
|
||||
auto entity = findEntityByID(entityID);
|
||||
if (entity) {
|
||||
entity->setCloneIDs(cloneIDs.value(entityID));
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
|
|
|
@ -110,13 +110,14 @@ public:
|
|||
// The newer API...
|
||||
void postAddEntity(EntityItemPointer entityItem);
|
||||
|
||||
EntityItemPointer addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
EntityItemPointer addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone = false);
|
||||
|
||||
// use this method if you only know the entityID
|
||||
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
|
||||
|
||||
// check if the avatar is a child of this entity, If so set the avatar parentID to null
|
||||
void unhookChildAvatar(const EntityItemID entityID);
|
||||
void cleanupCloneIDs(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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -131,6 +131,8 @@ public:
|
|||
OctreeDataFileReply,
|
||||
OctreeDataPersist,
|
||||
|
||||
EntityClone,
|
||||
|
||||
NUM_PACKET_TYPE
|
||||
};
|
||||
|
||||
|
@ -232,7 +234,8 @@ enum class EntityVersion : PacketVersion {
|
|||
SoftEntities,
|
||||
MaterialEntities,
|
||||
ShadowControl,
|
||||
MaterialData
|
||||
MaterialData,
|
||||
CloneableData
|
||||
};
|
||||
|
||||
enum class EntityScriptCallMethodVersion : PacketVersion {
|
||||
|
|
|
@ -413,8 +413,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
|
||||
this.cloneHotspot = function(props, controllerData) {
|
||||
if (entityIsCloneable(props)) {
|
||||
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
|
||||
var cloneID = cloneEntity(props, worldEntityProps);
|
||||
var cloneID = cloneEntity(props);
|
||||
return cloneID;
|
||||
}
|
||||
|
||||
|
|
|
@ -235,8 +235,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
// switch to grabbing
|
||||
var targetCloneable = entityIsCloneable(targetProps);
|
||||
if (targetCloneable) {
|
||||
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
|
||||
var cloneID = cloneEntity(targetProps, worldEntityProps);
|
||||
var cloneID = cloneEntity(targetProps);
|
||||
var cloneProps = Entities.getEntityProperties(cloneID);
|
||||
this.targetEntityID = cloneID;
|
||||
this.startNearGrabAction(controllerData, cloneProps);
|
||||
|
|
|
@ -35,6 +35,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
this.lastUnexpectedChildrenCheckTime = 0;
|
||||
this.robbed = false;
|
||||
this.highlightedEntity = null;
|
||||
this.cloneAllowed = true;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
500,
|
||||
|
@ -272,6 +273,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
this.checkForUnexpectedChildren(controllerData);
|
||||
this.robbed = false;
|
||||
this.cloneAllowed = true;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
|
@ -335,13 +337,16 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
var targetCloneable = entityIsCloneable(targetProps);
|
||||
|
||||
if (targetCloneable) {
|
||||
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
|
||||
var cloneID = cloneEntity(targetProps, worldEntityProps);
|
||||
var cloneProps = Entities.getEntityProperties(cloneID);
|
||||
this.grabbing = true;
|
||||
this.targetEntityID = cloneID;
|
||||
this.startNearParentingGrabEntity(controllerData, cloneProps);
|
||||
|
||||
if (this.cloneAllowed) {
|
||||
var cloneID = cloneEntity(targetProps);
|
||||
if (cloneID !== null) {
|
||||
var cloneProps = Entities.getEntityProperties(cloneID);
|
||||
this.grabbing = true;
|
||||
this.targetEntityID = cloneID;
|
||||
this.startNearParentingGrabEntity(controllerData, cloneProps);
|
||||
this.cloneAllowed = false; // prevent another clone call until inputs released
|
||||
}
|
||||
}
|
||||
} else if (targetProps) {
|
||||
this.grabbing = true;
|
||||
this.startNearParentingGrabEntity(controllerData, targetProps);
|
||||
|
|
|
@ -1040,12 +1040,13 @@ function loaded() {
|
|||
elWantsTrigger.checked = false;
|
||||
elIgnoreIK.checked = true;
|
||||
|
||||
elCloneable.checked = false;
|
||||
elCloneableDynamic.checked = false;
|
||||
elCloneable.checked = properties.cloneable;
|
||||
elCloneableDynamic.checked = properties.cloneDynamic;
|
||||
elCloneableAvatarEntity.checked = properties.cloneAvatarEntity;
|
||||
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
|
||||
elCloneableLimit.value = 0;
|
||||
elCloneableLifetime.value = 300;
|
||||
|
||||
elCloneableLimit.value = properties.cloneLimit;
|
||||
elCloneableLifetime.value = properties.cloneLifetime;
|
||||
|
||||
var grabbablesSet = false;
|
||||
var parsedUserData = {};
|
||||
try {
|
||||
|
@ -1069,27 +1070,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 +1440,12 @@ 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('cloneDynamic'));
|
||||
elCloneableAvatarEntity.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneAvatarEntity'));
|
||||
elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLifetime'));
|
||||
elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLimit'));
|
||||
|
||||
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.cloneDynamic;
|
||||
}
|
||||
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;
|
||||
cloneEntity = function(props) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -128,7 +128,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