Compare commits

...

10 Commits

Author SHA1 Message Date
ElementalAlchemist ee5a36f425 Default to filling the canvas with the key-out color 2 weeks ago
ElementalAlchemist 58f3b867f7 Better align bus stop signs with the front of the bus 2 weeks ago
ElementalAlchemist f678556ff5 Show icons for points we've driven past 2 weeks ago
ElementalAlchemist 95c0a86da0 Constantize the canvas width in case we want it widened in the future 2 weeks ago
ElementalAlchemist 4098619de9 Add key-out color on the ground 2 weeks ago
ElementalAlchemist c3bde04f3c Implement scaling 2 weeks ago
ElementalAlchemist b5a62e5a88 Implement canvas-based bus system 2 weeks ago
Mike Lang 2aadf79bfb thrimshim odo hack: assume unmatched time of day means dawn
until we can fix dawn detection.
2 weeks 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
2 weeks ago
Mike Lang cb08f49003 pubbot: update total var before sending to zulip
so if zulip is down it still saves
2 weeks ago

@ -573,6 +573,9 @@ def get_chat_messages(channel):
if end <= start:
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")
# 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>
<html>
<style>
#road-container {
width: 100%;
height: 100px;
left: 0;
top: 0;
position: absolute;
// margin: 980px 0 0 -100px; // uncomment to move to bottom of screen
z-index: 3;
}
#road-container div {
height: 100px;
width: 100%;
position: absolute;
background-position: 0px 0px;
}
#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>
<head>
<meta charset="utf-8" />
<title>Bus Progress</title>
<link rel="stylesheet" href="drive.css" />
<script src="drive.js"></script>
</head>
<body>
<canvas id="road" width="1920" height="100"></canvas>
<label id="scale">
Scale:
<input id="scale-input" type="number" value="1" min="0.1" step="0.1" />
</label>
<p id="scale-disclaimer">
Scale modifications will be applied on the next update, which occur every 10 seconds.
</p>
</body>
</html>

