diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index dd25aa4c4b..49b4b1ced4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -168,7 +168,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { QList avatarList; std::unordered_map avatarDataToNodes; - int listItem = 0; std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); @@ -176,7 +175,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // but not have yet sent data that's linked to the node. Check for that case and don't // consider those nodes. if (otherNodeData) { - listItem++; AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); avatarList << otherAvatar; avatarDataToNodes[otherAvatar] = otherNode; @@ -185,8 +183,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer(); ViewFrustum cameraView = nodeData->getViewFrustom(); - std::priority_queue sortedAvatars = AvatarData::sortAvatars( - avatarList, cameraView, + std::priority_queue sortedAvatars; + AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, [&](AvatarSharedPointer avatar)->uint64_t{ auto avatarNode = avatarDataToNodes[avatar]; diff --git a/interface/resources/avatar/animations/sitting.fbx b/interface/resources/avatar/animations/sitting.fbx new file mode 100644 index 0000000000..dfb51afb66 Binary files /dev/null and b/interface/resources/avatar/animations/sitting.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle.fbx b/interface/resources/avatar/animations/sitting_idle.fbx new file mode 100644 index 0000000000..ee03d942cd Binary files /dev/null and b/interface/resources/avatar/animations/sitting_idle.fbx differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e1b06e8ff0..b33ac3a37c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3104,23 +3104,18 @@ void Application::mouseMoveEvent(QMouseEvent* event) { if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_OVERLAY_ID) { - if (_mouseToOverlays) { - getOverlays().mouseMoveEvent(&mappedEvent); - } else { - getEntities()->mouseMoveEvent(&mappedEvent); - } + getOverlays().mouseMoveEvent(&mappedEvent); + getEntities()->mouseMoveEvent(&mappedEvent); } - if (!_mouseToOverlays) { - _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts - } + _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } - if (!_mouseToOverlays && _keyboardMouseDevice->isActive()) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseMoveEvent(event); } } @@ -3128,7 +3123,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) { void Application::mousePressEvent(QMouseEvent* event) { // Inhibit the menu if the user is using alt-mouse dragging _altPressed = false; - _mouseToOverlays = false; auto offscreenUi = DependencyManager::get(); // If we get a mouse press event it means it wasn't consumed by the offscreen UI, @@ -3145,23 +3139,20 @@ void Application::mousePressEvent(QMouseEvent* event) { event->buttons(), event->modifiers()); if (!_aboutToQuit) { - if (getOverlays().mousePressEvent(&mappedEvent)) { - _mouseToOverlays = true; - } else if (!_controllerScriptingInterface->areEntityClicksCaptured()) { + getOverlays().mousePressEvent(&mappedEvent); + if (!_controllerScriptingInterface->areEntityClicksCaptured()) { getEntities()->mousePressEvent(&mappedEvent); } } - if (!_mouseToOverlays) { - _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts - } + _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } - if (!_mouseToOverlays && hasFocus()) { + if (hasFocus()) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mousePressEvent(event); } @@ -3195,23 +3186,18 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { event->buttons(), event->modifiers()); if (!_aboutToQuit) { - if (_mouseToOverlays) { - getOverlays().mouseReleaseEvent(&mappedEvent); - } else { - getEntities()->mouseReleaseEvent(&mappedEvent); - } + getOverlays().mouseReleaseEvent(&mappedEvent); + getEntities()->mouseReleaseEvent(&mappedEvent); } - if (!_mouseToOverlays) { - _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts - } + _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; } - if (!_mouseToOverlays && hasFocus()) { + if (hasFocus()) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseReleaseEvent(event); } @@ -3457,7 +3443,7 @@ void Application::idle(float nsecsElapsed) { #ifdef Q_OS_WIN static std::once_flag once; std::call_once(once, [] { - initCpuUsage(); + initCpuUsage(); }); vec3 kernelUserAndSystem; diff --git a/interface/src/Application.h b/interface/src/Application.h index fa98f6bd6e..13c1458aee 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -676,8 +676,6 @@ private: void addAssetToWorldInfoDone(QString modelName); void addAssetToWorldError(QString modelName, QString errorText); - bool _mouseToOverlays { false }; - QQuickItem* _addAssetToWorldMessageBox{ nullptr }; QStringList _addAssetToWorldInfoKeys; // Model name QStringList _addAssetToWorldInfoMessages; // Info message diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f5fe82ef4b..6e1f44f5ac 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -351,7 +351,6 @@ void Avatar::simulate(float deltaTime, bool inView) { _jointDataSimulationRate.increment(); _skeletonModel->simulate(deltaTime, true); - _skeletonModelSimulationRate.increment(); locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointData = false; @@ -367,8 +366,8 @@ void Avatar::simulate(float deltaTime, bool inView) { } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. _skeletonModel->simulate(deltaTime, false); - _skeletonModelSimulationRate.increment(); } + _skeletonModelSimulationRate.increment(); } // update animation for display name fade in/out diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d806c042b9..7417f73102 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -157,15 +157,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { lock.unlock(); PerformanceTimer perfTimer("otherAvatars"); - uint64_t startTime = usecTimestampNow(); auto avatarMap = getHashCopy(); QList avatarList = avatarMap.values(); ViewFrustum cameraView; qApp->copyDisplayViewFrustum(cameraView); - std::priority_queue sortedAvatars = AvatarData::sortAvatars( - avatarList, cameraView, + std::priority_queue sortedAvatars; + AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, [](AvatarSharedPointer avatar)->uint64_t{ return std::static_pointer_cast(avatar)->getLastRenderUpdateTime(); @@ -194,10 +193,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { }); render::PendingChanges pendingChanges; - const uint64_t RENDER_UPDATE_BUDGET = 1500; // usec - const uint64_t MAX_UPDATE_BUDGET = 2000; // usec - uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET; - uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET; + uint64_t startTime = usecTimestampNow(); + const uint64_t UPDATE_BUDGET = 2000; // usec + uint64_t updateExpiry = startTime + UPDATE_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; @@ -223,7 +221,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); - if (now < renderExpiry) { + if (now < updateExpiry) { // we're within budget bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; if (inView && avatar->hasNewJointData()) { @@ -232,21 +230,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->simulate(deltaTime, inView); avatar->updateRenderItem(pendingChanges); avatar->setLastRenderUpdateTime(startTime); - } else if (now < maxExpiry) { - // we've spent most of our time budget, but we still simulate() the avatar as it if were out of view - // --> some avatars may freeze until their priority trickles up - bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; - if (inView && avatar->hasNewJointData()) { - numAVatarsNotUpdated++; - } - avatar->simulate(deltaTime, false); } else { - // we've spent ALL of our time budget --> bail on the rest of the avatar updates + // we've spent our full time budget --> bail on the rest of the avatar updates // --> more avatars may freeze until their priority trickles up // --> some scale or fade animations may glitch // --> some avatar velocity measurements may be a little off - // HACK: no time simulate, but we will take the time to count how many were tragically missed + // no time simulate, but we take the time to count how many were tragically missed bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; if (!inView) { break; diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp index 843779dd3b..0c3d863649 100644 --- a/interface/src/avatar/CauterizedModel.cpp +++ b/interface/src/avatar/CauterizedModel.cpp @@ -95,12 +95,6 @@ void CauterizedModel::createCollisionRenderItemSet() { Model::createCollisionRenderItemSet(); } -// Called within Model::simulate call, below. -void CauterizedModel::updateRig(float deltaTime, glm::mat4 parentTransform) { - Model::updateRig(deltaTime, parentTransform); - _needsUpdateClusterMatrices = true; -} - void CauterizedModel::updateClusterMatrices() { PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices"); diff --git a/interface/src/avatar/CauterizedModel.h b/interface/src/avatar/CauterizedModel.h index 01e0b13650..ba12aee32b 100644 --- a/interface/src/avatar/CauterizedModel.h +++ b/interface/src/avatar/CauterizedModel.h @@ -37,7 +37,6 @@ public: void createVisibleRenderItemSet() override; void createCollisionRenderItemSet() override; - virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override; virtual void updateClusterMatrices() override; void updateRenderItems() override; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 842939d938..969268c549 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -260,6 +260,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { return AvatarData::toByteArrayStateful(dataDetail); } +void MyAvatar::resetSensorsAndBody() { + qApp->getActiveDisplayPlugin()->resetSensors(); + reset(true, false, true); +} + void MyAvatar::centerBody() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "centerBody"); @@ -2483,6 +2488,45 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } } +bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) { + auto hipsIndex = getJointIndex("Hips"); + if (index != hipsIndex) { + qWarning() << "Pinning is only supported for the hips joint at the moment."; + return false; + } + + setPosition(position); + setOrientation(orientation); + + _rig->setMaxHipsOffsetLength(0.05f); + + auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); + if (it == _pinnedJoints.end()) { + _pinnedJoints.push_back(index); + } + + return true; +} + +bool MyAvatar::clearPinOnJoint(int index) { + auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); + if (it != _pinnedJoints.end()) { + _pinnedJoints.erase(it); + + auto hipsIndex = getJointIndex("Hips"); + if (index == hipsIndex) { + _rig->setMaxHipsOffsetLength(FLT_MAX); + } + + return true; + } + return false; +} + +float MyAvatar::getIKErrorOnLastSolve() const { + return _rig->getIKErrorOnLastSolve(); +} + // thread-safe void MyAvatar::addHoldAction(AvatarActionHold* holdAction) { std::lock_guard guard(_holdActionsMutex); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4f86256a2f..3cc665b533 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -99,6 +99,7 @@ public: void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); + Q_INVOKABLE void resetSensorsAndBody(); Q_INVOKABLE void centerBody(); // thread-safe Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe @@ -216,6 +217,11 @@ public: virtual void clearJointData(int index) override; virtual void clearJointsData() override; + Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation); + Q_INVOKABLE bool clearPinOnJoint(int index); + + Q_INVOKABLE float getIKErrorOnLastSolve() const; + Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; } @@ -527,6 +533,8 @@ private: bool didTeleport(); bool getIsAway() const { return _isAway; } void setAway(bool value); + + std::vector _pinnedJoints; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 4b77323bba..476abf8d4b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -166,7 +166,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); // evaluate AnimGraph animation and update jointStates. - CauterizedModel::updateRig(deltaTime, parentTransform); + Model::updateRig(deltaTime, parentTransform); Rig::EyeParameters eyeParams; eyeParams.worldHeadOrientation = headParams.worldHeadOrientation; @@ -179,7 +179,9 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->updateFromEyeParameters(eyeParams); } else { - CauterizedModel::updateRig(deltaTime, parentTransform); + // no need to call Model::updateRig() because otherAvatars get their joint state + // copied directly from AvtarData::_jointData (there are no Rig animations to blend) + _needsUpdateClusterMatrices = true; // This is a little more work than we really want. // diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 709d86c305..9847961f5a 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -345,23 +345,14 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) { const float LARGE_NEGATIVE_FLOAT = -9999999; glm::vec3 origin(pointCopy.x, pointCopy.y, LARGE_NEGATIVE_FLOAT); glm::vec3 direction(0, 0, 1); - // BoxFace thisFace; glm::vec3 thisSurfaceNormal; - // float distance; unsigned int bestStackOrder = 0; OverlayID bestOverlayID = UNKNOWN_OVERLAY_ID; while (i.hasNext()) { i.next(); OverlayID thisID = i.key(); - if (i.value()->is3D()) { - // auto thisOverlay = std::dynamic_pointer_cast(i.value()); - // if (thisOverlay && !thisOverlay->getIgnoreRayIntersection()) { - // if (thisOverlay->findRayIntersection(origin, direction, distance, thisFace, thisSurfaceNormal)) { - // return thisID; - // } - // } - } else { + if (!i.value()->is3D()) { auto thisOverlay = std::dynamic_pointer_cast(i.value()); if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() && thisOverlay->getBoundingRect().contains(pointCopy.x, pointCopy.y, false)) { diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index adeede17ad..173af3fdf6 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -189,6 +189,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorisLowerSpine()) { + if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { // for these types of targets we only allow twist at the lower-spine // (this prevents the hand targets from bending the spine too much and thereby driving the hips too far) glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans(); @@ -300,8 +301,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; if (angle > MIN_ADJUSTMENT_ANGLE) { // reduce angle by a fraction (for stability) - const float FRACTION = 0.5f; - angle *= FRACTION; + const float STABILITY_FRACTION = 0.5f; + angle *= STABILITY_FRACTION; deltaRotation = glm::angleAxis(angle, axis); // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's @@ -323,7 +324,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe glm::vec3 axis = glm::normalize(deltaRotation * leverArm); swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); float dotSign = copysignf(1.0f, twistPart.w); - deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation; + const float LIMIT_LEAK_FRACTION = 0.1f; + deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, LIMIT_LEAK_FRACTION)) * deltaRotation; } } } @@ -486,7 +488,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // measure new _hipsOffset for next frame // by looking for discrepancies between where a targeted endEffector is // and where it wants to be (after IK solutions are done) - glm::vec3 newHipsOffset = Vectors::ZERO; + + // use weighted average between HMD and other targets + float HMD_WEIGHT = 10.0f; + float OTHER_WEIGHT = 1.0f; + float totalWeight = 0.0f; + + glm::vec3 additionalHipsOffset = Vectors::ZERO; for (auto& target: targets) { int targetIndex = target.getIndex(); if (targetIndex == _headIndex && _headIndex != -1) { @@ -497,32 +505,61 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; - newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under); + additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under- actual); + totalWeight += OTHER_WEIGHT; } else if (target.getType() == IKTarget::Type::HmdHead) { - // we want to shift the hips to bring the head to its designated position glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - _hipsOffset += target.getTranslation() - actual; - // and ignore all other targets - newHipsOffset = _hipsOffset; - break; + glm::vec3 thisOffset = target.getTranslation() - actual; + glm::vec3 futureHipsOffset = _hipsOffset + thisOffset; + if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) { + // it is imperative to shift the hips and bring the head to its designated position + // so we slam newHipsOffset here and ignore all other targets + additionalHipsOffset = futureHipsOffset - _hipsOffset; + totalWeight = 0.0f; + break; + } else { + additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual); + totalWeight += HMD_WEIGHT; + } } } else if (target.getType() == IKTarget::Type::RotationAndPosition) { glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); glm::vec3 targetPosition = target.getTranslation(); - newHipsOffset += targetPosition - actualPosition; + additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition); + totalWeight += OTHER_WEIGHT; } } + if (totalWeight > 1.0f) { + additionalHipsOffset /= totalWeight; + } + + // Add downward pressure on the hips + additionalHipsOffset *= 0.95f; + additionalHipsOffset -= 1.0f; // smooth transitions by relaxing _hipsOffset toward the new value - const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f; + const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; - _hipsOffset += (newHipsOffset - _hipsOffset) * tau; + _hipsOffset += additionalHipsOffset * tau; + + // clamp the hips offset + float hipsOffsetLength = glm::length(_hipsOffset); + if (hipsOffsetLength > _maxHipsOffsetLength) { + _hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; + } + } } } return _relativePoses; } +void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) { + // manually adjust scale here + const float METERS_TO_CENTIMETERS = 100.0f; + _maxHipsOffsetLength = METERS_TO_CENTIMETERS * maxLength; +} + void AnimInverseKinematics::clearIKJointLimitHistory() { for (auto& pair : _constraints) { pair.second->clearHistory(); @@ -740,7 +777,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); std::vector minDots; - const float MAX_SPINE_SWING = PI / 14.0f; + const float MAX_SPINE_SWING = PI / 10.0f; minDots.push_back(cosf(MAX_SPINE_SWING)); stConstraint->setSwingLimits(minDots); if (0 == baseName.compare("Spine1", Qt::CaseSensitive) @@ -776,11 +813,11 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("Head", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_HEAD_TWIST = PI / 9.0f; + const float MAX_HEAD_TWIST = PI / 6.0f; stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST); std::vector minDots; - const float MAX_HEAD_SWING = PI / 10.0f; + const float MAX_HEAD_SWING = PI / 6.0f; minDots.push_back(cosf(MAX_HEAD_SWING)); stConstraint->setSwingLimits(minDots); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index c9560c7383..892a5616b2 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -39,6 +39,10 @@ public: void clearIKJointLimitHistory(); + void setMaxHipsOffsetLength(float maxLength); + + float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; } + protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const std::vector& targets); @@ -83,6 +87,7 @@ protected: // experimental data for moving hips during IK glm::vec3 _hipsOffset { Vectors::ZERO }; + float _maxHipsOffsetLength{ FLT_MAX }; int _headIndex { -1 }; int _hipsIndex { -1 }; int _hipsParentIndex { -1 }; @@ -90,6 +95,8 @@ protected: // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses // during the the cyclic coordinate descent algorithm int _maxTargetIndex { 0 }; + + float _maxErrorOnLastSolve { FLT_MAX }; }; #endif // hifi_AnimInverseKinematics_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 07462e9878..84e34adec7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -319,6 +319,39 @@ void Rig::clearIKJointLimitHistory() { } } +void Rig::setMaxHipsOffsetLength(float maxLength) { + _maxHipsOffsetLength = maxLength; + + if (_animNode) { + _animNode->traverse([&](AnimNode::Pointer node) { + auto ikNode = std::dynamic_pointer_cast(node); + if (ikNode) { + ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength); + } + return true; + }); + } +} + +float Rig::getMaxHipsOffsetLength() const { + return _maxHipsOffsetLength; +} + +float Rig::getIKErrorOnLastSolve() const { + float result = 0.0f; + + if (_animNode) { + _animNode->traverse([&](AnimNode::Pointer node) { + auto ikNode = std::dynamic_pointer_cast(node); + if (ikNode) { + result = ikNode->getMaxErrorOnLastSolve(); + } + return true; + }); + } + return result; +} + int Rig::getJointParentIndex(int childIndex) const { if (_animSkeleton && isIndexValid(childIndex)) { return _animSkeleton->getParentIndex(childIndex); @@ -1274,39 +1307,50 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { void Rig::copyJointsFromJointData(const QVector& jointDataVec) { PerformanceTimer perfTimer("copyJoints"); PROFILE_RANGE(simulation_animation_detail, "copyJoints"); - if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._relativePoses.size()) { - // make a vector of rotations in absolute-geometry-frame - const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses(); - std::vector rotations; - rotations.reserve(absoluteDefaultPoses.size()); - const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); - for (int i = 0; i < jointDataVec.size(); i++) { - const JointData& data = jointDataVec.at(i); - if (data.rotationSet) { - // JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame - rotations.push_back(rigToGeometryRot * data.rotation); - } else { - rotations.push_back(absoluteDefaultPoses[i].rot()); - } - } + if (!_animSkeleton) { + return; + } + if (jointDataVec.size() != (int)_internalPoseSet._relativePoses.size()) { + // animations haven't fully loaded yet. + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + } - // convert rotations from absolute to parent relative. - _animSkeleton->convertAbsoluteRotationsToRelative(rotations); - - // store new relative poses - const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses(); - for (int i = 0; i < jointDataVec.size(); i++) { - const JointData& data = jointDataVec.at(i); - _internalPoseSet._relativePoses[i].scale() = Vectors::ONE; - _internalPoseSet._relativePoses[i].rot() = rotations[i]; - if (data.translationSet) { - // JointData translations are in scaled relative-frame so we scale back to regular relative-frame - _internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation; - } else { - _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); - } + // make a vector of rotations in absolute-geometry-frame + const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses(); + std::vector rotations; + rotations.reserve(absoluteDefaultPoses.size()); + const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); + for (int i = 0; i < jointDataVec.size(); i++) { + const JointData& data = jointDataVec.at(i); + if (data.rotationSet) { + // JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame + rotations.push_back(rigToGeometryRot * data.rotation); + } else { + rotations.push_back(absoluteDefaultPoses[i].rot()); } } + + // convert rotations from absolute to parent relative. + _animSkeleton->convertAbsoluteRotationsToRelative(rotations); + + // store new relative poses + const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses(); + for (int i = 0; i < jointDataVec.size(); i++) { + const JointData& data = jointDataVec.at(i); + _internalPoseSet._relativePoses[i].scale() = Vectors::ONE; + _internalPoseSet._relativePoses[i].rot() = rotations[i]; + if (data.translationSet) { + // JointData translations are in scaled relative-frame so we scale back to regular relative-frame + _internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation; + } else { + _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); + } + } + + // build absolute poses and copy to externalPoseSet + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + QWriteLocker writeLock(&_externalPoseSetLock); + _externalPoseSet = _internalPoseSet; } void Rig::computeAvatarBoundingCapsule( diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 78a669b249..f1c87d0d3e 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -104,6 +104,10 @@ public: void clearJointAnimationPriority(int index); void clearIKJointLimitHistory(); + void setMaxHipsOffsetLength(float maxLength); + float getMaxHipsOffsetLength() const; + + float getIKErrorOnLastSolve() const; int getJointParentIndex(int childIndex) const; @@ -318,6 +322,8 @@ protected: bool _enabledAnimations { true }; mutable uint32_t _jointNameWarningCount { 0 }; + float _maxHipsOffsetLength { 1.0f }; + float _maxErrorOnLastSolve { 0.0f }; private: QMap _stateHandlers; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c1dd60a3b0..8025c680ca 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2324,61 +2324,57 @@ float AvatarData::_avatarSortCoefficientSize { 0.5f }; float AvatarData::_avatarSortCoefficientCenter { 0.25 }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; -std::priority_queue AvatarData::sortAvatars( - QList avatarList, - const ViewFrustum& cameraView, - std::function getLastUpdated, - std::function getBoundingRadius, - std::function shouldIgnore) { +void AvatarData::sortAvatars( + QList avatarList, + const ViewFrustum& cameraView, + std::priority_queue& sortedAvatarsOut, + std::function getLastUpdated, + std::function getBoundingRadius, + std::function shouldIgnore) { - uint64_t startTime = usecTimestampNow(); + PROFILE_RANGE(simulation, "sort"); + uint64_t now = usecTimestampNow(); glm::vec3 frustumCenter = cameraView.getPosition(); + const glm::vec3& forward = cameraView.getDirection(); + for (int32_t i = 0; i < avatarList.size(); ++i) { + const auto& avatar = avatarList.at(i); - std::priority_queue sortedAvatars; - { - PROFILE_RANGE(simulation, "sort"); - for (int32_t i = 0; i < avatarList.size(); ++i) { - const auto& avatar = avatarList.at(i); - - if (shouldIgnore(avatar)) { - continue; - } - - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - glm::vec3 avatarPosition = avatar->getPosition(); - glm::vec3 offset = avatarPosition - frustumCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - - // FIXME - AvatarData has something equivolent to this - float radius = getBoundingRadius(avatar); - - const glm::vec3& forward = cameraView.getDirection(); - float apparentSize = 2.0f * radius / distance; - float cosineAngle = glm::length(glm::dot(offset, forward) * forward) / distance; - float age = (float)(startTime - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND); - - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: - float priority = _avatarSortCoefficientSize * apparentSize - + _avatarSortCoefficientCenter * cosineAngle - + _avatarSortCoefficientAge * age; - - // decrement priority of avatars outside keyhole - if (distance > cameraView.getCenterRadius()) { - if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - sortedAvatars.push(AvatarPriority(avatar, priority)); + if (shouldIgnore(avatar)) { + continue; } + + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update + glm::vec3 avatarPosition = avatar->getPosition(); + glm::vec3 offset = avatarPosition - frustumCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + + // FIXME - AvatarData has something equivolent to this + float radius = getBoundingRadius(avatar); + + float apparentSize = 2.0f * radius / distance; + float cosineAngle = glm::dot(offset, forward) / distance; + float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND); + + // NOTE: we are adding values of different units to get a single measure of "priority". + // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. + // These weights are pure magic tuning and should be hard coded in the relation below, + // but are currently exposed for anyone who would like to explore fine tuning: + float priority = _avatarSortCoefficientSize * apparentSize + + _avatarSortCoefficientCenter * cosineAngle + + _avatarSortCoefficientAge * age; + + // decrement priority of avatars outside keyhole + if (distance > cameraView.getCenterRadius()) { + if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { + priority += OUT_OF_VIEW_PENALTY; + } + } + sortedAvatarsOut.push(AvatarPriority(avatar, priority)); } - return sortedAvatars; } QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 12209d9c31..c2240f400f 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -597,9 +597,10 @@ public: static const float OUT_OF_VIEW_PENALTY; - static std::priority_queue sortAvatars( + static void sortAvatars( QList avatarList, const ViewFrustum& cameraView, + std::priority_queue& sortedAvatarsOut, std::function getLastUpdated, std::function getBoundingRadius, std::function shouldIgnore); diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index d1fbaf767a..7ac8083d9c 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -336,8 +336,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm const double pStrength = 2.0; int width = image.width(); int height = image.height(); - // THe end result image for normal map is RGBA32 even though the A is not used - QImage result(width, height, QImage::Format_RGBA8888); + QImage result(width, height, QImage::Format_RGB888); for (int i = 0; i < width; i++) { const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); @@ -377,21 +376,19 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm glm::normalize(v); // convert to rgb from the value obtained computing the filter - QRgb qRgbValue = qRgb(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z)); + QRgb qRgbValue = qRgba(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z), 1.0); result.setPixel(i, j, qRgbValue); } } gpu::Texture* theTexture = nullptr; if ((image.width() > 0) && (image.height() > 0)) { - - gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - generateMips(theTexture, image, formatMip, true); } return theTexture; diff --git a/scripts/system/assets/models/teleport-seat.fbx b/scripts/system/assets/models/teleport-seat.fbx new file mode 100644 index 0000000000..cd7a9abc7e Binary files /dev/null and b/scripts/system/assets/models/teleport-seat.fbx differ diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index d3284352bf..c058f046db 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -17,12 +17,20 @@ var NUMBER_OF_STEPS = 6; var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx"); var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx"); +var SEAT_MODEL_URL = Script.resolvePath("../assets/models/teleport-seat.fbx"); + var TARGET_MODEL_DIMENSIONS = { x: 1.15, y: 0.5, z: 1.15 }; +var COLORS_TELEPORT_SEAT = { + red: 255, + green: 0, + blue: 170 +} + var COLORS_TELEPORT_CAN_TELEPORT = { red: 97, green: 247, @@ -35,29 +43,30 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = { blue: 141 }; -var COLORS_TELEPORT_TOO_CLOSE = { +var COLORS_TELEPORT_CANCEL = { red: 255, green: 184, blue: 73 }; var TELEPORT_CANCEL_RANGE = 1; -var USE_COOL_IN = true; var COOL_IN_DURATION = 500; +const handInfo = { + right: { + controllerInput: Controller.Standard.RightHand + }, + left: { + controllerInput: Controller.Standard.LeftHand + } +}; + function ThumbPad(hand) { this.hand = hand; var _thisPad = this; this.buttonPress = function(value) { _thisPad.buttonValue = value; - if (value === 0) { - if (activationTimeout !== null) { - Script.clearTimeout(activationTimeout); - activationTimeout = null; - } - - } }; } @@ -67,7 +76,6 @@ function Trigger(hand) { this.buttonPress = function(value) { _this.buttonValue = value; - }; this.down = function() { @@ -78,347 +86,224 @@ function Trigger(hand) { var coolInTimeout = null; +var TELEPORTER_STATES = { + IDLE: 'idle', + COOL_IN: 'cool_in', + TARGETTING_INVALID: 'targetting_invalid', +} + +var TARGET = { + NONE: 'none', // Not currently targetting anything + INVISIBLE: 'invisible', // The current target is an invvsible surface + INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.) + SURFACE: 'surface', // The current target is a valid surface + SEAT: 'seat', // The current target is a seat +} + function Teleporter() { var _this = this; - this.intersection = null; - this.rightOverlayLine = null; - this.leftOverlayLine = null; - this.targetOverlay = null; - this.cancelOverlay = null; - this.updateConnected = null; - this.smoothArrivalInterval = null; - this.teleportHand = null; - this.tooClose = false; - this.inCoolIn = false; + this.active = false; + this.state = TELEPORTER_STATES.IDLE; + this.currentTarget = TARGET.INVALID; - this.initialize = function() { - this.createMappings(); + this.overlayLines = { + left: null, + right: null, }; + this.updateConnected = null; + this.activeHand = null; - this.createMappings = function() { - teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); - teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName); + this.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); + this.teleportMappingInternal = Controller.newMapping(this.telporterMappingInternalName); - Controller.enableMapping(teleporter.telporterMappingInternalName); + // Setup overlays + this.cancelOverlay = Overlays.addOverlay("model", { + url: TOO_CLOSE_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + visible: false + }); + this.targetOverlay = Overlays.addOverlay("model", { + url: TARGET_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + visible: false + }); + this.seatOverlay = Overlays.addOverlay("model", { + url: SEAT_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + visible: false + }); + + this.enableMappings = function() { + Controller.enableMapping(this.telporterMappingInternalName); }; this.disableMappings = function() { Controller.disableMapping(teleporter.telporterMappingInternalName); }; - this.enterTeleportMode = function(hand) { + this.cleanup = function() { + this.disableMappings(); + Overlays.deleteOverlay(this.targetOverlay); + this.targetOverlay = null; + + Overlays.deleteOverlay(this.cancelOverlay); + this.cancelOverlay = null; + + Overlays.deleteOverlay(this.seatOverlay); + this.seatOverlay = null; + + this.deleteOverlayBeams(); + if (this.updateConnected === true) { + Script.update.disconnect(this, this.update); + } + }; + + this.enterTeleportMode = function(hand) { if (inTeleportMode === true) { return; } - if (isDisabled === 'both') { + if (isDisabled === 'both' || isDisabled === hand) { return; } inTeleportMode = true; - this.inCoolIn = true; + if (coolInTimeout !== null) { Script.clearTimeout(coolInTimeout); - } + + this.state = TELEPORTER_STATES.COOL_IN; coolInTimeout = Script.setTimeout(function() { - _this.inCoolIn = false; + if (_this.state === TELEPORTER_STATES.COOL_IN) { + _this.state = TELEPORTER_STATES.TARGETTING; + } }, COOL_IN_DURATION) - if (this.smoothArrivalInterval !== null) { - Script.clearInterval(this.smoothArrivalInterval); - } - if (activationTimeout !== null) { - Script.clearInterval(activationTimeout); - } - - this.teleportHand = hand; - this.initialize(); - Script.update.connect(this.update); + this.activeHand = hand; + this.enableMappings(); + Script.update.connect(this, this.update); this.updateConnected = true; - - - }; - this.createTargetOverlay = function(visible) { - if (visible == undefined) { - visible = true; - } - - if (_this.targetOverlay !== null) { - return; - } - var targetOverlayProps = { - url: TARGET_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: visible - }; - - _this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps); - - }; - - this.createCancelOverlay = function(visible) { - if (visible == undefined) { - visible = true; - } - - if (_this.cancelOverlay !== null) { - return; - } - - var cancelOverlayProps = { - url: TOO_CLOSE_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: visible - }; - - _this.cancelOverlay = Overlays.addOverlay("model", cancelOverlayProps); - }; - - this.deleteCancelOverlay = function() { - if (this.cancelOverlay === null) { - return; - } - - Overlays.deleteOverlay(this.cancelOverlay); - this.cancelOverlay = null; - } - - this.hideCancelOverlay = function() { - if (this.cancelOverlay === null) { - return; - } - - this.intersection = null; - Overlays.editOverlay(this.cancelOverlay, { visible: false }); - } - - this.showCancelOverlay = function() { - if (this.cancelOverlay === null) { - return this.createCancelOverlay(); - } - Overlays.editOverlay(this.cancelOverlay, { visible: true }); - } - - - this.deleteTargetOverlay = function() { - if (this.targetOverlay === null) { - return; - } - - Overlays.deleteOverlay(this.targetOverlay); - this.intersection = null; - this.targetOverlay = null; - } - - this.hideTargetOverlay = function() { - if (this.targetOverlay === null) { - return; - } - - this.intersection = null; - Overlays.editOverlay(this.targetOverlay, { visible: false }); - } - - this.showTargetOverlay = function() { - if (this.targetOverlay === null) { - return this.createTargetOverlay(); - } - Overlays.editOverlay(this.targetOverlay, { visible: true }); - } - - this.turnOffOverlayBeams = function() { - this.rightOverlayOff(); - this.leftOverlayOff(); - } - this.exitTeleportMode = function(value) { - if (activationTimeout !== null) { - Script.clearTimeout(activationTimeout); - activationTimeout = null; - } if (this.updateConnected === true) { - Script.update.disconnect(this.update); + Script.update.disconnect(this, this.update); } this.disableMappings(); - this.turnOffOverlayBeams(); + this.deleteOverlayBeams(); + this.hideTargetOverlay(); + this.hideCancelOverlay(); this.updateConnected = null; - this.inCoolIn = false; + this.state = TELEPORTER_STATES.IDLE; inTeleportMode = false; }; - this.update = function() { - if (isDisabled === 'both') { - return; - } - - if (teleporter.teleportHand === 'left') { - if (isDisabled === 'left') { - return; + this.deleteOverlayBeams = function() { + for (key in this.overlayLines) { + if (this.overlayLines[key] !== null) { + Overlays.deleteOverlay(this.overlayLines[key]); + this.overlayLines[key] = null; } - teleporter.leftRay(); - if ((leftPad.buttonValue === 0) && inTeleportMode === true) { - if (_this.inCoolIn === true) { - _this.exitTeleportMode(); - _this.hideTargetOverlay(); - _this.hideCancelOverlay(); - } else { - _this.teleport(); - } - return; - } - - } else { - if (isDisabled === 'right') { - return; - } - teleporter.rightRay(); - if ((rightPad.buttonValue === 0) && inTeleportMode === true) { - if (_this.inCoolIn === true) { - _this.exitTeleportMode(); - _this.hideTargetOverlay(); - _this.hideCancelOverlay(); - } else { - _this.teleport(); - } - return; - } - } - - }; - - this.rightRay = function() { - var pose = Controller.getPoseValue(Controller.Standard.RightHand); - var rightPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); - var rightRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : - Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - })); - - var rightPickRay = { - origin: rightPosition, - direction: Quat.getUp(rightRotation), - }; - - this.rightPickRay = rightPickRay; - - var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 50)); - - - var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity], true, true); - - if (rightIntersection.intersects) { - if (this.tooClose === true) { - this.hideTargetOverlay(); - - this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE); - if (this.cancelOverlay !== null) { - this.updateCancelOverlay(rightIntersection); - } else { - this.createCancelOverlay(); - } - } else { - if (this.inCoolIn === true) { - this.hideTargetOverlay(); - this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE); - if (this.cancelOverlay !== null) { - this.updateCancelOverlay(rightIntersection); - } else { - this.createCancelOverlay(); - } - } else { - this.hideCancelOverlay(); - - this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); - if (this.targetOverlay !== null) { - this.updateTargetOverlay(rightIntersection); - } else { - this.createTargetOverlay(); - } - } - - - } - - } else { - - this.hideTargetOverlay(); - this.rightLineOn(rightPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT); } } + this.update = function() { + if (_this.state === TELEPORTER_STATES.IDLE) { + return; + } - this.leftRay = function() { - var pose = Controller.getPoseValue(Controller.Standard.LeftHand); - var leftPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); - var leftRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : + // Get current hand pose information so that we can get the direction of the teleport beam + var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput); + var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); + var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); - var leftPickRay = { - origin: leftPosition, - direction: Quat.getUp(leftRotation), + var pickRay = { + origin: handPosition, + direction: Quat.getUp(handRotation), }; - this.leftPickRay = leftPickRay; + // We do up to 2 ray picks to find a teleport location. + // There are 2 types of teleport locations we are interested in: + // 1. A visible floor. This can be any entity surface that points within some degree of "up" + // 2. A seat. The seat can be visible or invisible. + // + // * In the first pass we pick against visible and invisible entities so that we can find invisible seats. + // We might hit an invisible entity that is not a seat, so we need to do a second pass. + // * In the second pass we pick against visible entities only. + // + var intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], false, true); - var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 50)); - - - var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity], true, true); - - if (leftIntersection.intersects) { - - if (this.tooClose === true) { - this.hideTargetOverlay(); - this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE); - if (this.cancelOverlay !== null) { - this.updateCancelOverlay(leftIntersection); - } else { - this.createCancelOverlay(); - } - } else { - if (this.inCoolIn === true) { - this.hideTargetOverlay(); - this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE); - if (this.cancelOverlay !== null) { - this.updateCancelOverlay(leftIntersection); - } else { - this.createCancelOverlay(); - } - } else { - this.hideCancelOverlay(); - this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); - - if (this.targetOverlay !== null) { - this.updateTargetOverlay(leftIntersection); - } else { - this.createTargetOverlay(); - } - } - - - } - - - } else { + var teleportLocationType = getTeleportTargetType(intersection); + if (teleportLocationType === TARGET.INVISIBLE) { + intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], true, true); + teleportLocationType = getTeleportTargetType(intersection); + } + if (teleportLocationType === TARGET.NONE) { this.hideTargetOverlay(); - this.leftLineOn(leftPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT); + this.hideCancelOverlay(); + this.hideSeatOverlay(); + + var farPosition = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 50)); + this.updateLineOverlay(_this.activeHand, pickRay.origin, farPosition, COLORS_TELEPORT_CANNOT_TELEPORT); + } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { + this.hideTargetOverlay(); + this.hideSeatOverlay(); + + this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL); + this.updateDestinationOverlay(this.cancelOverlay, intersection); + } else if (teleportLocationType === TARGET.SURFACE) { + if (this.state === TELEPORTER_STATES.COOL_IN) { + this.hideTargetOverlay(); + this.hideSeatOverlay(); + + this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL); + this.updateDestinationOverlay(this.cancelOverlay, intersection); + } else { + this.hideCancelOverlay(); + this.hideSeatOverlay(); + + this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); + this.updateDestinationOverlay(this.targetOverlay, intersection); + } + } else if (teleportLocationType === TARGET.SEAT) { + this.hideCancelOverlay(); + this.hideTargetOverlay(); + + this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_SEAT); + this.updateDestinationOverlay(this.seatOverlay, intersection); + } + + + if (((_this.activeHand == 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) { + this.exitTeleportMode(); + this.hideCancelOverlay(); + this.hideTargetOverlay(); + this.hideSeatOverlay(); + + if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) { + // Do nothing + } else if (teleportLocationType === TARGET.SEAT) { + Entities.callEntityMethod(intersection.entityID, 'sit'); + } else if (teleportLocationType === TARGET.SURFACE) { + var offset = getAvatarFootOffset(); + intersection.intersection.y += offset; + MyAvatar.position = intersection.intersection; + HMD.centerUI(); + } } }; - this.rightLineOn = function(closePoint, farPoint, color) { - if (this.rightOverlayLine === null) { + this.updateLineOverlay = function(hand, closePoint, farPoint, color) { + if (this.overlayLines[hand] === null) { var lineProperties = { start: closePoint, end: farPoint, @@ -431,10 +316,10 @@ function Teleporter() { glow: 1.0 }; - this.rightOverlayLine = Overlays.addOverlay("line3d", lineProperties); + this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties); } else { - var success = Overlays.editOverlay(this.rightOverlayLine, { + var success = Overlays.editOverlay(this.overlayLines[hand], { start: closePoint, end: farPoint, color: color @@ -442,47 +327,19 @@ function Teleporter() { } }; - this.leftLineOn = function(closePoint, farPoint, color) { - if (this.leftOverlayLine === null) { - var lineProperties = { - ignoreRayIntersection: true, - start: closePoint, - end: farPoint, - color: color, - visible: true, - alpha: 1, - solid: true, - glow: 1.0, - drawInFront: true - }; - - this.leftOverlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - var success = Overlays.editOverlay(this.leftOverlayLine, { - start: closePoint, - end: farPoint, - color: color - }); - } - }; - this.rightOverlayOff = function() { - if (this.rightOverlayLine !== null) { - Overlays.deleteOverlay(this.rightOverlayLine); - this.rightOverlayLine = null; - } + this.hideCancelOverlay = function() { + Overlays.editOverlay(this.cancelOverlay, { visible: false }); }; - this.leftOverlayOff = function() { - if (this.leftOverlayLine !== null) { - Overlays.deleteOverlay(this.leftOverlayLine); - this.leftOverlayLine = null; - } + this.hideTargetOverlay = function() { + Overlays.editOverlay(this.targetOverlay, { visible: false }); }; - this.updateTargetOverlay = function(intersection) { - _this.intersection = intersection; + this.hideSeatOverlay = function() { + Overlays.editOverlay(this.seatOverlay, { visible: false }); + }; + this.updateDestinationOverlay = function(overlayID, intersection) { var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP); var euler = Quat.safeEulerAngles(rotation); var position = { @@ -491,115 +348,15 @@ function Teleporter() { z: intersection.intersection.z }; - this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal); var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0); - Overlays.editOverlay(this.targetOverlay, { + Overlays.editOverlay(overlayID, { visible: true, position: position, rotation: towardUs }); }; - - this.updateCancelOverlay = function(intersection) { - _this.intersection = intersection; - - var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP); - var euler = Quat.safeEulerAngles(rotation); - var position = { - x: intersection.intersection.x, - y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2, - z: intersection.intersection.z - }; - - this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal); - var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0); - - Overlays.editOverlay(this.cancelOverlay, { - visible: true, - position: position, - rotation: towardUs - }); - }; - - this.triggerHaptics = function() { - var hand = this.teleportHand === 'left' ? 0 : 1; - var haptic = Controller.triggerShortHapticPulse(0.2, hand); - }; - - this.teleport = function(value) { - - if (value === undefined) { - this.exitTeleportMode(); - } - - if (this.intersection !== null) { - if (this.tooClose === true) { - this.exitTeleportMode(); - this.hideCancelOverlay(); - return; - } - var offset = getAvatarFootOffset(); - this.intersection.intersection.y += offset; - this.exitTeleportMode(); - // Disable smooth arrival, possibly temporarily - //this.smoothArrival(); - MyAvatar.position = _this.intersection.intersection; - _this.hideTargetOverlay(); - _this.hideCancelOverlay(); - HMD.centerUI(); - } - }; - - this.findMidpoint = function(start, end) { - var xy = Vec3.sum(start, end); - var midpoint = Vec3.multiply(0.5, xy); - return midpoint - }; - - this.getArrivalPoints = function(startPoint, endPoint) { - var arrivalPoints = []; - var i; - var lastPoint; - - for (i = 0; i < NUMBER_OF_STEPS; i++) { - if (i === 0) { - lastPoint = startPoint; - } - var newPoint = _this.findMidpoint(lastPoint, endPoint); - lastPoint = newPoint; - arrivalPoints.push(newPoint); - } - - arrivalPoints.push(endPoint); - - return arrivalPoints; - }; - - this.smoothArrival = function() { - - _this.arrivalPoints = _this.getArrivalPoints(MyAvatar.position, _this.intersection.intersection); - _this.smoothArrivalInterval = Script.setInterval(function() { - if (_this.arrivalPoints.length === 0) { - Script.clearInterval(_this.smoothArrivalInterval); - HMD.centerUI(); - return; - } - var landingPoint = _this.arrivalPoints.shift(); - MyAvatar.position = landingPoint; - - if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) { - _this.hideTargetOverlay(); - _this.hideCancelOverlay(); - } - - }, SMOOTH_ARRIVAL_SPACING); - } - - this.createTargetOverlay(false); - this.createCancelOverlay(false); - } //related to repositioning the avatar after you teleport @@ -611,20 +368,16 @@ function getAvatarFootOffset() { var jointName = d.joint; if (jointName === "RightUpLeg") { upperLeg = d.translation.y; - } - if (jointName === "RightLeg") { + } else if (jointName === "RightLeg") { lowerLeg = d.translation.y; - } - if (jointName === "RightFoot") { + } else if (jointName === "RightFoot") { foot = d.translation.y; - } - if (jointName === "RightToeBase") { + } else if (jointName === "RightToeBase") { toe = d.translation.y; - } - if (jointName === "RightToe_End") { + } else if (jointName === "RightToe_End") { toeTop = d.translation.y; } - }) + }); var offset = upperLeg + lowerLeg + foot + toe + toeTop; offset = offset / 100; @@ -655,7 +408,6 @@ var rightTrigger = new Trigger('right'); var mappingName, teleportMapping; -var activationTimeout = null; var TELEPORT_DELAY = 0; function isMoving() { @@ -668,17 +420,44 @@ function isMoving() { } }; +function parseJSON(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } +} // When determininig whether you can teleport to a location, the normal of the // point that is being intersected with is looked at. If this normal is more // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then // you can't teleport there. -var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; -function isValidTeleportLocation(position, surfaceNormal) { +const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; +function getTeleportTargetType(intersection) { + if (!intersection.intersects) { + return TARGET.NONE; + } + + var props = Entities.getEntityProperties(intersection.entityID, ['userData', 'visible']); + var data = parseJSON(props.userData); + if (data !== undefined && data.seat !== undefined) { + return TARGET.SEAT; + } + + if (!props.visible) { + return TARGET.INVISIBLE; + } + + var surfaceNormal = intersection.surfaceNormal; var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); - return angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || - angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || - Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE; + + if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || + angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || + Vec3.distance(MyAvatar.position, intersection.intersection) <= TELEPORT_CANCEL_RANGE) { + return TARGET.INVALID; + } else { + return TARGET.SURFACE; + } }; function registerMappings() { @@ -695,20 +474,13 @@ function registerMappings() { if (isDisabled === 'left' || isDisabled === 'both') { return; } - if (activationTimeout !== null) { - return - } if (leftTrigger.down()) { return; } if (isMoving() === true) { return; } - activationTimeout = Script.setTimeout(function() { - Script.clearTimeout(activationTimeout); - activationTimeout = null; - teleporter.enterTeleportMode('left') - }, TELEPORT_DELAY) + teleporter.enterTeleportMode('left') return; }); teleportMapping.from(Controller.Standard.RightPrimaryThumb) @@ -716,9 +488,6 @@ function registerMappings() { if (isDisabled === 'right' || isDisabled === 'both') { return; } - if (activationTimeout !== null) { - return - } if (rightTrigger.down()) { return; } @@ -726,11 +495,7 @@ function registerMappings() { return; } - activationTimeout = Script.setTimeout(function() { - teleporter.enterTeleportMode('right') - Script.clearTimeout(activationTimeout); - activationTimeout = null; - }, TELEPORT_DELAY) + teleporter.enterTeleportMode('right') return; }); }; @@ -741,18 +506,11 @@ var teleporter = new Teleporter(); Controller.enableMapping(mappingName); -Script.scriptEnding.connect(cleanup); - function cleanup() { teleportMapping.disable(); - teleporter.disableMappings(); - teleporter.deleteTargetOverlay(); - teleporter.deleteCancelOverlay(); - teleporter.turnOffOverlayBeams(); - if (teleporter.updateConnected !== null) { - Script.update.disconnect(teleporter.update); - } + teleporter.cleanup(); } +Script.scriptEnding.connect(cleanup); var isDisabled = false; var handleHandMessages = function(channel, message, sender) { diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 1768d475a7..dd2aaf346b 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -167,12 +167,12 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { isAA: HMD.active }); - var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.035; + var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.009; this.homeButtonID = Overlays.addOverlay("sphere", { name: "homeButton", - localPosition: {x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -0.01}, + localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, localRotation: Quat.angleAxis(0, Y_AXIS), - dimensions: { x: 0.04, y: 0.04, z: 0.02}, + dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, alpha: 0.0, visible: true, drawInFront: false, diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 31d069442f..d5065cb826 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -26,9 +26,15 @@ print("show tablet-ui"); var DEFAULT_WIDTH = 0.4375; - var DEFAULT_HMD_TABLET_SCALE = 100; - var HMD_TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_HMD_TABLET_SCALE; - UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (HMD_TABLET_SCALE / 100), null, activeHand, true); + var DEFAULT_TABLET_SCALE = 100; + var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode; + var TABLET_SCALE = DEFAULT_TABLET_SCALE; + if (toolbarMode) { + TABLET_SCALE = Settings.getValue("desktopTabletScale") || DEFAULT_TABLET_SCALE; + } else { + TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_TABLET_SCALE; + } + UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (TABLET_SCALE / 100), null, activeHand, true); UIWebTablet.register(); HMD.tabletID = UIWebTablet.tabletEntityID; HMD.homeButtonID = UIWebTablet.homeButtonID; diff --git a/scripts/tutorials/entity_scripts/sit.js b/scripts/tutorials/entity_scripts/sit.js new file mode 100644 index 0000000000..2ba19231e0 --- /dev/null +++ b/scripts/tutorials/entity_scripts/sit.js @@ -0,0 +1,257 @@ +(function() { + Script.include("/~/system/libraries/utils.js"); + + var SETTING_KEY = "com.highfidelity.avatar.isSitting"; + var ROLE = "fly"; + var ANIMATION_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx"; + var ANIMATION_FPS = 30; + var ANIMATION_FIRST_FRAME = 1; + var ANIMATION_LAST_FRAME = 10; + var RELEASE_KEYS = ['w', 'a', 's', 'd', 'UP', 'LEFT', 'DOWN', 'RIGHT']; + var RELEASE_TIME = 500; // ms + var RELEASE_DISTANCE = 0.2; // meters + var MAX_IK_ERROR = 20; + var DESKTOP_UI_CHECK_INTERVAL = 250; + var DESKTOP_MAX_DISTANCE = 5; + var SIT_DELAY = 25 + + this.entityID = null; + this.timers = {}; + this.animStateHandlerID = null; + + this.preload = function(entityID) { + this.entityID = entityID; + } + this.unload = function() { + if (MyAvatar.sessionUUID === this.getSeatUser()) { + this.sitUp(this.entityID); + } + if (this.interval) { + Script.clearInterval(this.interval); + this.interval = null; + } + this.cleanupOverlay(); + } + + this.setSeatUser = function(user) { + var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData; + userData = JSON.parse(userData); + + if (user) { + userData.seat.user = user; + } else { + delete userData.seat.user; + } + + Entities.editEntity(this.entityID, { + userData: JSON.stringify(userData) + }); + } + this.getSeatUser = function() { + var properties = Entities.getEntityProperties(this.entityID, ["userData", "position"]); + var userData = JSON.parse(properties.userData); + + if (userData.seat.user && userData.seat.user !== MyAvatar.sessionUUID) { + var avatar = AvatarList.getAvatar(userData.seat.user); + if (avatar && Vec3.distance(avatar.position, properties.position) > RELEASE_DISTANCE) { + return null; + } + } + return userData.seat.user; + } + + this.checkSeatForAvatar = function() { + var seatUser = this.getSeatUser(); + var avatarIdentifiers = AvatarList.getAvatarIdentifiers(); + for (var i in avatarIdentifiers) { + var avatar = AvatarList.getAvatar(avatarIdentifiers[i]); + if (avatar && avatar.sessionUUID === seatUser) { + return true; + } + } + return false; + } + + this.sitDown = function() { + if (this.checkSeatForAvatar()) { + print("Someone is already sitting in that chair."); + return; + } + + this.setSeatUser(MyAvatar.sessionUUID); + + var previousValue = Settings.getValue(SETTING_KEY); + Settings.setValue(SETTING_KEY, this.entityID); + if (previousValue === "") { + MyAvatar.characterControllerEnabled = false; + MyAvatar.hmdLeanRecenterEnabled = false; + MyAvatar.overrideRoleAnimation(ROLE, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); + MyAvatar.resetSensorsAndBody(); + } + + var that = this; + Script.setTimeout(function() { + var properties = Entities.getEntityProperties(that.entityID, ["position", "rotation"]); + var index = MyAvatar.getJointIndex("Hips"); + MyAvatar.pinJoint(index, properties.position, properties.rotation); + + that.animStateHandlerID = MyAvatar.addAnimationStateHandler(function(properties) { + return { headType: 0 }; + }, ["headType"]); + Script.update.connect(that, that.update); + Controller.keyPressEvent.connect(that, that.keyPressed); + Controller.keyReleaseEvent.connect(that, that.keyReleased); + for (var i in RELEASE_KEYS) { + Controller.captureKeyEvents({ text: RELEASE_KEYS[i] }); + } + }, SIT_DELAY); + } + + this.sitUp = function() { + this.setSeatUser(null); + + if (Settings.getValue(SETTING_KEY) === this.entityID) { + MyAvatar.restoreRoleAnimation(ROLE); + MyAvatar.characterControllerEnabled = true; + MyAvatar.hmdLeanRecenterEnabled = true; + + var index = MyAvatar.getJointIndex("Hips"); + MyAvatar.clearPinOnJoint(index); + + MyAvatar.resetSensorsAndBody(); + + Script.setTimeout(function() { + MyAvatar.bodyPitch = 0.0; + MyAvatar.bodyRoll = 0.0; + }, SIT_DELAY); + + Settings.setValue(SETTING_KEY, ""); + } + + MyAvatar.removeAnimationStateHandler(this.animStateHandlerID); + Script.update.disconnect(this, this.update); + Controller.keyPressEvent.disconnect(this, this.keyPressed); + Controller.keyReleaseEvent.disconnect(this, this.keyReleased); + for (var i in RELEASE_KEYS) { + Controller.releaseKeyEvents({ text: RELEASE_KEYS[i] }); + } + } + + this.sit = function () { + this.sitDown(); + } + + this.createOverlay = function() { + var text = "Click to sit"; + var textMargin = 0.05; + var lineHeight = 0.15; + + this.overlay = Overlays.addOverlay("text3d", { + position: { x: 0.0, y: 0.0, z: 0.0}, + dimensions: { x: 0.1, y: 0.1 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + color: { red: 255, green: 255, blue: 255 }, + topMargin: textMargin, + leftMargin: textMargin, + bottomMargin: textMargin, + rightMargin: textMargin, + text: text, + lineHeight: lineHeight, + alpha: 0.9, + backgroundAlpha: 0.9, + ignoreRayIntersection: true, + visible: true, + isFacingAvatar: true + }); + var textSize = Overlays.textSize(this.overlay, text); + var overlayDimensions = { + x: textSize.width + 2 * textMargin, + y: textSize.height + 2 * textMargin + } + var properties = Entities.getEntityProperties(this.entityID, ["position", "registrationPoint", "dimensions"]); + var yOffset = (1.0 - properties.registrationPoint.y) * properties.dimensions.y + (overlayDimensions.y / 2.0); + var overlayPosition = Vec3.sum(properties.position, { x: 0, y: yOffset, z: 0 }); + Overlays.editOverlay(this.overlay, { + position: overlayPosition, + dimensions: overlayDimensions + }); + } + this.cleanupOverlay = function() { + if (this.overlay !== null) { + Overlays.deleteOverlay(this.overlay); + this.overlay = null; + } + } + + + this.update = function(dt) { + if (MyAvatar.sessionUUID === this.getSeatUser()) { + var properties = Entities.getEntityProperties(this.entityID, ["position"]); + var avatarDistance = Vec3.distance(MyAvatar.position, properties.position); + var ikError = MyAvatar.getIKErrorOnLastSolve(); + if (avatarDistance > RELEASE_DISTANCE || ikError > MAX_IK_ERROR) { + print("IK error: " + ikError + ", distance from chair: " + avatarDistance); + this.sitUp(this.entityID); + } + } + } + this.keyPressed = function(event) { + if (isInEditMode()) { + return; + } + + if (RELEASE_KEYS.indexOf(event.text) !== -1) { + var that = this; + this.timers[event.text] = Script.setTimeout(function() { + that.sitUp(); + }, RELEASE_TIME); + } + } + this.keyReleased = function(event) { + if (RELEASE_KEYS.indexOf(event.text) !== -1) { + if (this.timers[event.text]) { + Script.clearTimeout(this.timers[event.text]); + delete this.timers[event.text]; + } + } + } + + this.canSitDesktop = function() { + var properties = Entities.getEntityProperties(this.entityID, ["position"]); + var distanceFromSeat = Vec3.distance(MyAvatar.position, properties.position); + return distanceFromSeat < DESKTOP_MAX_DISTANCE && !this.checkSeatForAvatar(); + } + + this.hoverEnterEntity = function(event) { + if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) { + return; + } + + var that = this; + this.interval = Script.setInterval(function() { + if (that.overlay === null) { + if (that.canSitDesktop()) { + that.createOverlay(); + } + } else if (!that.canSitDesktop()) { + that.cleanupOverlay(); + } + }, DESKTOP_UI_CHECK_INTERVAL); + } + this.hoverLeaveEntity = function(event) { + if (this.interval) { + Script.clearInterval(this.interval); + this.interval = null; + } + this.cleanupOverlay(); + } + + this.clickDownOnEntity = function () { + if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) { + return; + } + if (this.canSitDesktop()) { + this.sitDown(); + } + } +}); diff --git a/unpublishedScripts/marketplace/shortbow/README.md b/unpublishedScripts/marketplace/shortbow/README.md new file mode 100644 index 0000000000..2e056fecfb --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/README.md @@ -0,0 +1,111 @@ +# Shortbow + +Shortbow is a wave-based archery game. + +## Notes + +There are several design decisions that are based on certain characteristics of the High Fidelity platform, +and in particular, [server entity scripts](https://wiki.highfidelity.com/wiki/Creating_Entity_Server_Scripts), +which are touched on below. +It is recommended that you understand the basics of client entity scripts and server entity scripts (and their +differences) if you plan on digging into the shortbow code. + + * Client entity scripts end in `ClientEntity.js` and server entity scripts end in `ServerEntity.js`. + * Server entity scripts are not guaranteed to have access to *other* entities, and should not rely on it. + * You should not rely on using `Entities.getEntityProperties` to access the properties of entities + other than the entity that the server entity script is running on. This also applies to other + functions like `Entities.findEntities`. This means that something like the `ShortGameManager` (described below) + will not know the entity IDs of entities like the start button or scoreboard text entities, so it + has to find ways to work around that limitation. + * You can, however, use `Entities.editEntity` to edit other entities. + * *NOTE*: It is likely that this will change in the future, and server entity scripts will be able to + query the existence of other entities in some way. This will obviate the need for some of the workarounds + used in shortbow. + * The Entity Script Server (where server entity scripts) does not run a physics simulation + * Server entity scripts do not generate collision events like would be used with + `Script.addEventHandler(entityID, "collisionWithEntity", collideHandler)`. + * High Fidelity distributes its physics simulation to clients, so collisions need to be handled + there. In the case of enemies in shortbow, for instance, the collisions are handled in the + client entity script `enemeyClientEntity.js`. + * If no client is present to run the physics simulation, entities will not physically interact with other + entities. + * But, entities will still apply their basic physical properties. + +## Shortbow Game Manager + +This section describes both `shortbowServerEntity.js` and `shortbowGameManager.js`. The `shortbowServerEntity.js` script +exists on the main arena model entity, and is the parent of all of the other parts of the game, other than the +enemies and bows that are spawned during gameplay. + +The `shortbowServerEntity.js` script is a server entity script that runs the shortbow game. The actual logic for +the game is stored inside `shortbowGameManager.js`, in the `ShortbowGameManager` prototype. + +## Enemy Scripts + +These scripts exist on each enemy that is spawned. + +### enemyClientEntity.js + +This script handles collision events on the client. There are two collisions events that it is interested in: + + 1. Collisions with arrows + 1. Arrow entities have "projectile" in their name + 1. Any other entity with "projectile" in its name could be used to destroy the enemy + 1. Collisions with the gate that the enemies roll towards + 1. The gate entity has "GateCollider" in its name + +### enemyServerEntity.js + +This script acts as a fail-safe to work around some of the limitations that are mentioned under [Notes](#notes). +In particular, if no client is running the physics simulation of an enemy entity, it may fall through the floor +of the arena or never have a collision event generated against the gate. A server entity script also +cannot access the properties of another entity, so it can't know if the entity has moved far away from +the arena. + +To handle this, this script is used to periodically send heartbeats to the [ShortbowGameManager](#shortbow-game-manager) +that runs the game. The heartbeats include the position of the enemy. If the script that received the +heartbeats hasn't heard from `enemyServerEntity.js` in too long, or the entity has moved too far away, it +will remove it from it's local list of enemies that still need to be destroyed. This ensure that the game +doesn't get into a "hung" state where it's waiting for an enemy that will never escape through the gate or be +destroyed. + +## Start Button + +These scripts exist on the start button. + +### startGameButtonClientEntity.js + +This script sends a message to the [ShortbowGameManager](#shortbow-game-manager) to request that the game be started. + +### startGameButtonServerEntity.js + +When the shortbow game starts the start button is hidden, and when the shortbow game ends it is shown again. +As described in the [Notes](#notes), server entity scripts cannot access other entities, including their IDs. +Because of this, the [ShortbowGameManager](#shortbow-game-manager) has no way of knowing the id of the start button, +and thus being able to use `Entities.editEntity` to set its `visible` property to `true` or `false`. One way around +this, and is what is used here, is to use `Messages` on a common channel to send messages to a server entity script +on the start button to request that it be shown or hidden. + +## Display (Scoreboard) + +This script exists on each text entity that scoreboard is composed of. The scoreboard area is composed of a text entity for each of the following: Score, High Score, Wave, Lives. + +### displayServerEntity.js + +The same problem that exists for [startGameButtonServerEntity.js](#startgamebuttonserverentityjs) exists for +the text entities on the scoreboard. This script works around the problem in the same way, but instead of +receiving a message that tells it whether to hide or show itself, it receives a message that contains the +text to update the text entity with. For intance, the "lives" display entity will receive a message each +time a life is lost with the current number of lives. + +## How is the "common channel" determined that is used in some of the client and server entity scripts? + +Imagine that there are two instances of shortbow next to each. If the channel being used is always the same, +and not differentiated in some way between the two instances, a "start game" message sent from [startGameButtonClientEntity.js](#startgamebuttoncliententityjs) +on one game will be received and handled by both games, causing both of them to start. We need a way to create +a channel that is unique to the scripts that are running for a particular instance of shortbow. + +All of the entities in shortbow, besides the enemy entities, are children of the main arena entity that +runs the game. All of the scripts on these entities can access their parentID, so they can use +a prefix plus the parentID to generate a unique channel for that instance of shortbow. + diff --git a/unpublishedScripts/marketplace/shortbow/bow/Arrow_impact1.L.wav b/unpublishedScripts/marketplace/shortbow/bow/Arrow_impact1.L.wav new file mode 100644 index 0000000000..30aede7a5a Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/Arrow_impact1.L.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/Bow_draw.1.L.wav b/unpublishedScripts/marketplace/shortbow/bow/Bow_draw.1.L.wav new file mode 100644 index 0000000000..254e20d937 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/Bow_draw.1.L.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/String_release2.L.wav b/unpublishedScripts/marketplace/shortbow/bow/String_release2.L.wav new file mode 100644 index 0000000000..4f3ad767a2 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/String_release2.L.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/arrow-sparkle.png b/unpublishedScripts/marketplace/shortbow/bow/arrow-sparkle.png new file mode 100644 index 0000000000..6663b2ff1e Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/arrow-sparkle.png differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/arrow.fbx b/unpublishedScripts/marketplace/shortbow/bow/arrow.fbx new file mode 100644 index 0000000000..533e8eb1f7 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/arrow.fbx differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/bow-deadly.fbx b/unpublishedScripts/marketplace/shortbow/bow/bow-deadly.fbx new file mode 100644 index 0000000000..51f0a25c73 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/bow-deadly.fbx differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/bow.js b/unpublishedScripts/marketplace/shortbow/bow/bow.js new file mode 100644 index 0000000000..f8ef025728 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/bow/bow.js @@ -0,0 +1,671 @@ +// +// Created by Seth Alves on 2016-9-7 +// Copyright 2016 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 +/* global MyAvatar, Vec3, Controller, Quat */ + +var GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing"; +setGrabCommunications = function setFarGrabCommunications(on) { + Settings.setValue(GRAB_COMMUNICATIONS_SETTING, on ? "on" : ""); +} +getGrabCommunications = function getFarGrabCommunications() { + return !!Settings.getValue(GRAB_COMMUNICATIONS_SETTING, ""); +} + +// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 +var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral + +getGrabPointSphereOffset = function(handController) { + if (handController === Controller.Standard.RightHand) { + return GRAB_POINT_SPHERE_OFFSET; + } + return { + x: GRAB_POINT_SPHERE_OFFSET.x * -1, + y: GRAB_POINT_SPHERE_OFFSET.y, + z: GRAB_POINT_SPHERE_OFFSET.z + }; +}; + +// controllerWorldLocation is where the controller would be, in-world, with an added offset +getControllerWorldLocation = function (handController, doOffset) { + var orientation; + var position; + var pose = Controller.getPoseValue(handController); + var valid = pose.valid; + var controllerJointIndex; + if (pose.valid) { + if (handController === Controller.Standard.RightHand) { + controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); + } else { + controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + } + orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex)); + position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex))); + + // add to the real position so the grab-point is out in front of the hand, a bit + if (doOffset) { + var offset = getGrabPointSphereOffset(handController); + position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset)); + } + + } else if (!HMD.isHandControllerAvailable()) { + // NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493 + var VERTICAL_HEAD_LASER_OFFSET = 0.1; + position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0})); + orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); + valid = true; + } + + return {position: position, + translation: position, + orientation: orientation, + rotation: orientation, + valid: valid}; +}; + + + +// +// +// +// +// +// +// bow.js +// +// This script attaches to a bow that you can pick up with a hand controller. +// Created by James B. Pollack @imgntn on 10/19/2015 +// Copyright 2015 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 +// + +/*global Script, Controller, SoundCache, Entities, getEntityCustomData, setEntityCustomData, MyAvatar, Vec3, Quat, Messages */ + + +function getControllerLocation(controllerHand) { + var standardControllerValue = + (controllerHand === "right") ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + return getControllerWorldLocation(standardControllerValue, true); +}; + +(function() { + + Script.include("/~/system/libraries/utils.js"); + + const NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + + const NOTCH_ARROW_SOUND_URL = Script.resolvePath('notch.wav'); + const SHOOT_ARROW_SOUND_URL = Script.resolvePath('String_release2.L.wav'); + const STRING_PULL_SOUND_URL = Script.resolvePath('Bow_draw.1.L.wav'); + const ARROW_HIT_SOUND_URL = Script.resolvePath('Arrow_impact1.L.wav'); + + const ARROW_MODEL_URL = Script.resolvePath('arrow.fbx'); + const ARROW_DIMENSIONS = { + x: 0.20, + y: 0.19, + z: 0.93 + }; + + const MIN_ARROW_SPEED = 3.0; + const MAX_ARROW_SPEED = 30.0; + + const ARROW_TIP_OFFSET = 0.47; + const ARROW_GRAVITY = { + x: 0, + y: -9.8, + z: 0 + }; + + + const ARROW_LIFETIME = 15; // seconds + const ARROW_PARTICLE_LIFESPAN = 2; // seconds + + const TOP_NOTCH_OFFSET = 0.6; + const BOTTOM_NOTCH_OFFSET = 0.6; + + const LINE_DIMENSIONS = { + x: 5.0, + y: 5.0, + z: 5.0 + }; + + const DRAW_STRING_THRESHOLD = 0.80; + const DRAW_STRING_PULL_DELTA_HAPTIC_PULSE = 0.09; + const DRAW_STRING_MAX_DRAW = 0.7; + + + const MIN_ARROW_DISTANCE_FROM_BOW_REST = 0.2; + const MAX_ARROW_DISTANCE_FROM_BOW_REST = ARROW_DIMENSIONS.z - 0.2; + const MIN_HAND_DISTANCE_FROM_BOW_TO_KNOCK_ARROW = 0.25; + const MIN_ARROW_DISTANCE_FROM_BOW_REST_TO_SHOOT = 0.30; + + const NOTCH_OFFSET_FORWARD = 0.08; + const NOTCH_OFFSET_UP = 0.035; + + const SHOT_SCALE = { + min1: 0.0, + max1: 0.6, + min2: 1.0, + max2: 15.0 + }; + + const USE_DEBOUNCE = false; + + const TRIGGER_CONTROLS = [ + Controller.Standard.LT, + Controller.Standard.RT, + ]; + + function interval() { + var lastTime = new Date().getTime(); + + return function getInterval() { + var newTime = new Date().getTime(); + var delta = newTime - lastTime; + lastTime = newTime; + return delta; + }; + } + + var checkInterval = interval(); + + var _this; + + function Bow() { + _this = this; + return; + } + + const STRING_NAME = 'Hifi-Bow-String'; + const ARROW_NAME = 'Hifi-Arrow-projectile'; + + const STATE_IDLE = 0; + const STATE_ARROW_GRABBED = 1; + + Bow.prototype = { + topString: null, + aiming: false, + arrowTipPosition: null, + preNotchString: null, + stringID: null, + arrow: null, + stringData: { + currentColor: { + red: 255, + green: 255, + blue: 255 + } + }, + + state: STATE_IDLE, + sinceLastUpdate: 0, + preload: function(entityID) { + this.entityID = entityID; + this.stringPullSound = SoundCache.getSound(STRING_PULL_SOUND_URL); + this.shootArrowSound = SoundCache.getSound(SHOOT_ARROW_SOUND_URL); + this.arrowHitSound = SoundCache.getSound(ARROW_HIT_SOUND_URL); + this.arrowNotchSound = SoundCache.getSound(NOTCH_ARROW_SOUND_URL); + var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData; + print(userData); + this.userData = JSON.parse(userData); + this.stringID = null; + }, + + unload: function() { + Messages.sendLocalMessage('Hifi-Hand-Disabler', "none"); + Entities.deleteEntity(this.arrow); + }, + + startEquip: function(entityID, args) { + this.hand = args[0]; + this.bowHand = args[0]; + this.stringHand = this.bowHand === "right" ? "left" : "right"; + + Entities.editEntity(_this.entityID, { + collidesWith: "", + }); + + var data = getEntityCustomData('grabbableKey', this.entityID, {}); + data.grabbable = false; + setEntityCustomData('grabbableKey', this.entityID, data); + + this.initString(); + + var self = this; + this.updateIntervalID = Script.setInterval(function() { self.update(); }, 11); + }, + + getStringHandPosition: function() { + return getControllerLocation(this.stringHand).position; + }, + + releaseEquip: function(entityID, args) { + Script.clearInterval(this.updateIntervalID); + this.updateIntervalID = null; + + Messages.sendLocalMessage('Hifi-Hand-Disabler', "none"); + + var data = getEntityCustomData('grabbableKey', this.entityID, {}); + data.grabbable = true; + setEntityCustomData('grabbableKey', this.entityID, data); + Entities.deleteEntity(this.arrow); + this.resetStringToIdlePosition(); + this.destroyArrow(); + Entities.editEntity(_this.entityID, { + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar" + }); + }, + + update: function(entityID) { + var self = this; + self.deltaTime = checkInterval(); + //debounce during debugging -- maybe we're updating too fast? + if (USE_DEBOUNCE === true) { + self.sinceLastUpdate = self.sinceLastUpdate + self.deltaTime; + + if (self.sinceLastUpdate > 60) { + self.sinceLastUpdate = 0; + } else { + return; + } + } + + //invert the hands because our string will be held with the opposite hand of the first one we pick up the bow with + this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[(this.hand === 'left') ? 1 : 0]); + + this.bowProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); + var notchPosition = this.getNotchPosition(this.bowProperties); + var stringHandPosition = this.getStringHandPosition(); + var handToNotch = Vec3.subtract(notchPosition, stringHandPosition); + var pullBackDistance = Vec3.length(handToNotch); + + if (this.state === STATE_IDLE) { + this.pullBackDistance = 0; + + this.resetStringToIdlePosition(); + if (this.triggerValue >= DRAW_STRING_THRESHOLD && pullBackDistance < MIN_HAND_DISTANCE_FROM_BOW_TO_KNOCK_ARROW && !this.backHandBusy) { + //the first time aiming the arrow + var handToDisable = (this.hand === 'right' ? 'left' : 'right'); + this.state = STATE_ARROW_GRABBED; + } + } + + if (this.state === STATE_ARROW_GRABBED) { + if (!this.arrow) { + var handToDisable = (this.hand === 'right' ? 'left' : 'right'); + Messages.sendLocalMessage('Hifi-Hand-Disabler', handToDisable); + this.playArrowNotchSound(); + this.arrow = this.createArrow(); + this.playStringPullSound(); + } + + if (this.triggerValue < DRAW_STRING_THRESHOLD) { + if (pullBackDistance >= (MIN_ARROW_DISTANCE_FROM_BOW_REST_TO_SHOOT)) { + // The arrow has been pulled far enough back that we can release it + Messages.sendLocalMessage('Hifi-Hand-Disabler', "none"); + this.updateArrowPositionInNotch(true, true); + this.arrow = null; + this.state = STATE_IDLE; + this.resetStringToIdlePosition(); + } else { + // The arrow has not been pulled far enough back so we just remove the arrow + Messages.sendLocalMessage('Hifi-Hand-Disabler', "none"); + Entities.deleteEntity(this.arrow); + this.arrow = null; + this.state = STATE_IDLE; + this.resetStringToIdlePosition(); + } + } else { + this.updateArrowPositionInNotch(false, true); + this.updateString(); + } + } + }, + + destroyArrow: function() { + var children = Entities.getChildrenIDs(this.entityID); + children.forEach(function(childID) { + var childName = Entities.getEntityProperties(childID, ["name"]).name; + if (childName == ARROW_NAME) { + Entities.deleteEntity(childID); + // Continue iterating through children in case we've ended up in + // a bad state where there are multiple arrows. + } + }); + }, + + createArrow: function() { + this.playArrowNotchSound(); + + var arrow = Entities.addEntity({ + name: ARROW_NAME, + type: 'Model', + modelURL: ARROW_MODEL_URL, + shapeType: 'simple-compound', + dimensions: ARROW_DIMENSIONS, + position: this.bowProperties.position, + parentID: this.entityID, + dynamic: false, + collisionless: true, + collisionSoundURL: ARROW_HIT_SOUND_URL, + damping: 0.01, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + }, + creatorSessionUUID: MyAvatar.sessionUUID + }) + }); + + var makeArrowStick = function(entityA, entityB, collision) { + Entities.editEntity(entityA, { + localAngularVelocity: { + x: 0, + y: 0, + z: 0 + }, + localVelocity: { + x: 0, + y: 0, + z: 0 + }, + gravity: { + x: 0, + y: 0, + z: 0 + }, + parentID: entityB, + dynamic: false, + collisionless: true, + collidesWith: "" + }); + Script.removeEventHandler(arrow, "collisionWithEntity", makeArrowStick); + }; + + Script.addEventHandler(arrow, "collisionWithEntity", makeArrowStick); + + return arrow; + }, + + initString: function() { + // Check for existence of string + var children = Entities.getChildrenIDs(this.entityID); + children.forEach(function(childID) { + var childName = Entities.getEntityProperties(childID, ["name"]).name; + if (childName == STRING_NAME) { + this.stringID = childID; + } + }); + + // If thie string wasn't found, create it + if (this.stringID === null) { + this.stringID = Entities.addEntity({ + collisionless: true, + dimensions: { "x": 5, "y": 5, "z": 5 }, + ignoreForCollisions: 1, + linePoints: [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ], + lineWidth: 5, + color: { red: 153, green: 102, blue: 51 }, + name: STRING_NAME, + parentID: this.entityID, + localPosition: { "x": 0, "y": 0.6, "z": 0.1 }, + localRotation: { "w": 1, "x": 0, "y": 0, "z": 0 }, + type: 'Line', + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } + + this.resetStringToIdlePosition(); + }, + + // This resets the string to a straight line + resetStringToIdlePosition: function() { + Entities.editEntity(this.stringID, { + linePoints: [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ], + lineWidth: 5, + localPosition: { "x": 0, "y": 0.6, "z": 0.1 }, + localRotation: { "w": 1, "x": 0, "y": 0, "z": 0 }, + }); + }, + + updateString: function() { + var upVector = Quat.getUp(this.bowProperties.rotation); + var upOffset = Vec3.multiply(upVector, TOP_NOTCH_OFFSET); + var downVector = Vec3.multiply(-1, Quat.getUp(this.bowProperties.rotation)); + var downOffset = Vec3.multiply(downVector, BOTTOM_NOTCH_OFFSET); + var backOffset = Vec3.multiply(-0.1, Quat.getFront(this.bowProperties.rotation)); + + var topStringPosition = Vec3.sum(this.bowProperties.position, upOffset); + this.topStringPosition = Vec3.sum(topStringPosition, backOffset); + var bottomStringPosition = Vec3.sum(this.bowProperties.position, downOffset); + this.bottomStringPosition = Vec3.sum(bottomStringPosition, backOffset); + + var stringProps = Entities.getEntityProperties(this.stringID, ['position', 'rotation']); + var handPositionLocal = Vec3.subtract(this.arrowRearPosition, stringProps.position); + handPositionLocal = Vec3.multiplyQbyV(Quat.inverse(stringProps.rotation), handPositionLocal); + + var linePoints = [ + { x: 0, y: 0, z: 0 }, + handPositionLocal, + { x: 0, y: -1.2, z: 0 }, + ]; + + Entities.editEntity(this.stringID, { + linePoints: linePoints, + }); + }, + + getNotchPosition: function(bowProperties) { + var frontVector = Quat.getFront(bowProperties.rotation); + var notchVectorForward = Vec3.multiply(frontVector, NOTCH_OFFSET_FORWARD); + var upVector = Quat.getUp(bowProperties.rotation); + var notchVectorUp = Vec3.multiply(upVector, NOTCH_OFFSET_UP); + var notchPosition = Vec3.sum(bowProperties.position, notchVectorForward); + notchPosition = Vec3.sum(notchPosition, notchVectorUp); + return notchPosition; + }, + + updateArrowPositionInNotch: function(shouldReleaseArrow, doHapticPulses) { + //set the notch that the arrow should go through + var notchPosition = this.getNotchPosition(this.bowProperties); + //set the arrow rotation to be between the notch and other hand + var stringHandPosition = this.getStringHandPosition(); + var handToNotch = Vec3.subtract(notchPosition, stringHandPosition); + var arrowRotation = Quat.rotationBetween(Vec3.FRONT, handToNotch); + + var backHand = this.hand === 'left' ? 1 : 0; + var pullBackDistance = Vec3.length(handToNotch); + // pulse as arrow is drawn + if (doHapticPulses && + Math.abs(pullBackDistance - this.pullBackDistance) > DRAW_STRING_PULL_DELTA_HAPTIC_PULSE) { + Controller.triggerHapticPulse(1, 20, backHand); + this.pullBackDistance = pullBackDistance; + } + + if (pullBackDistance > DRAW_STRING_MAX_DRAW) { + pullBackDistance = DRAW_STRING_MAX_DRAW; + } + + var handToNotchDistance = Vec3.length(handToNotch); + var stringToNotchDistance = Math.max(MIN_ARROW_DISTANCE_FROM_BOW_REST, Math.min(MAX_ARROW_DISTANCE_FROM_BOW_REST, handToNotchDistance)); + var halfArrowVec = Vec3.multiply(Vec3.normalize(handToNotch), ARROW_DIMENSIONS.z / 2.0); + var offset = Vec3.subtract(notchPosition, Vec3.multiply(Vec3.normalize(handToNotch), stringToNotchDistance - ARROW_DIMENSIONS.z / 2.0)); + + var arrowPosition = offset; + + // Set arrow rear position + var frontVector = Quat.getFront(arrowRotation); + var frontOffset = Vec3.multiply(frontVector, -ARROW_TIP_OFFSET); + var arrorRearPosition = Vec3.sum(arrowPosition, frontOffset); + this.arrowRearPosition = arrorRearPosition; + + //if we're not shooting, we're updating the arrow's orientation + if (shouldReleaseArrow !== true) { + Entities.editEntity(this.arrow, { + position: arrowPosition, + rotation: arrowRotation + }); + } else { + //shoot the arrow + var arrowAge = Entities.getEntityProperties(this.arrow, ["age"]).age; + + //scale the shot strength by the distance you've pulled the arrow back and set its release velocity to be + // in the direction of the v + var arrowForce = this.scaleArrowShotStrength(stringToNotchDistance); + var handToNotchNorm = Vec3.normalize(handToNotch); + + var releaseVelocity = Vec3.multiply(handToNotchNorm, arrowForce); + + //make the arrow physical, give it gravity, a lifetime, and set our velocity + var arrowProperties = { + dynamic: true, + collisionless: false, + collidesWith: "static,dynamic,otherAvatar", // workaround: not with kinematic --> no collision with bow + velocity: releaseVelocity, + parentID: NULL_UUID, + gravity: ARROW_GRAVITY, + lifetime: arrowAge + ARROW_LIFETIME, + }; + + // add a particle effect to make the arrow easier to see as it flies + var arrowParticleProperties = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 0, + alphaSpread: 0, + alphaStart: 0.3, + azimuthFinish: 3.1, + azimuthStart: -3.14159, + color: { red: 255, green: 255, blue: 255 }, + colorFinish: { red: 255, green: 255, blue: 255 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 255, green: 255, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: -0.7, y: 0.0, z: 0.0, w: 0.7 }, + emitRate: 0.01, + emitSpeed: 0, + emitterShouldTrail: 0, + isEmitting: 1, + lifespan: ARROW_PARTICLE_LIFESPAN, + lifetime: ARROW_PARTICLE_LIFESPAN + 1, + maxParticles: 1000, + name: 'arrow-particles', + parentID: this.arrow, + particleRadius: 0.132, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.35, + radiusSpread: 0, + radiusStart: 0.132, + speedSpread: 0, + textures: Script.resolvePath('arrow-sparkle.png'), + type: 'ParticleEffect' + }; + + Entities.addEntity(arrowParticleProperties); + + // actually shoot the arrow + Entities.editEntity(this.arrow, arrowProperties); + + // play the sound of a shooting arrow + this.playShootArrowSound(); + + Entities.addAction("travel-oriented", this.arrow, { + forward: { x: 0, y: 0, z: -1 }, + angularTimeScale: 0.1, + tag: "arrow from hifi-bow", + ttl: ARROW_LIFETIME + }); + + + } + }, + + scaleArrowShotStrength: function(value) { + var percentage = (value - MIN_ARROW_DISTANCE_FROM_BOW_REST) + / (MAX_ARROW_DISTANCE_FROM_BOW_REST - MIN_ARROW_DISTANCE_FROM_BOW_REST); + return MIN_ARROW_SPEED + (percentage * (MAX_ARROW_SPEED - MIN_ARROW_SPEED)) ; + }, + + playStringPullSound: function() { + var audioProperties = { + volume: 0.10, + position: this.bowProperties.position + }; + this.stringPullInjector = Audio.playSound(this.stringPullSound, audioProperties); + }, + + playShootArrowSound: function(sound) { + var audioProperties = { + volume: 0.15, + position: this.bowProperties.position + }; + Audio.playSound(this.shootArrowSound, audioProperties); + }, + + playArrowNotchSound: function() { + var audioProperties = { + volume: 0.15, + position: this.bowProperties.position + }; + Audio.playSound(this.arrowNotchSound, audioProperties); + }, + + changeStringPullSoundVolume: function(pullBackDistance) { + var audioProperties = { + volume: this.scaleSoundVolume(pullBackDistance), + position: this.bowProperties.position + }; + + this.stringPullInjector.options = audioProperties; + }, + + scaleSoundVolume: function(value) { + var min1 = SHOT_SCALE.min1; + var max1 = SHOT_SCALE.max1; + var min2 = 0; + var max2 = 0.2; + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); + }, + + handleMessages: function(channel, message, sender) { + if (sender !== MyAvatar.sessionUUID) { + return; + } + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + try { + var data = JSON.parse(message); + var action = data.action; + var hand = data.joint; + var isBackHand = ((_this.hand == "left" && hand == "RightHand") || + (_this.hand == "right" && hand == "LeftHand")); + if ((action == "equip" || action == "grab") && isBackHand) { + _this.backHandBusy = true; + } + if (action == "release" && isBackHand) { + _this.backHandBusy = false; + } + } catch (e) { + print("WARNING: bow.js -- error parsing Hifi-Object-Manipulation message: " + message); + } + } + }; + + var bow = new Bow(); + + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(bow.handleMessages); + + return bow; +}); diff --git a/unpublishedScripts/marketplace/shortbow/bow/bow.json b/unpublishedScripts/marketplace/shortbow/bow/bow.json new file mode 100644 index 0000000000..a510df729f --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/bow/bow.json @@ -0,0 +1,44 @@ +{ + "Entities": [ + { + "clientOnly": 0, + "collisionsWillMove": 1, + "compoundShapeURL": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow_collision_hull.obj", + "created": "2017-02-14T18:54:38Z", + "dimensions": { + "x": 0.039999999105930328, + "y": 1.2999999523162842, + "z": 0.20000000298023224 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -9.8000001907348633, + "z": 0 + }, + "id": "{73954924-2e18-4787-91a7-092c2afb6242}", + "lastEdited": 1487098438422164, + "lastEditedBy": "{d2da5e17-9125-414d-ac4e-cd7fba6c22f8}", + "modelURL": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow-deadly.fbx", + "name": "WG.Hifi-Bow", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "queryAACube": { + "scale": 1.3159027099609375, + "x": -0.65795135498046875, + "y": -0.65795135498046875, + "z": -0.65795135498046875 + }, + "rotation": { + "w": 0.9717707633972168, + "x": 0.15437555313110352, + "y": -0.10472267866134644, + "z": -0.14421302080154419 + }, + "script": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow.js", + "shapeType": "compound", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}" + } + ], + "Version": 67 +} diff --git a/unpublishedScripts/marketplace/shortbow/bow/bow.svo.json b/unpublishedScripts/marketplace/shortbow/bow/bow.svo.json new file mode 100644 index 0000000000..1ef66860a6 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/bow/bow.svo.json @@ -0,0 +1,32 @@ +{ + "Entities": [ { + "collisionsWillMove": 1, + "compoundShapeURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow_collision_hull.obj", + "created": "2016-09-01T23:57:55Z", + "dimensions": { + "x": 0.039999999105930328, + "y": 1.2999999523162842, + "z": 0.20000000298023224 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -1, + "z": 0 + }, + "modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow-deadly.fbx", + "name": "Hifi-Bow", + "rotation": { + "w": 0.9718012809753418, + "x": 0.15440607070922852, + "y": -0.10469216108322144, + "z": -0.14418250322341919 + }, + "script": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow.js", + "shapeType": "compound", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}" + } + ], + "Version": 57 +} \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shortbow/bow/bow_collision_hull.obj b/unpublishedScripts/marketplace/shortbow/bow/bow_collision_hull.obj new file mode 100644 index 0000000000..d25786e74f --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/bow/bow_collision_hull.obj @@ -0,0 +1,21 @@ +v -0.016461 -0.431491 -0.033447 +v -0.007624 0.437384 -0.046243 +v 0.011984 -0.424659 -0.03691 +v 0.015514 0.425913 -0.028648 +v -0.010788 -0.421429 0.093711 +v 0.007135 -0.423115 0.098735 +v -0.010208 0.425558 0.096005 +v 0.006734 0.43913 0.088902 + +f 1 2 3 +f 3 2 4 +f 5 6 7 +f 7 6 8 +f 1 5 2 +f 2 5 7 +f 3 4 6 +f 6 4 8 +f 1 3 5 +f 5 3 6 +f 2 7 4 +f 4 7 8 diff --git a/unpublishedScripts/marketplace/shortbow/bow/notch.wav b/unpublishedScripts/marketplace/shortbow/bow/notch.wav new file mode 100644 index 0000000000..2aa67bac33 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/bow/notch.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/bow/spawnBow.js b/unpublishedScripts/marketplace/shortbow/bow/spawnBow.js new file mode 100644 index 0000000000..cb94b05556 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/bow/spawnBow.js @@ -0,0 +1,67 @@ +// +// Created by Ryan Huffman on 1/10/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 leftHandPosition = { + "x": 0, + "y": 0.0559, + "z": 0.0159 +}; +var leftHandRotation = Quat.fromPitchYawRollDegrees(90, -90, 0); +var rightHandPosition = Vec3.multiplyVbyV(leftHandPosition, { x: -1, y: 0, z: 0 }); +var rightHandRotation = Quat.fromPitchYawRollDegrees(90, 90, 0); + +var userData = { + "grabbableKey": { + "grabbable": true + }, + "wearable": { + "joints": { + "LeftHand": [ + leftHandPosition, + leftHandRotation + ], + "RightHand": [ + rightHandPosition, + rightHandRotation + ] + } + } +}; + +var id = Entities.addEntity({ + "position": MyAvatar.position, + "collisionsWillMove": 1, + "compoundShapeURL": Script.resolvePath("bow_collision_hull.obj"), + "created": "2016-09-01T23:57:55Z", + "dimensions": { + "x": 0.039999999105930328, + "y": 1.2999999523162842, + "z": 0.20000000298023224 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -9.8, + "z": 0 + }, + "modelURL": Script.resolvePath("bow-deadly.fbx"), + "name": "Hifi-Bow", + "rotation": { + "w": 0.9718012809753418, + "x": 0.15440607070922852, + "y": -0.10469216108322144, + "z": -0.14418250322341919 + }, + "script": Script.resolvePath("bow.js"), + "shapeType": "compound", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}", + "lifetime": 600 +}); +print("Created bow:", id); diff --git a/unpublishedScripts/marketplace/shortbow/enemyClientEntity.js b/unpublishedScripts/marketplace/shortbow/enemyClientEntity.js new file mode 100644 index 0000000000..3abdaa46fb --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/enemyClientEntity.js @@ -0,0 +1,61 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals utils */ + +(function() { + Script.include('utils.js'); + + function Enemy() { + } + Enemy.prototype = { + preload: function(entityID) { + this.entityID = entityID; + + // To avoid sending extraneous messages and checking entities that we've already + // seen, we keep track of which entities we've collided with previously. + this.entityIDsThatHaveCollidedWithMe = []; + + Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this)); + + var userData = Entities.getEntityProperties(this.entityID, 'userData').userData; + var data = utils.parseJSON(userData); + if (data !== undefined && data.gameChannel !== undefined) { + this.gameChannel = data.gameChannel; + } else { + print("enemyEntity.js | ERROR: userData does not contain a game channel and/or team number"); + } + }, + onCollide: function(entityA, entityB, collision) { + if (this.entityIDsThatHaveCollidedWithMe.indexOf(entityB) > -1) { + return; + } + this.entityIDsThatHaveCollidedWithMe.push(entityB); + + var colliderName = Entities.getEntityProperties(entityB, 'name').name; + + if (colliderName.indexOf("projectile") > -1) { + Messages.sendMessage(this.gameChannel, JSON.stringify({ + type: "enemy-killed", + entityID: this.entityID, + position: Entities.getEntityProperties(this.entityID, 'position').position + })); + Entities.deleteEntity(this.entityID); + } else if (colliderName.indexOf("GateCollider") > -1) { + Messages.sendMessage(this.gameChannel, JSON.stringify({ + type: "enemy-escaped", + entityID: this.entityID, + position: Entities.getEntityProperties(this.entityID, 'position').position + })); + Entities.deleteEntity(this.entityID); + } + } + }; + + return new Enemy(); +}); diff --git a/unpublishedScripts/marketplace/shortbow/enemyServerEntity.js b/unpublishedScripts/marketplace/shortbow/enemyServerEntity.js new file mode 100644 index 0000000000..bd3f76c94e --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/enemyServerEntity.js @@ -0,0 +1,41 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals utils */ + +(function() { + Script.include('utils.js'); + + function Enemy() { + } + Enemy.prototype = { + preload: function(entityID) { + this.entityID = entityID; + var userData = Entities.getEntityProperties(this.entityID, 'userData').userData; + var data = utils.parseJSON(userData); + if (data !== undefined && data.gameChannel !== undefined) { + this.gameChannel = data.gameChannel; + } else { + print("enemyServerEntity.js | ERROR: userData does not contain a game channel and/or team number"); + } + var self = this; + this.heartbeatTimerID = Script.setInterval(function() { + Messages.sendMessage(self.gameChannel, JSON.stringify({ + type: "enemy-heartbeat", + entityID: self.entityID, + position: Entities.getEntityProperties(self.entityID, 'position').position + })); + }, 1000); + }, + unload: function() { + Script.clearInterval(this.heartbeatTimerID); + } + }; + + return new Enemy(); +}); diff --git a/unpublishedScripts/marketplace/shortbow/models/Amber.fbx b/unpublishedScripts/marketplace/shortbow/models/Amber.fbx new file mode 100644 index 0000000000..4da921f70a Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/models/Amber.fbx differ diff --git a/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx b/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx new file mode 100644 index 0000000000..817e39cc3c Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx differ diff --git a/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx b/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx new file mode 100644 index 0000000000..b82755a8ca Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx differ diff --git a/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx b/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx new file mode 100644 index 0000000000..b689fe2eee Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx differ diff --git a/unpublishedScripts/marketplace/shortbow/shortbow.js b/unpublishedScripts/marketplace/shortbow/shortbow.js new file mode 100644 index 0000000000..641e9c45a6 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/shortbow.js @@ -0,0 +1,877 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals SHORTBOW_ENTITIES:true */ + +// This is a copy of the data in shortbow.json, which is an export of the shortbow +// scene. +// +// Because .json can't be Script.include'd directly, the contents are copied over +// to here and exposed as a global variable. +// + +SHORTBOW_ENTITIES = +{ + "Entities": [ + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{02f39515-cab4-41d5-b315-5fb41613f844}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708750446, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -5.1684012413024902, + "y": 0.54034698009490967, + "z": -11.257695198059082 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": -1.3604279756546021, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 2, + "y": 0.69999998807907104, + "z": 0.0099999997764825821 + }, + "id": "{3eae601e-3c6e-49ab-8f40-dedee32f7573}", + "lastEdited": 1487894036038423, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "lineHeight": 0.5, + "name": "SB.DisplayScore", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 1.5265679359436035, + "z": -9.5913219451904297 + }, + "queryAACube": { + "scale": 2.118985652923584, + "x": -5.1109838485717773, + "y": -1803.69189453125, + "z": -26.774648666381836 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"score\"}" + }, + { + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.71920669078826904, + "y": 3.3160061836242676, + "z": 2.2217941284179688 + }, + "id": "{04288f77-64df-4323-ac38-9c1960a393a5}", + "lastEdited": 1487893058314990, + "lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}", + "modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx", + "name": "SB.StartButton", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -9.8358345031738281, + "y": 0.45674961805343628, + "z": -13.044205665588379 + }, + "queryAACube": { + "scale": 4.0558013916015625, + "x": -7.844393253326416, + "y": -1805.730224609375, + "z": -31.195960998535156 + }, + "rotation": { + "w": 1, + "x": 1.52587890625e-05, + "y": 1.52587890625e-05, + "z": 1.52587890625e-05 + }, + "script": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"wantsTrigger\":true}}" + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 2, + "y": 0.69999998807907104, + "z": 0.0099999997764825821 + }, + "id": "{1196096f-bcc9-4b19-970d-605113474c1b}", + "lastEdited": 1487894037900323, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "lineHeight": 0.5, + "name": "SB.DisplayHighScore", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 0.26189804077148438, + "z": -9.5913219451904297 + }, + "queryAACube": { + "scale": 2.118985652923584, + "x": -5.11102294921875, + "y": -1804.95654296875, + "z": -26.77461051940918 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"highscore\"}" + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 1.4120937585830688, + "y": 0.71569448709487915, + "z": 0.0099999997764825821 + }, + "id": "{293c294d-1df5-461e-82a3-66abee852d44}", + "lastEdited": 1487894033695485, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "lineHeight": 0.5, + "name": "SB.DisplayWave", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 1.5265679359436035, + "z": -7.2889409065246582 + }, + "queryAACube": { + "scale": 1.5831384658813477, + "x": -4.8431310653686523, + "y": -1803.4239501953125, + "z": -24.204343795776367 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"wave\"}" + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 1.4120937585830688, + "y": 0.71569448709487915, + "z": 0.0099999997764825821 + }, + "id": "{379afa7b-c668-4c4e-b290-e5c37fb3440c}", + "lastEdited": 1487893055310428, + "lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}", + "lineHeight": 0.5, + "name": "SB.DisplayLives", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 0.26189804077148438, + "z": -7.2889409065246582 + }, + "queryAACube": { + "scale": 1.5831384658813477, + "x": -4.8431692123413086, + "y": -1804.6885986328125, + "z": -24.204303741455078 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"lives\"}" + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{760e81a1-a804-4f5e-9769-393d021fc8fe}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440234633, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -1.89238440990448, + "y": -5.3368110656738281, + "z": 11.512755393981934 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 1.9147146940231323, + "y": -1809.7066650390625, + "z": -4.8219971656799316 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{0a76d0ac-6353-467b-8edc-56417d5a987c}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440235124, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 3.6569130420684814, + "y": -5.3365960121154785, + "z": 10.01292610168457 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 7.4640579223632812, + "y": -1809.7066650390625, + "z": -6.3216567039489746 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{f8549c8a-e646-4feb-bbaf-70e7d5be755a}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440235339, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 8.8902750015258789, + "y": -5.3364419937133789, + "z": 10.195274353027344 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 12.697414398193359, + "y": -1809.7066650390625, + "z": -6.1391491889953613 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{f3aea4ae-4445-4a2d-8d61-e9fd72f04008}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708751269, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -2.5027251243591309, + "y": 0.54042834043502808, + "z": -11.257777214050293 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 1.3052481412887573, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{cc1ac907-124b-4372-8c4c-82d175546725}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708751135, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 2.7972855567932129, + "y": 0.54059004783630371, + "z": -11.257938385009766 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 6.6052589416503906, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{e25ce690-e267-4c51-80a0-f63a72474b8a}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708751527, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.17114110291004181, + "y": 0.54050993919372559, + "z": -11.257858276367188 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 3.979114294052124, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{91ee2285-38f8-4795-b6bc-7abc8dcde07c}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708750806, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 5.4656705856323242, + "y": 0.54067152738571167, + "z": -11.258020401000977 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 9.2736434936523438, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{d81e5fae-8a8d-4186-bbd2-0c3ae737b0f2}", + "ignoreForCollisions": 1, + "lastEdited": 1487892552671000, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 9.6099967956542969, + "y": 0.64012420177459717, + "z": -9.9802846908569336 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 13.417934417724609, + "y": -1803.730712890625, + "z": -26.314868927001953 + }, + "rotation": { + "w": 0.22495110332965851, + "x": -2.9734959753113799e-05, + "y": 0.97437006235122681, + "z": 2.9735869247815572e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{7056e21e-bce6-4c4b-bbca-36bea1dce303}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708750993, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 8.1799373626708984, + "y": 0.54075431823730469, + "z": -11.258102416992188 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 11.987911224365234, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{ed073620-e304-4b8e-b12a-5371b595bbf6}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440234415, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -2.3618791103363037, + "y": -2.0691573619842529, + "z": 11.254574775695801 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 1.4453276395797729, + "y": -1806.43896484375, + "z": -5.0802912712097168 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{32ed7820-c386-4da1-b676-7e63762861a3}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440234854, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.64757472276687622, + "y": -2.5217375755310059, + "z": 10.08248233795166 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 4.454803466796875, + "y": -1806.8917236328125, + "z": -6.2522788047790527 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 26.619264602661133, + "y": 14.24090576171875, + "z": 39.351066589355469 + }, + "id": "{d4c8f577-944d-4d50-ac85-e56387c0ef0a}", + "lastEdited": 1487892440231278, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx", + "name": "SB.Platform", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.097909502685070038, + "y": -1.0163799524307251, + "z": 2.0321114063262939 + }, + "queryAACube": { + "scale": 49.597328186035156, + "x": -20.681917190551758, + "y": -1829.9739990234375, + "z": -38.890060424804688 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shapeType": "static-mesh", + "type": "Model" + }, + { + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 23.341892242431641, + "y": 12.223045349121094, + "z": 32.012016296386719 + }, + "friction": 1, + "id": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "lastEdited": 1487892440231832, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx", + "name": "SB.Scoreboard", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "queryAACube": { + "scale": 41.461017608642578, + "x": -20.730508804321289, + "y": -20.730508804321289, + "z": -20.730508804321289 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "serverScripts": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js", + "shapeType": "static-mesh", + "type": "Model" + }, + { + "clientOnly": 0, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 15.710711479187012, + "y": 4.7783288955688477, + "z": 1.6129581928253174 + }, + "id": "{84cdff6e-a68d-4bbf-8660-2d6a8c2f1fd0}", + "lastEdited": 1487892440231522, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.GateCollider", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.31728419661521912, + "y": -4.3002614974975586, + "z": -12.531644821166992 + }, + "queryAACube": { + "scale": 16.50031852722168, + "x": -3.913693904876709, + "y": -1816.709716796875, + "z": -36.905204772949219 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + } + ], + "Version": 68 +}; + +// Add LocalPosition to entity data if parent properties are available +var entities = SHORTBOW_ENTITIES.Entities; +var entitiesByID = {}; +var i, entity; +for (i = 0; i < entities.length; ++i) { + entity = entities[i]; + entitiesByID[entity.id] = entity; +} +for (i = 0; i < entities.length; ++i) { + entity = entities[i]; + if (entity.parentID !== undefined) { + var parent = entitiesByID[entity.parentID]; + if (parent !== undefined) { + entity.localPosition = Vec3.subtract(entity.position, parent.position); + delete entity.position; + } + } +} diff --git a/unpublishedScripts/marketplace/shortbow/shortbow.json b/unpublishedScripts/marketplace/shortbow/shortbow.json new file mode 100644 index 0000000000..47934baea5 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/shortbow.json @@ -0,0 +1,840 @@ +{ + "Entities": [ + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{02f39515-cab4-41d5-b315-5fb41613f844}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708750446, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -5.1684012413024902, + "y": 0.54034698009490967, + "z": -11.257695198059082 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": -1.3604279756546021, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 2, + "y": 0.69999998807907104, + "z": 0.0099999997764825821 + }, + "id": "{3eae601e-3c6e-49ab-8f40-dedee32f7573}", + "lastEdited": 1487894036038423, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "lineHeight": 0.5, + "name": "SB.DisplayScore", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 1.5265679359436035, + "z": -9.5913219451904297 + }, + "queryAACube": { + "scale": 2.118985652923584, + "x": -5.1109838485717773, + "y": -1803.69189453125, + "z": -26.774648666381836 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"score\"}" + }, + { + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.71920669078826904, + "y": 3.3160061836242676, + "z": 2.2217941284179688 + }, + "id": "{04288f77-64df-4323-ac38-9c1960a393a5}", + "lastEdited": 1487893058314990, + "lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}", + "modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx", + "name": "SB.StartButton", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -9.8358345031738281, + "y": 0.45674961805343628, + "z": -13.044205665588379 + }, + "queryAACube": { + "scale": 4.0558013916015625, + "x": -7.844393253326416, + "y": -1805.730224609375, + "z": -31.195960998535156 + }, + "rotation": { + "w": 1, + "x": 1.52587890625e-05, + "y": 1.52587890625e-05, + "z": 1.52587890625e-05 + }, + "script": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"wantsTrigger\":true}}" + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 2, + "y": 0.69999998807907104, + "z": 0.0099999997764825821 + }, + "id": "{1196096f-bcc9-4b19-970d-605113474c1b}", + "lastEdited": 1487894037900323, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "lineHeight": 0.5, + "name": "SB.DisplayHighScore", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 0.26189804077148438, + "z": -9.5913219451904297 + }, + "queryAACube": { + "scale": 2.118985652923584, + "x": -5.11102294921875, + "y": -1804.95654296875, + "z": -26.77461051940918 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"highscore\"}" + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 1.4120937585830688, + "y": 0.71569448709487915, + "z": 0.0099999997764825821 + }, + "id": "{293c294d-1df5-461e-82a3-66abee852d44}", + "lastEdited": 1487894033695485, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "lineHeight": 0.5, + "name": "SB.DisplayWave", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 1.5265679359436035, + "z": -7.2889409065246582 + }, + "queryAACube": { + "scale": 1.5831384658813477, + "x": -4.8431310653686523, + "y": -1803.4239501953125, + "z": -24.204343795776367 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"wave\"}" + }, + { + "backgroundColor": { + "blue": 65, + "green": 78, + "red": 82 + }, + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 1.4120937585830688, + "y": 0.71569448709487915, + "z": 0.0099999997764825821 + }, + "id": "{379afa7b-c668-4c4e-b290-e5c37fb3440c}", + "lastEdited": 1487893055310428, + "lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}", + "lineHeight": 0.5, + "name": "SB.DisplayLives", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -8.0707607269287109, + "y": 0.26189804077148438, + "z": -7.2889409065246582 + }, + "queryAACube": { + "scale": 1.5831384658813477, + "x": -4.8431692123413086, + "y": -1804.6885986328125, + "z": -24.204303741455078 + }, + "rotation": { + "w": 0.70708787441253662, + "x": -1.52587890625e-05, + "y": 0.70708787441253662, + "z": -1.52587890625e-05 + }, + "text": "0", + "type": "Text", + "userData": "{\"displayType\":\"lives\"}" + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{760e81a1-a804-4f5e-9769-393d021fc8fe}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440234633, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -1.89238440990448, + "y": -5.3368110656738281, + "z": 11.512755393981934 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 1.9147146940231323, + "y": -1809.7066650390625, + "z": -4.8219971656799316 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{0a76d0ac-6353-467b-8edc-56417d5a987c}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440235124, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 3.6569130420684814, + "y": -5.3365960121154785, + "z": 10.01292610168457 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 7.4640579223632812, + "y": -1809.7066650390625, + "z": -6.3216567039489746 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{f8549c8a-e646-4feb-bbaf-70e7d5be755a}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440235339, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 8.8902750015258789, + "y": -5.3364419937133789, + "z": 10.195274353027344 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 12.697414398193359, + "y": -1809.7066650390625, + "z": -6.1391491889953613 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{f3aea4ae-4445-4a2d-8d61-e9fd72f04008}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708751269, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -2.5027251243591309, + "y": 0.54042834043502808, + "z": -11.257777214050293 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 1.3052481412887573, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{cc1ac907-124b-4372-8c4c-82d175546725}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708751135, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 2.7972855567932129, + "y": 0.54059004783630371, + "z": -11.257938385009766 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 6.6052589416503906, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{e25ce690-e267-4c51-80a0-f63a72474b8a}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708751527, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.17114110291004181, + "y": 0.54050993919372559, + "z": -11.257858276367188 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 3.979114294052124, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{91ee2285-38f8-4795-b6bc-7abc8dcde07c}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708750806, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 5.4656705856323242, + "y": 0.54067152738571167, + "z": -11.258020401000977 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 9.2736434936523438, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{d81e5fae-8a8d-4186-bbd2-0c3ae737b0f2}", + "ignoreForCollisions": 1, + "lastEdited": 1487892552671000, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 9.6099967956542969, + "y": 0.64012420177459717, + "z": -9.9802846908569336 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 13.417934417724609, + "y": -1803.730712890625, + "z": -26.314868927001953 + }, + "rotation": { + "w": 0.22495110332965851, + "x": -2.9734959753113799e-05, + "y": 0.97437006235122681, + "z": 2.9735869247815572e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{7056e21e-bce6-4c4b-bbca-36bea1dce303}", + "ignoreForCollisions": 1, + "lastEdited": 1487892708750993, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.BowSpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 8.1799373626708984, + "y": 0.54075431823730469, + "z": -11.258102416992188 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 11.987911224365234, + "y": -1803.830078125, + "z": -27.592727661132812 + }, + "rotation": { + "w": 0.17366324365139008, + "x": 4.9033405957743526e-07, + "y": -0.98480510711669922, + "z": -2.9563087082351558e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{ed073620-e304-4b8e-b12a-5371b595bbf6}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440234415, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": -2.3618791103363037, + "y": -2.0691573619842529, + "z": 11.254574775695801 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 1.4453276395797729, + "y": -1806.43896484375, + "z": -5.0802912712097168 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "collidesWith": "", + "collisionMask": 0, + "collisionless": 1, + "color": { + "blue": 171, + "green": 50, + "red": 62 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 0.24400754272937775, + "y": 0.24400754272937775, + "z": 0.24400754272937775 + }, + "id": "{32ed7820-c386-4da1-b676-7e63762861a3}", + "ignoreForCollisions": 1, + "lastEdited": 1487892440234854, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.EnemySpawn", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.64757472276687622, + "y": -2.5217375755310059, + "z": 10.08248233795166 + }, + "queryAACube": { + "scale": 0.42263346910476685, + "x": 4.454803466796875, + "y": -1806.8917236328125, + "z": -6.2522788047790527 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + }, + { + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 26.619264602661133, + "y": 14.24090576171875, + "z": 39.351066589355469 + }, + "id": "{d4c8f577-944d-4d50-ac85-e56387c0ef0a}", + "lastEdited": 1487892440231278, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx", + "name": "SB.Platform", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.097909502685070038, + "y": -1.0163799524307251, + "z": 2.0321114063262939 + }, + "queryAACube": { + "scale": 49.597328186035156, + "x": -20.681917190551758, + "y": -1829.9739990234375, + "z": -38.890060424804688 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shapeType": "static-mesh", + "type": "Model" + }, + { + "clientOnly": 0, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 23.341892242431641, + "y": 12.223045349121094, + "z": 32.012016296386719 + }, + "friction": 1, + "id": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "lastEdited": 1487892440231832, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx", + "name": "SB.Scoreboard", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "queryAACube": { + "scale": 41.461017608642578, + "x": -20.730508804321289, + "y": -20.730508804321289, + "z": -20.730508804321289 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "serverScripts": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js", + "shapeType": "static-mesh", + "type": "Model" + }, + { + "clientOnly": 0, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2017-02-23T23:28:32Z", + "dimensions": { + "x": 15.710711479187012, + "y": 4.7783288955688477, + "z": 1.6129581928253174 + }, + "id": "{84cdff6e-a68d-4bbf-8660-2d6a8c2f1fd0}", + "lastEdited": 1487892440231522, + "lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}", + "name": "SB.GateCollider", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}", + "position": { + "x": 0.31728419661521912, + "y": -4.3002614974975586, + "z": -12.531644821166992 + }, + "queryAACube": { + "scale": 16.50031852722168, + "x": -3.913693904876709, + "y": -1816.709716796875, + "z": -36.905204772949219 + }, + "rotation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "visible": 0 + } + ], + "Version": 68 +} diff --git a/unpublishedScripts/marketplace/shortbow/shortbowGameManager.js b/unpublishedScripts/marketplace/shortbow/shortbowGameManager.js new file mode 100644 index 0000000000..bd42e40427 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/shortbowGameManager.js @@ -0,0 +1,621 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals ShortbowGameManager:true, utils */ + +Script.include('utils.js'); + +// +--------+ +-----------+ +-----------------+ +// | | | |<-----+ | +// | IDLE +----->| PLAYING | | BETWEEN_WAVES | +// | | | +----->| | +// +--------+ +-----+-----+ +-----------------+ +// ^ | +// | v +// | +-------------+ +// | | | +// +---------+ GAME_OVER | +// | | +// +-------------+ +var GAME_STATES = { + IDLE: 0, + PLAYING: 1, + BETWEEN_WAVES: 2, + GAME_OVER: 3 +}; + +// Load the sounds that we will be using in the game so they are ready to be +// used when we need them. +var BEGIN_BUILDING_SOUND = SoundCache.getSound(Script.resolvePath("sounds/gameOn.wav")); +var GAME_OVER_SOUND = SoundCache.getSound(Script.resolvePath("sounds/gameOver.wav")); +var WAVE_COMPLETE_SOUND = SoundCache.getSound(Script.resolvePath("sounds/waveComplete.wav")); +var EXPLOSION_SOUND = SoundCache.getSound(Script.resolvePath("sounds/explosion.wav")); +var TARGET_HIT_SOUND = SoundCache.getSound(Script.resolvePath("sounds/targetHit.wav")); +var ESCAPE_SOUND = SoundCache.getSound(Script.resolvePath("sounds/escape.wav")); + +const STARTING_NUMBER_OF_LIVES = 6; +const ENEMIES_PER_WAVE_MULTIPLIER = 2; +const POINTS_PER_KILL = 100; +const ENEMY_SPEED = 3.0; + +// Encode a set of key-value pairs into a param string. Does NOT do any URL escaping. +function encodeURLParams(params) { + var paramPairs = []; + for (var key in params) { + paramPairs.push(key + "=" + params[key]); + } + return paramPairs.join("&"); +} + +function sendAndUpdateHighScore(entityID, score, wave, numPlayers, onResposeReceived) { + const URL = 'https://script.google.com/macros/s/AKfycbwbjCm9mGd1d5BzfAHmVT_XKmWyUYRkjCEqDOKm1368oM8nqWni/exec'; + print("Sending high score"); + + const paramString = encodeURLParams({ + entityID: entityID, + score: score, + wave: wave, + numPlayers: numPlayers + }); + + var request = new XMLHttpRequest(); + request.onreadystatechange = function() { + print("ready state: ", request.readyState, request.status, request.readyState === request.DONE, request.response); + if (request.readyState === request.DONE && request.status === 200) { + print("Got response for high score: ", request.response); + var response = JSON.parse(request.responseText); + if (response.highScore !== undefined) { + onResposeReceived(response.highScore); + } + } + }; + request.open('GET', URL + "?" + paramString); + request.timeout = 10000; + request.send(); +} + +function findChildrenWithName(parentID, name) { + var childrenIDs = Entities.getChildrenIDs(parentID); + var matchingIDs = []; + for (var i = 0; i < childrenIDs.length; ++i) { + var id = childrenIDs[i]; + var childName = Entities.getEntityProperties(id, 'name').name; + if (childName === name) { + matchingIDs.push(id); + } + } + return matchingIDs; +} + +function getPropertiesForEntities(entityIDs, desiredProperties) { + var properties = []; + for (var i = 0; i < entityIDs.length; ++i) { + properties.push(Entities.getEntityProperties(entityIDs[i], desiredProperties)); + } + return properties; +} + + +var baseEnemyProperties = { + "name": "SB.Enemy", + "damping": 0, + "linearDamping": 0, + "angularDamping": 0, + "acceleration": { + "x": 0, + "y": -9, + "z": 0 + }, + "angularVelocity": { + "x": -0.058330666273832321, + "y": -0.77943277359008789, + "z": -2.1163818836212158 + }, + "clientOnly": 0, + "collisionsWillMove": 1, + "dimensions": { + "x": 0.63503998517990112, + "y": 0.63503998517990112, + "z": 0.63503998517990112 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -15, + "z": 0 + }, + "lifetime": 30, + "id": "{ed8f7339-8bbd-4750-968e-c3ceb9d64721}", + "modelURL": Script.resolvePath("models/Amber.fbx"), + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "queryAACube": { + "scale": 1.0999215841293335, + "x": -0.54996079206466675, + "y": -0.54996079206466675, + "z": -0.54996079206466675 + }, + "shapeType": "sphere", + "type": "Model", + "script": Script.resolvePath('enemyClientEntity.js'), + "serverScripts": Script.resolvePath('enemyServerEntity.js') +}; + +function searchForChildren(parentID, names, callback, timeoutMs) { + // Map from name to entity ID for the children that have been found + var foundEntities = {}; + for (var i = 0; i < names.length; ++i) { + foundEntities[names[i]] = null; + } + + const CHECK_EVERY_MS = 500; + const maxChecks = Math.ceil(timeoutMs / CHECK_EVERY_MS); + + var check = 0; + var intervalID = Script.setInterval(function() { + check++; + + var childrenIDs = Entities.getChildrenIDs(parentID); + print("\tNumber of children:", childrenIDs.length); + + for (var i = 0; i < childrenIDs.length; ++i) { + print("\t\t" + i + ".", Entities.getEntityProperties(childrenIDs[i]).name); + var id = childrenIDs[i]; + var name = Entities.getEntityProperties(id, 'name').name; + var idx = names.indexOf(name); + if (idx > -1) { + foundEntities[name] = id; + print(name, id); + names.splice(idx, 1); + } + } + + if (names.length === 0 || check >= maxChecks) { + Script.clearInterval(intervalID); + callback(foundEntities); + } + }, CHECK_EVERY_MS); +} + +ShortbowGameManager = function(rootEntityID, bowPositions, spawnPositions) { + print("Starting game manager"); + var self = this; + + this.gameState = GAME_STATES.IDLE; + + this.rootEntityID = rootEntityID; + this.bowPositions = bowPositions; + this.rootPosition = null; + this.spawnPositions = spawnPositions; + + this.loadedChildren = false; + + const START_BUTTON_NAME = 'SB.StartButton'; + const WAVE_DISPLAY_NAME = 'SB.DisplayWave'; + const SCORE_DISPLAY_NAME = 'SB.DisplayScore'; + const LIVES_DISPLAY_NAME = 'SB.DisplayLives'; + const HIGH_SCORE_DISPLAY_NAME = 'SB.DisplayHighScore'; + + const SEARCH_FOR_CHILDREN_TIMEOUT = 5000; + + searchForChildren(rootEntityID, [ + START_BUTTON_NAME, + WAVE_DISPLAY_NAME, + SCORE_DISPLAY_NAME, + LIVES_DISPLAY_NAME, + HIGH_SCORE_DISPLAY_NAME + ], function(children) { + self.loadedChildren = true; + self.startButtonID = children[START_BUTTON_NAME]; + self.waveDisplayID = children[WAVE_DISPLAY_NAME]; + self.scoreDisplayID = children[SCORE_DISPLAY_NAME]; + self.livesDisplayID = children[LIVES_DISPLAY_NAME]; + self.highScoreDisplayID = children[HIGH_SCORE_DISPLAY_NAME]; + + sendAndUpdateHighScore(self.rootEntityID, self.score, self.waveNumber, 1, self.setHighScore.bind(self)); + + self.reset(); + }, SEARCH_FOR_CHILDREN_TIMEOUT); + + // Gameplay state + this.waveNumber = 0; + this.livesLeft = STARTING_NUMBER_OF_LIVES; + this.score = 0; + this.nextWaveTimer = null; + this.spawnEnemyTimers = []; + this.remainingEnemies = []; + this.bowIDs = []; + + this.startButtonChannelName = 'button-' + this.rootEntityID; + + // Entity client and server scripts will send messages to this channel + this.commChannelName = "shortbow-" + this.rootEntityID; + Messages.subscribe(this.commChannelName); + Messages.messageReceived.connect(this, this.onReceivedMessage); + print("Listening on: ", this.commChannelName); + Messages.sendMessage(this.commChannelName, 'hi'); +}; +ShortbowGameManager.prototype = { + reset: function() { + Entities.editEntity(this.startButtonID, { visible: true }); + }, + cleanup: function() { + Messages.unsubscribe(this.commChannelName); + Messages.messageReceived.disconnect(this, this.onReceivedMessage); + + if (this.checkEnemiesTimer) { + Script.clearInterval(this.checkEnemiesTimer); + this.checkEnemiesTimer = null; + } + + for (var i = this.bowIDs.length - 1; i >= 0; i--) { + Entities.deleteEntity(this.bowIDs[i]); + } + this.bowIDs = []; + for (i = 0; i < this.remainingEnemies.length; i++) { + Entities.deleteEntity(this.remainingEnemies[i].id); + } + this.remainingEnemies = []; + + this.gameState = GAME_STATES.IDLE; + }, + startGame: function() { + if (this.gameState !== GAME_STATES.IDLE) { + print("shortbowGameManagerManager.js | Error, trying to start game when not in idle state"); + return; + } + + if (this.loadedChildren === false) { + print('shortbowGameManager.js | Children have not loaded, not allowing game to start'); + return; + } + + print("Game started!!"); + + this.rootPosition = Entities.getEntityProperties(this.rootEntityID, 'position').position; + + Entities.editEntity(this.startButtonID, { visible: false }); + + // Spawn bows + var bowSpawnEntityIDs = findChildrenWithName(this.rootEntityID, 'SB.BowSpawn'); + var bowSpawnProperties = getPropertiesForEntities(bowSpawnEntityIDs, ['position', 'rotation']); + for (var i = 0; i < bowSpawnProperties.length; ++i) { + const props = bowSpawnProperties[i]; + Vec3.print("Creating bow: " + i, props.position); + this.bowIDs.push(Entities.addEntity({ + "position": props.position, + "rotation": props.rotation, + "collisionsWillMove": 1, + "compoundShapeURL": Script.resolvePath("bow/bow_collision_hull.obj"), + "created": "2016-09-01T23:57:55Z", + "dimensions": { + "x": 0.039999999105930328, + "y": 1.2999999523162842, + "z": 0.20000000298023224 + }, + "dynamic": 1, + "gravity": { + "x": 0, + "y": -9.8, + "z": 0 + }, + "modelURL": Script.resolvePath("bow/bow-deadly.fbx"), + "name": "WG.Hifi-Bow", + "script": Script.resolvePath("bow/bow.js"), + "shapeType": "compound", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}" + })); + } + + // Initialize game state + this.waveNumber = 0; + this.setScore(0); + this.setLivesLeft(STARTING_NUMBER_OF_LIVES); + + this.nextWaveTimer = Script.setTimeout(this.startNextWave.bind(this), 100); + this.spawnEnemyTimers = []; + this.checkEnemiesTimer = null; + this.remainingEnemies = []; + + // SpawnQueue is a list of enemies left to spawn. Each entry looks like: + // + // { spawnAt: 1000, position: { x: 0, y: 0, z: 0 } } + // + // where spawnAt is the number of millseconds after the start of the wave + // to spawn the enemy. The list is sorted by spawnAt, ascending. + this.spawnQueue = []; + + this.gameState = GAME_STATES.BETWEEN_WAVES; + + Audio.playSound(BEGIN_BUILDING_SOUND, { + volume: 1.0, + position: this.rootPosition + }); + }, + startNextWave: function() { + if (this.gameState !== GAME_STATES.BETWEEN_WAVES) { + return; + } + + print("Starting next wave"); + this.gameState = GAME_STATES.PLAYING; + this.waveNumber++; + this.remainingEnemies= []; + this.spawnQueue = []; + this.spawnStartTime = Date.now(); + + Entities.editEntity(this.waveDisplayID, { text: this.waveNumber}); + + var numberOfEnemiesLeftToSpawn = this.waveNumber * ENEMIES_PER_WAVE_MULTIPLIER; + var delayBetweenSpawns = 2000 / Math.max(1, Math.log(this.waveNumber)); + var currentDelay = 2000; + + print("Number of enemies:", numberOfEnemiesLeftToSpawn); + this.checkEnemiesTimer = Script.setInterval(this.checkEnemies.bind(this), 100); + + var enemySpawnEntityIDs = findChildrenWithName(this.rootEntityID, 'SB.EnemySpawn'); + var enemySpawnProperties = getPropertiesForEntities(enemySpawnEntityIDs, ['position', 'rotation']); + + for (var i = 0; i < numberOfEnemiesLeftToSpawn; ++i) { + print("Adding enemy"); + var idx = Math.floor(Math.random() * enemySpawnProperties.length); + var props = enemySpawnProperties[idx]; + this.spawnQueue.push({ + spawnAt: currentDelay, + position: props.position, + rotation: props.rotation, + velocity: Vec3.multiply(ENEMY_SPEED, Quat.getFront(props.rotation)) + + }); + currentDelay += delayBetweenSpawns; + } + + print("Starting wave", this.waveNumber); + + }, + checkWaveComplete: function() { + if (this.gameState !== GAME_STATES.PLAYING) { + return; + } + + if (this.spawnQueue.length === 0 && this.remainingEnemies.length === 0) { + this.gameState = GAME_STATES.BETWEEN_WAVES; + Script.setTimeout(this.startNextWave.bind(this), 5000); + + Script.clearInterval(this.checkEnemiesTimer); + this.checkEnemiesTimer = null; + + // Play after 1.5s to let other sounds finish playing + var self = this; + Script.setTimeout(function() { + Audio.playSound(WAVE_COMPLETE_SOUND, { + volume: 1.0, + position: self.rootPosition + }); + }, 1500); + } + }, + setHighScore: function(highScore) { + print("Setting high score to:", this.highScoreDisplayID, highScore); + Entities.editEntity(this.highScoreDisplayID, { text: highScore }); + }, + setLivesLeft: function(lives) { + lives = Math.max(0, lives); + this.livesLeft = lives; + Entities.editEntity(this.livesDisplayID, { text: this.livesLeft }); + }, + setScore: function(score) { + this.score = score; + Entities.editEntity(this.scoreDisplayID, { text: this.score }); + }, + checkEnemies: function() { + if (this.gameState !== GAME_STATES.PLAYING) { + return; + } + + // Check the spawn queueu to see if there are any enemies that need to + // be spawned + var waveElapsedTime = Date.now() - this.spawnStartTime; + while (this.spawnQueue.length > 0 && waveElapsedTime > this.spawnQueue[0].spawnAt) { + baseEnemyProperties.position = this.spawnQueue[0].position; + baseEnemyProperties.rotation = this.spawnQueue[0].rotation; + baseEnemyProperties.velocity= this.spawnQueue[0].velocity; + + baseEnemyProperties.userData = JSON.stringify({ + gameChannel: this.commChannelName, + grabbableKey: { + grabbable: false + } + }); + + var entityID = Entities.addEntity(baseEnemyProperties); + this.remainingEnemies.push({ + id: entityID, + lastKnownPosition: baseEnemyProperties.position, + lastHeartbeat: Date.now() + }); + this.spawnQueue.splice(0, 1); + Script.setTimeout(function() { + const JUMP_SPEED = 5.0; + var velocity = Entities.getEntityProperties(entityID, 'velocity').velocity; + velocity.y += JUMP_SPEED; + Entities.editEntity(entityID, { velocity: velocity }); + + }, 500 + Math.random() * 4000); + } + + // Check the list of remaining enemies to see if any are too far away + // or haven't been heard from in awhile - if so, delete them. + var enemiesEscaped = false; + const MAX_UNHEARD_TIME_BEFORE_DESTROYING_ENTITY_MS = 5000; + const MAX_DISTANCE_FROM_GAME_BEFORE_DESTROYING_ENTITY = 200; + for (var i = this.remainingEnemies.length - 1; i >= 0; --i) { + var enemy = this.remainingEnemies[i]; + var timeSinceLastHeartbeat = Date.now() - enemy.lastHeartbeat; + var distance = Vec3.distance(enemy.lastKnownPosition, this.rootPosition); + if (timeSinceLastHeartbeat > MAX_UNHEARD_TIME_BEFORE_DESTROYING_ENTITY_MS + || distance > MAX_DISTANCE_FROM_GAME_BEFORE_DESTROYING_ENTITY) { + + print("EXPIRING: ", enemy.id); + Entities.deleteEntity(enemy.id); + this.remainingEnemies.splice(i, 1); + Audio.playSound(TARGET_HIT_SOUND, { + volume: 1.0, + position: this.rootPosition + }); + this.setScore(this.score + POINTS_PER_KILL); + enemiesEscaped = true; + } + } + + if (enemiesEscaped) { + this.checkWaveComplete(); + } + }, + endGame: function() { + if (this.gameState !== GAME_STATES.PLAYING) { + return; + } + + var self = this; + Script.setTimeout(function() { + Audio.playSound(GAME_OVER_SOUND, { + volume: 1.0, + position: self.rootPosition + }); + }, 1500); + + this.gameState = GAME_STATES.GAME_OVER; + print("GAME OVER"); + + // Update high score + sendAndUpdateHighScore(this.rootEntityID, this.score, this.waveNumber, 1, this.setHighScore.bind(this)); + + // Cleanup + Script.clearTimeout(this.nextWaveTimer); + this.nextWaveTimer = null; + var i; + for (i = 0; i < this.spawnEnemyTimers.length; ++i) { + Script.clearTimeout(this.spawnEnemyTimers[i]); + } + this.spawnEnemyTimers = []; + + Script.clearInterval(this.checkEnemiesTimer); + this.checkEnemiesTimer = null; + + + for (i = this.bowIDs.length - 1; i >= 0; i--) { + var id = this.bowIDs[i]; + print("Checking bow: ", id); + var userData = utils.parseJSON(Entities.getEntityProperties(id, 'userData').userData); + var bowIsHeld = userData.grabKey !== undefined && userData.grabKey !== undefined && userData.grabKey.refCount > 0; + print("Held: ", bowIsHeld); + if (!bowIsHeld) { + Entities.deleteEntity(id); + this.bowIDs.splice(i, 1); + } + } + + for (i = 0; i < this.remainingEnemies.length; i++) { + Entities.deleteEntity(this.remainingEnemies[i].id); + } + this.remainingEnemies = []; + + // Wait a short time before showing the start button so that any current sounds + // can finish playing. + const WAIT_TO_REENABLE_GAME_TIMEOUT_MS = 3000; + Script.setTimeout(function() { + Entities.editEntity(this.startButtonID, { visible: true }); + this.gameState = GAME_STATES.IDLE; + }.bind(this), WAIT_TO_REENABLE_GAME_TIMEOUT_MS); + }, + onReceivedMessage: function(channel, messageJSON, senderID) { + if (channel === this.commChannelName) { + var message = utils.parseJSON(messageJSON); + if (message === undefined) { + print("shortbowGameManager.js | Received non-json message:", JSON.stringify(messageJSON)); + return; + } + switch (message.type) { + case 'start-game': + this.startGame(); + break; + case 'enemy-killed': + this.onEnemyKilled(message.entityID, message.position); + break; + case 'enemy-escaped': + this.onEnemyEscaped(message.entityID); + break; + case 'enemy-heartbeat': + this.onEnemyHeartbeat(message.entityID, message.position); + break; + default: + print("shortbowGameManager.js | Ignoring unknown message type: ", message.type); + break; + } + } + }, + onEnemyKilled: function(entityID, position) { + if (this.gameState !== GAME_STATES.PLAYING) { + return; + } + + for (var i = this.remainingEnemies.length - 1; i >= 0; --i) { + var enemy = this.remainingEnemies[i]; + if (enemy.id === entityID) { + this.remainingEnemies.splice(i, 1); + Audio.playSound(TARGET_HIT_SOUND, { + volume: 1.0, + position: this.rootPosition + }); + + // Update score + this.setScore(this.score + POINTS_PER_KILL); + print("SCORE: ", this.score); + + this.checkWaveComplete(); + break; + } + } + }, + onEnemyEscaped: function(entityID, position) { + if (this.gameState !== GAME_STATES.PLAYING) { + return; + } + + var enemiesEscaped = false; + for (var i = this.remainingEnemies.length - 1; i >= 0; --i) { + var enemy = this.remainingEnemies[i]; + if (enemy.id === entityID) { + Entities.deleteEntity(enemy.id); + this.remainingEnemies.splice(i, 1); + this.setLivesLeft(this.livesLeft - 1); + Audio.playSound(ESCAPE_SOUND, { + volume: 1.0, + position: this.rootPosition + }); + enemiesEscaped = true; + } + } + if (this.livesLeft <= 0) { + this.endGame(); + } else if (enemiesEscaped) { + this.checkWaveComplete(); + } + }, + onEnemyHeartbeat: function(entityID, position) { + for (var i = 0; i < this.remainingEnemies.length; i++) { + var enemy = this.remainingEnemies[i]; + if (enemy.id === entityID) { + enemy.lastHeartbeat = Date.now(); + enemy.lastKnownPosition = position; + break; + } + } + } +}; diff --git a/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js b/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js new file mode 100644 index 0000000000..385220717f --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js @@ -0,0 +1,42 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals TEMPLATES:true, SHORTBOW_ENTITIES, ShortbowGameManager */ + +(function() { + Script.include('utils.js'); + Script.include('shortbow.js'); + Script.include('shortbowGameManager.js'); + + TEMPLATES = SHORTBOW_ENTITIES.Entities; + + this.entityID = null; + var gameManager = null; + this.preload = function(entityID) { + this.entityID = entityID; + + var bowPositions = []; + var spawnPositions = []; + for (var i = 0; i < TEMPLATES.length; ++i) { + var template = TEMPLATES[i]; + if (template.name === "SB.BowSpawn") { + bowPositions.push(template.localPosition); + } else if (template.name === "SB.EnemySpawn") { + spawnPositions.push(template.localPosition); + } + } + + gameManager = new ShortbowGameManager(this.entityID, bowPositions, spawnPositions); + }; + this.unload = function() { + if (gameManager) { + gameManager.cleanup(); + gameManager = null; + } + }; +}); diff --git a/unpublishedScripts/marketplace/shortbow/sounds/escape.wav b/unpublishedScripts/marketplace/shortbow/sounds/escape.wav new file mode 100644 index 0000000000..b576703916 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/escape.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/explosion.wav b/unpublishedScripts/marketplace/shortbow/sounds/explosion.wav new file mode 100644 index 0000000000..13ee9993e6 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/explosion.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/fight.wav b/unpublishedScripts/marketplace/shortbow/sounds/fight.wav new file mode 100644 index 0000000000..439684fe70 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/fight.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/gameOn.wav b/unpublishedScripts/marketplace/shortbow/sounds/gameOn.wav new file mode 100644 index 0000000000..8da091b1e3 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/gameOn.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/gameOver.wav b/unpublishedScripts/marketplace/shortbow/sounds/gameOver.wav new file mode 100644 index 0000000000..04eb9fd7ee Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/gameOver.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/letTheGamesBegin.wav b/unpublishedScripts/marketplace/shortbow/sounds/letTheGamesBegin.wav new file mode 100644 index 0000000000..c8884a04ea Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/letTheGamesBegin.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/spawn.wav b/unpublishedScripts/marketplace/shortbow/sounds/spawn.wav new file mode 100644 index 0000000000..ad6579993f Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/spawn.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/targetHit.wav b/unpublishedScripts/marketplace/shortbow/sounds/targetHit.wav new file mode 100644 index 0000000000..5357fc94a6 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/targetHit.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/tenSecondsRemaining.wav b/unpublishedScripts/marketplace/shortbow/sounds/tenSecondsRemaining.wav new file mode 100644 index 0000000000..b5375b3024 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/tenSecondsRemaining.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/sounds/waveComplete.wav b/unpublishedScripts/marketplace/shortbow/sounds/waveComplete.wav new file mode 100644 index 0000000000..5fb9f27b45 Binary files /dev/null and b/unpublishedScripts/marketplace/shortbow/sounds/waveComplete.wav differ diff --git a/unpublishedScripts/marketplace/shortbow/spawnShortbow.js b/unpublishedScripts/marketplace/shortbow/spawnShortbow.js new file mode 100644 index 0000000000..6b0be7f7f5 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/spawnShortbow.js @@ -0,0 +1,210 @@ +// +// Created by Ryan Huffman on 1/10/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 +// +/* globals utils, SHORTBOW_ENTITIES, TEMPLATES:true */ + +Script.include('utils.js'); +Script.include('shortbow.js'); +Script.include('shortbowGameManager.js'); +TEMPLATES = SHORTBOW_ENTITIES.Entities; + +// Merge two objects into a new object. If a key name appears in both a and b, +// the value in a will be used. +// +// @param {object} a +// @param {object} b +// @returns {object} The new object +function mergeObjects(a, b) { + var obj = {}; + var key; + for (key in b) { + obj[key] = b[key]; + } + for (key in a) { + obj[key] = a[key]; + } + return obj; +} + +// Spawn an entity from a template. +// +// The overrides can be used to override or add properties in the template. For instance, +// it's common to override the `position` property so that you can set the position +// of the entity to be spawned. +// +// @param {string} templateName The name of the template to spawn +// @param {object} overrides An object containing properties that will override +// any properties set in the template. +function spawnTemplate(templateName, overrides) { + var template = getTemplate(templateName); + if (template === null) { + print("ERROR, unknown template name:", templateName); + return null; + } + print("Spawning: ", templateName); + var properties = mergeObjects(overrides, template); + return Entities.addEntity(properties); +} + +function spawnTemplates(templateName, overrides) { + var templates = getTemplates(templateName); + if (template.length === 0) { + print("ERROR, unknown template name:", templateName); + return []; + } + + var spawnedEntities = []; + for (var i = 0; i < templates.length; ++i) { + print("Spawning: ", templateName); + var properties = mergeObjects(overrides, templates[i]); + spawnedEntities.push(Entities.addEntity(properties)); + } + return spawnedEntities; +} + +// TEMPLATES contains a dictionary of different named entity templates. An entity +// template is just a list of properties. +// +// @param name Name of the template to get +// @return {object} The matching template, or null if not found +function getTemplate(name) { + for (var i = 0; i < TEMPLATES.length; ++i) { + if (TEMPLATES[i].name === name) { + return TEMPLATES[i]; + } + } + return null; +} +function getTemplates(name) { + var templates = []; + for (var i = 0; i < TEMPLATES.length; ++i) { + if (TEMPLATES[i].name === name) { + templates.push(TEMPLATES[i]); + } + } + return templates; +} + + +// Cleanup Shortbow template data +for (var i = 0; i < TEMPLATES.length; ++i) { + var template = TEMPLATES[i]; + + // Fixup model url + if (template.type === "Model") { + var urlParts = template.modelURL.split("/"); + var filename = urlParts[urlParts.length - 1]; + var newURL = Script.resolvePath("models/" + filename); + print("Updated url", template.modelURL, "to", newURL); + template.modelURL = newURL; + } +} + +var entityIDs = []; + +var scoreboardID = null; +var buttonID = null; +var waveDisplayID = null; +var scoreDisplayID = null; +var highScoreDisplayID = null; +var livesDisplayID = null; +var platformID = null; +function createLocalGame() { + var rootPosition = utils.findSurfaceBelowPosition(MyAvatar.position); + rootPosition.y += 6.11; + + scoreboardID = spawnTemplate("SB.Scoreboard", { + position: rootPosition + }); + entityIDs.push(scoreboardID); + + // Create start button + buttonID = spawnTemplate("SB.StartButton", { + parentID: scoreboardID, + script: Script.resolvePath("startGameButtonClientEntity.js"), + userData: JSON.stringify({ + grabbableKey: { + wantsTrigger: true + } + }) + }); + entityIDs.push(buttonID); + + + waveDisplayID = spawnTemplate("SB.DisplayWave", { + parentID: scoreboardID, + userData: JSON.stringify({ + displayType: "wave" + }) + }); + entityIDs.push(waveDisplayID); + + scoreDisplayID = spawnTemplate("SB.DisplayScore", { + parentID: scoreboardID, + userData: JSON.stringify({ + displayType: "score" + }) + }); + entityIDs.push(scoreDisplayID); + + livesDisplayID = spawnTemplate("SB.DisplayLives", { + parentID: scoreboardID, + userData: JSON.stringify({ + displayType: "lives" + }) + }); + entityIDs.push(livesDisplayID); + + highScoreDisplayID = spawnTemplate("SB.DisplayHighScore", { + parentID: scoreboardID, + userData: JSON.stringify({ + displayType: "highscore" + }) + }); + entityIDs.push(highScoreDisplayID); + + platformID = spawnTemplate("SB.Platform", { + parentID: scoreboardID + }); + entityIDs.push(platformID); + + spawnTemplate("SB.GateCollider", { + parentID: scoreboardID, + visible: false + }); + entityIDs.push(platformID); + + Entities.editEntity(scoreboardID, { + serverScripts: Script.resolvePath('shortbowServerEntity.js') + }); + + spawnTemplates("SB.BowSpawn", { + parentID: scoreboardID, + visible: false + }); + spawnTemplates("SB.EnemySpawn", { + parentID: scoreboardID, + visible: false + }); + + var bowPositions = []; + var spawnPositions = []; + for (var i = 0; i < TEMPLATES.length; ++i) { + var template = TEMPLATES[i]; + + if (template.name === "SB.BowSpawn") { + bowPositions.push(Vec3.sum(rootPosition, template.localPosition)); + Vec3.print("Pushing bow position", Vec3.sum(rootPosition, template.localPosition)); + } else if (template.name === "SB.EnemySpawn") { + spawnPositions.push(Vec3.sum(rootPosition, template.localPosition)); + Vec3.print("Pushing spawnposition", Vec3.sum(rootPosition, template.localPosition)); + } + } +} + +createLocalGame(); +Script.stop(); diff --git a/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js b/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js new file mode 100644 index 0000000000..c15b93c047 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js @@ -0,0 +1,41 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals utils */ + +(function() { + Script.include('utils.js'); + + function StartButton() { + } + StartButton.prototype = { + preload: function(entityID) { + this.entityID = entityID; + this.commChannel = "shortbow-" + Entities.getEntityProperties(entityID, 'parentID').parentID; + Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this)); + }, + signalAC: function() { + Messages.sendMessage(this.commChannel, JSON.stringify({ + type: 'start-game' + })); + }, + onCollide: function(entityA, entityB, collision) { + var colliderName = Entities.getEntityProperties(entityB, 'name').name; + + if (colliderName.indexOf("projectile") > -1) { + this.signalAC(); + } + } + }; + + StartButton.prototype.startNearTrigger = StartButton.prototype.signalAC; + StartButton.prototype.startFarTrigger = StartButton.prototype.signalAC; + StartButton.prototype.clickDownOnEntity = StartButton.prototype.signalAC; + + return new StartButton(); +}); diff --git a/unpublishedScripts/marketplace/shortbow/utils.js b/unpublishedScripts/marketplace/shortbow/utils.js new file mode 100644 index 0000000000..3ef82dcc13 --- /dev/null +++ b/unpublishedScripts/marketplace/shortbow/utils.js @@ -0,0 +1,57 @@ +// +// Created by Ryan Huffman on 1/10/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 +// + +/* globals utils:true */ + +if (!Function.prototype.bind) { + Function.prototype.bind = function(oThis) { + if (typeof this !== 'function') { + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + NOP = function() {}, + fBound = function() { + return fToBind.apply(this instanceof NOP + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + if (this.prototype) { + // Function.prototype doesn't have a prototype property + NOP.prototype = this.prototype; + } + fBound.prototype = new NOP(); + + return fBound; + }; +} + +utils = { + parseJSON: function(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } + }, + findSurfaceBelowPosition: function(pos) { + var result = Entities.findRayIntersection({ + origin: pos, + direction: { x: 0.0, y: -1.0, z: 0.0 } + }, true); + if (result.intersects) { + return result.intersection; + } + return pos; + } +};