Compare commits

...

10 Commits

Author SHA1 Message Date
ElementalAlchemist ee5a36f425 Default to filling the canvas with the key-out color 10 months ago
ElementalAlchemist 58f3b867f7 Better align bus stop signs with the front of the bus 10 months ago
ElementalAlchemist f678556ff5 Show icons for points we've driven past 10 months ago
ElementalAlchemist 95c0a86da0 Constantize the canvas width in case we want it widened in the future 10 months ago
ElementalAlchemist 4098619de9 Add key-out color on the ground 10 months ago
ElementalAlchemist c3bde04f3c Implement scaling 10 months ago
ElementalAlchemist b5a62e5a88 Implement canvas-based bus system 10 months ago
Mike Lang 2aadf79bfb thrimshim odo hack: assume unmatched time of day means dawn
until we can fix dawn detection.
10 months ago
Mike Lang bf9da27ca4 restreamer: refuse to load more than 2h of chat
this hard locks up the server due to merge taking a very long time
10 months ago
Mike Lang cb08f49003 pubbot: update total var before sending to zulip
so if zulip is down it still saves
10 months ago

@ -573,6 +573,9 @@ def get_chat_messages(channel):
if end <= start: if end <= start:
return "End must be after start", 400 return "End must be after start", 400
if end - start > datetime.timedelta(hours=2):
return "Cannot request more than 2h of chat", 400
hours_path = os.path.join(app.static_folder, channel, "chat") hours_path = os.path.join(app.static_folder, channel, "chat")
# This process below may fail if a batch is deleted out from underneath us. # This process below may fail if a batch is deleted out from underneath us.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

@ -0,0 +1,9 @@
#scale {
display: block;
margin-top: 20px;
}
#scale-disclaimer {
font-size: 75%;
font-style: italic;
}

@ -1,54 +1,19 @@
<!doctype html> <!doctype html>
<html> <html>
<head>
<style> <meta charset="utf-8" />
#road-container { <title>Bus Progress</title>
width: 100%; <link rel="stylesheet" href="drive.css" />
height: 100px; <script src="drive.js"></script>
left: 0; </head>
top: 0; <body>
position: absolute; <canvas id="road" width="1920" height="100"></canvas>
// margin: 980px 0 0 -100px; // uncomment to move to bottom of screen <label id="scale">
z-index: 3; Scale:
} <input id="scale-input" type="number" value="1" min="0.1" step="0.1" />
</label>
#road-container div { <p id="scale-disclaimer">
height: 100px; Scale modifications will be applied on the next update, which occur every 10 seconds.
width: 100%; </p>
position: absolute; </body>
background-position: 0px 0px; </html>
}
#timeofday-left {
z-index: 5;
}
#timeofday-right {
z-index: 4;
}
#stops {
z-index: 6;
background-image: url(stops.png);
}
#bus {
background-repeat: no-repeat;
margin-left: 27px;
z-index: 8;
}
</style>
<body>
<div id="road-container">
<div id="bus"></div>
<div id="timeofday-left"></div>
<div id="timeofday-right"></div>
<div id="stops"></div>
</div>
<script src="drive.js"></script>
</body>