@ -1,122 +1,214 @@
const PAGE_WIDTH = 1920;
const MINUTES_PER_PAGE = 60;
const POINT_WIDTH = PAGE_WIDTH * 8 * 60 / MINUTES_PER_PAGE;
const MILES_PER_PAGE = 45;
const BUS_POSITION_X = 93;
const BASE_ODO = 109.3;
const UPDATE_INTERVAL_MS = 5000
const WUBLOADER_URL = "";
const SKY_URLS = {
day: "db_day.png",
dawn: "db_dawn.png",
dusk: "db_dusk.png",
night: "db_night.png",
};
const BUS_URLS = {
day: "bus_day.png",
dawn: "bus_day.png",
dusk: "bus_day.png",
night: "bus_night.png",
const COLORS = {
day: {
sky: "#41cee2",
ground: "#e5931b",
surface: "#b77616",
},
dusk: {
sky: "#db92be",
ground: "#dd926a",
surface: "#b17555",
},
night: {
sky: "#121336",
ground: "#30201a",
surface: "#261a15",
},
dawn: {
sky: "#2b2f87",
ground: "#724d41",
surface: "#5b3e34",
},
};
function setSkyElements(left, right, timeToTransition) {
const leftElement = document.getElementById("timeofday-left");
const rightElement = document.getElementById("timeofday-right");
const busElement = document.getElementById("bus");
const KEY_OUT_COLOR = "#2b6ec6";
leftElement.style.backgroundImage = `url(${SKY_URLS[left]})`;
rightElement.style.backgroundImage = `url(${SKY_URLS[right]})`;
// The width from the left side of the bus image to the front of the bus
const BUS_FRONT_OFFSET = 73;
if (left === right) {
leftElement.style.width = "100%";
} else {
const transitionPercent = timeToTransition / MINUTES_PER_PAGE;
leftElement.style.width = `${transitionPercent * 100}%`
}
// Start time of each day phase
const DAY_START_MINUTES = 450;
const DUSK_START_MINUTES = 1140;
const NIGHT_START_MINUTES = 1200;
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) {
switch (timeofday) {
case "dawn":
// Bus stop positions are recorded in miles with the 0 position
// at route start. This array can be looped every point.
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":
return [19 * 60, "dusk"]; // 7pm
case "dawn":
return "dusk";
case "dusk":
return [20 * 60, "night"]; // 8pm
return "night";
case "night":
return [6 * 60 + 40, "dawn"]; // 6:40am
return "dawn";
}
}
function setSky(timeofday, clock) {
const [transition, newSky] = nextSkyTransition(timeofday, clock);
// 1440 minutes in 24h, this code will return time remaining even if
// the transition is in the morning and we're currently in the evening.
const timeToTransition = (1440 + transition - clock) % 1440;
if (timeToTransition < MINUTES_PER_PAGE) {
// Transition on screen
setSkyElements(timeofday, newSky, timeToTransition);
} else {
// No transition on screen
setSkyElements(timeofday, timeofday, undefined);
function phaseStartTime(timeOfDay) {
switch (timeOfDay) {
case "day":
return DAY_START_MINUTES;
case "dusk":
return DUSK_START_MINUTES;
case "night":
return NIGHT_START_MINUTES;
case "dawn":
return DAWN_START_MINUTES;
}
}
function setOdo(odo) {
const distancePixels = PAGE_WIDTH * (odo - BASE_ODO) / MILES_PER_PAGE;
const offset = (BUS_POSITION_X - distancePixels) % POINT_WIDTH;
function drawBackground(context, timeOfDay, leftX, width) {
const skyColor = COLORS[timeOfDay].sky;
const groundColor = COLORS[timeOfDay].ground;
const surfaceColor = COLORS[timeOfDay].surface;
const stopsElement = document.getElementById("stops");
stopsElement.style.backgroundPosition = `${offset}px 0px`;
context.fillStyle = KEY_OUT_COLOR;
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() {
const busDataResponse = await fetch(`${WUBLOADER_URL}/thrimshim/bus/buscam`);
async function drawRoad() {
const busDataResponse = await fetch("/thrimshim/bus/buscam");
if (!busDataResponse.ok) {
return;
}
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
setSky("day", 7 * 60);
setOdo(BASE_ODO);
// Testing mode. Set true to enable.
const test = false;
if (test) {
let h = 0;
// Set to how long 1h of in-game time should take in real time
const hourTimeMs = 1 * 1000;
// Set to how often to update the screen
const interval = 30;
setInterval(() => {
h += interval / hourTimeMs;
setOdo(BASE_ODO + 45 * h);
if (h < 19) {
setSky("day", 60 * h);
} else {
m = (h % 24) * 60;
let tod;
if (m < 6 * 60 + 40) {
tod = "night";
} else if (m < 19 * 60) {
tod = "dawn";
} else if (m < 20 * 60) {
tod = "dusk";
const canvas = document.getElementById("road");
if (!canvas.getContext) {
return;
}
const context = canvas.getContext("2d");
// Clear the previous canvas before starting
context.clearRect(0, 0, CANVAS_PIXEL_WIDTH, 100);
// Background the whole thing as the key-out color in case we need to bail
// out before drawing (e.g. we're in a non-DB game menu)
context.fillStyle = KEY_OUT_COLOR;
context.fillRect(0, 0, CANVAS_PIXEL_WIDTH, 100);
const currentTime = busData.clock_minutes;
const distance = busData.odometer;
const timeOfDay = busData.timeofday;
drawBackground(context, timeOfDay, 0, BUS_FRONT_OFFSET);
const maxWidth = CANVAS_PIXEL_WIDTH - BUS_FRONT_OFFSET;
// The default scaling factor (1) is 20 seconds per pixel at max speed.
// This gives us
// - 3px per minute
// - 4px per mile
let scaleFactor = +document.getElementById("scale-input").value;
if (scaleFactor === 0 || isNaN(scaleFactor)) {
scaleFactor = 1;
}
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 {
tod = "night";
distanceToNextPoint = 469.3 - currentPointProgress;
}
distanceToNextPoint += BUS_FRONT_OFFSET / (4 * scaleFactor);
if (distanceToNextPoint >= 360) {
distanceToNextPoint -= 360;
}
setSky(tod, m);
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;
}
}
if (nextBusStopPosition === null) {
nextBusStopPosition = 360 + BUS_STOP_POSITIONS[0];
}
const nextBusStopDistance = nextBusStopPosition - distanceTrackedOnRoute;
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);
}
}, interval);
} else {
// Do first update immediately, then every UPDATE_INTERVAL_MS
setInterval(update, UPDATE_INTERVAL_MS);
update();
}
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
FROM bus_data
WHERE clock IS NOT NULL
AND timeofday IS NOT NULL
AND channel = %(channel)s
AND timestamp > %(start)s
AND timestamp <= %(end)s
@ -849,7 +848,8 @@ def get_odometer(channel):
clock_face = None
else:
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
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
entries_str = " ({} entries of ${:.2f})".format(entries, giveaway_amount)
logging.info("New donation total: {}{}{}".format(msg["d"], increase_str, entries_str))
total = msg["d"]
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))
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))
total = msg["d"]
elif msg["c"].startswith("bid:"):
log["type"] = "prize"

Loading…
Cancel
Save