buttonboxrewardses

This commit is contained in:
legop3
2026-04-18 19:11:26 -04:00
parent fe8d3df421
commit 3513a11068
26 changed files with 1103 additions and 128 deletions
@@ -0,0 +1,13 @@
module.exports = {
id: 'assignmentRoulette',
name: 'Rover Reassignment',
goal: 70,
async run(ctx) {
const moved = ctx.rerollAssignments();
ctx.sendAlert({
color: '#8bc34a',
title: 'Rover Reassignment',
message: `Reassigned ${moved} user(s).`,
});
},
};
@@ -0,0 +1,41 @@
const STEP_MS = 220;
const STEPS = 10;
module.exports = {
id: 'cameraWhiplash',
name: 'Camera Wiggle',
goal: 35,
async run(ctx) {
const rovers = ctx
.listOnlineRovers()
.filter((rover) => Boolean(rover?.cameraServo?.enabled));
if (!rovers.length) {
return;
}
let step = 0;
const timer = setInterval(() => {
step += 1;
rovers.forEach((rover) => {
const nudge = (Math.random() * 24 - 12).toFixed(2);
try {
ctx.issueCommand(String(rover.id), {
type: 'servo',
servo: { nudge: Number(nudge) },
});
} catch (err) {
ctx.logger.warn('cameraWhiplash servo failed', { roverId: rover.id, error: err.message });
}
});
if (step >= STEPS) {
clearInterval(timer);
}
}, STEP_MS);
ctx.sendAlert({
color: '#00bcd4',
title: 'Camera Wiggle',
message: `Wobble running on ${rovers.length} rover(s).`,
});
},
};
@@ -0,0 +1,32 @@
const LETTERS = 'abcdefghijklmnopqrstuvwxyz';
function randLetter() {
return LETTERS[Math.floor(Math.random() * LETTERS.length)];
}
module.exports = {
id: 'chatSpam',
name: 'Chat Spam',
goal: 50,
async run(ctx) {
const nickname = randLetter();
const messageCount = 8;
for (let i = 0; i < messageCount; i += 1) {
const len = 3 + Math.floor(Math.random() * 14);
let text = '';
for (let j = 0; j < len; j += 1) {
text += randLetter();
}
try {
ctx.sendExternalMessage({
nickname,
role: 'spectator',
roverId: null,
text,
});
} catch {
// ignore spam errors
}
}
},
};
@@ -0,0 +1,97 @@
const DURATION_MS = 60 * 1000;
let activeTimer = null;
async function stopDarkness(ctx, effect = {}) {
if (activeTimer) {
clearTimeout(activeTimer);
activeTimer = null;
}
const prevLights = Array.isArray(effect.prevLights) ? effect.prevLights : [];
await Promise.all(
prevLights.map(async (entry) => {
try {
await ctx.setHomeAssistantEntityState(entry.id, entry.state === 'on' ? 'on' : 'off');
} catch (err) {
ctx.logger.warn('darkness restore light failed', { entityId: entry.id, error: err.message });
}
}),
);
const prevNightVision = effect.prevNightVision && typeof effect.prevNightVision === 'object'
? effect.prevNightVision
: {};
Object.entries(prevNightVision).forEach(([roverId, wasOn]) => {
try {
ctx.issueCommand(String(roverId), {
type: 'nightVision',
nightVision: { action: wasOn ? 'on' : 'off' },
});
} catch (err) {
ctx.logger.warn('darkness restore nightVision failed', { roverId, error: err.message });
}
});
ctx.clearEffect('darkness');
}
function startDarkness(ctx, effect) {
if (activeTimer) {
clearTimeout(activeTimer);
activeTimer = null;
}
const endsAt = Number(effect.endsAt || Date.now() + DURATION_MS);
const remaining = Math.max(0, endsAt - Date.now());
ctx.saveEffect('darkness', effect);
activeTimer = setTimeout(() => {
stopDarkness(ctx, effect).catch((err) => {
ctx.logger.warn('darkness stop failed', { error: err.message });
});
}, remaining);
}
module.exports = {
id: 'darkness',
name: 'Darkness',
goal: 65,
async run(ctx) {
const entities = ctx.getHomeAssistantEntities();
const prevLights = entities.map((entity) => ({ id: entity.id, state: entity.state === 'on' ? 'on' : 'off' }));
await Promise.all(
entities.map(async (entity) => {
try {
await ctx.setHomeAssistantEntityState(entity.id, 'off');
} catch (err) {
ctx.logger.warn('darkness light off failed', { entityId: entity.id, error: err.message });
}
}),
);
const prevNightVision = {};
ctx.listOnlineRovers().forEach((rover) => {
const state = rover?.nightVision?.state;
const nightVisionOn = Boolean(state && state.nightVisionOn === true);
prevNightVision[String(rover.id)] = nightVisionOn;
try {
ctx.issueCommand(String(rover.id), {
type: 'nightVision',
nightVision: { action: 'off' },
});
} catch (err) {
ctx.logger.warn('darkness nightVision off failed', { roverId: rover.id, error: err.message });
}
});
const effect = { endsAt: Date.now() + DURATION_MS, prevLights, prevNightVision };
startDarkness(ctx, effect);
ctx.sendAlert({ color: '#212121', title: 'Darkness', message: 'Darkness effect active for 60 seconds.' });
},
async recover(ctx, effect) {
if (!effect || Number(effect.endsAt || 0) <= Date.now()) {
await stopDarkness(ctx, effect || {});
return;
}
startDarkness(ctx, effect);
},
};
@@ -0,0 +1,15 @@
module.exports = {
id: 'discordStalkerPing',
name: 'Stalker Ping',
goal: 150,
async run(ctx) {
ctx.publishEvent({
source: 'buttonBoxReward',
type: 'buttonBox.discordStalkerPing',
payload: {
message: 'Pinged by button box masher!!',
},
});
ctx.sendAlert({ color: '#5865f2', title: 'Stalker Ping', message: 'Sent stalker ping to Discord.' });
},
};
@@ -0,0 +1,22 @@
const DOCK_COMMAND_BASE64 = Buffer.from([143]).toString('base64');
module.exports = {
id: 'dockPanic',
name: 'All Dock',
goal: 30,
async run(ctx) {
const rovers = ctx.listOnlineRovers();
rovers.forEach((rover) => {
try {
ctx.issueCommand(String(rover.id), { type: 'raw', raw: DOCK_COMMAND_BASE64 });
} catch (err) {
ctx.logger.warn('dockPanic failed', { roverId: rover.id, error: err.message });
}
});
ctx.sendAlert({
color: '#ff9800',
title: 'All Dock',
message: `Issued seek-dock to ${rovers.length} rover(s).`,
});
},
};
@@ -0,0 +1,41 @@
const NAMES = ['x', 'q', 'z', 'm', 'n', 'p', 'k', 'r'];
const BURSTS = 14;
const TICK_MS = 180;
module.exports = {
id: 'ghostTypingSpam',
name: 'Typing Spam',
goal: 40,
async run(ctx) {
let tick = 0;
const active = new Set();
const timer = setInterval(() => {
tick += 1;
const name = NAMES[Math.floor(Math.random() * NAMES.length)] + String(Math.floor(Math.random() * 10));
const on = Math.random() > 0.35;
try {
ctx.sendExternalTyping({
nickname: name,
role: 'spectator',
roverId: null,
isTyping: on,
});
if (on) active.add(name);
} catch {
// ignore
}
if (tick >= BURSTS) {
clearInterval(timer);
active.forEach((ghost) => {
try {
ctx.sendExternalTyping({ nickname: ghost, role: 'spectator', roverId: null, isTyping: false });
} catch {
// ignore
}
});
}
}, TICK_MS);
ctx.sendAlert({ color: '#9c27b0', title: 'Typing Spam', message: 'Ghost typing burst started.' });
},
};
@@ -0,0 +1,57 @@
const STROBE_MS = 10000;
const TICK_MS = 350;
let activeTimer = null;
async function applyAll(ctx, state) {
const entities = ctx.getHomeAssistantEntities();
await Promise.all(
entities.map(async (entity) => {
try {
await ctx.setHomeAssistantEntityState(entity.id, state);
} catch (err) {
ctx.logger.warn('lightStrobe entity set failed', { entityId: entity.id, error: err.message });
}
}),
);
}
function startStrobe(ctx, effect = {}) {
if (activeTimer) {
clearInterval(activeTimer);
activeTimer = null;
}
const endsAt = Number(effect.endsAt || Date.now() + STROBE_MS);
let on = Boolean(effect.on);
ctx.saveEffect('lightStrobe', { endsAt, on });
activeTimer = setInterval(async () => {
if (Date.now() >= endsAt) {
clearInterval(activeTimer);
activeTimer = null;
ctx.clearEffect('lightStrobe');
return;
}
on = !on;
await applyAll(ctx, on ? 'on' : 'off');
ctx.saveEffect('lightStrobe', { endsAt, on });
}, TICK_MS);
}
module.exports = {
id: 'lightStrobe',
name: 'Light Strobe',
goal: 55,
async run(ctx) {
startStrobe(ctx, { endsAt: Date.now() + STROBE_MS, on: false });
ctx.sendAlert({ color: '#ffc107', title: 'Light Strobe', message: 'Room light strobe started.' });
},
async recover(ctx, effect) {
if (!effect || Number(effect.endsAt || 0) <= Date.now()) {
ctx.clearEffect('lightStrobe');
return;
}
startStrobe(ctx, effect);
},
};
+85
View File
@@ -0,0 +1,85 @@
const DURATION_MS = 25 * 1000;
let activeTimer = null;
function clearTimer() {
if (activeTimer) {
clearTimeout(activeTimer);
activeTimer = null;
}
}
async function restore(ctx, effect = {}) {
clearTimer();
const prevMode = effect.prevMode;
const prevReason = effect.prevReason;
if (prevMode) {
try {
ctx.setMode(prevMode, 'buttonbox:modeJamRestore');
} catch (err) {
ctx.logger.warn('modeJam restore mode failed', { error: err.message });
}
}
try {
if (prevReason == null || prevReason === '') {
ctx.clearAdminReason('buttonbox:modeJamRestore');
} else {
ctx.setAdminReason(prevReason, 'buttonbox:modeJamRestore');
}
} catch (err) {
ctx.logger.warn('modeJam restore reason failed', { error: err.message });
}
ctx.clearEffect('modeJam');
}
function scheduleRestore(ctx, effect = {}) {
clearTimer();
const endsAt = Number(effect.endsAt || Date.now() + DURATION_MS);
const remaining = Math.max(0, endsAt - Date.now());
const next = { ...effect, endsAt };
ctx.saveEffect('modeJam', next);
activeTimer = setTimeout(() => {
restore(ctx, next).catch((err) => {
ctx.logger.warn('modeJam restore failed', { error: err.message });
});
}, remaining);
}
module.exports = {
id: 'modeJam',
name: 'Admin Mode',
goal: 200,
async run(ctx) {
const prevMode = ctx.getMode();
const prevReason = ctx.getAdminReasonText();
const jamReason = `Button box chaos active until ${new Date(Date.now() + DURATION_MS).toLocaleTimeString()}`;
try {
ctx.setMode('admin', 'buttonbox:modeJam');
} catch (err) {
ctx.logger.warn('modeJam set mode failed', { error: err.message });
}
try {
ctx.setAdminReason(jamReason, 'buttonbox:modeJam');
} catch (err) {
ctx.logger.warn('modeJam set reason failed', { error: err.message });
}
scheduleRestore(ctx, {
prevMode,
prevReason,
endsAt: Date.now() + DURATION_MS,
});
ctx.sendAlert({ color: '#ff5722', title: 'Admin Mode', message: 'Server forced into admin mode temporarily.' });
},
async recover(ctx, effect) {
if (!effect || Number(effect.endsAt || 0) <= Date.now()) {
await restore(ctx, effect || {});
return;
}
scheduleRestore(ctx, effect);
},
};
@@ -0,0 +1,16 @@
const COLORS = ['#f44336', '#ff9800', '#ffeb3b', '#4caf50', '#2196f3', '#9c27b0', '#e91e63'];
const TITLES = ['RARARARARARARARARE', 'CHIRPET CHIRPET CHIRPET', 'meowmowmoowmomwowm', 'theleash'];
module.exports = {
id: 'rogueEventSpam',
name: 'Event Flood',
goal: 45,
async run(ctx) {
for (let i = 0; i < 10; i += 1) {
const color = COLORS[Math.floor(Math.random() * COLORS.length)];
const title = TITLES[Math.floor(Math.random() * TITLES.length)];
const message = `Event ${i + 1} / 10`;
ctx.sendAlert({ color, title, message });
}
},
};
+45
View File
@@ -0,0 +1,45 @@
const dockPanic = require('./definitions/dockPanic');
const cameraWhiplash = require('./definitions/cameraWhiplash');
const lightStrobe = require('./definitions/lightStrobe');
const ghostTypingSpam = require('./definitions/ghostTypingSpam');
const darkness = require('./definitions/darkness');
const discordStalkerPing = require('./definitions/discordStalkerPing');
const rogueEventSpam = require('./definitions/rogueEventSpam');
const modeJam = require('./definitions/modeJam');
const assignmentRoulette = require('./definitions/assignmentRoulette');
const chatSpam = require('./definitions/chatSpam');
const orderedRewards = [
dockPanic,
cameraWhiplash,
lightStrobe,
ghostTypingSpam,
darkness,
discordStalkerPing,
rogueEventSpam,
modeJam,
assignmentRoulette,
chatSpam,
];
const rewardById = new Map(orderedRewards.map((reward, idx) => [reward.id, { ...reward, number: idx + 1 }]));
function listRewards() {
return orderedRewards.map((reward, idx) => ({ ...reward, number: idx + 1 }));
}
function getRewardById(id) {
return rewardById.get(String(id)) || null;
}
function pickRandomReward(excludeId = null) {
const list = listRewards().filter((reward) => !excludeId || reward.id !== excludeId);
if (!list.length) return null;
return list[Math.floor(Math.random() * list.length)] || null;
}
module.exports = {
listRewards,
getRewardById,
pickRandomReward,
};