diff --git a/LogWatcher.cs b/LogWatcher.cs index 3adc0873..40458125 100644 --- a/LogWatcher.cs +++ b/LogWatcher.cs @@ -1,4 +1,4 @@ -// Copyright(c) 2019 pypy. All rights reserved. +// Copyright(c) 2019 pypy. All rights reserved. // // This work is licensed under the terms of the MIT license. // For a copy, see . @@ -28,6 +28,7 @@ namespace VRCX private readonly List m_LogList; private Thread m_Thread; private bool m_ResetLog; + private static bool ShaderKeywordsLimitReached = false; // NOTE // FileSystemWatcher() is unreliable @@ -189,7 +190,10 @@ namespace VRCX { offset += 2; if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) == true || - ParseLogLocation(fileInfo, logContext, line, offset) == true) + ParseLogLocation(fileInfo, logContext, line, offset) == true || + ParseLogPortalSpawn(fileInfo, logContext, line, offset) == true || + ParseLogJoinBlocked(fileInfo, logContext, line, offset) == true || + ParseLogVideoError(fileInfo, logContext, line, offset) == true || { continue; } @@ -197,7 +201,8 @@ namespace VRCX continue; } - if (ParseLogNotification(fileInfo, logContext, line, 34) == true) + if (ParseLogNotification(fileInfo, logContext, line, 34) == true || + ParseLogShaderKeywordsLimit(fileInfo, logContext, line, 34) == true) { continue; } @@ -270,6 +275,7 @@ namespace VRCX location, logContext.RecentWorldName }); + ShaderKeywordsLimitReached = false; return true; } @@ -345,6 +351,104 @@ namespace VRCX return false; } + private bool ParseLogPortalSpawn(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2021.04.06 11:25:45 Log - [Network Processing] RPC invoked ConfigurePortal on (Clone [1600004] Portals/PortalInternalDynamic) for Natsumi-sama + + if (string.Compare(line, offset, "RPC invoked ConfigurePortal on (Clone [", 0, 39, StringComparison.Ordinal) != 0) + { + return false; + } + + var pos = line.LastIndexOf("] Portals/PortalInternalDynamic) for "); + if (pos < 0) + { + return false; + } + + //var portalId = line.Substring(offset + 39, pos - (offset + 39)); + var data = line.Substring(pos + 37); + + AppendLog(new[] + { + fileInfo.Name, + ConvertLogTimeToISO8601(line), + "portal-spawn", + data + }); + + return true; + } + + private bool ParseLogShaderKeywordsLimit(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2021.04.04 12:21:06 Error - Maximum number (256) of shader keywords exceeded, keyword _TOGGLESIMPLEBLUR_ON will be ignored. + + if (string.Compare(line, offset, "Maximum number (256) of shader keywords exceeded", 0, 48, StringComparison.Ordinal) != 0) + { + return false; + } + + if (ShaderKeywordsLimitReached == true) + { + return false; + } + + AppendLog(new[] + { + fileInfo.Name, + ConvertLogTimeToISO8601(line), + "event", + "Shader Keyword Limit has been reached" + }); + ShaderKeywordsLimitReached = true; + + return true; + } + + private bool ParseLogJoinBlocked(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2021.04.07 09:34:37 Error - [Behaviour] Master is not sending any events! Moving to a new instance. + + if (string.Compare(line, offset, "Master is not sending any events! Moving to a new instance.", 0, 59, StringComparison.Ordinal) != 0) + { + return false; + } + + AppendLog(new[] + { + fileInfo.Name, + ConvertLogTimeToISO8601(line), + "event", + "Joining instance blocked by master" + }); + + return true; + } + + private bool ParseLogVideoError(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2021.04.08 06:37:45 Error - [Video Playback] ERROR: Video unavailable + // 2021.04.08 06:40:07 Error - [Video Playback] ERROR: Private video + + if (string.Compare(line, offset, "ERROR: ", 0, 7, StringComparison.Ordinal) != 0) + { + return false; + } + + var data = line.Substring(offset + 7); + + AppendLog(new[] + { + fileInfo.Name, + ConvertLogTimeToISO8601(line), + "event", + "VideoError: " + data + }); + + return true; + } + private bool ParseLogNotification(FileInfo fileInfo, LogContext logContext, string line, int offset) { // 2021.01.03 05:48:58 Log - Received Notification: < Notification from username:pypy, sender user id:usr_4f76a584-9d4b-46f6-8209-8305eb683661 to of type: friendRequest, id: not_3a8f66eb-613c-4351-bee3-9980e6b5652c, created at: 01/14/2021 15:38:40 UTC, details: {{}}, type:friendRequest, m seen:False, message: ""> received at 01/02/2021 16:48:58 UTC diff --git a/html/src/app.js b/html/src/app.js index 734ba544..38d03a74 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4365,6 +4365,12 @@ speechSynthesis.getVoices(); case 'unmute': this.speak(`${noty.sourceDisplayName} has unmuted you`); break; + case 'PortalSpawn': + this.speak(`${noty.data} has spawned a portal`); + break; + case 'Event': + this.speak(noty.data); + break; default: break; } @@ -4436,6 +4442,12 @@ speechSynthesis.getVoices(); case 'unmute': AppApi.XSNotification('VRCX', `${noty.sourceDisplayName} has unmuted you`, timeout, image); break; + case 'PortalSpawn': + AppApi.XSNotification('VRCX', `${noty.data} has spawned a portal`, timeout, image); + break; + case 'Event': + AppApi.XSNotification('VRCX', noty.data, timeout, image); + break; default: break; } @@ -4506,6 +4518,12 @@ speechSynthesis.getVoices(); case 'unmute': AppApi.DesktopNotification(noty.sourceDisplayName, `has unmuted you`, image); break; + case 'PortalSpawn': + AppApi.DesktopNotification(noty.data, `has spawned a portal`, image); + break; + case 'Event': + AppApi.DesktopNotification('Event', noty.data, ''); + break; default: break; } @@ -5757,6 +5775,23 @@ speechSynthesis.getVoices(); }; break; + case 'portal-spawn': + tableData = { + created_at: gameLog.dt, + type: 'PortalSpawn', + data: gameLog.userDisplayName + }; + break; + + + case 'event': + tableData = { + created_at: gameLog.dt, + type: 'Event', + data: gameLog.event + }; + break; + default: break; } @@ -6920,6 +6955,8 @@ speechSynthesis.getVoices(); sharedFeedFilters.noty.block = 'On'; sharedFeedFilters.noty.mute = 'On'; sharedFeedFilters.noty.unmute = 'On'; + sharedFeedFilters.noty.PortalSpawn = 'Everyone'; + sharedFeedFilters.noty.Event = 'On'; sharedFeedFilters.wrist.Location = 'On'; sharedFeedFilters.wrist.OnPlayerJoined = 'Everyone'; sharedFeedFilters.wrist.OnPlayerLeft = 'Everyone'; @@ -6942,6 +6979,8 @@ speechSynthesis.getVoices(); sharedFeedFilters.wrist.block = 'On'; sharedFeedFilters.wrist.mute = 'On'; sharedFeedFilters.wrist.unmute = 'On'; + sharedFeedFilters.wrist.PortalSpawn = 'Everyone'; + sharedFeedFilters.wrist.Event = 'On'; configRepository.setString('sharedFeedFilters', JSON.stringify(sharedFeedFilters)); } diff --git a/html/src/index.pug b/html/src/index.pug index c3b2244c..97f0572c 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -167,7 +167,7 @@ html template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="gameLogTable.filters[0].value" multiple clearable collapse-tags style="flex:1" placeholder="Filter") - el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'Notification']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'Notification', 'PortalSpawn', 'Event']" :key="type" :label="type" :value="type") el-input(v-model="gameLogTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px") el-button(type="default" @click="resetGameLog()" icon="el-icon-refresh" circle style="flex:none") el-table-column(type="expand") @@ -184,7 +184,10 @@ html el-table-column(label="Detail" prop="data") template(v-once #default="scope") location(v-if="scope.row.type === 'Location'" :location="scope.row.data[0]" :hint="scope.row.data[1]") - span.x-link(v-else-if="scope.row.type !== 'Notification'" v-text="scope.row.data" @click="lookupUser(scope.row.data)") + template(v-else-if="scope.row.type === 'Event'") + span(v-text="scope.row.data") + template(v-else-if="scope.row.type === 'Notification'") + span.x-link(v-else v-text="scope.row.data" @click="lookupUser(scope.row.data)") //- search .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'") @@ -1510,6 +1513,12 @@ html // div // span.toggle-name Unmute // toggle-switch(:options="toggleSwitchOptionsOn" group="switchNotyGrouprequestunmute" v-model="sharedFeedFilters.noty.unmute" class="toggle-switch") + div + span.toggle-name Portal Spawn + toggle-switch(:options="toggleSwitchOptionsEveryone" group="switchNotyGrouprequestPortalSpawn" v-model="sharedFeedFilters.noty.PortalSpawn" class="toggle-switch") + div + span.toggle-name Events + toggle-switch(:options="toggleSwitchOptionsOn" group="switchNotyGrouprequestEvent" v-model="sharedFeedFilters.noty.Event" class="toggle-switch") template(#footer) el-button(type="small" @click="cancelSharedFeedFilters") Cancel el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save @@ -1583,6 +1592,12 @@ html // div // span.toggle-name Unmute // toggle-switch(:options="toggleSwitchOptionsOn" group="switchWristGrouprequestunmute" v-model="sharedFeedFilters.wrist.unmute" class="toggle-switch") + div + span.toggle-name Portal Spawn + toggle-switch(:options="toggleSwitchOptionsEveryone" group="switchWristGrouprequestPortalSpawn" v-model="sharedFeedFilters.wrist.PortalSpawn" class="toggle-switch") + div + span.toggle-name Events + toggle-switch(:options="toggleSwitchOptionsOn" group="switchWristGrouprequestEvent" v-model="sharedFeedFilters.wrist.Event" class="toggle-switch") template(#footer) el-button(type="small" @click="cancelSharedFeedFilters") Cancel el-button(type="primary" size="small" @click="saveSharedFeedFilters") Save diff --git a/html/src/service/gamelog.js b/html/src/service/gamelog.js index 44438cb3..c729d5a0 100644 --- a/html/src/service/gamelog.js +++ b/html/src/service/gamelog.js @@ -28,6 +28,14 @@ function parseRawGameLog(dt, type, args) { gameLog.json = args[0]; break; + case 'portal-spawn': + gameLog.userDisplayName = args[0]; + break; + + case 'event': + gameLog.event = args[0]; + break; + default: break; } diff --git a/html/src/vr.js b/html/src/vr.js index 88e67bf1..337f7cd2 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -952,6 +952,12 @@ speechSynthesis.getVoices(); case 'unmute': text = `${noty.sourceDisplayName} has unmuted you`; break; + case 'PortalSpawn': + text = `${noty.data} has spawned a portal`; + break; + case 'Event': + text = noty.data; + break; default: break; } diff --git a/html/src/vr.pug b/html/src/vr.pug index d5e54685..a8bcdac5 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -117,17 +117,27 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | 🛑 #[span.name(v-text="feed.sourceDisplayName")] has blocked you + | 🛑 #[span.name(v-text="feed.sourceDisplayName")] div(v-else-if="feed.type === 'mute'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | 🔇 #[span.name(v-text="feed.sourceDisplayName")] has muted you + | 🔇 #[span.name(v-text="feed.sourceDisplayName")] div(v-else-if="feed.type === 'unmute'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | 🎤 #[span.name(v-text="feed.sourceDisplayName")] has unmuted you + | 🎤 #[span.name(v-text="feed.sourceDisplayName")] + div(v-else-if="feed.type === 'PortalSpawn'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | ✨ #[span.name(v-text="feed.data")] + div(v-else-if="feed.type === 'Event'" class="x-friend-item") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | 🛑 #[span.name(v-text="feed.data")] template(v-else) template(v-for="feed in wristFeed") .x-friend-item(v-if="feed.type === 'GPS'" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") @@ -240,6 +250,16 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | #[span.name(v-text="feed.sourceDisplayName")] has unmuted you + div(v-else-if="feed.type === 'PortalSpawn'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | #[span.name(v-text="feed.data")] has spawned a portal + div(v-else-if="feed.type === 'Event'" class="x-friend-item") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | Event: #[span.name(v-text="feed.data")] .x-containerbottom div(style="display:flex;flex-direction:row") template(v-if="devices.length")