@ -1,122 +1,214 @@
const COLORS = {
const PAGE_WIDTH = 1920; day: {
const MINUTES_PER_PAGE = 60; sky: "#41cee2",
const POINT_WIDTH = PAGE_WIDTH * 8 * 60 / MINUTES_PER_PAGE; ground: "#e5931b",
const MILES_PER_PAGE = 45; surface: "#b77616",
const BUS_POSITION_X = 93; },
const BASE_ODO = 109.3; dusk: {
const UPDATE_INTERVAL_MS = 5000 sky: "#db92be",
const WUBLOADER_URL = ""; ground: "#dd926a",
const SKY_URLS = { surface: "#b17555",
day: "db_day.png", },
dawn: "db_dawn.png", night: {
dusk: "db_dusk.png", sky: "#121336",
night: "db_night.png", ground: "#30201a",
}; surface: "#261a15",
const BUS_URLS = { },
day: "bus_day.png", dawn: {
dawn: "bus_day.png", sky: "#2b2f87",
dusk: "bus_day.png", ground: "#724d41",
night: "bus_night.png", surface: "#5b3e34",
},
}; };
function setSkyElements(left, right, timeToTransition) { const KEY_OUT_COLOR = "#2b6ec6";
const leftElement = document.getElementById("timeofday-left");
const rightElement = document.getElementById("timeofday-right");
const busElement = document.getElementById("bus");
leftElement.style.backgroundImage = `url(${SKY_URLS[left]})`; // The width from the left side of the bus image to the front of the bus
rightElement.style.backgroundImage = `url(${SKY_URLS[right]})`; const BUS_FRONT_OFFSET = 73;
if (left === right) { // Start time of each day phase
leftElement.style.width = "100%"; const DAY_START_MINUTES = 450;
} else { const DUSK_START_MINUTES = 1140;
const transitionPercent = timeToTransition / MINUTES_PER_PAGE; const NIGHT_START_MINUTES = 1200;
leftElement.style.width = `${transitionPercent * 100}%` const DAWN_START_MINUTES = 400;
}
bus.style.backgroundImage = `url(${BUS_URLS[left]})`; const BUS_STOP_OFFSET = 8;
} const POINT_OFFSET = 17;
function nextSkyTransition(timeofday, clock) { // Bus stop positions are recorded in miles with the 0 position
switch (timeofday) { // at route start. This array can be looped every point.
case "dawn": const BUS_STOP_POSITIONS = [1, 55.2, 125.4, 166.3, 233.9, 295.2];
const BUS_DAY_IMAGE = new Image();
BUS_DAY_IMAGE.src = "bus_day.png";
const BUS_NIGHT_IMAGE = new Image();
BUS_NIGHT_IMAGE.src = "bus_night.png";
const BUS_STOP_IMAGE = new Image();
BUS_STOP_IMAGE.src = "db_stop.png";
const POINT_IMAGE = new Image();
POINT_IMAGE.src = "point.png";
// This should match the HTML canvas width
const CANVAS_PIXEL_WIDTH = 1920;
function nextPhase(timeOfDay) {
switch (timeOfDay) {
case "day": case "day":
return [19 * 60, "dusk"]; // 7pm case "dawn":
return "dusk";
case "dusk": case "dusk":
return [20 * 60, "night"]; // 8pm return "night";
case "night": case "night":
return [6 * 60 + 40, "dawn"]; // 6:40am return "dawn";
} }
} }
function setSky(timeofday, clock) { function phaseStartTime(timeOfDay) {
const [transition, newSky] = nextSkyTransition(timeofday, clock); switch (timeOfDay) {
// 1440 minutes in 24h, this code will return time remaining even if case "day":
// the transition is in the morning and we're currently in the evening. return DAY_START_MINUTES;
const timeToTransition = (1440 + transition - clock) % 1440; case "dusk":
if (timeToTransition < MINUTES_PER_PAGE) { return DUSK_START_MINUTES;
// Transition on screen case "night":
setSkyElements(timeofday, newSky, timeToTransition); return NIGHT_START_MINUTES;
} else { case "dawn":
// No transition on screen return DAWN_START_MINUTES;
setSkyElements(timeofday, timeofday, undefined);
} }
} }
function setOdo(odo) { function drawBackground(context, timeOfDay, leftX, width) {
const distancePixels = PAGE_WIDTH * (odo - BASE_ODO) / MILES_PER_PAGE; const skyColor = COLORS[timeOfDay].sky;
const offset = (BUS_POSITION_X - distancePixels) % POINT_WIDTH; const groundColor = COLORS[timeOfDay].ground;
const surfaceColor = COLORS[timeOfDay].surface;
const stopsElement = document.getElementById("stops"); context.fillStyle = KEY_OUT_COLOR;
stopsElement.style.backgroundPosition = `${offset}px 0px`; context.fillRect(leftX, 80, width, 20);
context.fillStyle = COLORS[timeOfDay].sky;
context.fillRect(leftX, 0, width, 80);
context.fillStyle = COLORS[timeOfDay].surface;
context.fillRect(leftX, 80, width, 1);
context.fillStyle = COLORS[timeOfDay].ground;
context.fillRect(leftX, 81, width, 7);
context.fillRect(leftX, 89, width, 3);
context.fillRect(leftX, 94, width, 2);
context.fillRect(leftX, 99, width, 1);
} }
async function update() { async function drawRoad() {
const busDataResponse = await fetch(`${WUBLOADER_URL}/thrimshim/bus/buscam`); const busDataResponse = await fetch("/thrimshim/bus/buscam");
if (!busDataResponse.ok) { if (!busDataResponse.ok) {
return; return;
} }
const busData = await busDataResponse.json(); const busData = await busDataResponse.json();
console.log("Got data:", busData);
setOdo(busData.odometer);
setSky(busData.timeofday, busData.clock_minutes);
}
// Initial conditions, before the first refresh finishes const canvas = document.getElementById("road");
setSky("day", 7 * 60); if (!canvas.getContext) {
setOdo(BASE_ODO); return;
}
// Testing mode. Set true to enable. const context = canvas.getContext("2d");
const test = false;
if (test) { // Clear the previous canvas before starting
let h = 0; context.clearRect(0, 0, CANVAS_PIXEL_WIDTH, 100);
// Set to how long 1h of in-game time should take in real time // Background the whole thing as the key-out color in case we need to bail
const hourTimeMs = 1 * 1000; // out before drawing (e.g. we're in a non-DB game menu)
// Set to how often to update the screen context.fillStyle = KEY_OUT_COLOR;
const interval = 30; context.fillRect(0, 0, CANVAS_PIXEL_WIDTH, 100);
setInterval(() => {
h += interval / hourTimeMs; const currentTime = busData.clock_minutes;
setOdo(BASE_ODO + 45 * h); const distance = busData.odometer;
if (h < 19) { const timeOfDay = busData.timeofday;
setSky("day", 60 * h);
} else { drawBackground(context, timeOfDay, 0, BUS_FRONT_OFFSET);
m = (h % 24) * 60;
let tod; const maxWidth = CANVAS_PIXEL_WIDTH - BUS_FRONT_OFFSET;
if (m < 6 * 60 + 40) { // The default scaling factor (1) is 20 seconds per pixel at max speed.
tod = "night"; // This gives us
} else if (m < 19 * 60) { // - 3px per minute
tod = "dawn"; // - 4px per mile
} else if (m < 20 * 60) { let scaleFactor = +document.getElementById("scale-input").value;
tod = "dusk"; if (scaleFactor === 0 || isNaN(scaleFactor)) {
} else { scaleFactor = 1;
tod = "night"; }
const startMinute = busData.clock_minutes;
const timeDuration = maxWidth / (3 * scaleFactor);
let previousTime = startMinute;
let previousTimeOfDay = timeOfDay;
let remainingDuration = timeDuration;
let x = BUS_FRONT_OFFSET;
while (remainingDuration > 0) {
const nextTimeOfDay = nextPhase(previousTimeOfDay);
const nextStartTime = phaseStartTime(nextTimeOfDay);
let thisDuration = nextStartTime - previousTime;
if (thisDuration < 0) {
thisDuration += 1440;
}
// TODO Figure out scaling factor
const pixelWidth = thisDuration * 3 * scaleFactor;
drawBackground(context, previousTimeOfDay, x, pixelWidth);
remainingDuration -= thisDuration;
previousTime = nextStartTime;
previousTimeOfDay = nextTimeOfDay;
x += pixelWidth;
}
x = 0;
const currentPointProgress = distance % 360;
let distanceToNextPoint;
if (currentPointProgress <= 109.3) {
distanceToNextPoint = 109.3 - currentPointProgress;
} else {
distanceToNextPoint = 469.3 - currentPointProgress;
}
distanceToNextPoint += BUS_FRONT_OFFSET / (4 * scaleFactor);
if (distanceToNextPoint >= 360) {
distanceToNextPoint -= 360;
}
x += distanceToNextPoint * 4 * scaleFactor;
context.drawImage(POINT_IMAGE, x - POINT_OFFSET, 0);
while (x < CANVAS_PIXEL_WIDTH) {
x += 360 * 4 * scaleFactor;
context.drawImage(POINT_IMAGE, x - POINT_OFFSET, 0);
}
const distanceOnRoute = (distance - 109.3) % 360;
let distanceTracked = distanceOnRoute - BUS_FRONT_OFFSET / (4 * scaleFactor);
if (distanceTracked < 0) {
distanceTracked += 720;
}
x = 0;
while (x < CANVAS_PIXEL_WIDTH) {
const distanceTrackedOnRoute = distanceTracked % 360;
let nextBusStopPosition = null;
for (const busStopPosition of BUS_STOP_POSITIONS) {
if (busStopPosition >= distanceTrackedOnRoute + 1) {
nextBusStopPosition = busStopPosition;
break;
} }
setSky(tod, m);
} }
}, interval); if (nextBusStopPosition === null) {
} else { nextBusStopPosition = 360 + BUS_STOP_POSITIONS[0];
// Do first update immediately, then every UPDATE_INTERVAL_MS }
setInterval(update, UPDATE_INTERVAL_MS); const nextBusStopDistance = nextBusStopPosition - distanceTrackedOnRoute;
update(); distanceTracked += nextBusStopDistance;
x += nextBusStopDistance * 4 * scaleFactor;
context.drawImage(BUS_STOP_IMAGE, x - BUS_STOP_OFFSET, 0);
}
if (timeOfDay === "night") {
context.drawImage(BUS_NIGHT_IMAGE, 0, 0);
} else {
context.drawImage(BUS_DAY_IMAGE, 0, 0);
}
} }
window.addEventListener("DOMContentLoaded", (event) => {
drawRoad();
setInterval(drawRoad, 10000);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

@ -835,7 +835,6 @@ def get_odometer(channel):
SELECT timestamp, clock, timeofday SELECT timestamp, clock, timeofday
FROM bus_data FROM bus_data
WHERE clock IS NOT NULL WHERE clock IS NOT NULL
AND timeofday IS NOT NULL
AND channel = %(channel)s AND channel = %(channel)s
AND timestamp > %(start)s AND timestamp > %(start)s
AND timestamp <= %(end)s AND timestamp <= %(end)s
@ -849,7 +848,8 @@ def get_odometer(channel):
clock_face = None clock_face = None
else: else:
clock12h = result.clock clock12h = result.clock
timeofday = result.timeofday # HACK: assume null means dawn, as we can reliably detect everything else.
timeofday = result.timeofday or "dawn"
clock24h = clock12h clock24h = clock12h
if time_is_pm(conn, result.timestamp, clock12h, timeofday): if time_is_pm(conn, result.timestamp, clock12h, timeofday):

@ -153,11 +153,11 @@ def main(conf_file, message_log_file, name=socket.gethostname()):
log["giveaway_entries"] = entries log["giveaway_entries"] = entries
entries_str = " ({} entries of ${:.2f})".format(entries, giveaway_amount) entries_str = " ({} entries of ${:.2f})".format(entries, giveaway_amount)
logging.info("New donation total: {}{}{}".format(msg["d"], increase_str, entries_str)) logging.info("New donation total: {}{}{}".format(msg["d"], increase_str, entries_str))
total = msg["d"]
if increase is not None and increase > 0: if increase is not None and increase > 0:
client.send_to_stream("bot-spam", "Donation Firehose", "Donation total is now ${:.2f}{}{}".format(msg["d"], increase_str, entries_str)) client.send_to_stream("bot-spam", "Donation Firehose", "Donation total is now ${:.2f}{}{}".format(msg["d"], increase_str, entries_str))
if increase is not None and increase >= 500: if increase is not None and increase >= 500:
client.send_to_stream("bot-spam", "Notable Donations", "Large donation of ${:.2f} (total ${:.2f}){}".format(increase, msg['d'], entries_str)) client.send_to_stream("bot-spam", "Notable Donations", "Large donation of ${:.2f} (total ${:.2f}){}".format(increase, msg['d'], entries_str))
total = msg["d"]
elif msg["c"].startswith("bid:"): elif msg["c"].startswith("bid:"):
log["type"] = "prize" log["type"] = "prize"

Loading…
Cancel
Save