diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 568418afe1..d65612351c 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -571,7 +571,9 @@ Function HandlePostInstallOptions ; both launches use the explorer trick in case the user has elevated permissions for the installer ; it won't be possible to use this approach if either application should be launched with a command line param ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' + ; create shortcut with ARGUMENTS + CreateShortCut "$TEMP\SandboxShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface" + Exec '"$WINDIR\explorer.exe" "$TEMP\SandboxShortcut.lnk"' ${Else} Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' ${EndIf} diff --git a/cmake/templates/VersionInfo.rc.in b/cmake/templates/VersionInfo.rc.in new file mode 100644 index 0000000000..ad192ed87d --- /dev/null +++ b/cmake/templates/VersionInfo.rc.in @@ -0,0 +1,22 @@ +// Language and character set information as described at +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381049(v=vs.85).aspx +#define US_ENGLISH_UNICODE "040904B0" + +// More information about the format of this file can be found at +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx +1 VERSIONINFO +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK US_ENGLISH_UNICODE + BEGIN + VALUE "CompanyName", "@BUILD_ORGANIZATION@" + VALUE "FileDescription", "@APP_FULL_NAME@" + VALUE "FileVersion", "@BUILD_VERSION@" + VALUE "InternalName", "@TARGET_NAME@" + VALUE "OriginalFilename", "@TARGET_NAME@.exe" + VALUE "ProductName", "@APP_FULL_NAME@" + VALUE "ProductVersion", "@BUILD_VERSION@" + END + END +END diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index bf9d7d04a6..abe7ed176a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -237,6 +237,7 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; + userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; } else { // this node is an agent const QHostAddress& addr = node->getLocalSocket().getAddress(); @@ -312,6 +313,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; + userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; newNode->setPermissions(userPerms); return newNode; } diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b43376c374..131c4ee509 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -133,8 +133,12 @@ elseif (WIN32) set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc") configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT}) + set(APP_FULL_NAME "High Fidelity Interface") + set(CONFIGURE_VERSION_INFO_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.rc") + configure_file("${HF_CMAKE_DIR}/templates/VersionInfo.rc.in" ${CONFIGURE_VERSION_INFO_RC_OUTPUT}) + # add an executable that also has the icon itself and the configured rc file as resources - add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT}) + add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT} ${CONFIGURE_VERSION_INFO_RC_OUTPUT}) if (NOT DEV_BUILD) add_custom_command( diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index ca3a2da577..5e05601ce4 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -20,10 +20,10 @@ ScrollingWindow { anchors.centerIn: parent UpdateDialog { id: updateDialog - + implicitWidth: backgroundRectangle.width implicitHeight: backgroundRectangle.height - + readonly property int contentWidth: 500 readonly property int logoSize: 60 readonly property int borderWidth: 30 @@ -36,7 +36,7 @@ ScrollingWindow { signal triggerBuildDownload signal closeUpdateDialog - + Rectangle { id: backgroundRectangle color: "#ffffff" @@ -47,7 +47,7 @@ ScrollingWindow { Image { id: logo - source: "../images/interface-logo.svg" + source: "../images/hifi-logo.svg" width: updateDialog.logoSize height: updateDialog.logoSize anchors { @@ -65,7 +65,7 @@ ScrollingWindow { topMargin: updateDialog.borderWidth top: parent.top } - + Rectangle { id: header width: parent.width - updateDialog.logoSize - updateDialog.inputSpacing diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 8c1b78af79..70eab82910 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -113,9 +113,8 @@ Rectangle { } FiraSansRegular { id: users; - visible: action === 'concurrency'; - text: onlineUsers; - size: textSize; + text: (action === 'concurrency') ? onlineUsers : 'snapshot'; + size: (action === 'concurrency') ? textSize : textSizeSmall; color: hifi.colors.white; anchors { verticalCenter: usersImage.verticalCenter; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 351c09beee..a379ebd80a 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -118,11 +118,26 @@ void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& ro } } +void AnimSkeleton::saveNonMirroredPoses(const AnimPoseVec& poses) const { + _nonMirroredPoses.clear(); + for (int i = 0; i < (int)_nonMirroredIndices.size(); ++i) { + _nonMirroredPoses.push_back(poses[_nonMirroredIndices[i]]); + } +} + +void AnimSkeleton::restoreNonMirroredPoses(AnimPoseVec& poses) const { + for (int i = 0; i < (int)_nonMirroredIndices.size(); ++i) { + int index = _nonMirroredIndices[i]; + poses[index] = _nonMirroredPoses[i]; + } +} void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const { + saveNonMirroredPoses(poses); convertRelativePosesToAbsolute(poses); mirrorAbsolutePoses(poses); convertAbsolutePosesToRelative(poses); + restoreNonMirroredPoses(poses); } void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { @@ -189,8 +204,14 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) } // build mirror map. + _nonMirroredIndices.clear(); _mirrorMap.reserve(_joints.size()); for (int i = 0; i < (int)joints.size(); i++) { + if (_joints[i].name.endsWith("tEye")) { + // HACK: we don't want to mirror some joints so we remember their indices + // so we can restore them after a future mirror operation + _nonMirroredIndices.push_back(i); + } int mirrorJointIndex = -1; if (_joints[i].name.startsWith("Left")) { QString mirrorJointName = QString(_joints[i].name).replace(0, 4, "Right"); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 68cce11326..e1c6ae95c8 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -57,6 +57,9 @@ public: void convertAbsoluteRotationsToRelative(std::vector& rotations) const; + void saveNonMirroredPoses(const AnimPoseVec& poses) const; + void restoreNonMirroredPoses(AnimPoseVec& poses) const; + void mirrorRelativePoses(AnimPoseVec& poses) const; void mirrorAbsolutePoses(AnimPoseVec& poses) const; @@ -75,6 +78,8 @@ protected: AnimPoseVec _absoluteDefaultPoses; AnimPoseVec _relativePreRotationPoses; AnimPoseVec _relativePostRotationPoses; + mutable AnimPoseVec _nonMirroredPoses; + std::vector _nonMirroredIndices; std::vector _mirrorMap; // no copies diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5208b893ac..062991c187 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -85,18 +85,26 @@ public: } void beforeAboutToQuit() { + Lock lock(_checkDevicesMutex); _quit = true; } void run() override { - while (!_quit) { + while (true) { + { + Lock lock(_checkDevicesMutex); + if (_quit) { + break; + } + _audioClient->checkDevices(); + } QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); - _audioClient->checkDevices(); } } private: AudioClient* _audioClient { nullptr }; + Mutex _checkDevicesMutex; bool _quit { false }; }; diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 5380aaa6ce..5fd7e4dba3 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -451,6 +451,9 @@ bool OctreePacketData::appendValue(const QVector& value) { bit = 0; } } + if (bit != 0) { + destinationBuffer++; + } int boolsSize = destinationBuffer - start; success = append(start, boolsSize); if (success) { @@ -683,6 +686,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(glm::vec3)); return sizeof(uint16_t) + length * sizeof(glm::vec3); @@ -692,6 +699,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); const unsigned char *start = dataBytes; @@ -706,6 +717,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(float)); return sizeof(uint16_t) + length * sizeof(float); @@ -715,6 +730,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); int bit = 0; diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index 02f92d87e7..fb6f291e19 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -10,9 +10,15 @@ #include + #ifdef Q_OS_WIN -#include -#include +#include + +//#include +//#include + +#include +#pragma comment(lib, "dxgi.lib") #elif defined(Q_OS_MAC) #include @@ -53,9 +59,101 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) CGLDestroyRendererInfo(rendererInfo); #elif defined(Q_OS_WIN) + + struct ConvertLargeIntegerToQString { + QString convert(const LARGE_INTEGER& version) { + QString value; + value.append(QString::number(uint32_t(((version.HighPart & 0xFFFF0000) >> 16) & 0x0000FFFF))); + value.append("."); + value.append(QString::number(uint32_t((version.HighPart) & 0x0000FFFF))); + value.append("."); + value.append(QString::number(uint32_t(((version.LowPart & 0xFFFF0000) >> 16) & 0x0000FFFF))); + value.append("."); + value.append(QString::number(uint32_t((version.LowPart) & 0x0000FFFF))); + return value; + } + } convertDriverVersionToString; + + // Create the DXGI factory + // Let s get into DXGI land: + HRESULT hr = S_OK; + + IDXGIFactory1* pFactory = nullptr; + hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory) ); + if (hr != S_OK || pFactory == nullptr) { + qCDebug(shared) << "Unable to create DXGI"; + return this; + } + + std::vector validAdapterList; + using AdapterEntry = std::pair, std::vector>; + std::vector adapterToOutputs; + // Enumerate adapters and outputs + { + UINT adapterNum = 0; + IDXGIAdapter1* pAdapter = nullptr; + while (pFactory->EnumAdapters1(adapterNum, &pAdapter) != DXGI_ERROR_NOT_FOUND) { + + // Found an adapter, get descriptor + DXGI_ADAPTER_DESC1 adapterDesc; + pAdapter->GetDesc1(&adapterDesc); + + LARGE_INTEGER version; + hr = pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version); + + std::wstring wDescription (adapterDesc.Description); + std::string description(wDescription.begin(), wDescription.end()); + qCDebug(shared) << "Found adapter: " << description.c_str() + << " Driver version: " << convertDriverVersionToString.convert(version); + + AdapterEntry adapterEntry; + adapterEntry.first.first = adapterDesc; + adapterEntry.first.second = version; + + + + UINT outputNum = 0; + IDXGIOutput * pOutput; + bool hasOutputConnectedToDesktop = false; + while (pAdapter->EnumOutputs(outputNum, &pOutput) != DXGI_ERROR_NOT_FOUND) { + + // FOund an output attached to the adapter, get descriptor + DXGI_OUTPUT_DESC outputDesc; + pOutput->GetDesc(&outputDesc); + + adapterEntry.second.push_back(outputDesc); + + std::wstring wDeviceName(outputDesc.DeviceName); + std::string deviceName(wDeviceName.begin(), wDeviceName.end()); + qCDebug(shared) << " Found output: " << deviceName.c_str() << " desktop: " << (outputDesc.AttachedToDesktop ? "true" : "false") + << " Rect [ l=" << outputDesc.DesktopCoordinates.left << " r=" << outputDesc.DesktopCoordinates.right + << " b=" << outputDesc.DesktopCoordinates.bottom << " t=" << outputDesc.DesktopCoordinates.top << " ]"; + + hasOutputConnectedToDesktop |= (bool) outputDesc.AttachedToDesktop; + + pOutput->Release(); + outputNum++; + } + + adapterToOutputs.push_back(adapterEntry); + + // add this adapter to the valid list if has output + if (hasOutputConnectedToDesktop && !adapterEntry.second.empty()) { + validAdapterList.push_back(adapterNum); + } + + pAdapter->Release(); + adapterNum++; + } + } + pFactory->Release(); + + + // THis was the previous technique used to detect the platform we are running on on windows. + /* // COM must be initialized already using CoInitialize. E.g., by the audio subsystem. CComPtr spLoc = NULL; - HRESULT hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, (LPVOID *)&spLoc); + hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, (LPVOID *)&spLoc); if (hr != S_OK || spLoc == NULL) { qCDebug(shared) << "Unable to connect to WMI"; return this; @@ -139,7 +237,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) var.ChangeType(CIM_UINT64); // We're going to receive some integral type, but it might not be uint. // We might be hosed here. The parameter is documented to be UINT32, but that's only 4 GB! const ULONGLONG BYTES_PER_MEGABYTE = 1024 * 1024; - _dedicatedMemoryMB = (uint) (var.ullVal / BYTES_PER_MEGABYTE); + _dedicatedMemoryMB = (uint64_t) (var.ullVal / BYTES_PER_MEGABYTE); } else { qCDebug(shared) << "Unable to get video AdapterRAM"; @@ -149,6 +247,22 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) } hr = spEnumInst->Next(WBEM_INFINITE, 1, &spInstance.p, &uNumOfInstances); } + */ + + if (!validAdapterList.empty()) { + auto& adapterEntry = adapterToOutputs[validAdapterList.front()]; + + std::wstring wDescription(adapterEntry.first.first.Description); + std::string description(wDescription.begin(), wDescription.end()); + _name = QString(description.c_str()); + + _driver = convertDriverVersionToString.convert(adapterEntry.first.second); + + const ULONGLONG BYTES_PER_MEGABYTE = 1024 * 1024; + _dedicatedMemoryMB = (uint64_t)(adapterEntry.first.first.DedicatedVideoMemory / BYTES_PER_MEGABYTE); + _isValid = true; + } + #endif return this; } diff --git a/libraries/shared/src/GPUIdent.h b/libraries/shared/src/GPUIdent.h index 4e844b0e54..8615e61b08 100644 --- a/libraries/shared/src/GPUIdent.h +++ b/libraries/shared/src/GPUIdent.h @@ -14,17 +14,19 @@ #ifndef hifi_GPUIdent_h #define hifi_GPUIdent_h +#include + class GPUIdent { public: - unsigned int getMemory() { return _dedicatedMemoryMB; } + uint64_t getMemory() { return _dedicatedMemoryMB; } QString getName() { return _name; } QString getDriver() { return _driver; } bool isValid() { return _isValid; } // E.g., GPUIdent::getInstance()->getMemory(); static GPUIdent* getInstance(const QString& vendor = "", const QString& renderer = "") { return _instance.ensureQuery(vendor, renderer); } private: - uint _dedicatedMemoryMB { 0 }; + uint64_t _dedicatedMemoryMB { 0 }; QString _name { "" }; QString _driver { "" }; bool _isQueried { false }; diff --git a/server-console/packager.js b/server-console/packager.js index bc3b8989d2..89bcd7cb71 100644 --- a/server-console/packager.js +++ b/server-console/packager.js @@ -37,7 +37,7 @@ if (osType == "Darwin") { } else if (osType == "Windows_NT") { options["version-string"] = { CompanyName: "High Fidelity, Inc.", - FileDescription: SHORT_NAME, + FileDescription: FULL_NAME, ProductName: FULL_NAME, OriginalFilename: EXEC_NAME + ".exe" } diff --git a/server-console/src/main.js b/server-console/src/main.js index d8a6f30ac1..6c82230601 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -868,6 +868,12 @@ function onContentLoaded() { homeServer.start(); } + // If we were launched with the launchInterface option, then we need to launch interface now + if (argv.launchInterface) { + log.debug("Interface launch requested... argv.launchInterface:", argv.launchInterface); + startInterface(); + } + // If we were launched with the shutdownWatcher option, then we need to watch for the interface app // shutting down. The interface app will regularly update a running state file which we will check. // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. diff --git a/tutorial/tutorial.js b/tutorial/tutorial.js index 4908465779..3d2b4ce36b 100644 --- a/tutorial/tutorial.js +++ b/tutorial/tutorial.js @@ -715,7 +715,8 @@ var stepTurnAround = function(name) { this.tempTag = name + "-temporary"; this.onActionBound = this.onAction.bind(this); - this.numTimesTurnPressed = 0; + this.numTimesSnapTurnPressed = 0; + this.numTimesSmoothTurnPressed = 0; } stepTurnAround.prototype = { start: function(onFinish) { @@ -724,19 +725,26 @@ stepTurnAround.prototype = { showEntitiesWithTag(this.tag); - this.numTimesTurnPressed = 0; + this.numTimesSnapTurnPressed = 0; + this.numTimesSmoothTurnPressed = 0; + this.smoothTurnDown = false; Controller.actionEvent.connect(this.onActionBound); this.interval = Script.setInterval(function() { - debug("TurnAround | Checking if finished", this.numTimesTurnPressed); + debug("TurnAround | Checking if finished", + this.numTimesSnapTurnPressed, this.numTimesSmoothTurnPressed); var FORWARD_THRESHOLD = 90; - var REQ_NUM_TIMES_PRESSED = 3; + var REQ_NUM_TIMES_SNAP_TURN_PRESSED = 3; + var REQ_NUM_TIMES_SMOOTH_TURN_PRESSED = 2; var dir = Quat.getFront(MyAvatar.orientation); var angle = Math.atan2(dir.z, dir.x); var angleDegrees = ((angle / Math.PI) * 180); - if (this.numTimesTurnPressed >= REQ_NUM_TIMES_PRESSED && Math.abs(angleDegrees) < FORWARD_THRESHOLD) { + var hasTurnedEnough = this.numTimesSnapTurnPressed >= REQ_NUM_TIMES_SNAP_TURN_PRESSED + || this.numTimesSmoothTurnPressed >= REQ_NUM_TIMES_SMOOTH_TURN_PRESSED; + var facingForward = Math.abs(angleDegrees) < FORWARD_THRESHOLD + if (hasTurnedEnough && facingForward) { Script.clearInterval(this.interval); this.interval = null; playSuccessSound(); @@ -746,9 +754,19 @@ stepTurnAround.prototype = { }, onAction: function(action, value) { var STEP_YAW_ACTION = 6; + var SMOOTH_YAW_ACTION = 4; + if (action == STEP_YAW_ACTION && value != 0) { - debug("TurnAround | Got yaw action"); - this.numTimesTurnPressed += 1; + debug("TurnAround | Got step yaw action"); + ++this.numTimesSnapTurnPressed; + } else if (action == SMOOTH_YAW_ACTION) { + debug("TurnAround | Got smooth yaw action"); + if (this.smoothTurnDown && value === 0) { + this.smoothTurnDown = false; + ++this.numTimesSmoothTurnPressed; + } else if (!this.smoothTurnDown && value !== 0) { + this.smoothTurnDown = true; + } } }, cleanup: function() { @@ -971,6 +989,7 @@ TutorialManager = function() { var currentStep = null; var startedTutorialAt = 0; var startedLastStepAt = 0; + var didFinishTutorial = false; var wentToEntryStepNum; var VERSION = 1; @@ -1032,6 +1051,7 @@ TutorialManager = function() { info("DONE WITH TUTORIAL"); currentStepNum = -1; currentStep = null; + didFinishTutorial = true; return false; } else { info("Starting step", currentStepNum); @@ -1069,8 +1089,11 @@ TutorialManager = function() { // This is a message sent from the "entry" portal in the courtyard, // after the tutorial has finished. this.enteredEntryPortal = function() { - info("Got enteredEntryPortal, tracking"); - this.trackStep("wentToEntry", wentToEntryStepNum); + info("Got enteredEntryPortal"); + if (didFinishTutorial) { + info("Tracking wentToEntry"); + this.trackStep("wentToEntry", wentToEntryStepNum); + } } }