Skip to content

Commit 72831a5

Browse files
authored
Allow for tech to solo/focus incentives (#24)
* Solo works on dashboard * Implement soloing in backend * Install deps
1 parent ef42c51 commit 72831a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3493
-499
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"html-webpack-plugin": "^5.6.0",
106106
"markdown-it": "^14.1.0",
107107
"mini-css-extract-plugin": "^2.7.7",
108-
"nodecg-cli": "^8.6.8",
108+
"nodecg": "^2.4.3",
109109
"npm-run-all": "^4.1.5",
110110
"sass": "~1.32",
111111
"sass-loader": "^12.6.0",

pnpm-lock.yaml

+3,319-477
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schemas/soloedBidID.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"anyOf": [
4+
{
5+
"type": "number"
6+
},
7+
{
8+
"type": "null"
9+
}
10+
],
11+
"default": null
12+
}

src/browser_shared/replicant_store.ts

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import Vue from 'vue';
3737
import type { Store } from 'vuex';
3838
import { namespace } from 'vuex-class';
3939
import { Module, Mutation, VuexModule, getModule } from 'vuex-module-decorators';
40+
import { soloedBidID } from '@esa-layouts/extension/util/replicants';
41+
import { SoloedBidID } from '@esa-layouts/types/schemas/soloedBidID';
4042

4143
const sc = new SpeedcontrolUtilBrowser(nodecg);
4244

@@ -111,6 +113,7 @@ export const reps: {
111113
runDataActiveRunSurrounding: sc.runDataActiveRunSurrounding,
112114
runDataArray: sc.runDataArray,
113115
serverTimestamp: nodecg.Replicant('serverTimestamp'),
116+
soloedBidID: nodecg.Replicant('soloedBidID'),
114117
streamDeckData: nodecg.Replicant('streamDeckData'),
115118
timer: sc.timer,
116119
ttsVoices: nodecg.Replicant('ttsVoices'),
@@ -151,6 +154,7 @@ export interface ReplicantTypes {
151154
runDataActiveRunSurrounding: RunDataActiveRunSurrounding;
152155
runDataArray: RunDataArray;
153156
serverTimestamp: ServerTimestamp;
157+
soloedBidID: SoloedBidID;
154158
streamDeckData: StreamDeckData;
155159
timer: Timer;
156160
ttsVoices: TtsVoices;

src/dashboard/bids/components/Bid.vue

+21-1
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
<template v-if="isPinned">mdi-pin-off</template>
1616
<template v-else>mdi-pin</template>
1717
</v-icon>
18+
<v-spacer />
19+
<v-btn elevation="2" :color="isSoloed ? 'warning' : ''" @click="toggleSolo">
20+
<v-icon>
21+
<template v-if="isSoloed">mdi-crosshairs-gps</template>
22+
<template v-else>mdi-crosshairs</template>
23+
</v-icon>
24+
</v-btn>
1825
</media-card>
1926
</template>
2027

2128
<script lang="ts">
2229
import { Vue, Component, Prop } from 'vue-property-decorator';
2330
import MediaCard from '@esa-layouts/dashboard/_misc/components/MediaCard.vue';
24-
import { Bids, Omnibar } from '@esa-layouts/types/schemas';
31+
import { Bids, Omnibar, SoloedBidID } from '@esa-layouts/types/schemas';
2532
import { replicantNS } from '@esa-layouts/browser_shared/replicant_store';
2633
import { storeModule } from '../store';
2734
@@ -34,13 +41,26 @@ export default class extends Vue {
3441
@Prop({ type: Object, required: true }) readonly bid!: Bids[0];
3542
@Prop({ type: Number, required: true }) readonly index!: number;
3643
@replicantNS.State((s) => s.reps.omnibar.pin) readonly currentPin!: Omnibar['pin'];
44+
@replicantNS.State((s) => s.reps.soloedBidID) readonly soloedBidID!: SoloedBidID;
3745
3846
get isPinned(): boolean {
3947
return this.currentPin?.type === 'Bid' && this.currentPin.id === this.bid.id;
4048
}
4149
50+
get isSoloed(): boolean {
51+
return this.soloedBidID === this.bid.id;
52+
}
53+
4254
pin(): void {
4355
storeModule.pinItem({ id: this.bid.id, pinned: !this.isPinned });
4456
}
57+
58+
toggleSolo(): void {
59+
if (this.isSoloed) {
60+
storeModule.clearSolo();
61+
} else {
62+
storeModule.setSolo(this.bid.id);
63+
}
64+
}
4565
}
4666
</script>

src/dashboard/bids/store.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { replicantModule, ReplicantModule, ReplicantTypes } from '@esa-layouts/browser_shared/replicant_store';
2-
import { Omnibar } from '@esa-layouts/types/schemas';
2+
import { Omnibar, SoloedBidID } from '@esa-layouts/types/schemas';
33
import Vue from 'vue';
44
import Vuex, { Store } from 'vuex';
55
import { getModule, Module, Mutation, VuexModule } from 'vuex-module-decorators';
@@ -20,6 +20,22 @@ class OurModule extends VuexModule {
2020
val: { ...replicantModule.repsTyped.omnibar, pin: pinned ? { type: 'Bid', id } : null },
2121
});
2222
}
23+
24+
@Mutation
25+
setSolo(id: number): void {
26+
replicantModule.setReplicant<SoloedBidID>({
27+
name: 'soloedBidID',
28+
val: id,
29+
});
30+
}
31+
32+
@Mutation
33+
clearSolo(): void {
34+
replicantModule.setReplicant<SoloedBidID>({
35+
name: 'soloedBidID',
36+
val: null,
37+
});
38+
}
2339
}
2440

