So, five hundred cigarettes. Haha what a funny thought. Obviously to do it I didn't somehow find a map with spy's cigarette model and reload it 500 times after snatching only it (sorry if you assumed I did), I just edited my collected props to put it in. But, how'd I do that?
Well, our prop snatching data, like a lot of our data, is stored in what's called a SQL database (typically pronounced like the word "Sequel"). So, I just grabbed my handy SQL database viewer/editor and used that. Of course, I had to be sure what I was editing in to get the desired result.
Our jazz_propdata table had columns for the Steam ID of the player who snatched it, the name of the map they snatched it from, the filepath of the snatched model or the material in the case of brush stuff, the type of thing snatched (prop, brush, or displacement), the total number snatched, the number of them that haven't been turned in yet, and the amount of jazzbucks you get when it's turned in. So it's just adding this info to a new entry in the database. My steamid, a map name (dm_juicyasszone, naturally), the path for the cigarette model, it's a prop, 500 snatched, 500 not turned in, and whatever for value, think I did 50. Easy. But. Hm.
I'd been wondering about this since I added the roadtrip functionality: does it actually handle the changes in worth from the multiplier being changed properly? Oh ho, ho ho. Well.
So, a little more technical of a description. Unsurprisingly, SQL is the programming language that a SQL database is made and edited in. You define tables with columns of certain info. Each record (a row) needs to somehow be uniquely identifiable, in what's known as the Primary Key. This is why account numbers exist for stuff like banks, or how your username has to be unique for something like Steam, so the database has some way of telling two sets of information apart. Two John Smiths both using the same bank shouldn't be putting money into or taking money out of the other's accounts, two josegonzales2008s shouldn't share a library or Steam wallet funds if they're on opposite ends of the Earth.
Another way of defining the primary key is to use several columns combined as the primary key. This is what we did for our table here. Each entry needs a unique combination of user id, map name, prop name, and prop type. Comparing to that, each time a prop is snatched, it first tries to add to the counts of an existing row, and if that fails, it creates the new row.
All well and good... well...
So, yeah. Definitely didn't work well for the roadtrip's dynamic multiplier. Whatever you first snatched, that was the value every other copy of that prop used after. So if you snatched a chair right at the start, went through the map getting every shard, and then snatched another of that chair, that +1 you earned to your multiplier just, didn't count. Went through several different maps in the roadtrip and returned to that map? Doesn't matter how much bigger your multiplier got, you get that original value for the chairs there.
Alright, well, let's add a column for the multiplier, and add that to the primary key, easy enough. Remove the multiplier in a couple spots in the code, put it back in a couple others, easy. Well, we had to lose a little precision for the values, because the calculated value before the multiplier was a decimal number while the SQL table stores it as an integer, but that was very minor, only up to +1 dollar per reset. Does it add up? Eventually I'm sure, but for how much you were losing to not getting the multiplier you should've been it wasn't such an issue. Certainly not worth making the table even bigger/slower for the game to run with. Had that all done up, and almost pushed it.
But then I had a thought. Wait a minute. Brushes are only stored as a material name. Doesn't that mean that all of the brushes in a single map also use a single worth?
Oh. Oh no.
Quick little test confirmed it. Shit. We've been lying to people about how much things they snatched have been worth the whole time.
I suspect this might have been thought of at some point, and is likely why the map name was even there to begin with. Using map name means that the price locking issue was confined to each individual map. I'd have to look through the history to confirm it, but, I imagine snatching non-static props was probably the first thing added (it's by far the easiest thing to do from a programming standpoint), and the map name storage solution was probably added then, where the issue of multiple values was nearly nonexistent. (Brushes are the major issue, but this even affected props a little too. Stuff that's in the map at its spawn is given a calculated value, while things that spawn in dynamically [most NPCs, items from crates, etc.] just get a value of $1. I imagine these weren't especially noticeable/maybe were noticed but decided to be rare/minor enough to not be addressed.)
But then when brush snatching was eventually added, the issue wasn't even thought of. After all, you don't ever really see the full value that the feed would've told you, and even if you did you'd have to pay attention to that and compare it to the number you got when you went to the turn in. Easy to lose in the shuffle with just one map, let alone if you went several without a turn in.
So, remove the multiplier split I did, and just do the price as a primary key. Pretty simple solution. Except, SQL tables can't have their primary key edited while they exist. We delete it (called dropping the table) on a NG+ Reset, so it'd only be negatively affected until the users' servers do that. But it'd do it even worse than the current bug, it would just straight up not add to the existing row if it couldn't insert the new one. Very not ideal.
We also have the issue of the map name being just sorta completely vestigial now. Not doing much but taking up a lot of space in the table (text strings are very storage inefficient, especially in SQL tables where the maximum size is allocated for each. In our case 128 characters worth of space per entry, which would be almost half of the table's total space on disk).
But, it gave me a nice little thing to look for for finding old copies of the table. When the map starts, it checks for this value in the table and if it finds it, it grabs the data from the existing table, drops it, makes the new copy, and then inserts the info back in, combining records as needed. The end user doesn't even have to see that it happened. Huzzah. Fixed the issue for the future, and fixed the fix causing issues on existing games.
And that's the story of how 500 cigarettes ate up like a week of my dev life. And I'm glad it did, because, damn. If this were real money someone would be going to jail.
