I upgraded our local MariaDB for compatibility with the latest server
dumps (https://mariadb.org/mariadb-dump-file-compatibility-change/),
and I thiiiink what I'm seeing is that, also in this version of
MariaDB, the default value for the `ssl` option is `true`? That is,
command-line clients will try to connect over SSL by default—which
isn't generally supported on development servers, where this task runs.
I could probably fix this with a change to my local config? But I
figure I can't really picture a scenario where this option being set in
the task would be *wrong*, but I *can* see it saving future people time
if they're working in a similar environment. So, let's just set it!
This is a Ruby language server that integrates with my editor! Static
analysis of Ruby and Rails is pretty tricky, but it's working and I
think that's neat!!
Oh huh, TIL in Ruby `^` *always* means "start of line", whereas in many
languages' regular expression engines it means "start of string" unless
you enable a special multiline flag for the pattern.
I've fixed this in a number of expressions now!
I'm noticing this in the context of doing some security training work
where this the cause of a sample vulnerability, but, looking at our own
case, I don't think there was anything *abusable* here? But this is
just more correct, so let's be more correct!
I think the parens are silly now that this paragraph is just kinda all
bonus clarification info anyway. And I wanted to explain the cost
computation for the potions, and highlight the bundle thing!
If the item names are long, it helps to give them more room to breathe!
Whereas if they're short, it looks silly and makes it harder to scan
the table.
Just an extra bit of help for e.g. Dyeworks items with long names!
Huh, I thought I'd tried some invalid dates and they gave me
*surprising* output instead of raising an error. Well, maybe it can do
both, depending on exactly the nature of the unexpected input?
In any case, I found that a bad month name like "UwU" raised an error.
So, let's catch it if so!
Oh right, if I assume "date in the past means it's for next year", then
that means that, when the date *does pass*, we won't realize it!
e.g. if Owls says "Dyeable Thru July 15", then on July 14 we'll parse
that as July 15, 2024; but on July 16 we'll parse it as July 16, 2025,
and so we'll think it's *still* dyeable. Under this logic, it's
actually impossible for a limited Dyeworks date to *ever* be in the
past, I think!
I think 3 months is a good compromise: it gives Owls plenty of time to
update, but allows for events that could last as long as 9 months into
the future, if I'm doing my math right.
The table layout algo can get a bit funky about how it assigns extra
space, I want to encourage things like "Total: 5 items" etc not to
wrap, esp in the Dyeworks case where it's quite long!
There's more and more going on in here! Let's omit the base item name,
increase the table width a bit in this case, and tweak the rest a bit
while we're here.
I uhhh literally didn't know Dyeworks was a gacha system until Kaye
from the Owls team told me lmao
I should maybe uhh read more guides instead of assuming I've osmosed
things correctly oops!
Ahh, I started a tabs-y file (as I default to these days), but copied
code from a spaces-y file, and didn't notice. (My laptop editor isn't
configured to flag this for me, oops!)
Fixed!
This doesn't affect a lot right now, it was previously defaulting to
UTC I think? And I've confirmed that, while timestamps are stored in
the database as UTC, they're not interpreted any differently with this
setting. (Or, rather, it's loaded as a `DateTime` object for the same
moment in time, but in the NST time zone. Good!)
But this feels like a more useful default for displaying things for
development etc, and moreover, I'm working on some logic for things
like "when do limited-time Dyeworks items expire exactly?", and that
logic is made *much* simpler if we can just compare dates in NST by
default rather than fudge around with the zones on them and figuring
out the correct midnight!
(Although, as I type this, I think I maybe have thought of an easier
way to do it? So maybe this change won't actually be necessary for
that, but it still feels like a more sensible default for us
regardless!)
There's just starting to be a lot going on, so I pulled them out into
here!
I also considered a like, `Item::DyeworksStatus` class, and then you'd
go like, `item.dyeworks.buyable?`. But idk, I think it's nice that the
current API is simple for callers, and being able to do things like
`items.filter(&:dyeworks_buyable?)` is pretty darn convenient.
This solution lets us keep the increasing number of Dyeworks methods
from polluting the main `item.rb`, while still keeping the API
identical!
Oh this was a fun little dev environment bug: I ran `public_data:pull`
on my laptop before migrating my database, so the `items` table pulled
as the latest production version, which included the migrations, but
they hadn't been marked as "run" yet.
So Rails was still telling me I needed to run them, but the migrations
themselves were crashing, with stuff like "there's already a column
with this name!"
This change ensures that `public_data:pull` won't run until migrations
are done, to prevent silly accidents like that.
Silly mistake, right, we might not have a trade value listed! This is
relevant for the new Dyeworks items that just came out like a few hours
ago, which Owls doesn't have info for yet.
Lmao I've been testing with an outfit that has all the kinds of items,
so I didn't notice that this new refactor to `@items[:dyeworks]` style
of tracking the items returns `nil` when there's none, instead of `[]`.
(I always make this mistake when I use `group_by` lmao sob)
In this change, we give the `@items` hash a default value, so that will
stop happening!
Oh right, this previously logic was silly: we can't count on the
*interval itself* to be reliably resetting the FPS counter state,
because the interval might not be firing!
I think this fix worked when I tried brief tests, but didn't work when
I did an (accidental) longer test, because the browser switched to a
more aggressive throttle mode, and the previous mode was close enough
on the resets for it to be fine, whereas this time the FPS counter
state got way too old.
Now, we reset the FPS counter state *exactly* when the page comes back.
We have a feature to check the movie's FPS, and pause it if it gets too
low, as a guard against especially low-performance movies. But this was
triggering in an *expected* case, where browsers intentionally throttle
interval events when a page is in the background (e.g. you switch to
another tab).
Now, our rendering is aware of page visibility: when the page is
hidden, don't bother rendering, and keep resetting the FPS counter
state, so that we can pick up with a fresh FPS counter when the page
comes back.
This doesn't matter-matter, it's mostly just so that when we SSH into
the production machine, the prompt presents itself as `impress` rather
than as `localhost`!
Oh weird, even with `flush: true`, `content_for` will ignore an empty
block and *not* flush out the previous content. This could cause rows
whose subtitles *should* have been empty (e.g. no NC trade value) to
display the previous row's value instead.
Let's make this whole situation a bit more robust by having the
*template* clear out the subtitle right before calling the block. That
way, a previous row's value *can't* get in, no matter what.
I think it helps a bit to have only the label be dotted-underlined, to
hint that I'm offering help about what that *means*, but clear the way
for the value itself to be more visible and less cluttered.
I thought to myself, "I wonder if it's possible to use a sneaky hacky
`content_for` trick to be able to run this code in the template." And
indeed it is!
It's tricky cuz like, I want to render this template, and I want to
provide _multiple_ slots of content to it. So, in this variant, we keep
the block as being primarily for the actions, but also optionally
accept `content_for :subtitle` inside that block, too.
Executing that correctly is a bit tricky! The subtitle comes *before*
the actions. So, we `yield` the actions block immediately, save it to a
variable, and *then* get the subtitle block.
Previously, I added a Dyeworks section that was incorrect: the base
item being available in the NC Mall does *not* mean you can necessarily
dye it with a potion!
In this change, we lean on Owls to tell us more about Dyeworks status,
and only group items in this section that Owls has marked as "Permanent
Dyeworks".
We don't have support for limited-time Dyeworks items yet—I've sent out
a message asking the Owls team for more info on what they do for those
items!
I started writing this up, then sent a preview to a friend, and he was
like "oh cool, but also this is not correct?"
I didn't realize Dyeworks has limited-time support to be *able* to dye
certain items. Hey, glad we're writing this guide for people like me,
then! lol
I wonder if we can lean on Owls for this. It seems like they already
list "Permanent Dyeworks" for some items, I wonder if they say
something special for active limited-edition Dyeworks items!
In this change, instead of *always* inferring the Dyeworks base item
from the item name at runtime, we now have a database field that tracks
it, and auto-populates whenever an item *seems* to need a Dyeworks base
item but doesn't have one yet.
This will enable us to set the base item manually in cases where it
can't be inferred, and load Dyeworks base items for the Item Getting
Guide in one query with `includes(:dyeworks_base_item)`.
This migration does a bit more of the fix-em-up scripting work *in* the
migration itself than I usually do, mainly because there's so much in
this one that I think being extra-explicit is useful. We make sure to
do it gracefully though!
This works for most of the current 1,094 Dyeworks items! But there are
a few exceptions, for cases where the base item name is not quite the
same (e.g. the Dyeworks version is more concise). Maybe we'll add a
database field to override this?
- Dyeworks Baby Blue: Baby Valentine Jumper
- Dyeworks Baby Pink: Baby Valentine Jumper
- Dyeworks Black: Field of Flowers
- Dyeworks Black: Games Master Challenge 2010 Lulu Shirt
- Dyeworks Blue: Field of Flowers
- Dyeworks Blue: Stars and Glitter Facepaint
- Dyeworks Brown: Hanging Winter Candles Garland
- Dyeworks Green: Stars and Glitter Facepaint
- Dyeworks Magenta: Lovely Berry Blush
- Dyeworks Orange & Pink: Winter Lights Effects
- Dyeworks Orange: Games Master Challenge 2010 Lulu Shirt
- Dyeworks Peach: Lovely Berry Blush
- Dyeworks Purple: Baby Valentine Jumper
- Dyeworks Purple: Games Master Challenge 2010 Lulu Shirt
- Dyeworks Purple: Hanging Winter Candles Garland
- Dyeworks Purple: Stars and Glitter Facepaint
- Dyeworks Red & Green: Winter Lights Effects
- Dyeworks Silver: Hanging Winter Candles Garland
- Dyeworks Soft Pink: Lovely Berry Blush
- Dyeworks Yellow & Magenta: Winter Lights Effects
- Dyeworks Yellow: Field of Flowers
We could do a whole thing about like, checking singular vs plural, but
I'd rather just keep it simpler; I think it's clear from context that
we're talking about a category, so plural is fine even if it's not
actually more than one.
Okay so, like 30 minutes ago I added fallback behavior for cases where
we can't correctly infer the color from a PB item's name… and then I
pulled it up in the color and found that, oh, right, there are already
3 PB items that *correctly* return `nil` for `Item#pb_color`: Aisha
Collar, Elephante Hat, and Ixi Collar.
This is because they're common items that apply to many colors, like
the basics, but also many other less-special or older color variants.
They are the most likely case where we'll return `nil`.
So, I've updated our fallback UI to, instead of talk vaguely about
missing data, just assume that we're dealing with basic items. In the
rare window of time where a new color is released, and we have PB items
for it but no manual color data yet, this can just incorrectly say
"Basic Colors" and that's fine.