2541
const store = new Store({

src/extension/omnibar.ts

+62-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ import { v4 as uuid } from 'uuid';
88
import { get as nodecg } from './util/nodecg';
99
import obs from './util/obs';
1010
import { mq } from './util/rabbitmq';
11-
import { assetsDonationAlertAssets, bids, donationAlerts, donationTotalMilestones, omnibar, prizes } from './util/replicants';
11+
import {
12+
assetsDonationAlertAssets,
13+
bids,
14+
donationAlerts,
15+
donationTotalMilestones,
16+
omnibar,
17+
prizes,
18+
soloedBidID,
19+
} from './util/replicants';
1220
import { sc } from './util/speedcontrol';
1321

1422
const config = nodecg().bundleConfig;
@@ -20,6 +28,7 @@ function filterUpcomingRuns(run: RunData): boolean {
2028

2129
// Gets next upcoming run from the cache (after refilling it if needed).
2230
let upcomingRunsCache: RunData[] = [];
31+
2332
function getUpcomingRun(): RunData | undefined {
2433
// Filter out any already passed runs (according to schedule) from cache.
2534
upcomingRunsCache = upcomingRunsCache.filter(filterUpcomingRuns);
@@ -38,6 +47,7 @@ function filterPrizes(prize: Prizes[0]): boolean {
3847

3948
// Gets next currently active prize from the cache (after refilling it if needed).
4049
let prizesCache: Prizes = [];
50+
4151
function getPrize(): Prizes[0] | undefined {
4252
// Filter out any currently inactive prizes from cache.
4353
prizesCache = prizesCache.filter(filterPrizes);
@@ -54,6 +64,20 @@ let lastBidId = -1;
5464
function getClonedBid(): Bids[0] | undefined {
5565
// Just return nothing if there are no bids to show.
5666
if (!bids.value.length) return undefined;
67+
68+
// if we have a solo, show that one
69+
if (soloedBidID.value) {
70+
const soloedBid = clone(bids.value).find((b) => b.id === soloedBidID.value);
71+
72+
// Make sure the bid id actually exists.
73+
if (soloedBid) {
74+
return soloedBid;
75+
}
76+
77+
// remove the solo if the bid was deleted
78+
soloedBidID.value = null;
79+
}
80+
5781
let filtered = clone(bids.value).filter((b) => b.id !== lastBidId);
5882
if (!filtered.length) filtered = clone(bids.value);
5983
const choices = filtered.reduce<{ bid: Bids[0], cumulativeWeight: number }[]>((prev, bid) => {
@@ -75,6 +99,7 @@ function getClonedBid(): Bids[0] | undefined {
7599

76100
// Gets a random active milestone.
77101
let lastMilestoneId = '';
102+
78103
function getMilestone(): DonationTotalMilestones[0] | undefined {
79104
const active = clone(donationTotalMilestones.value).filter((m) => m.enabled && m.amount);
80105
// Just return nothing if there are no active milestones to show.
@@ -88,6 +113,7 @@ function getMilestone(): DonationTotalMilestones[0] | undefined {
88113
}
89114

90115
let loopsWithoutResult = 0;
116+
91117
async function showNext(): Promise<void> {
92118
// If there is a pin to start showing.
93119
const { pin } = omnibar.value;
@@ -130,7 +156,7 @@ async function showNext(): Promise<void> {
130156
omnibar.value.pin = null;
131157
// showNext(); This is done in the "omnibar" replicant change listener
132158
}
133-
// If there is alerts in the queue to show.
159+
// If there is alerts in the queue to show.
134160
} else if (omnibar.value.alertQueue.length) {
135161
const alert = omnibar.value.alertQueue.shift();
136162
if (alert) {
@@ -176,9 +202,15 @@ async function showNext(): Promise<void> {
176202
loopsWithoutResult += 1;
177203
if (next.type === 'UpcomingRun') {
178204
const run = getUpcomingRun();
179-
if (!run) { showNext(); return; }
180-
omnibar.value.current = { ...next,
181-
props: { ...next.props,
205+
if (!run) {
206+
showNext();
207+
return;
208+
}
209+
210+
omnibar.value.current = {
211+
...next,
212+
props: {
213+
...next.props,
182214
run,
183215
dash: {
184216
text: 'Up next',
@@ -190,13 +222,23 @@ async function showNext(): Promise<void> {
190222
};
191223
} else if (next.type === 'Prize') {
192224
const prize = getPrize();
193-
if (!prize) { showNext(); return; }
225+
if (!prize) {
226+
showNext();
227+
return;
228+
}
229+
194230
omnibar.value.current = { ...next, props: { ...next.props, prize } };
195231
} else if (next.type === 'Milestone') {
196232
const milestone = getMilestone();
197-
if (!milestone) { showNext(); return; }
198-
omnibar.value.current = { ...next,
199-
props: { ...next.props,
233+
if (!milestone) {
234+
showNext();
235+
return;
236+
}
237+
238+
omnibar.value.current = {
239+
...next,
240+
props: {
241+
...next.props,
200242
milestone,
201243
dash: {
202244
text: 'Upcoming Milestone',
@@ -207,8 +249,14 @@ async function showNext(): Promise<void> {
207249
};
208250
} else if (next.type === 'Bid') {
209251
const bid = getClonedBid();
210-
if (!bid) { showNext(); return; }
211-
omnibar.value.current = { ...next,
252+
253+
if (!bid) {
254+
showNext();
255+
return;
256+
}
257+
258+
omnibar.value.current = {
259+
...next,
212260
props: {
213261
...next.props,
214262
bid,
@@ -249,8 +297,8 @@ omnibar.on('change', (newVal, oldVal) => {
249297
// If nothing is currently being shown, and the rotation is filled from being empty,
250298
// or we get alerts in the queue, trigger the cycle to start up again.
251299
if (!newVal.current && oldVal
252-
&& ((newVal.rotation.length && !oldVal.rotation.length)
253-
|| (newVal.alertQueue.length && !oldVal.alertQueue.length))) {
300+
&& ((newVal.rotation.length && !oldVal.rotation.length)
301+
|| (newVal.alertQueue.length && !oldVal.alertQueue.length))) {
254302
showNext();
255303
}
256304

@@ -304,7 +352,7 @@ nodecg().listenFor('omnibarPlaySound', async (data: { amount?: number } | undefi
304352
sourceName: config.obs.names.sources.donationSound,
305353
});
306354
nodecg().log.debug('[Omnibar] omnibarPlaySound media restarted');
307-
// If different, explicitily set it. This also starts the playback.
355+
// If different, explicitily set it. This also starts the playback.
308356
} else {
309357
await obs.conn.send('SetSourceSettings', {
310358
sourceName: config.obs.names.sources.donationSound,

src/extension/tracker/bids.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function processRawBids(rawBids: Tracker.Bid[]): Tracker.FormattedBid[] {
3030
name: bid.fields.name,
3131
total: parseFloat(bid.fields.total),
3232
game: bid.fields.speedrun__name,
33-
category: bid.fields.speedrun__category,
33+
category: bid.fields.speedrun__category || '',
3434
endTime: bid.fields.speedrun__endtime
3535
? Date.parse(bid.fields.speedrun__endtime) : undefined,
3636
war: !bid.fields.istarget,

src/extension/util/replicants.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import {
55
BigbuttonPlayerMap,
66
CapturePositions,
77
Commentators,
8-
CommentatorsNew, Countdown,
8+
CommentatorsNew,
9+
Countdown,
910
CurrentRunDelay,
1011
DelayedTimer,
11-
DonationAlerts, DonationReader,
12-
DonationReaderNew, DonationsToRead,
12+
DonationAlerts,
13+
DonationReader,
14+
DonationReaderNew,
15+
DonationsToRead,
1316
DonationTotal,
1417
DonationTotalMilestones,
1518
GameLayouts,
@@ -25,13 +28,15 @@ import {
2528
ReaderIntroduction,
2629
ServerTimestamp,
2730
StreamDeckData,
28-
TaskmasterTimestamps, TtsVoices,
31+
TaskmasterTimestamps,
32+
TtsVoices,
2933
UpcomingRunID,
3034
VideoPlayer,
3135
} from '@esa-layouts/types/schemas';
3236
import type NodeCGTypes from '@nodecg/types';
3337
import { HoraroImportStatus, OengusImportStatus, TwitchAPIData, TwitchChannelInfo } from 'speedcontrol-util/types/schemas';
3438
import { get as nodecg } from './nodecg';
39+
import { SoloedBidID } from '@esa-layouts/types/schemas/soloedBidID';
3540

3641
/**
3742
* This is where you can declare all your replicant to import easily into other files,
@@ -71,6 +76,7 @@ export const otherStreamData = nodecg().Replicant<OtherStreamData>('otherStreamD
7176
export const prizes = nodecg().Replicant<Prizes>('prizes', { persistent: false }) as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<Prizes>;
7277
export const readerIntroduction = nodecg().Replicant<ReaderIntroduction>('readerIntroduction') as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<ReaderIntroduction>;
7378
export const serverTimestamp = nodecg().Replicant<ServerTimestamp>('serverTimestamp') as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<ServerTimestamp>;
79+
export const soloedBidID = nodecg().Replicant<SoloedBidID>('soloedBidID') as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<SoloedBidID>;
7480
export const streamDeckData = nodecg().Replicant<StreamDeckData>('streamDeckData') as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<StreamDeckData>;
7581
export const taskmasterTimestamps = nodecg().Replicant<TaskmasterTimestamps>('taskmasterTimestamps') as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<TaskmasterTimestamps>;
7682
export const ttsVoices = nodecg().Replicant<TtsVoices>('ttsVoices') as unknown as NodeCGTypes.ServerReplicantWithSchemaDefault<TtsVoices>;

src/types/schemas/additionalDonations.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/bids.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/bigbuttonPlayerMap.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/capturePositions.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/commentators.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/commentatorsNew.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/configschema.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/countdown.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/currentRunDelay.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/delayedTimer.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

src/types/schemas/donationAlerts.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* prettier-ignore */
12
/* eslint-disable */
23
/**
34
* This file was automatically generated by json-schema-to-typescript.

0 commit comments

Comments
 (0)