diff --git a/.eslintrc.js b/.eslintrc.js index 83fda730e5..bc5de1d65d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -58,6 +58,7 @@ module.exports = { "RayPick": false, "LaserPointers": false, "ContextOverlay": false + "module": false }, "rules": { "brace-style": ["error", "1tbs", { "allowSingleLine": false }], diff --git a/interface/resources/icons/tablet-icons/clap-a.svg b/interface/resources/icons/tablet-icons/clap-a.svg new file mode 100644 index 0000000000..60df3e0795 --- /dev/null +++ b/interface/resources/icons/tablet-icons/clap-a.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/clap-i.svg b/interface/resources/icons/tablet-icons/clap-i.svg new file mode 100644 index 0000000000..fbd9ed0f9c --- /dev/null +++ b/interface/resources/icons/tablet-icons/clap-i.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index a0664f4f9b..04cf5a671e 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -18,10 +18,12 @@ #include #include +#include #include #include #include +#include #include #include @@ -39,7 +41,8 @@ #endif static const char* KEY_FILE = "hifikey"; -static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile +static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; +static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; void initialize() { static bool initialized = false; @@ -52,11 +55,8 @@ void initialize() { } QString keyFilePath() { - return PathUtils::getAppDataFilePath(KEY_FILE); -} - -QString imageFilePath() { - return PathUtils::getAppDataFilePath(IMAGE_FILE); + auto accountManager = DependencyManager::get(); + return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE)); } // use the cached _passphrase if it exists, otherwise we need to prompt @@ -131,13 +131,12 @@ bool writeKeys(const char* filename, RSA* keys) { return retval; } - -// BEGIN copied code - this will be removed/changed at some point soon // copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. // We will have a different implementation in practice, but this gives us a start for now // -// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably +// TODO: we don't really use the private keys returned - we can see how this evolves, but probably // we should just return a list of public keys? +// or perhaps return the RSA* instead? QPair generateRSAKeypair() { RSA* keyPair = RSA_new(); @@ -265,6 +264,15 @@ RSA* readPrivateKey(const char* filename) { return key; } +// QT's QByteArray will convert to Base64 without any embedded newlines. This just +// writes it with embedded newlines, which is more readable. +void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) { + for (int i = 0; i < b64Array.size(); i += 64) { + file.write(b64Array.mid(i, 64)); + file.write("\n"); + } +} + void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) { // use the ones in the wallet auto wallet = DependencyManager::get(); @@ -287,8 +295,7 @@ void Wallet::setPassphrase(const QString& passphrase) { _publicKeys.clear(); } -// encrypt some stuff -bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) { +bool Wallet::writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath) { // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be // a constant. We can review this later - there are ways to generate keys @@ -299,16 +306,12 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile initializeAESKeys(ivec, ckey, _salt); int tempSize, outSize; + QByteArray inputFileBuffer; + QBuffer buffer(&inputFileBuffer); + buffer.open(QIODevice::WriteOnly); - // read entire unencrypted file into memory - QFile inputFile(inputFilePath); - if (!inputFile.exists()) { - qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist"; - return false; - } - inputFile.open(QIODevice::ReadOnly); - QByteArray inputFileBuffer = inputFile.readAll(); - inputFile.close(); + // another spot where we are assuming only jpgs + pixmap->save(&buffer, "jpg"); // reserve enough capacity for encrypted bytes unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE]; @@ -337,16 +340,21 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile EVP_CIPHER_CTX_free(ctx); qCDebug(commerce) << "encrypted buffer size" << outSize; QByteArray output((const char*)outputFileBuffer, outSize); + + // now APPEND to the file, + QByteArray b64output = output.toBase64(); QFile outputFile(outputFilePath); - outputFile.open(QIODevice::WriteOnly); - outputFile.write(output); + outputFile.open(QIODevice::Append); + outputFile.write(IMAGE_HEADER); + outputBase64WithNewlines(outputFile, b64output); + outputFile.write(IMAGE_FOOTER); outputFile.close(); delete[] outputFileBuffer; return true; } -bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { +bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); @@ -357,9 +365,31 @@ bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBuf qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist"; return false; } - inputFile.open(QIODevice::ReadOnly); - QByteArray encryptedBuffer = inputFile.readAll(); + inputFile.open(QIODevice::ReadOnly | QIODevice::Text); + bool foundHeader = false; + bool foundFooter = false; + + QByteArray base64EncryptedBuffer; + + while (!inputFile.atEnd()) { + QString line(inputFile.readLine()); + if (!foundHeader) { + foundHeader = (line == IMAGE_HEADER); + } else { + foundFooter = (line == IMAGE_FOOTER); + if (!foundFooter) { + base64EncryptedBuffer.append(line); + } + } + } inputFile.close(); + if (! (foundHeader && foundFooter)) { + qCDebug(commerce) << "couldn't parse" << inputFilePath << foundHeader << foundFooter; + return false; + } + + // convert to bytes + auto encryptedBuffer = QByteArray::fromBase64(base64EncryptedBuffer); // setup decrypted buffer unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()]; @@ -428,6 +458,9 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateRSAKeypair(); + + // TODO: redo this soon -- need error checking and so on + writeSecurityImage(_securityImage, keyFilePath()); sendKeyFilePathIfExists(); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); @@ -493,27 +526,30 @@ void Wallet::chooseSecurityImage(const QString& filename) { if (_securityImage) { delete _securityImage; } - // temporary... QString path = qApp->applicationDirPath(); path.append("/resources/qml/hifi/commerce/wallet/"); path.append(filename); + // now create a new security image pixmap _securityImage = new QPixmap(); qCDebug(commerce) << "loading data for pixmap from" << path; _securityImage->load(path); - // encrypt it and save. - if (encryptFile(path, imageFilePath())) { - qCDebug(commerce) << "emitting pixmap"; - - updateImageProvider(); + // update the image now + updateImageProvider(); + // we could be choosing the _inital_ security image. If so, there + // will be no hifikey file yet. If that is the case, we are done. If + // there _is_ a keyfile, we need to update it (similar to changing the + // passphrase, we need to do so into a temp file and move it). + if (!QFile(keyFilePath()).exists()) { emit securityImageResult(true); - } else { - qCDebug(commerce) << "failed to encrypt security image"; - emit securityImageResult(false); + return; } + + bool success = writeWallet(); + emit securityImageResult(success); } void Wallet::getSecurityImage() { @@ -526,10 +562,11 @@ void Wallet::getSecurityImage() { return; } - // decrypt and return - QString filePath(imageFilePath()); - QFileInfo fileInfo(filePath); - if (fileInfo.exists() && decryptFile(filePath, &data, &dataLen)) { + bool success = false; + // decrypt and return. Don't bother if we have no file to decrypt, or + // no salt set yet. + QFileInfo fileInfo(keyFilePath()); + if (fileInfo.exists() && _salt.size() > 0 && readSecurityImage(keyFilePath(), &data, &dataLen)) { // create the pixmap _securityImage = new QPixmap(); _securityImage->loadFromData(data, dataLen, "jpg"); @@ -538,11 +575,9 @@ void Wallet::getSecurityImage() { updateImageProvider(); delete[] data; - emit securityImageResult(true); - } else { - qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)"; - emit securityImageResult(false); + success = true; } + emit securityImageResult(success); } void Wallet::sendKeyFilePathIfExists() { QString filePath(keyFilePath()); @@ -566,35 +601,47 @@ void Wallet::reset() { QFile keyFile(keyFilePath()); - QFile imageFile(imageFilePath()); keyFile.remove(); - imageFile.remove(); +} +bool Wallet::writeWallet(const QString& newPassphrase) { + RSA* keys = readKeys(keyFilePath().toStdString().c_str()); + if (keys) { + // we read successfully, so now write to a new temp file + QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); + QString oldPassphrase = *_passphrase; + if (!newPassphrase.isEmpty()) { + setPassphrase(newPassphrase); + } + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { + if (writeSecurityImage(_securityImage, tempFileName)) { + // ok, now move the temp file to the correct spot + QFile(QString(keyFilePath())).remove(); + QFile(tempFileName).rename(QString(keyFilePath())); + qCDebug(commerce) << "wallet written successfully"; + return true; + } else { + qCDebug(commerce) << "couldn't write security image to temp wallet"; + } + } else { + qCDebug(commerce) << "couldn't write keys to temp wallet"; + } + // if we are here, we failed, so cleanup + QFile(tempFileName).remove(); + if (!newPassphrase.isEmpty()) { + setPassphrase(oldPassphrase); + } + + } else { + qCDebug(commerce) << "couldn't read wallet - bad passphrase?"; + // TODO: review this, but it seems best to reset the passphrase + // since we couldn't decrypt the existing wallet (or is doesn't + // exist perhaps). + setPassphrase(""); + } + return false; } bool Wallet::changePassphrase(const QString& newPassphrase) { qCDebug(commerce) << "changing passphrase"; - RSA* keys = readKeys(keyFilePath().toStdString().c_str()); - if (keys) { - // we read successfully, so now write to a new temp file - // save old passphrase just in case - // TODO: force re-enter? - QString oldPassphrase = *_passphrase; - setPassphrase(newPassphrase); - QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); - if (writeKeys(tempFileName.toStdString().c_str(), keys)) { - // ok, now move the temp file to the correct spot - QFile(QString(keyFilePath())).remove(); - QFile(tempFileName).rename(QString(keyFilePath())); - qCDebug(commerce) << "passphrase changed successfully"; - return true; - } else { - qCDebug(commerce) << "couldn't write keys"; - QFile(tempFileName).remove(); - setPassphrase(oldPassphrase); - return false; - } - } - qCDebug(commerce) << "couldn't decrypt keys with current passphrase, clearing"; - setPassphrase(QString("")); - return false; + return writeWallet(newPassphrase); } diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index f3c2ac399b..b8913e9a5e 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -55,14 +55,15 @@ signals: private: QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; - QByteArray _salt {"iamsalt!"}; + QByteArray _salt; QByteArray _iv; QByteArray _ckey; QString* _passphrase { new QString("") }; + bool writeWallet(const QString& newPassphrase = QString("")); void updateImageProvider(); - bool encryptFile(const QString& inputFilePath, const QString& outputFilePath); - bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); + bool writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath); + bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); }; #endif // hifi_Wallet_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 22b2cffbcb..c6bad008e4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -909,9 +909,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { QVector jointsData; const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy - auto& fbxJoints = _animation->getGeometry().joints; - auto& originalFbxJoints = _model->getFBXGeometry().joints; - bool allowTranslation = entity->getAnimationAllowTranslation(); int frameCount = frames.size(); if (frameCount <= 0) { return; @@ -946,19 +943,37 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { return; } + QStringList animationJointNames = _animation->getGeometry().getJointNames(); + auto& fbxJoints = _animation->getGeometry().joints; + + auto& originalFbxJoints = _model->getFBXGeometry().joints; + auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + + bool allowTranslation = entity->getAnimationAllowTranslation(); + const QVector& rotations = frames[_lastKnownCurrentFrame].rotations; const QVector& translations = frames[_lastKnownCurrentFrame].translations; jointsData.resize(_jointMapping.size()); for (int j = 0; j < _jointMapping.size(); j++) { int index = _jointMapping[j]; + if (index >= 0) { glm::mat4 translationMat; - if (!allowTranslation){ - translationMat = glm::translate(originalFbxJoints[index].translation); - } else if (index < translations.size()) { - translationMat = glm::translate(translations[index]); - } + + if (allowTranslation) { + if(index < translations.size()){ + translationMat = glm::translate(translations[index]); + } + } else if (index < animationJointNames.size()){ + QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation + + if (originalFbxIndices.contains(jointName)) { + // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. + int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + } + } glm::mat4 rotationMat; if (index < rotations.size()) { rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); @@ -975,7 +990,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { jointData.rotationSet = true; } } - // Set the data in the entity entity->setAnimationJointsData(jointsData); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 5f66a78679..8257c883a2 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -761,6 +761,7 @@ QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResu obj.setProperty("distance", rayPickResult.distance); QScriptValue intersection = vec3toScriptValue(engine, rayPickResult.intersection); obj.setProperty("intersection", intersection); + obj.setProperty("intersects", rayPickResult.type != NONE); QScriptValue surfaceNormal = vec3toScriptValue(engine, rayPickResult.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); return obj; diff --git a/libraries/ui/src/ui/ImageProvider.cpp b/libraries/ui/src/ui/ImageProvider.cpp index c74ed0cb44..7bbad43f2e 100644 --- a/libraries/ui/src/ui/ImageProvider.cpp +++ b/libraries/ui/src/ui/ImageProvider.cpp @@ -51,5 +51,9 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize return _securityImage->copy(); } } - return QPixmap(); + // otherwise just return a grey pixmap. This avoids annoying error messages in qml we would get + // when sending a 'null' pixmap (QPixmap()) + QPixmap greyPixmap(200, 200); + greyPixmap.fill(QColor("darkGrey")); + return greyPixmap; } diff --git a/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json new file mode 100644 index 0000000000..2dca45a0de --- /dev/null +++ b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json @@ -0,0 +1,386 @@ +{ + "RightShoulder": { + "rotations": { + "x": 0.5578474402427673, + "y": -0.44214877486228943, + "z": 0.4979960322380066, + "w": 0.4952884614467621 + } + }, + "RightArm": { + "rotations": { + "x": 0.6266362071037292, + "y": -0.02882515825331211, + "z": -0.3233867585659027, + "w": 0.7084611654281616 + } + }, + "RightForeArm": { + "rotations": { + "x": 0.00004018074105260894, + "y": 0.06418406218290329, + "z": -0.5193823575973511, + "w": 0.8521281480789185 + } + }, + "RightHand": { + "rotations": { + "x": -0.21368850767612457, + "y": 0.02043931558728218, + "z": -0.02227853797376156, + "w": 0.9764339923858643 + } + }, + "RightHandPinky1": { + "rotations": { + "x": -0.003497191471979022, + "y": -0.01721140556037426, + "z": 0.19736060500144958, + "w": 0.980173647403717 + } + }, + "RightHandPinky2": { + "rotations": { + "x": 0.000005483317181642633, + "y": -0.00008023700502235442, + "z": -0.06867633014917374, + "w": 0.997639000415802 + } + }, + "RightHandPinky3": { + "rotations": { + "x": 7.18885644346301e-8, + "y": -2.7131383717460267e-7, + "z": 0.007620905060321093, + "w": 0.9999709725379944 + } + }, + "RightHandPinky4": { + "rotations": { + "x": -5.719068774112657e-9, + "y": 5.142213126418937e-7, + "z": -0.0075718737207353115, + "w": 0.999971330165863 + } + }, + "RightHandRing1": { + "rotations": { + "x": -0.0013452530838549137, + "y": -0.017564140260219574, + "z": 0.0761696845293045, + "w": 0.9969393610954285 + } + }, + "RightHandRing2": { + "rotations": { + "x": 0.0000010375651982030831, + "y": -0.00002211921673733741, + "z": -0.04599710553884506, + "w": 0.9989416003227234 + } + }, + "RightHandRing3": { + "rotations": { + "x": -2.7102969868408877e-10, + "y": 1.9202734335976857e-7, + "z": 0.0016911650309339166, + "w": 0.9999985694885254 + } + }, + "RightHandRing4": { + "rotations": { + "x": 2.3246689018208144e-9, + "y": -3.364403156069784e-8, + "z": 0.0004951066803187132, + "w": 0.9999998807907104 + } + }, + "RightHandMiddle1": { + "rotations": { + "x": 0.0012630893616005778, + "y": -0.017612185329198837, + "z": -0.07168931514024734, + "w": 0.9972707033157349 + } + }, + "RightHandMiddle2": { + "rotations": { + "x": 2.3561028683616314e-7, + "y": 0.000020313073036959395, + "z": 0.011195243336260319, + "w": 0.9999373555183411 + } + }, + "RightHandMiddle3": { + "rotations": { + "x": 6.375214667286855e-8, + "y": 4.750924631480302e-7, + "z": 0.00237679248675704, + "w": 0.9999971985816956 + } + }, + "RightHandMiddle4": { + "rotations": { + "x": 6.717256439969788e-8, + "y": 3.876683507542111e-8, + "z": -0.005236906465142965, + "w": 0.9999862909317017 + } + }, + "RightHandIndex1": { + "rotations": { + "x": 0.002164300065487623, + "y": -0.017346171662211418, + "z": -0.12158434838056564, + "w": 0.9924271702766418 + } + }, + "RightHandIndex2": { + "rotations": { + "x": -0.00000143755482895358, + "y": -0.0001614764187252149, + "z": 0.008941099047660828, + "w": 0.9999601244926453 + } + }, + "RightHandIndex3": { + "rotations": { + "x": 7.458467621290765e-8, + "y": 5.365728839024086e-7, + "z": 0.03373909369111061, + "w": 0.999430775642395 + } + }, + "RightHandIndex4": { + "rotations": { + "x": 4.511302997833866e-10, + "y": -2.259726272768603e-7, + "z": -0.009632252156734467, + "w": 0.9999536275863647 + } + }, + "RightHandThumb1": { + "rotations": { + "x": -0.0783928632736206, + "y": -0.3033908009529114, + "z": -0.26653754711151123, + "w": 0.9114638566970825 + } + }, + "RightHandThumb2": { + "rotations": { + "x": 0.0031029442325234413, + "y": 0.07382386922836304, + "z": 0.005253761075437069, + "w": 0.9972526431083679 + } + }, + "RightHandThumb3": { + "rotations": { + "x": 0.0040440745651721954, + "y": -0.04943573474884033, + "z": 0.007246015593409538, + "w": 0.9987428188323975 + } + }, + "RightHandThumb4": { + "rotations": { + "x": -0.00009280416270485148, + "y": -0.01658034883439541, + "z": -0.00014316302258521318, + "w": 0.999862551689148 + } + }, + "LeftShoulder": { + "rotations": { + "x": 0.5578474402427673, + "y": 0.44214877486228943, + "z": -0.4979960322380066, + "w": 0.4952884614467621 + } + }, + "LeftArm": { + "rotations": { + "x": 0.626636266708374, + "y": 0.028824958950281143, + "z": 0.3233867585659027, + "w": 0.7084611654281616 + } + }, + "LeftForeArm": { + "rotations": { + "x": 0.00004015670492663048, + "y": -0.06418408453464508, + "z": 0.5193824768066406, + "w": 0.8521282076835632 + } + }, + "LeftHand": { + "rotations": { + "x": -0.21368853747844696, + "y": -0.02043931558728218, + "z": 0.02227853797376156, + "w": 0.9764339923858643 + } + }, + "LeftHandPinky1": { + "rotations": { + "x": -0.003497188910841942, + "y": 0.01721140556037426, + "z": -0.19736060500144958, + "w": 0.980173647403717 + } + }, + "LeftHandPinky2": { + "rotations": { + "x": 0.000005479304491018411, + "y": 0.00008023556438274682, + "z": 0.06867631524801254, + "w": 0.997639000415802 + } + }, + "LeftHandPinky3": { + "rotations": { + "x": 7.229602516645173e-8, + "y": 2.709063835482084e-7, + "z": -0.007620909716933966, + "w": 0.9999709725379944 + } + }, + "LeftHandPinky4": { + "rotations": { + "x": -5.52988677071653e-9, + "y": -5.13755651354586e-7, + "z": 0.007571868598461151, + "w": 0.999971330165863 + } + }, + "LeftHandRing1": { + "rotations": { + "x": -0.001345252152532339, + "y": 0.017564138397574425, + "z": -0.0761696845293045, + "w": 0.9969393610954285 + } + }, + "LeftHandRing2": { + "rotations": { + "x": 0.000001034482806971937, + "y": 0.000022119218556326814, + "z": 0.04599710553884506, + "w": 0.9989416003227234 + } + }, + "LeftHandRing3": { + "rotations": { + "x": -2.8012464570181805e-10, + "y": -1.923183816643359e-7, + "z": -0.0016911652637645602, + "w": 0.9999985694885254 + } + }, + "LeftHandRing4": { + "rotations": { + "x": -1.1168596047994583e-9, + "y": 3.33529932561305e-8, + "z": -0.0004951058072037995, + "w": 0.9999998807907104 + } + }, + "LeftHandMiddle1": { + "rotations": { + "x": 0.0012630895944312215, + "y": 0.01761218160390854, + "z": 0.07168931514024734, + "w": 0.9972707033157349 + } + }, + "LeftHandMiddle2": { + "rotations": { + "x": 2.37378529277521e-7, + "y": -0.00002031277836067602, + "z": -0.011195233091711998, + "w": 0.9999373555183411 + } + }, + "LeftHandMiddle3": { + "rotations": { + "x": 7.146466884933034e-8, + "y": -4.7555812443533796e-7, + "z": -0.0023767934180796146, + "w": 0.9999971985816956 + } + }, + "LeftHandMiddle4": { + "rotations": { + "x": 6.549178976911207e-8, + "y": -3.865041975359418e-8, + "z": 0.005236904136836529, + "w": 0.9999862909317017 + } + }, + "LeftHandIndex1": { + "rotations": { + "x": 0.002164299599826336, + "y": 0.017346171662211418, + "z": 0.12158433347940445, + "w": 0.9924271702766418 + } + }, + "LeftHandIndex2": { + "rotations": { + "x": -0.000001437780269952782, + "y": 0.00016147761198226362, + "z": -0.008941099047660828, + "w": 0.9999601244926453 + } + }, + "LeftHandIndex3": { + "rotations": { + "x": 7.61426193207626e-8, + "y": -5.373883027459669e-7, + "z": -0.03373908996582031, + "w": 0.999430775642395 + } + }, + "LeftHandIndex4": { + "rotations": { + "x": -5.311697748311417e-10, + "y": 2.26380109324964e-7, + "z": 0.009632255882024765, + "w": 0.9999536275863647 + } + }, + "LeftHandThumb1": { + "rotations": { + "x": -0.07839284837245941, + "y": 0.3033908009529114, + "z": 0.26653754711151123, + "w": 0.9114638566970825 + } + }, + "LeftHandThumb2": { + "rotations": { + "x": 0.0031029372476041317, + "y": -0.07382386922836304, + "z": -0.005253763869404793, + "w": 0.9972526431083679 + } + }, + "LeftHandThumb3": { + "rotations": { + "x": 0.004044072702527046, + "y": 0.049435727298259735, + "z": -0.0072460174560546875, + "w": 0.9987428188323975 + } + }, + "LeftHandThumb4": { + "rotations": { + "x": -0.00009280881931772456, + "y": 0.016580356284976006, + "z": 0.00014314628788270056, + "w": 0.999862551689148 + } + } +} diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx new file mode 100644 index 0000000000..a3830b9838 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx differ diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx new file mode 100644 index 0000000000..1877e963f9 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx differ diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js new file mode 100644 index 0000000000..b2d8ce55db --- /dev/null +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -0,0 +1,61 @@ +"use strict"; + +/* + clapApp.js + unpublishedScripts/marketplace/clap/clapApp.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 High Fidelity, Inc. + + Distributed under the Apache License, Version 2.0. + See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +// Entry Script for the clap app + +// Load up engine +var APP_NAME = "CLAP"; +var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v9")); +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +// Define Menu +var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg?foxv2"); +var whiteIcon = Script.resolvePath("icons/tablet-icons/clap-i.svg?foxv2"); + +if (Settings.getValue("clapAppEnabled") === undefined) { + Settings.setValue("clapAppEnabled", true); +} +var isActive = Settings.getValue("clapAppEnabled"); + +var activeButton = tablet.addButton({ + icon: whiteIcon, + activeIcon: blackIcon, + text: APP_NAME, + isActive: isActive, + sortOrder: 11 +}); + +if (isActive) { + ClapEngine.connectEngine(); +} + +function onClick(enabled) { + + isActive = !isActive; + Settings.setValue("clapAppEnabled", isActive); + activeButton.editProperties({ + isActive: isActive + }); + if (isActive) { + ClapEngine.connectEngine(); + } else { + ClapEngine.disconnectEngine(); + } +} +activeButton.clicked.connect(onClick); + +Script.scriptEnding.connect(function () { + ClapEngine.disconnectEngine(); + activeButton.clicked.disconnect(onClick); + tablet.removeButton(activeButton); +}); diff --git a/unpublishedScripts/marketplace/clap/entities/ClapParticle.json b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json new file mode 100644 index 0000000000..bf1b70665b --- /dev/null +++ b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json @@ -0,0 +1,58 @@ +{ + "alpha": 0.01, + "alphaFinish": 0.0, + "alphaSpread": 1, + "alphaStart": 0.05, + "color": { + "blue": 200, + "green": 200, + "red": 200 + }, + "colorFinish": { + "blue": 200, + "green": 200, + "red": 200 + }, + "colorStart": { + "blue": 200, + "green": 200, + "red": 200 + }, + "created": "2017-09-09T16:01:38Z", + "dimensions": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "x": 0.25, + "y": 0.25, + "z": 0.25 + }, + "emitOrientation": { + "w": 1, + "x": 0, + "y": 0, + "z": 0 + }, + "emitRate": 100, + "emitSpeed": 0.125, + "lifespan": 0.5, + "lifetime": 2, + "script": "(function(){return{preload:function(id){Script.setTimeout(function(){Entities.editEntity(id,{isEmitting:false});},200);}}})", + "maxParticles": 100, + "particleRadius": 0.05, + "polarFinish": 1.4311699867248535, + "polarStart": 1.3962633609771729, + "radiusFinish": 0.01, + "radiusSpread": 0.2, + "radiusStart": 0.05, + "speedSpread": 0, + "emitShouldTrail": true, + "type": "ParticleEffect" +} diff --git a/unpublishedScripts/marketplace/clap/icons/clap-a.svg b/unpublishedScripts/marketplace/clap/icons/clap-a.svg new file mode 100644 index 0000000000..60df3e0795 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-a.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/icons/clap-i.svg b/unpublishedScripts/marketplace/clap/icons/clap-i.svg new file mode 100644 index 0000000000..fbd9ed0f9c --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-i.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js new file mode 100644 index 0000000000..9e7a592c01 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js @@ -0,0 +1,144 @@ +"use strict"; + +/* + clapApp.js + unpublishedScripts/marketplace/clap/scripts/clapDebugger.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 High Fidelity, Inc. + + Distributed under the Apache License, Version 2.0. + See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +var DEBUG_RIGHT_HAND; +var DEBUG_LEFT_HAND; +var DEBUG_CLAP_LEFT; +var DEBUG_CLAP_RIGHT; +var DEBUG_CLAP; +var DEBUG_CLAP_DIRECTION; + +// Debug Values: +var DEBUG_CORRECT = { + red: 0, + green: 255, + blue: 0 +}; +var DEBUG_WRONG = { + red: 255, + green: 0, + blue: 0 +}; + +var DEBUG_VOLUME = { + red: 255, + green: 255, + blue: 128 +}; + +module.exports = { + disableDebug: function () { + Overlays.deleteOverlay(DEBUG_RIGHT_HAND); + Overlays.deleteOverlay(DEBUG_LEFT_HAND); + Overlays.deleteOverlay(DEBUG_CLAP_LEFT); + Overlays.deleteOverlay(DEBUG_CLAP_RIGHT); + Overlays.deleteOverlay(DEBUG_CLAP_DIRECTION); + }, + + debugPositions: function (leftAlignmentWorld, leftHandPositionOffset, leftHandDownWorld, rightAlignmentWorld, rightHandPositionOffset, rightHandDownWorld, tolerance) { + + Overlays.editOverlay(DEBUG_CLAP_LEFT, { + color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + position: leftHandPositionOffset + }); + + Overlays.editOverlay(DEBUG_CLAP_RIGHT, { + color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + position: rightHandPositionOffset + }); + + Overlays.editOverlay(DEBUG_LEFT_HAND, { + color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + start: leftHandPositionOffset, + end: Vec3.sum(leftHandPositionOffset, Vec3.multiply(leftHandDownWorld, 0.2)) + }); + + Overlays.editOverlay(DEBUG_RIGHT_HAND, { + color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + start: rightHandPositionOffset, + end: Vec3.sum(rightHandPositionOffset, Vec3.multiply(rightHandDownWorld, 0.2)) + }); + }, + + debugClapLine: function (start, end, visible) { + Overlays.editOverlay(DEBUG_CLAP_DIRECTION, { + start: start, + end: end, + visible: visible + }); + }, + + enableDebug: function () { + DEBUG_RIGHT_HAND = Overlays.addOverlay("line3d", { + color: DEBUG_WRONG, + start: MyAvatar.position, + end: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + + DEBUG_LEFT_HAND = Overlays.addOverlay("line3d", { + color: DEBUG_WRONG, + start: MyAvatar.position, + end: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + + DEBUG_CLAP_LEFT = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_WRONG, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + DEBUG_CLAP_RIGHT = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_WRONG, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + + DEBUG_CLAP_DIRECTION = Overlays.addOverlay("line3d", { + color: DEBUG_VOLUME, + start: MyAvatar.position, + end: MyAvatar.position, + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + } +}; diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js new file mode 100644 index 0000000000..1a5f6665e1 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js @@ -0,0 +1,313 @@ +"use strict"; + +/* + clapEngine.js + unpublishedScripts/marketplace/clap/clapApp.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 High Fidelity, Inc. + + Distributed under the Apache License, Version 2.0. + See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + + Main Heart of the clap script> Does both keyboard binding and tracking of the gear.. + +*/ +var DEG_TO_RAD = Math.PI / 180; +// If angle is closer to 0 from 25 degrees, then "clap" can happen; +var COS_OF_TOLERANCE = Math.cos(25 * DEG_TO_RAD); +var DISTANCE = 0.3; +var CONTROL_MAP_PACKAGE = "com.highfidelity.avatar.clap.active"; + +var CLAP_MENU = "Clap"; +var ENABLE_PARTICLE_MENU = "Enable Clap Particles"; +var ENABLE_DEBUG_MENU = "Enable Claping Helper"; + +var sounds = [ + "clap1.wav", + "clap2.wav", + "clap3.wav", + "clap4.wav", + "clap5.wav", + "clap6.wav" +]; + + +var ClapParticle = Script.require(Script.resolvePath("../entities/ClapParticle.json?V3")); +var ClapAnimation = Script.require(Script.resolvePath("../animations/ClapAnimation.json?V3")); +var ClapDebugger = Script.require(Script.resolvePath("ClapDebugger.js?V3")); + +var settingDebug = false; +var settingParticlesOn = true; + +function setJointRotation(map) { + Object.keys(map).forEach(function (key, index) { + MyAvatar.setJointRotation(MyAvatar.getJointIndex(key), map[key].rotations); + }); +} +// Load Sounds to Cache +var cache = []; +for (var index in sounds) { + cache.push(SoundCache.getSound(Script.resolvePath("../sounds/" + sounds[index]))); +} + + +var previousIndex; +var clapOn = false; +var animClap = false; +var animThrottle; + +function clap(volume, position, rotation) { + var index; + // Make sure one does not generate consequtive sounds + do { + index = Math.floor(Math.random() * cache.length); + } while (index === previousIndex); + + previousIndex = index; + + Audio.playSound(cache[index], { + position: position, + volume: volume / 4 + Math.random() * (volume / 3) + }); + + if (settingParticlesOn) { + ClapParticle.orientation = Quat.multiply(MyAvatar.orientation, rotation); + ClapParticle.position = position; + ClapParticle.emitSpeed = volume > 1 ? 1 : volume; + Entities.addEntity(ClapParticle, true); + } + + +} +// Helper Functions +function getHandFingerAnim(side) { + return Script.resolvePath("../animations/Clap_" + side + '.fbx'); +} + +// Disable all role animations related to fingers for side +function overrideFingerRoleAnimation(side) { + var anim = getHandFingerAnim(side); + MyAvatar.overrideRoleAnimation(side + "HandGraspOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandGraspClosed", anim, 30, true, 0, 0); + if (HMD.active) { + MyAvatar.overrideRoleAnimation(side + "HandPointIntro", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandPointHold", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandPointOutro", anim, 30, true, 0, 0); + + MyAvatar.overrideRoleAnimation(side + "IndexPointOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointClosed", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseClosed", anim, 30, true, 0, 0); + + MyAvatar.overrideRoleAnimation(side + "ThumbRaiseOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "ThumbRaiseClosed", anim, 30, true, 0, 0); + } +} +// Re-enable all role animations for fingers +function restoreFingerRoleAnimation(side) { + MyAvatar.restoreRoleAnimation(side + "HandGraspOpen"); + MyAvatar.restoreRoleAnimation(side + "HandGraspClosed"); + if (HMD.active) { + MyAvatar.restoreRoleAnimation(side + "HandPointIntro"); + MyAvatar.restoreRoleAnimation(side + "HandPointHold"); + MyAvatar.restoreRoleAnimation(side + "HandPointOutro"); + MyAvatar.restoreRoleAnimation(side + "IndexPointOpen"); + + MyAvatar.restoreRoleAnimation(side + "IndexPointClosed"); + MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseClosed"); + MyAvatar.restoreRoleAnimation(side + "ThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation(side + "ThumbRaiseClosed"); + } +} + + +function menuListener(menuItem) { + if (menuItem === ENABLE_PARTICLE_MENU) { + settingParticlesOn = Menu.isOptionChecked(ENABLE_PARTICLE_MENU); + } else if (menuItem === ENABLE_DEBUG_MENU) { + var debugOn = Menu.isOptionChecked(ENABLE_DEBUG_MENU); + + if (debugOn) { + settingDebug = true; + ClapDebugger.enableDebug(); + } else { + settingDebug = false; + ClapDebugger.disableDebug(); + } + } +} + +function update(dt) { + + // NOTICE: Someof this stuff is unnessary for the actual: But they are done for Debug Purposes! + // Forexample, the controller doesnt really need to know where it is in the world, only its relation to the other controller! + + var leftHand = Controller.getPoseValue(Controller.Standard.LeftHand); + var rightHand = Controller.getPoseValue(Controller.Standard.RightHand); + + // Get Offset position for palms, not the controllers that are at the wrists (7.5 cm up) + + var leftWorldRotation = Quat.multiply(MyAvatar.orientation, leftHand.rotation); + var rightWorldRotation = Quat.multiply(MyAvatar.orientation, rightHand.rotation); + + var leftWorldUpNormal = Quat.getUp(leftWorldRotation); + var rightWorldUpNormal = Quat.getUp(rightWorldRotation); + + var leftHandDownWorld = Vec3.multiply(-1, Quat.getForward(leftWorldRotation)); + var rightHandDownWorld = Vec3.multiply(-1, Quat.getForward(rightWorldRotation)); + + // + var leftHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, leftHand.translation)); + var rightHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, rightHand.translation)); + + var rightHandPositionOffset = Vec3.sum(rightHandWorldPosition, Vec3.multiply(rightWorldUpNormal, 0.035)); + var leftHandPositionOffset = Vec3.sum(leftHandWorldPosition, Vec3.multiply(leftWorldUpNormal, 0.035)); + + var leftToRightWorld = Vec3.subtract(leftHandPositionOffset, rightHandPositionOffset); + var rightToLeftWorld = Vec3.subtract(rightHandPositionOffset, leftHandPositionOffset); + + var leftAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(leftToRightWorld), leftHandDownWorld); + var rightAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(rightToLeftWorld), rightHandDownWorld); + + var distance = Vec3.distance(rightHandPositionOffset, leftHandPositionOffset); + + var matchTolerance = leftAlignmentWorld > COS_OF_TOLERANCE && rightAlignmentWorld > COS_OF_TOLERANCE; + + if (settingDebug) { + ClapDebugger.debugPositions(leftAlignmentWorld, + leftHandPositionOffset, leftHandDownWorld, + rightAlignmentWorld, rightHandPositionOffset, + rightHandDownWorld, COS_OF_TOLERANCE); + } + // Using subtract, because these will be heading to opposite directions + var angularVelocity = Vec3.length(Vec3.subtract(leftHand.angularVelocity, rightHand.angularVelocity)); + var velocity = Vec3.length(Vec3.subtract(leftHand.velocity, rightHand.velocity)); + + if (matchTolerance && distance < DISTANCE && !animClap) { + if (settingDebug) { + ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, true); + } + if (!animThrottle) { + overrideFingerRoleAnimation("left"); + overrideFingerRoleAnimation("right"); + animClap = true; + } else { + Script.clearTimeout(animThrottle); + animThrottle = false; + } + } else if (animClap && distance > DISTANCE) { + if (settingDebug) { + ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, false); + } + animThrottle = Script.setTimeout(function () { + restoreFingerRoleAnimation("left"); + restoreFingerRoleAnimation("right"); + animClap = false; + }, 500); + } + + if (distance < DISTANCE && matchTolerance && !clapOn) { + clapOn = true; + + var midClap = Vec3.mix(rightHandPositionOffset, leftHandPositionOffset, 0.5); + var volume = velocity / 2 + angularVelocity / 5; + + clap(volume, midClap, Quat.lookAtSimple(rightHandPositionOffset, leftHandPositionOffset)); + } else if (distance > DISTANCE && !matchTolerance) { + clapOn = false; + } +} + + +module.exports = { + connectEngine: function () { + if (!Menu.menuExists(CLAP_MENU)) { + Menu.addMenu(CLAP_MENU); + } + + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_PARTICLE_MENU, + isCheckable: true, + isChecked: settingParticlesOn + }); + } + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_DEBUG_MENU, + isCheckable: true, + isChecked: settingDebug + }); + } + + + Menu.menuItemEvent.connect(menuListener); + + var controls = Controller.newMapping(CONTROL_MAP_PACKAGE); + var Keyboard = Controller.Hardware.Keyboard; + + controls.from(Keyboard.K).to(function (down) { + if (down) { + + setJointRotation(ClapAnimation); + Script.setTimeout(function () { + // As soon as an animation bug is fixed, this will kick and get fixed.s. + overrideFingerRoleAnimation("left"); + overrideFingerRoleAnimation("right"); + + var lh = MyAvatar.getJointPosition("LeftHand"); + var rh = MyAvatar.getJointPosition("RightHand"); + var midClap = Vec3.mix(rh, lh, 0.5); + var volume = 0.5 + Math.random() * 0.5; + var position = midClap; + + clap(volume, position, Quat.fromVec3Degrees(0, 0, 0)); + }, 50); // delay is present to allow for frames to catch up. + } else { + + restoreFingerRoleAnimation("left"); + + restoreFingerRoleAnimation("right"); + MyAvatar.clearJointsData(); + } + }); + Controller.enableMapping(CONTROL_MAP_PACKAGE); + + if (settingDebug) { + ClapDebugger.enableDebug(); + } + + Script.update.connect(update); + + Script.scriptEnding.connect(this.disconnectEngine); + }, + disconnectEngine: function () { + if (settingDebug) { + ClapDebugger.disableDebug(); + } + if (Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_PARTICLE_MENU); + } + if (Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_DEBUG_MENU); + } + + if (Menu.menuExists(CLAP_MENU)) { + Menu.removeMenu(CLAP_MENU); + } + restoreFingerRoleAnimation('left'); + restoreFingerRoleAnimation('right'); + + Controller.disableMapping(CONTROL_MAP_PACKAGE); + try { + Script.update.disconnect(update); + } catch (e) { + print("Script.update connection did not exist on disconnection. Skipping"); + } + } +}; diff --git a/unpublishedScripts/marketplace/clap/sounds/clap1.wav b/unpublishedScripts/marketplace/clap/sounds/clap1.wav new file mode 100644 index 0000000000..679cb7e732 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap1.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap2.wav b/unpublishedScripts/marketplace/clap/sounds/clap2.wav new file mode 100644 index 0000000000..b43c745e52 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap2.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap3.wav b/unpublishedScripts/marketplace/clap/sounds/clap3.wav new file mode 100644 index 0000000000..47a4a8d48f Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap3.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap4.wav b/unpublishedScripts/marketplace/clap/sounds/clap4.wav new file mode 100644 index 0000000000..4e8dfca1a0 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap4.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap5.wav b/unpublishedScripts/marketplace/clap/sounds/clap5.wav new file mode 100644 index 0000000000..1f38864db4 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap5.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap6.wav b/unpublishedScripts/marketplace/clap/sounds/clap6.wav new file mode 100644 index 0000000000..4591f79d9d Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap6.wav differ