diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 8d9a5e6951..dd25aa4c4b 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -384,18 +384,20 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { if (includeThisAvatar) { numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); - _stats.numOthersIncluded++; - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); - // remember the last time we sent details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + // remember the last time we sent details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + } } avatarPacketList->endSegment(); diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index f8f728f834..5f99dd68bc 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -324,16 +324,8 @@ void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) { void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) { switch (killedNode->getType()) { case NodeType::EntityServer: { - if (!_shuttingDown) { - if (_entitiesScriptEngine) { - _entitiesScriptEngine->unloadAllEntityScripts(); - _entitiesScriptEngine->stop(); - } - - resetEntitiesScriptEngine(); - - _entityViewer.clear(); - } + clear(); + break; } case NodeType::Agent: { @@ -440,12 +432,12 @@ void EntityScriptServer::clear() { _entitiesScriptEngine->stop(); } + _entityViewer.clear(); + // reset the engine if (!_shuttingDown) { resetEntitiesScriptEngine(); } - - _entityViewer.clear(); } void EntityScriptServer::shutdownScriptEngine() { diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index c5f055bed0..7642a66867 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -996,6 +996,10 @@ function saveSettings() { if (password && password.length > 0) { formJSON["security"]["http_password"] = sha256_digest(password); } + var verify_password = formJSON["security"]["verify_http_password"]; + if (verify_password && verify_password.length > 0) { + formJSON["security"]["verify_http_password"] = sha256_digest(verify_password); + } } // verify that the password and confirmation match before saving @@ -1010,7 +1014,6 @@ function saveSettings() { bootbox.alert({"message": "Passwords must match!", "title":"Password Error"}); canPost = false; } else { - formJSON["security"]["http_password"] = sha256_digest(password); delete formJSON["security"]["verify_http_password"]; } } diff --git a/interface/resources/avatar/animations/touch_point_closed_left.fbx b/interface/resources/avatar/animations/touch_point_closed_left.fbx new file mode 100644 index 0000000000..8835de4f7f Binary files /dev/null and b/interface/resources/avatar/animations/touch_point_closed_left.fbx differ diff --git a/interface/resources/avatar/animations/touch_point_closed_right.fbx b/interface/resources/avatar/animations/touch_point_closed_right.fbx new file mode 100644 index 0000000000..c4efe4ff90 Binary files /dev/null and b/interface/resources/avatar/animations/touch_point_closed_right.fbx differ diff --git a/interface/resources/avatar/animations/touch_point_open_left.fbx b/interface/resources/avatar/animations/touch_point_open_left.fbx new file mode 100644 index 0000000000..cbab6cd55d Binary files /dev/null and b/interface/resources/avatar/animations/touch_point_open_left.fbx differ diff --git a/interface/resources/avatar/animations/touch_point_open_right.fbx b/interface/resources/avatar/animations/touch_point_open_right.fbx new file mode 100644 index 0000000000..b54abe2b2e Binary files /dev/null and b/interface/resources/avatar/animations/touch_point_open_right.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_closed_left.fbx b/interface/resources/avatar/animations/touch_thumb_closed_left.fbx new file mode 100644 index 0000000000..5a731144ba Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_closed_left.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_closed_right.fbx b/interface/resources/avatar/animations/touch_thumb_closed_right.fbx new file mode 100644 index 0000000000..d947cdb1fd Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_closed_right.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_open_left.fbx b/interface/resources/avatar/animations/touch_thumb_open_left.fbx new file mode 100644 index 0000000000..b50cbd8541 Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_open_left.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_open_right.fbx b/interface/resources/avatar/animations/touch_thumb_open_right.fbx new file mode 100644 index 0000000000..ca861d0250 Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_open_right.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_point_closed_left.fbx b/interface/resources/avatar/animations/touch_thumb_point_closed_left.fbx new file mode 100644 index 0000000000..bf8023b54a Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_point_closed_left.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_point_closed_right.fbx b/interface/resources/avatar/animations/touch_thumb_point_closed_right.fbx new file mode 100644 index 0000000000..46c0cd48c4 Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_point_closed_right.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_point_open_left.fbx b/interface/resources/avatar/animations/touch_thumb_point_open_left.fbx new file mode 100644 index 0000000000..23c36f2172 Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_point_open_left.fbx differ diff --git a/interface/resources/avatar/animations/touch_thumb_point_open_right.fbx b/interface/resources/avatar/animations/touch_thumb_point_open_right.fbx new file mode 100644 index 0000000000..5016832aab Binary files /dev/null and b/interface/resources/avatar/animations/touch_thumb_point_open_right.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 834a3fc277..975f01855d 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -128,7 +128,41 @@ "id": "rightHandGrasp", "interpTarget": 3, "interpDuration": 3, - "transitions": [] + "transitions": [ + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" } + ] } ] }, @@ -166,6 +200,108 @@ "children": [] } ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] }, @@ -175,7 +311,7 @@ "data": { "alpha": 0.0, "boneSet": "leftHand", - "alphaVar" : "leftHandOverlayAlpha" + "alphaVar": "leftHandOverlayAlpha" }, "children": [ { @@ -188,7 +324,41 @@ "id": "leftHandGrasp", "interpTarget": 3, "interpDuration": 3, - "transitions": [] + "transitions": [ + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } + ] } ] }, @@ -226,6 +396,108 @@ "children": [] } ] + }, + { + "id": "leftIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] }, diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index c1fea7c09b..cf5ea98b81 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -13,6 +13,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 import "../styles-uit" import "../controls-uit" as HifiControls @@ -29,7 +30,9 @@ Rectangle { property int myCardHeight: 90 property int rowHeight: 70 property int actionButtonWidth: 55 - property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth + property int actionButtonAllowance: actionButtonWidth * 2 + property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth + property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance) property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities. @@ -52,6 +55,16 @@ Rectangle { letterboxMessage.visible = true letterboxMessage.popupRadius = 0 } + Settings { + id: settings + category: "pal" + property bool filtered: false + property int nearDistance: 30 + } + function refreshWithFilter() { + // We should just be able to set settings.filtered to filter.checked, but see #3249, so send to .js for saving. + pal.sendToScript({method: 'refresh', params: {filter: filter.checked && {distance: settings.nearDistance}}}); + } // This is the container for the PAL Rectangle { @@ -88,11 +101,32 @@ Rectangle { audioLevel: myData.audioLevel isMyCard: true // Size - width: nameCardWidth + width: minNameCardWidth height: parent.height // Anchors anchors.left: parent.left } + Row { + HifiControls.CheckBox { + id: filter + checked: settings.filtered + text: "in view" + boxSize: reload.height * 0.70 + onCheckedChanged: refreshWithFilter() + } + HifiControls.GlyphButton { + id: reload + glyph: hifi.glyphs.reload + width: reload.height + onClicked: refreshWithFilter() + } + spacing: 50 + anchors { + right: parent.right + top: parent.top + topMargin: 10 + } + } } // Rectangles used to cover up rounded edges on bottom of MyInfo Rectangle Rectangle { @@ -306,45 +340,7 @@ Rectangle { } } } - // Refresh button - Rectangle { - // Size - width: hifi.dimensions.tableHeaderHeight-1 - height: hifi.dimensions.tableHeaderHeight-1 - // Anchors - anchors.left: table.left - anchors.leftMargin: 4 - anchors.top: table.top - // Style - color: hifi.colors.tableBackgroundLight - // Actual refresh icon - HiFiGlyphs { - id: reloadButton - text: hifi.glyphs.reloadSmall - // Size - size: parent.width*1.5 - // Anchors - anchors.fill: parent - // Style - horizontalAlignment: Text.AlignHCenter - color: hifi.colors.darkGray - } - MouseArea { - id: reloadButtonArea - // Anchors - anchors.fill: parent - hoverEnabled: true - // Everyone likes a responsive refresh button! - // So use onPressed instead of onClicked - onPressed: { - reloadButton.color = hifi.colors.lightGrayText - pal.sendToScript({method: 'refresh'}) - } - onReleased: reloadButton.color = (containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.darkGray) - onEntered: reloadButton.color = hifi.colors.baseGrayHighlight - onExited: reloadButton.color = (pressed ? hifi.colors.lightGrayText: hifi.colors.darkGray) - } - } + // Separator between user and admin functions Rectangle { // Size @@ -501,7 +497,7 @@ Rectangle { if (alreadyRefreshed === true) { letterbox('', '', 'The last editor of this object is either you or not among this list of users.'); } else { - pal.sendToScript({method: 'refresh', params: message.params}); + pal.sendToScript({method: 'refresh', params: {selected: message.params}}); } } else { // If we've already refreshed the PAL and found the avatar in the model diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3de7906f56..d48fe19a99 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -548,6 +548,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f; const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; +const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), @@ -570,6 +571,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT), _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR), _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR), + _tabletVisibleToOthersSetting("tabletVisibleToOthers", DEFAULT_TABLET_VISIBLE_TO_OTHERS), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _scaleMirror(1.0f), _rotateMirror(0.0f), @@ -781,6 +783,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { + getOverlays().deleteOverlay(getTabletScreenID()); + getOverlays().deleteOverlay(getTabletHomeButtonID()); + getOverlays().deleteOverlay(getTabletFrameID()); + }); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected @@ -1216,6 +1223,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (entity && entity->wantsKeyboardFocus()) { setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); setKeyboardFocusEntity(entityItemID); + } else { + setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); } }); @@ -2348,6 +2357,11 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) { updateSystemTabletMode(); } +void Application::setTabletVisibleToOthersSetting(bool value) { + _tabletVisibleToOthersSetting.set(value); + updateSystemTabletMode(); +} + void Application::setSettingConstrainToolbarPosition(bool setting) { _constrainToolbarPosition.set(setting); DependencyManager::get()->setConstrainToolbarToCenterX(setting); @@ -3093,6 +3107,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { getOverlays().mouseMoveEvent(&mappedEvent); getEntities()->mouseMoveEvent(&mappedEvent); } + _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -3125,7 +3140,6 @@ void Application::mousePressEvent(QMouseEvent* event) { if (!_aboutToQuit) { getOverlays().mousePressEvent(&mappedEvent); - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { getEntities()->mousePressEvent(&mappedEvent); } @@ -3429,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; @@ -6902,5 +6916,10 @@ OverlayID Application::getTabletScreenID() const { OverlayID Application::getTabletHomeButtonID() const { auto HMD = DependencyManager::get(); - return HMD->getCurrentHomeButtonUUID(); + return HMD->getCurrentHomeButtonID(); +} + +QUuid Application::getTabletFrameID() const { + auto HMD = DependencyManager::get(); + return HMD->getCurrentTabletFrameID(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 662523ce1d..13c1458aee 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -218,6 +218,8 @@ public: void setDesktopTabletBecomesToolbarSetting(bool value); bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } void setHmdTabletBecomesToolbarSetting(bool value); + bool getTabletVisibleToOthersSetting() { return _tabletVisibleToOthersSetting.get(); } + void setTabletVisibleToOthersSetting(bool value); float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); @@ -300,6 +302,7 @@ public: OverlayID getTabletScreenID() const; OverlayID getTabletHomeButtonID() const; + QUuid getTabletFrameID() const; // may be an entity or an overlay signals: void svoImportRequested(const QString& url); @@ -561,6 +564,7 @@ private: Setting::Handle _desktopTabletScale; Setting::Handle _desktopTabletBecomesToolbarSetting; Setting::Handle _hmdTabletBecomesToolbarSetting; + Setting::Handle _tabletVisibleToOthersSetting; Setting::Handle _constrainToolbarPosition; float _scaleMirror; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index f930424569..cf3261ee88 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -192,6 +192,8 @@ QVariantMap Camera::getViewFrustum() { result["orientation"].setValue(frustum.getOrientation()); result["projection"].setValue(frustum.getProjection()); result["centerRadius"].setValue(frustum.getCenterRadius()); + result["fieldOfView"].setValue(frustum.getFieldOfView()); + result["aspectRatio"].setValue(frustum.getAspectRatio()); return result; } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 64ecb1dc29..f5fe82ef4b 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -933,6 +933,10 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { } glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch(index) { case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); @@ -969,6 +973,10 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { } glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch(index) { case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b1a09b46ce..d806c042b9 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -85,7 +85,7 @@ AvatarManager::AvatarManager(QObject* parent) : // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) { if (enabled) { - removeAvatar(nodeID); + removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); } }); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1ea9891732..842939d938 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -823,7 +823,7 @@ void MyAvatar::saveData() { auto hmdInterface = DependencyManager::get(); _avatarEntitiesLock.withReadLock([&] { for (auto entityID : _avatarEntityData.keys()) { - if (hmdInterface->getCurrentTabletUIID() == entityID) { + if (hmdInterface->getCurrentTabletFrameID() == entityID) { // don't persist the tablet between domains / sessions continue; } @@ -2410,6 +2410,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c } glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch (index) { case CONTROLLER_LEFTHAND_INDEX: { return getLeftHandControllerPoseInAvatarFrame().getRotation(); @@ -2443,6 +2447,10 @@ glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { } glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch (index) { case CONTROLLER_LEFTHAND_INDEX: { return getLeftHandControllerPoseInAvatarFrame().getTranslation(); diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 463a21ded8..d895d5da4c 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -29,8 +29,8 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(glm::quat orientation READ getOrientation) Q_PROPERTY(bool mounted READ isMounted) Q_PROPERTY(bool showTablet READ getShouldShowTablet) - Q_PROPERTY(QUuid tabletID READ getCurrentTabletUIID WRITE setCurrentTabletUIID) - Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonUUID WRITE setCurrentHomeButtonUUID) + Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID) + Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID) Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) public: @@ -90,11 +90,11 @@ public: void setShouldShowTablet(bool value) { _showTablet = value; } bool getShouldShowTablet() const { return _showTablet; } - void setCurrentTabletUIID(QUuid tabletID) { _tabletUIID = tabletID; } - QUuid getCurrentTabletUIID() const { return _tabletUIID; } + void setCurrentTabletFrameID(QUuid tabletID) { _tabletUIID = tabletID; } + QUuid getCurrentTabletFrameID() const { return _tabletUIID; } - void setCurrentHomeButtonUUID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } - QUuid getCurrentHomeButtonUUID() const { return _homeButtonID; } + void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } + QUuid getCurrentHomeButtonID() const { return _homeButtonID; } void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; } QUuid getCurrentTabletScreenID() const { return _tabletScreenID; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index dd05d5c0e1..d291510556 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -102,7 +102,11 @@ void setupPreferences() { auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter)); } - + { + auto getter = []()->bool { return qApp->getTabletVisibleToOthersSetting(); }; + auto setter = [](bool value) { qApp->setTabletVisibleToOthersSetting(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Tablet Is Visible To Others", getter, setter)); + } // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index ff5177ed3a..70b1fa4b71 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -39,7 +39,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _isDashedLine(base3DOverlay->_isDashedLine), _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _drawInFront(base3DOverlay->_drawInFront), - _isAA(base3DOverlay->_isAA) + _isAA(base3DOverlay->_isAA), + _isGrabbable(base3DOverlay->_isGrabbable) { setTransform(base3DOverlay->getTransform()); } @@ -59,15 +60,19 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert } else if (result["position"].isValid()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]), parentID, parentJointIndex, success); - result["position"] = vec3toVariant(localPosition); + if (success) { + result["position"] = vec3toVariant(localPosition); + } } if (result["localOrientation"].isValid()) { result["orientation"] = result["localOrientation"]; } else if (result["orientation"].isValid()) { glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]), - parentID, parentJointIndex, success); - result["orientation"] = quatToVariant(localOrientation); + parentID, parentJointIndex, success); + if (success) { + result["orientation"] = quatToVariant(localOrientation); + } } return result; @@ -125,6 +130,11 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { needRenderItemUpdate = true; } + auto isGrabbable = properties["grabbable"]; + if (isGrabbable.isValid()) { + setIsGrabbable(isGrabbable.toBool()); + } + if (properties["position"].isValid()) { setLocalPosition(vec3FromVariant(properties["position"])); needRenderItemUpdate = true; @@ -227,6 +237,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "drawInFront") { return _drawInFront; } + if (property == "grabbable") { + return _isGrabbable; + } if (property == "parentID") { return getParentID(); } @@ -246,6 +259,8 @@ bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3 } void Base3DOverlay::locationChanged(bool tellPhysics) { + SpatiallyNestable::locationChanged(tellPhysics); + auto itemID = getRenderItemID(); if (render::Item::isValidID(itemID)) { render::ScenePointer scene = qApp->getMain3DScene(); @@ -253,8 +268,6 @@ void Base3DOverlay::locationChanged(bool tellPhysics) { pendingChanges.updateItem(itemID); scene->enqueuePendingChanges(pendingChanges); } - // Overlays can't currently have children. - // SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also } void Base3DOverlay::parentDeleted() { diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 7906b9d6c0..a1c23e5cd8 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -38,6 +38,7 @@ public: bool getIsSolidLine() const { return !_isDashedLine; } bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } bool getDrawInFront() const { return _drawInFront; } + bool getIsGrabbable() const { return _isGrabbable; } virtual bool isAA() const { return _isAA; } @@ -47,6 +48,7 @@ public: void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } void setDrawInFront(bool value) { _drawInFront = value; } void setIsAA(bool value) { _isAA = value; } + void setIsGrabbable(bool value) { _isGrabbable = value; } virtual AABox getBounds() const override = 0; @@ -71,6 +73,7 @@ protected: bool _ignoreRayIntersection; bool _drawInFront; bool _isAA; + bool _isGrabbable { false }; }; #endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index c18d9ddaef..ad7fbd6cc2 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -341,28 +341,18 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) { return UNKNOWN_OVERLAY_ID; } QMapIterator i(_overlaysHUD); - i.toBack(); 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.hasPrevious()) { - i.previous(); + 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)) { @@ -406,16 +396,25 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, const QScriptValue& overlayIDsToInclude, const QScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { - float bestDistance = std::numeric_limits::max(); - bool bestIsFront = false; const QVector overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude); const QVector overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard); + return findRayIntersectionInternal(ray, precisionPicking, + overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); +} + + +RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { + float bestDistance = std::numeric_limits::max(); + bool bestIsFront = false; + RayToOverlayIntersectionResult result; QMapIterator i(_overlaysWorld); - i.toBack(); - while (i.hasPrevious()) { - i.previous(); + while (i.hasNext()) { + i.next(); OverlayID thisID = i.key(); auto thisOverlay = std::dynamic_pointer_cast(i.value()); @@ -700,8 +699,9 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) { } } -PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray, - RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType) { +PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray, + RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, + PointerEvent::EventType eventType) { auto thisOverlay = std::dynamic_pointer_cast(overlay); @@ -719,11 +719,41 @@ PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay r return pointerEvent; } -void Overlays::mousePressEvent(QMouseEvent* event) { + +RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRay ray) { + QVector overlaysToInclude; + QVector overlaysToDiscard; + RayToOverlayIntersectionResult rayPickResult; + + // first priority is tablet screen + overlaysToInclude << qApp->getTabletScreenID(); + rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + if (rayPickResult.intersects) { + return rayPickResult; + } + // then tablet home button + overlaysToInclude.clear(); + overlaysToInclude << qApp->getTabletHomeButtonID(); + rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + if (rayPickResult.intersects) { + return rayPickResult; + } + // then tablet frame + overlaysToInclude.clear(); + overlaysToInclude << OverlayID(qApp->getTabletFrameID()); + rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + if (rayPickResult.intersects) { + return rayPickResult; + } + // then whatever + return findRayIntersection(ray); +} + +bool Overlays::mousePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mousePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; @@ -732,19 +762,18 @@ void Overlays::mousePressEvent(QMouseEvent* event) { if (thisOverlay) { auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press); emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); - } else { - emit mousePressOffOverlay(); + return true; } - } else { - emit mousePressOffOverlay(); } + emit mousePressOffOverlay(); + return false; } -void Overlays::mouseReleaseEvent(QMouseEvent* event) { +bool Overlays::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); if (rayPickResult.intersects) { // Only Web overlays can have focus. @@ -756,13 +785,14 @@ void Overlays::mouseReleaseEvent(QMouseEvent* event) { } _currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID; + return false; } -void Overlays::mouseMoveEvent(QMouseEvent* event) { +bool Overlays::mouseMoveEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); if (rayPickResult.intersects) { // Only Web overlays can have focus. @@ -802,4 +832,34 @@ void Overlays::mouseMoveEvent(QMouseEvent* event) { _currentHoverOverOverlayID = UNKNOWN_OVERLAY_ID; } } + return false; +} + +QVector Overlays::findOverlays(const glm::vec3& center, float radius) const { + QVector result; + + QMapIterator i(_overlaysWorld); + int checked = 0; + while (i.hasNext()) { + checked++; + i.next(); + OverlayID thisID = i.key(); + auto overlay = std::dynamic_pointer_cast(i.value()); + if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) { + // get AABox in frame of overlay + glm::vec3 dimensions = overlay->getDimensions(); + glm::vec3 low = dimensions * -0.5f; + AABox overlayFrameBox(low, dimensions); + + Transform overlayToWorldMatrix = overlay->getTransform(); + glm::mat4 worldToOverlayMatrix = glm::inverse(overlayToWorldMatrix.getMatrix()); + glm::vec3 overlayFrameSearchPosition = glm::vec3(worldToOverlayMatrix * glm::vec4(center, 1.0f)); + glm::vec3 penetration; + if (overlayFrameBox.findSpherePenetration(overlayFrameSearchPosition, radius, penetration)) { + result.append(thisID); + } + } + } + + return result; } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 7c6ba34f58..5c22e46880 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -100,9 +100,9 @@ public: OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } OverlayID addOverlay(Overlay::Pointer overlay); - void mousePressEvent(QMouseEvent* event); - void mouseReleaseEvent(QMouseEvent* event); - void mouseMoveEvent(QMouseEvent* event); + bool mousePressEvent(QMouseEvent* event); + bool mouseReleaseEvent(QMouseEvent* event); + bool mouseMoveEvent(QMouseEvent* event); void cleanupAllOverlays(); @@ -206,6 +206,16 @@ public slots: bool visibleOnly = false, bool collidableOnly = false); + /**jsdoc + * Return a list of 3d overlays with bounding boxes that touch the given sphere + * + * @function Overlays.findOverlays + * @param {Vec3} center the point to search from. + * @param {float} radius search radius + * @return {List of Overlays.OverlayID} list of overlays withing the radius + */ + QVector findOverlays(const glm::vec3& center, float radius) const; + /**jsdoc * Check whether an overlay's assets have been loaded. For example, if the * overlay is an "image" overlay, this will indicate whether the its image @@ -317,6 +327,12 @@ private: OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; + + RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); }; #endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 277a86e93f..aa06741638 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -53,7 +53,7 @@ namespace render { return overlay->getBounds(); } template <> int payloadGetLayer(const Overlay::Pointer& overlay) { - // MAgic number while we are defining the layering mechanism: + // Magic number while we are defining the layering mechanism: const int LAYER_NO_AA = 3; const int LAYER_2D = 2; const int LAYER_3D_FRONT = 1; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index cdbf5f2a85..48e5d673c9 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -182,7 +182,7 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) - << "from AvatarHashMap"; + << "from AvatarHashMap" << removalReason; emit avatarRemovedEvent(removedAvatar->getSessionUUID()); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index cd7f1235bb..a5bd0135e4 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1419,8 +1419,7 @@ QVector EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare return; } parent->forEachChild([&](SpatiallyNestablePointer child) { - if (child->getParentJointIndex() == jointIndex && - child->getNestableType() != NestableType::Overlay) { + if (child->getParentJointIndex() == jointIndex) { result.push_back(child->getID()); } }); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index adef3ce2b5..2cd96a84c7 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -44,6 +44,8 @@ public: // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; + bool isGeometryLoaded() const { return (bool)_fbxGeometry; } + const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } const GeometryMeshes& getMeshes() const { return *_meshes; } const std::shared_ptr getShapeMaterial(int shapeID) const; 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/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index f38d24c31f..c8a7b61aa7 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -16,8 +16,8 @@ void UserActivityLoggerScriptingInterface::enabledEdit() { logAction("enabled_edit"); } -void UserActivityLoggerScriptingInterface::openedTablet() { - logAction("opened_tablet"); +void UserActivityLoggerScriptingInterface::openedTablet(bool visibleToOthers) { + logAction("opened_tablet", { { "visible_to_others", visibleToOthers } }); } void UserActivityLoggerScriptingInterface::closedTablet() { diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index b827b2262a..cf38450891 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -21,7 +21,7 @@ class UserActivityLoggerScriptingInterface : public QObject, public Dependency { Q_OBJECT public: Q_INVOKABLE void enabledEdit(); - Q_INVOKABLE void openedTablet(); + Q_INVOKABLE void openedTablet(bool visibleToOthers); Q_INVOKABLE void closedTablet(); Q_INVOKABLE void openedMarketplace(); Q_INVOKABLE void toggledAway(bool isAway); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 2042a16801..7c373274e4 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -114,7 +114,7 @@ public: void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); - bool isLoaded() const { return (bool)_renderGeometry; } + bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 76b98c6217..608fa937c8 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -150,7 +150,6 @@ signals: private: bool getRequestsDomainListData(); void setRequestsDomainListData(bool requests); - bool _requestsDomainListData; }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 40a77eda55..5d8813e988 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -20,7 +20,7 @@ var DEFAULT_SCRIPTS = [ "system/bubble.js", "system/snapshot.js", "system/help.js", - "system/pal.js", //"system/mod.js", // older UX, if you prefer + "system/pal.js", // "system/mod.js", // older UX, if you prefer "system/goto.js", "system/marketplaces/marketplaces.js", "system/edit.js", @@ -54,9 +54,6 @@ if (previousSetting === true || previousSetting === 'true') { previousSetting = true; } - - - if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { Menu.addMenuItem({ menuName: MENU_CATEGORY, @@ -78,11 +75,11 @@ function runDefaultsSeparately() { Script.load(DEFAULT_SCRIPTS[i]); } } + // start all scripts if (Menu.isOptionChecked(MENU_ITEM)) { // we're debugging individual default scripts // so we load each into its own ScriptEngine instance - debuggingDefaultScripts = true; runDefaultsSeparately(); } else { // include all default scripts into this ScriptEngine @@ -90,32 +87,14 @@ if (Menu.isOptionChecked(MENU_ITEM)) { } function menuItemEvent(menuItem) { - if (menuItem == MENU_ITEM) { - - isChecked = Menu.isOptionChecked(MENU_ITEM); + if (menuItem === MENU_ITEM) { + var isChecked = Menu.isOptionChecked(MENU_ITEM); if (isChecked === true) { Settings.setValue(SETTINGS_KEY, true); } else if (isChecked === false) { Settings.setValue(SETTINGS_KEY, false); } - Window.alert('You must reload all scripts for this to take effect.') - } - - -} - - - -function stopLoadedScripts() { - // remove debug script loads - var runningScripts = ScriptDiscoveryService.getRunning(); - for (var i in runningScripts) { - var scriptName = runningScripts[i].name; - for (var j in DEFAULT_SCRIPTS) { - if (DEFAULT_SCRIPTS[j].slice(-scriptName.length) === scriptName) { - ScriptDiscoveryService.stopScript(runningScripts[i].url); - } - } + Menu.triggerOption("Reload All Scripts"); } } @@ -126,7 +105,6 @@ function removeMenuItem() { } Script.scriptEnding.connect(function() { - stopLoadedScripts(); removeMenuItem(); }); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 74a3c3d25b..f0b6663bec 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -331,6 +331,12 @@ Grabber.prototype.pressEvent = function(event) { } var pickRay = Camera.computePickRay(event.x, event.y); + + var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); + if (overlayResult.intersects) { + return; + } + var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking if (!pickResults.intersects) { // didn't click on anything diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ad6adf7722..14d737de55 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -14,7 +14,7 @@ /* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications, - Menu, HMD */ + Menu, HMD, isInEditMode */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE @@ -420,7 +420,7 @@ function entityHasActions(entityID) { function findRayIntersection(pickRay, precise, include, exclude) { var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true); - var overlays = Overlays.findRayIntersection(pickRay); + var overlays = Overlays.findRayIntersection(pickRay, precise, [], [HMD.tabletID]); if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { return entities; } @@ -665,6 +665,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) { // override default sphere with a user specified model, if it exists. overlayInfoSet.overlays.push(Overlays.addOverlay("model", { + name: "hotspot overlay", url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL, position: hotspot.worldPosition, rotation: { @@ -797,7 +798,7 @@ function MyController(hand) { }; this.actionID = null; // action this script created... - this.grabbedEntity = null; // on this entity. + this.grabbedThingID = null; // on this entity. this.grabbedOverlay = null; this.state = STATE_OFF; this.pointer = null; // entity-id of line object @@ -874,14 +875,19 @@ function MyController(hand) { }; this.callEntityMethodOnGrabbed = function(entityMethodName) { + if (this.grabbedIsOverlay) { + return; + } var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); + Entities.callEntityMethod(this.grabbedThingID, entityMethodName, args); }; this.setState = function(newState, reason) { - if ((isInEditMode() && this.grabbedEntity !== HMD.tabletID )&& (newState !== STATE_OFF && - newState !== STATE_SEARCHING && - newState !== STATE_OVERLAY_STYLUS_TOUCHING)) { + if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) && + (newState !== STATE_OFF && + newState !== STATE_SEARCHING && + newState !== STATE_OVERLAY_STYLUS_TOUCHING && + newState !== STATE_OVERLAY_LASER_TOUCHING)) { return; } setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING)); @@ -924,6 +930,7 @@ function MyController(hand) { if (!this.grabPointSphere) { this.grabPointSphere = Overlays.addOverlay("sphere", { + name: "grabPointSphere", localPosition: getGrabPointSphereOffset(this.handToController()), localRotation: { x: 0, y: 0, z: 0, w: 1 }, dimensions: GRAB_POINT_SPHERE_RADIUS * 2, @@ -954,6 +961,7 @@ function MyController(hand) { var brightColor = colorPow(color, 0.06); if (this.searchSphere === null) { var sphereProperties = { + name: "searchSphere", position: location, rotation: rotation, outerRadius: size * 1.2, @@ -976,7 +984,8 @@ function MyController(hand) { innerAlpha: 1.0, outerAlpha: 0.0, outerRadius: size * 1.2, - visible: true + visible: true, + ignoreRayIntersection: true }); } }; @@ -987,6 +996,7 @@ function MyController(hand) { } var stylusProperties = { + name: "stylus", url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", localPosition: Vec3.sum({ x: 0.0, y: WEB_TOUCH_Y_OFFSET, @@ -1021,6 +1031,7 @@ function MyController(hand) { this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { + name: "line", glow: 1.0, start: closePoint, end: farPoint, @@ -1196,6 +1207,13 @@ function MyController(hand) { } } + var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); + for (var j = 0; j < candidateOverlays.length; j++) { + if (this.isTablet(candidateOverlays[j])) { + nearWeb = true; + } + } + if (nearWeb) { this.showStylus(); var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1444,7 +1462,7 @@ function MyController(hand) { var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && - this.getOtherHandController().grabbedEntity == hotspot.entityID); + this.getOtherHandController().grabbedThingID == hotspot.entityID); var hasParent = true; if (props.parentID === NULL_UUID) { hasParent = false; @@ -1613,7 +1631,7 @@ function MyController(hand) { var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY); - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; this.isInitialGrab = false; this.preparingHoldRelease = false; @@ -1621,7 +1639,7 @@ function MyController(hand) { this.checkForUnexpectedChildren(); if ((this.triggerSmoothedReleased() && this.secondaryReleased())) { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "trigger released"); return; } @@ -1642,8 +1660,9 @@ function MyController(hand) { if (potentialEquipHotspot) { if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) { this.grabbedHotspot = potentialEquipHotspot; - this.grabbedEntity = potentialEquipHotspot.entityID; - this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'"); + this.grabbedThingID = potentialEquipHotspot.entityID; + this.grabbedIsOverlay = false; + this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'"); return; } @@ -1654,6 +1673,11 @@ function MyController(hand) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); + var candidateOverlays = Overlays.findOverlays(handPosition, NEAR_GRAB_RADIUS); + var grabbableOverlays = candidateOverlays.filter(function(overlayID) { + return Overlays.getProperty(overlayID, "grabbable"); + }); + if (rayPickInfo.entityID) { this.intersectionDistance = rayPickInfo.distance; if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { @@ -1665,6 +1689,23 @@ function MyController(hand) { this.intersectionDistance = 0; } + if (grabbableOverlays.length > 0) { + grabbableOverlays.sort(function(a, b) { + var aPosition = Overlays.getProperty(a, "position"); + var aDistance = Vec3.distance(aPosition, handPosition); + var bPosition = Overlays.getProperty(b, "position"); + var bDistance = Vec3.distance(bPosition, handPosition); + return aDistance - bDistance; + }); + this.grabbedThingID = grabbableOverlays[0]; + this.grabbedIsOverlay = true; + if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { + this.setState(STATE_NEAR_GRABBING, "near grab overlay '" + + Overlays.getProperty(this.grabbedThingID, "name") + "'"); + return; + } + } + var entity; if (grabbableEntities.length > 0) { // sort by distance @@ -1675,7 +1716,8 @@ function MyController(hand) { }); entity = grabbableEntities[0]; name = entityPropertiesCache.getProps(entity).name; - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; if (this.entityWantsTrigger(entity)) { if (this.triggerSmoothedGrab()) { this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); @@ -1686,7 +1728,7 @@ function MyController(hand) { } else { // If near something grabbable, grab it! if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { - this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); + this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'"); return; } else { // potentialNearGrabEntity = entity; @@ -1709,7 +1751,8 @@ function MyController(hand) { name = entityPropertiesCache.getProps(entity).name; if (this.entityWantsTrigger(entity)) { if (this.triggerSmoothedGrab()) { - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); return; } else { @@ -1717,7 +1760,8 @@ function MyController(hand) { } } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.grabbedDistance = rayPickInfo.distance; this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); return; @@ -1797,7 +1841,8 @@ function MyController(hand) { Entities.sendHoverOverEntity(entity, pointerEvent); } - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'"); return true; @@ -1925,7 +1970,8 @@ function MyController(hand) { } if (this.triggerSmoothedGrab() && (!isEditing() || this.isTablet(entity))) { - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'"); return true; } @@ -2036,7 +2082,7 @@ function MyController(hand) { var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); var now = Date.now(); // add the action and initialize some variables @@ -2066,7 +2112,7 @@ function MyController(hand) { var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); this.actionID = NULL_UUID; - this.actionID = Entities.addAction("spring", this.grabbedEntity, { + this.actionID = Entities.addAction("spring", this.grabbedThingID, { targetPosition: this.currentObjectPosition, linearTimeScale: timeScale, targetRotation: this.currentObjectRotation, @@ -2091,12 +2137,12 @@ function MyController(hand) { this.ensureDynamic = function() { // if we distance hold something and keep it very still before releasing it, it ends up // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(this.grabbedEntity, ["velocity", "dynamic", "parentID"]); + var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); if (props.dynamic && props.parentID == NULL_UUID) { var velocity = props.velocity; if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD velocity = { x: 0.0, y: 0.2, z:0.0 }; - Entities.editEntity(this.grabbedEntity, { velocity: velocity }); + Entities.editEntity(this.grabbedThingID, { velocity: velocity }); } } }; @@ -2118,7 +2164,7 @@ function MyController(hand) { var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); var now = Date.now(); var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds @@ -2173,7 +2219,7 @@ function MyController(hand) { newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position); - var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); + var handControllerData = getEntityCustomData('handControllerKey', this.grabbedThingID, defaultMoveWithHeadData); if (handControllerData.disableMoveWithHead !== true) { // mix in head motion if (MOVE_WITH_HEAD) { @@ -2202,7 +2248,7 @@ function MyController(hand) { this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); - var success = Entities.updateAction(this.grabbedEntity, this.actionID, { + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { targetPosition: newTargetPosition, linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), targetRotation: this.currentObjectRotation, @@ -2219,7 +2265,7 @@ function MyController(hand) { }; this.setupHoldAction = function() { - this.actionID = Entities.addAction("hold", this.grabbedEntity, { + this.actionID = Entities.addAction("hold", this.grabbedThingID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -2309,17 +2355,30 @@ function MyController(hand) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); if (this.entityActivated) { - var saveGrabbedID = this.grabbedEntity; + var saveGrabbedID = this.grabbedThingID; this.release(); - this.grabbedEntity = saveGrabbedID; + this.grabbedThingID = saveGrabbedID; } - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - if (FORCE_IGNORE_IK) { + var grabbedProperties; + if (this.grabbedIsOverlay) { + grabbedProperties = { + position: Overlays.getProperty(this.grabbedThingID, "position"), + rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), + parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), + parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + dynamic: false, + shapeType: "none" + }; this.ignoreIK = true; } else { - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + if (FORCE_IGNORE_IK) { + this.ignoreIK = true; + } else { + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedThingID, DEFAULT_GRABBABLE_DATA); + this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + } } var handRotation; @@ -2358,7 +2417,8 @@ function MyController(hand) { this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); } - var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); + var isPhysical = propsArePhysical(grabbedProperties) || + (!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID)); if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) { // grab entity via action if (!this.setupHoldAction()) { @@ -2366,7 +2426,7 @@ function MyController(hand) { } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedEntity, + grabbedEntity: this.grabbedThingID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } else { @@ -2392,83 +2452,87 @@ function MyController(hand) { reparentProps.localRotation = this.offsetRotation; } - if (grabbedProperties.userData.length > 0) { - try{ - var userData = JSON.parse(grabbedProperties.userData); - var grabInfo = userData.grabbableKey; - if (grabInfo && grabInfo.cloneable) { - // Check if - var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50}) - var count = 0; - worldEntities.forEach(function(item) { - var item = Entities.getEntityProperties(item, ["name"]); - if (item.name === grabbedProperties.name) { - count++; + if (this.grabbedIsOverlay) { + Overlays.editOverlay(this.grabbedThingID, reparentProps); + } else { + if (grabbedProperties.userData.length > 0) { + try{ + var userData = JSON.parse(grabbedProperties.userData); + var grabInfo = userData.grabbableKey; + if (grabInfo && grabInfo.cloneable) { + // Check if + var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50}) + var count = 0; + worldEntities.forEach(function(item) { + var item = Entities.getEntityProperties(item, ["name"]); + if (item.name === grabbedProperties.name) { + count++; + } + }) + var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var cUserData = Object.assign({}, userData); + var cProperties = Object.assign({}, cloneableProps); + + if (count > limit) { + delete cloneableProps; + delete lifetime; + delete cUserData; + delete cProperties; + return; } - }) - var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); - var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; - var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; - var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; - var cUserData = Object.assign({}, userData); - var cProperties = Object.assign({}, cloneableProps); - if (count > limit) { - delete cloneableProps; - delete lifetime; - delete cUserData; - delete cProperties; - return; + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id + + cProperties.dynamic = dynamic; + cProperties.locked = false; + cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + this.grabbedThingID = Entities.addEntity(cProperties); + grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); + var _this = this; + + Script.setTimeout(function () { + // This is needed to wait for the grabbed entity to have been instanciated. + _this.callEntityMethodOnGrabbed("startEquip"); + },400); } - - delete cUserData.grabbableKey.cloneLifetime; - delete cUserData.grabbableKey.cloneable; - delete cUserData.grabbableKey.cloneDynamic; - delete cUserData.grabbableKey.cloneLimit; - delete cProperties.id - - cProperties.dynamic = dynamic; - cProperties.locked = false; - cUserData.grabbableKey.triggerable = true; - cUserData.grabbableKey.grabbable = true; - cProperties.lifetime = lifetime; - cProperties.userData = JSON.stringify(cUserData); - this.grabbedEntity = Entities.addEntity(cProperties); - grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); - var _this = this; - - Script.setTimeout(function () { - // This is needed to wait for the grabbed entity to have been instanciated. - _this.callEntityMethodOnGrabbed("startEquip"); - },400); - } - }catch(e) { - print("ERROR: " + e); + }catch(e) {} } + Entities.editEntity(this.grabbedThingID, reparentProps); } - Entities.editEntity(this.grabbedEntity, reparentProps); if (this.thisHandIsParent(grabbedProperties)) { // this should never happen, but if it does, don't set previous parent to be this hand. - // this.previousParentID[this.grabbedEntity] = NULL; - // this.previousParentJointIndex[this.grabbedEntity] = -1; + // this.previousParentID[this.grabbedThingID] = NULL; + // this.previousParentJointIndex[this.grabbedThingID] = -1; } else { - this.previousParentID[this.grabbedEntity] = grabbedProperties.parentID; - this.previousParentJointIndex[this.grabbedEntity] = grabbedProperties.parentJointIndex; + this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; + this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', - grabbedEntity: this.grabbedEntity, + grabbedEntity: this.grabbedThingID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } - Entities.editEntity(this.grabbedEntity, { - velocity: { x: 0, y: 0, z: 0 }, - angularVelocity: { x: 0, y: 0, z: 0 }, - // dynamic: false - }); + if (!this.grabbedIsOverlay) { + Entities.editEntity(this.grabbedThingID, { + velocity: { x: 0, y: 0, z: 0 }, + angularVelocity: { x: 0, y: 0, z: 0 }, + // dynamic: false + }); + } if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("startNearGrab"); @@ -2534,26 +2598,39 @@ function MyController(hand) { if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { // store the offset attach points into preferences. - if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { - var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedThingID) { + var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]); if (prefprops && prefprops.localPosition && prefprops.localRotation) { storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, prefprops.localPosition, prefprops.localRotation); } } - var grabbedEntity = this.grabbedEntity; + var grabbedEntity = this.grabbedThingID; this.release(); - this.grabbedEntity = grabbedEntity; + this.grabbedThingID = grabbedEntity; this.setState(STATE_NEAR_GRABBING, "drop gesture detected"); return; } this.prevDropDetected = dropDetected; } - var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "parentJointIndex", + var props; + if (this.grabbedIsOverlay) { + props = { + localPosition: Overlays.getProperty(this.grabbedThingID, "localPosition"), + parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), + parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + position: Overlays.getProperty(this.grabbedThingID, "position"), + rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), + dimensions: Overlays.getProperty(this.grabbedThingID, "dimensions"), + registrationPoint: { x: 0.5, y: 0.5, z: 0.5 } + }; + } else { + props = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "parentID", "parentJointIndex", "position", "rotation", "dimensions", "registrationPoint"]); + } if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" state this.callEntityMethodOnGrabbed("releaseGrab"); @@ -2565,7 +2642,7 @@ function MyController(hand) { // someone took it from us or otherwise edited the parentID. end the grab. We don't do this // for equipped things so that they can be adjusted while equipped. this.callEntityMethodOnGrabbed("releaseGrab"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "someone took it"); return; } @@ -2647,7 +2724,7 @@ function MyController(hand) { if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl - var success = Entities.updateAction(this.grabbedEntity, this.actionID, { + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -2661,14 +2738,14 @@ function MyController(hand) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); } else { print("continueNearGrabbing -- updateAction failed"); - Entities.deleteAction(this.grabbedEntity, this.actionID); + Entities.deleteAction(this.grabbedThingID, this.actionID); this.setupHoldAction(); } } }; this.maybeScale = function(props) { - if (!objectScalingEnabled || this.isTablet(this.grabbedEntity)) { + if (!objectScalingEnabled || this.isTablet(this.grabbedThingID) || this.grabbedIsOverlay) { return; } @@ -2690,7 +2767,7 @@ function MyController(hand) { this.getOtherHandController().getHandPosition())); var currentRescale = scalingCurrentDistance / this.scalingStartDistance; var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions); - Entities.editEntity(this.grabbedEntity, { dimensions: newDimensions }); + Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions }); } }; @@ -2741,7 +2818,7 @@ function MyController(hand) { this.nearTrigger = function(deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopNearTrigger"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "trigger released"); return; } @@ -2751,7 +2828,7 @@ function MyController(hand) { this.farTrigger = function(deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "trigger released"); return; } @@ -2766,9 +2843,9 @@ function MyController(hand) { var intersection = findRayIntersection(pickRay, true, [], [], true); if (intersection.accurate || intersection.overlayID) { this.lastPickTime = now; - if (intersection.entityID != this.grabbedEntity) { + if (intersection.entityID != this.grabbedThingID) { this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "laser moved off of entity"); return; } @@ -2790,13 +2867,13 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { var pointerEvent = { type: "Press", id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), + pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, @@ -2804,8 +2881,8 @@ function MyController(hand) { isPrimaryHeld: true }; - Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent); + Entities.sendMousePressOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendClickDownOnEntity(this.grabbedThingID, pointerEvent); this.touchingEnterTimer = 0; this.touchingEnterPointerEvent = pointerEvent; @@ -2827,7 +2904,7 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { var pointerEvent; @@ -2835,7 +2912,7 @@ function MyController(hand) { pointerEvent = { type: "Release", id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), + pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, @@ -2848,11 +2925,11 @@ function MyController(hand) { pointerEvent.isPrimaryHeld = false; } - Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent); + Entities.sendMouseReleaseOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendClickReleaseOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendHoverLeaveEntity(this.grabbedThingID, pointerEvent); } - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; }; @@ -2860,7 +2937,7 @@ function MyController(hand) { this.touchingEnterTimer += dt; - entityPropertiesCache.addEntity(this.grabbedEntity); + entityPropertiesCache.addEntity(this.grabbedThingID); if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { this.setState(STATE_OFF, "released trigger"); @@ -2868,7 +2945,7 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { @@ -2878,15 +2955,15 @@ function MyController(hand) { return; } - if (Entities.keyboardFocusEntity != this.grabbedEntity) { + if (Entities.keyboardFocusEntity != this.grabbedThingID) { Overlays.keyboardFocusOverlay = 0; - Entities.keyboardFocusEntity = this.grabbedEntity; + Entities.keyboardFocusEntity = this.grabbedThingID; } var pointerEvent = { type: "Move", id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), + pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, @@ -2897,8 +2974,8 @@ function MyController(hand) { var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { - Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent); + Entities.sendMouseMoveOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendHoldingClickOnEntity(this.grabbedThingID, pointerEvent); this.deadspotExpired = true; } @@ -2908,7 +2985,7 @@ function MyController(hand) { } Reticle.setVisible(false); } else { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "grabbed entity was destroyed"); return; } @@ -2993,7 +3070,7 @@ function MyController(hand) { Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent); Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent); } - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; }; @@ -3016,7 +3093,7 @@ function MyController(hand) { if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "pulled away from overlay"); return; } @@ -3073,7 +3150,7 @@ function MyController(hand) { } Reticle.setVisible(false); } else { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "grabbed overlay was destroyed"); return; } @@ -3082,7 +3159,7 @@ function MyController(hand) { this.release = function() { this.turnOffVisualizations(); - if (this.grabbedEntity !== null) { + if (this.grabbedThingID !== null) { if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("releaseEquip"); } @@ -3090,35 +3167,49 @@ function MyController(hand) { // Make a small release haptic pulse if we really were holding something Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); + Entities.deleteAction(this.grabbedThingID, this.actionID); } else { // no action, so it's a parenting grab - if (this.previousParentID[this.grabbedEntity] === NULL_UUID) { - Entities.editEntity(this.grabbedEntity, { - parentID: this.previousParentID[this.grabbedEntity], - parentJointIndex: this.previousParentJointIndex[this.grabbedEntity] - }); - this.ensureDynamic(); + if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { + if (this.grabbedIsOverlay) { + Overlays.editOverlay(this.grabbedThingID, { + parentID: NULL_UUID, + parentJointIndex: -1 + }); + } else { + Entities.editEntity(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] + }); + this.ensureDynamic(); + } } else { - // we're putting this back as a child of some other parent, so zero its velocity - Entities.editEntity(this.grabbedEntity, { - parentID: this.previousParentID[this.grabbedEntity], - parentJointIndex: this.previousParentJointIndex[this.grabbedEntity], - velocity: {x: 0, y: 0, z: 0}, - angularVelocity: {x: 0, y: 0, z: 0} - }); + if (this.grabbedIsOverlay) { + Overlays.editOverlay(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + }); + } else { + // we're putting this back as a child of some other parent, so zero its velocity + Entities.editEntity(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0} + }); + } } } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'release', - grabbedEntity: this.grabbedEntity, + grabbedEntity: this.grabbedThingID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } this.actionID = null; - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; this.grabbedHotspot = null; @@ -3206,9 +3297,13 @@ function MyController(hand) { } _this.previouslyUnhooked[childID] = now; + // we don't know if it's an entity or an overlay Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); + Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); + } else { Entities.editEntity(childID, { parentID: NULL_UUID }); + Overlays.editOverlay(childID, { parentID: NULL_UUID }); } } }); @@ -3327,8 +3422,8 @@ var handleHandMessages = function(channel, message, sender) { selectedController.release(); var wearableEntity = data.entityID; entityPropertiesCache.addEntity(wearableEntity); - selectedController.grabbedEntity = wearableEntity; - var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedEntity); + selectedController.grabbedThingID = wearableEntity; + var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedThingID); if (hotspots.length > 0) { if (hotspotIndex >= hotspots.length) { hotspotIndex = 0; diff --git a/scripts/system/controllers/squeezeHands.js b/scripts/system/controllers/squeezeHands.js index 3f1d21b46c..75e6249dd6 100644 --- a/scripts/system/controllers/squeezeHands.js +++ b/scripts/system/controllers/squeezeHands.js @@ -25,10 +25,13 @@ var OVERLAY_RAMP_RATE = 8.0; var animStateHandlerID; -var isPointingIndex = false; +var isBothIndexesPointing = false; var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; -var indexfingerJointNames = ["LeftHandIndex1", "LeftHandIndex2", "LeftHandIndex3", "RightHandIndex1", "RightHandIndex2", "RightHandIndex3"]; +var isLeftIndexPointing = false; +var isRightIndexPointing = false; +var isLeftThumbRaised = false; +var isRightThumbRaised = false; function clamp(val, min, max) { return Math.min(Math.max(val, min), max); @@ -46,17 +49,32 @@ function init() { Script.update.connect(update); animStateHandlerID = MyAvatar.addAnimationStateHandler( animStateHandler, - ["leftHandOverlayAlpha", "rightHandOverlayAlpha", "leftHandGraspAlpha", "rightHandGraspAlpha"] + [ + "leftHandOverlayAlpha", "leftHandGraspAlpha", + "rightHandOverlayAlpha", "rightHandGraspAlpha", + "isLeftHandGrasp", "isLeftIndexPoint", "isLeftThumbRaise", "isLeftIndexPointAndThumbRaise", + "isRightHandGrasp", "isRightIndexPoint", "isRightThumbRaise", "isRightIndexPointAndThumbRaise", + ] ); Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.messageReceived.connect(handleMessages); } function animStateHandler(props) { - return { leftHandOverlayAlpha: leftHandOverlayAlpha, - leftHandGraspAlpha: lastLeftTrigger, - rightHandOverlayAlpha: rightHandOverlayAlpha, - rightHandGraspAlpha: lastRightTrigger }; + return { + leftHandOverlayAlpha: leftHandOverlayAlpha, + leftHandGraspAlpha: lastLeftTrigger, + rightHandOverlayAlpha: rightHandOverlayAlpha, + rightHandGraspAlpha: lastRightTrigger, + isLeftHandGrasp: !isBothIndexesPointing && !isLeftIndexPointing && !isLeftThumbRaised, + isLeftIndexPoint: (isBothIndexesPointing || isLeftIndexPointing) && !isLeftThumbRaised, + isLeftThumbRaise: !isBothIndexesPointing && !isLeftIndexPointing && isLeftThumbRaised, + isLeftIndexPointAndThumbRaise: (isBothIndexesPointing || isLeftIndexPointing) && isLeftThumbRaised, + isRightHandGrasp: !isBothIndexesPointing && !isRightIndexPointing && !isRightThumbRaised, + isRightIndexPoint: (isBothIndexesPointing || isRightIndexPointing) && !isRightThumbRaised, + isRightThumbRaise: !isBothIndexesPointing && !isRightIndexPointing && isRightThumbRaised, + isRightIndexPointAndThumbRaise: (isBothIndexesPointing || isRightIndexPointing) && isRightThumbRaised + }; } function update(dt) { @@ -84,13 +102,11 @@ function update(dt) { rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1); } - // Point index finger. - if (isPointingIndex) { - var zeroRotation = { x: 0, y: 0, z: 0, w: 1 }; - for (var i = 0; i < indexfingerJointNames.length; i++) { - MyAvatar.setJointRotation(indexfingerJointNames[i], zeroRotation); - } - } + // Pointing index fingers and raising thumbs + isLeftIndexPointing = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftIndexPoint) === 1; + isRightIndexPointing = rightHandPose.valid && Controller.getValue(Controller.Standard.RightIndexPoint) === 1; + isLeftThumbRaised = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftThumbUp) === 1; + isRightThumbRaised = rightHandPose.valid && Controller.getValue(Controller.Standard.RightThumbUp) === 1; } function handleMessages(channel, message, sender) { @@ -98,13 +114,7 @@ function handleMessages(channel, message, sender) { var data = JSON.parse(message); if (data.pointIndex !== undefined) { print("pointIndex: " + data.pointIndex); - isPointingIndex = data.pointIndex; - - if (!isPointingIndex) { - for (var i = 0; i < indexfingerJointNames.length; i++) { - MyAvatar.clearJointData(indexfingerJointNames[i]); - } - } + isBothIndexesPointing = data.pointIndex; } } } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index da39edf8ba..ad3af3a659 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -564,6 +564,11 @@ function findClickedEntity(event) { var pickRay = Camera.computePickRay(event.x, event.y); + var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); + if (overlayResult.intersects) { + return null; + } + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking var lightResult = lightOverlayManager.findRayIntersection(pickRay); lightResult.accurate = true; diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 367ef05aea..dd2aaf346b 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -7,8 +7,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global getControllerWorldLocation, setEntityCustomData, Tablet, WebTablet:true, HMD, Settings, Script, - Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp */ +/* global getControllerWorldLocation, Tablet, WebTablet:true, HMD, Settings, Script, + Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4 */ Script.include(Script.resolvePath("../libraries/utils.js")); Script.include(Script.resolvePath("../libraries/controllers.js")); @@ -34,7 +34,7 @@ var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; -// var TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; +var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; // returns object with two fields: // * position - position in front of the user @@ -112,10 +112,19 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { this.dpi = DEFAULT_DPI * (DEFAULT_WIDTH / this.width); } + var modelURL; + if (Settings.getValue("tabletVisibleToOthers")) { + modelURL = TABLET_MODEL_PATH; + } else { + modelURL = LOCAL_TABLET_MODEL_PATH; + } + var tabletProperties = { name: "WebTablet Tablet", type: "Model", - modelURL: TABLET_MODEL_PATH, + modelURL: modelURL, + url: modelURL, // for overlay + grabbable: true, // for overlay userData: JSON.stringify({ "grabbableKey": {"grabbable": true} }), @@ -127,7 +136,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { this.calculateTabletAttachmentProperties(hand, true, tabletProperties); this.cleanUpOldTablets(); - this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly); + + if (Settings.getValue("tabletVisibleToOthers")) { + this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly); + this.tabletIsOverlay = false; + } else { + this.tabletEntityID = Overlays.addOverlay("model", tabletProperties); + this.tabletIsOverlay = true; + } if (this.webOverlayID) { Overlays.deleteOverlay(this.webOverlayID); @@ -151,12 +167,12 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { isAA: HMD.active }); - var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.035; - this.homeButtonEntity = Overlays.addOverlay("sphere", { + 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, @@ -165,7 +181,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { }); this.receive = function (channel, senderID, senderUUID, localOnly) { - if (_this.homeButtonEntity == senderID) { + if (_this.homeButtonID == senderID) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var onHomeScreen = tablet.onHomeScreen(); if (onHomeScreen) { @@ -184,7 +200,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { }; this.getLocation = function() { - return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]); + if (this.tabletIsOverlay) { + var location = Overlays.getProperty(this.tabletEntityID, "localPosition"); + var orientation = Overlays.getProperty(this.tabletEntityID, "localOrientation"); + return { + localPosition: location, + localRotation: orientation + }; + } else { + return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]); + } }; this.clicked = false; @@ -242,8 +267,12 @@ WebTablet.prototype.getOverlayObject = function () { WebTablet.prototype.destroy = function () { Overlays.deleteOverlay(this.webOverlayID); - Entities.deleteEntity(this.tabletEntityID); - Overlays.deleteOverlay(this.homeButtonEntity); + if (this.tabletIsOverlay) { + Overlays.deleteOverlay(this.tabletEntityID); + } else { + Entities.deleteEntity(this.tabletEntityID); + } + Overlays.deleteOverlay(this.homeButtonID); HMD.displayModeChanged.disconnect(this.myOnHmdChanged); Controller.mousePressEvent.disconnect(this.myMousePressEvent); @@ -426,10 +455,16 @@ WebTablet.prototype.getPosition = function () { WebTablet.prototype.mousePressEvent = function (event) { var pickRay = Camera.computePickRay(event.x, event.y); - var entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]); // non-accurate picking - if (entityPickResults.intersects && entityPickResults.entityID === this.tabletEntityID) { - var overlayPickResults = Overlays.findRayIntersection(pickRay); - if (overlayPickResults.intersects && overlayPickResults.overlayID === HMD.homeButtonID) { + var entityPickResults; + if (this.tabletIsOverlay) { + entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); + } else { + entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]); + } + if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || + entityPickResults.overlayID === this.tabletEntityID)) { + var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID], []); + if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var onHomeScreen = tablet.onHomeScreen(); if (onHomeScreen) { @@ -438,11 +473,15 @@ WebTablet.prototype.mousePressEvent = function (event) { tablet.gotoHomeScreen(); this.setHomeButtonTexture(); } - } else if (!HMD.active && (!overlayPickResults.intersects || !overlayPickResults.overlayID === this.webOverlayID)) { + } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) { this.dragging = true; var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection); - this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; + if (this.tabletIsOverlay) { + this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); + } else { + this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; + } } } }; @@ -488,9 +527,15 @@ WebTablet.prototype.mouseMoveEvent = function (event) { var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance)); var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint); var localPosition = Vec3.sum(this.initialLocalPosition, localOffset); - Entities.editEntity(this.tabletEntityID, { - localPosition: localPosition - }); + if (this.tabletIsOverlay) { + Overlays.editOverlay(this.tabletEntityID, { + localPosition: localPosition + }); + } else { + Entities.editEntity(this.tabletEntityID, { + localPosition: localPosition + }); + } } } }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index b9bae72d14..9c1626caf4 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -3866,6 +3866,12 @@ SelectionDisplay = (function() { var somethingClicked = false; var pickRay = generalComputePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); + if (result.intersects) { + // mouse clicks on the tablet should override the edit affordances + return false; + } + // before we do a ray test for grabbers, disable the ray intersection for our selection box Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 36ecc1f084..9df4b2df92 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -37,6 +37,15 @@ var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); +function projectVectorOntoPlane(normalizedVector, planeNormal) { + return Vec3.cross(planeNormal, Vec3.cross(normalizedVector, planeNormal)); +} +function angleBetweenVectorsInPlane(from, to, normal) { + var projectedFrom = projectVectorOntoPlane(from, normal); + var projectedTo = projectVectorOntoPlane(to, normal); + return Vec3.orientedAngle(projectedFrom, projectedTo, normal); +} + // // Overlays. // @@ -229,7 +238,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'refresh': removeOverlays(); - populateUserList(message.params); + // If filter is specified from .qml instead of through settings, update the settings. + if (message.params.filter !== undefined) { + Settings.setValue('pal/filtered', !!message.params.filter); + } + populateUserList(message.params.selected); UserActivityLogger.palAction("refresh", ""); break; case 'updateGain': @@ -271,13 +284,42 @@ function addAvatarNode(id) { color: color(selected, false, 0.0), ignoreRayIntersection: false}, selected, !conserveResources); } +// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter. +var avatarsOfInterest = {}; function populateUserList(selectData) { + var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')}; var data = [], avatars = AvatarList.getAvatarIdentifiers(); - conserveResources = avatars.length > 20; + avatarsOfInterest = {}; + var myPosition = filter && Camera.position, + frustum = filter && Camera.frustum, + verticalHalfAngle = filter && (frustum.fieldOfView / 2), + horizontalHalfAngle = filter && (verticalHalfAngle * frustum.aspectRatio), + orientation = filter && Camera.orientation, + front = filter && Quat.getFront(orientation), + verticalAngleNormal = filter && Quat.getRight(orientation), + horizontalAngleNormal = filter && Quat.getUp(orientation); avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging var avatar = AvatarList.getAvatar(id); + var name = avatar.sessionDisplayName; + if (!name) { + // Either we got a data packet but no identity yet, or something is really messed up. In any case, + // we won't be able to do anything with this user, so don't include them. + // In normal circumstances, a refresh will bring in the new user, but if we're very heavily loaded, + // we could be losing and gaining people randomly. + print('No avatar identity data for', id); + return; + } + if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) { + return; + } + var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition)); + var horizontal = normal && angleBetweenVectorsInPlane(normal, front, horizontalAngleNormal); + var vertical = normal && angleBetweenVectorsInPlane(normal, front, verticalAngleNormal); + if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) { + return; + } var avatarPalDatum = { - displayName: avatar.sessionDisplayName, + displayName: name, userName: '', sessionId: id || '', audioLevel: 0.0, @@ -289,10 +331,12 @@ function populateUserList(selectData) { addAvatarNode(id); // No overlay for ourselves // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. Users.requestUsernameFromID(id); + avatarsOfInterest[id] = true; } data.push(avatarPalDatum); print('PAL data:', JSON.stringify(avatarPalDatum)); }); + conserveResources = Object.keys(avatarsOfInterest).length > 20; sendToQml({ method: 'users', params: data }); if (selectData) { selectData[2] = true; @@ -317,8 +361,8 @@ var pingPong = true; function updateOverlays() { var eye = Camera.position; AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id) { - return; // don't update ourself + if (!id || !avatarsOfInterest[id]) { + return; // don't update ourself, or avatars we're not interested in } var avatar = AvatarList.getAvatar(id); if (!avatar) { diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 632cb40bb5..d5065cb826 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -26,12 +26,19 @@ 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.homeButtonEntity; + HMD.homeButtonID = UIWebTablet.homeButtonID; + HMD.tabletScreenID = UIWebTablet.webOverlayID; } function hideTabletUI() { @@ -48,6 +55,7 @@ UIWebTablet = null; HMD.tabletID = null; HMD.homeButtonID = null; + HMD.tabletScreenID = null; } } @@ -77,7 +85,7 @@ hideTabletUI(); HMD.closeTablet(); } else if (HMD.showTablet && !tabletShown && !toolbarMode) { - UserActivityLogger.openedTablet(); + UserActivityLogger.openedTablet(Settings.getValue("tabletVisibleToOthers")); showTabletUI(); } else if (!HMD.showTablet && tabletShown) { UserActivityLogger.closedTablet(); @@ -126,5 +134,6 @@ Entities.deleteEntity(HMD.tabletID); HMD.tabletID = null; HMD.homeButtonID = null; + HMD.tabletScreenID = null; }); }()); // END LOCAL_SCOPE 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; + } +};