Srsly, which one is the original?
For years industrialists have been faced with finding those precious originals in the monotone sea of blueprint originals and blueprint copies.
This is the story of how we fixed it.
First things first: Why did they look the same to begin with?
The problem had to do with type IDs in our inventory system; a blueprint that made "Ogres" or "Thoraxes" had a certain type ID regardless of it being an original (BPO) or a copy (BPC) and that type ID defined what that blueprint was for. As the type ID is a part of our inventory table then every item has one, but attributes specific to a given type are not part of those tables. These attributes are stored in subsystem-specific tables, such as in the Science and Industry (S&I) subsystem tables in the case of blueprints.
When you fetch a list of items in a given location from the inventory system, what you get is just the basic "everyone has these" attributes of the items and no subsystem-specific data. Thus, since BPOs and BPCs have all their basic attributes set to the same values, in particular the type ID, the client would have had to make an extra server call for blueprints (to the S&I subsystem) to figure out if they were originals or copies. And that's exactly what happened when you did a "show info" on the blueprints.
We didn't want to be so cruel to our poor server, however, as to be making these extra server calls for all the blueprints in a hangar when you opened it. That's just hostile programming since most of the time that extra information isn't required. We also didn't want to be conditionally joining with subsystem-specific tables in the database every time you needed to look at an inventory item. That's SQL-terrorism.
So how did we change this so that you could distinguish between BPOs and BPCs in a server and DB friendly manner?
We took the first step in Tyrannis 1.2 late last year, see the dev blog 64 bits should be enough for everybody. Although this was focused on moving the inventory systems item IDs to 64 bits then we did more. We changed the items table, increased the bitcount of the type ID and other columns, removed an obsolete column and merged two columns. That last part here is important. Previously we had a 'singleton' bit-column that indicated that this particular item was not a part of a stack and that its item ID could be used as a foreign key in other tables. We also had a non-negative 'quantity' column which specified how many items were in the stack. Some of you may have spotted that these are really mutually exclusive. Singletons must have a stacksize of exactly 1 and anything with a quantity > 1 couldn't be a singleton (quantity of 1 does not imply a singleton though).
So, saving DB space and bandwidth, these two got merged into an integer 'quantity' column with the new semantic that negative quantities implied a singleton. We just mass-updated the inventory table turning all 'singleton==1' items into 'quantity=-1'.
The next step was taken in Incursion 1.3, see the dev blog Inventory corification part 2 - creation of CARBON Inventory. This was a continuation of the previous phase but here we cleaned up idiosyncrasies, made some long overdue hardening against exploits and simplified the programming interface for inventory items a bit AND re-introduced the 'singleton' column into the DBRow datastructure, but this time as a virtual column. A virtual column looks like a normal column to programmers, but instead of merely looking up an attribute value that we got from a DB query, a specified function is called to determine what to return based on other attributes (in this case the quantity).
Combine these two changes and what we have is a 30-bit range of the negative quantity to encode extra type-specific information for singletons and a unified programming method of accessing it. We utilised this fact and changed the way we create blueprint singletons so that originals have their 'quantity' set to -1 and copies to -2.
So, now the client can look at the type, see it's a blueprint, check the singleton virtual column and get either a '1' or a '2' (we always return non-negative values from the singleton lookup) and choose a different icon based on that, all without any extra server calls or additional DB load.
Nifty, ain't it?
Now that we have the item backend sorted and a fancy blueprint copy singleton flag we are all set, right?
Not quite. This works fine when displaying BPOs and BPCs in you hangar or cargo hold since we now know from the item record whether the blueprint singleton flag is set to "original" or "copy". But what about places where we don't have an item, such as in the Loyalty Point Store? There we don't have a real item and therefore won't have any data in the blueprint support tables since the blueprints you are being offered haven't been created yet. In that particular case we can take advantage of the fact that only copies are available in the LP Store and the relevant metadata regarding runs and quality is embedded in the offer data thus we can pass that along when you show info for a non-existent blueprint to get the right data you would expect for the real item.
So what am I getting at?
Because we now have a type with two very different forms, with separate icons and a multiplicity of associated metadata we end up having to introduce a lot of special cases for just this one class of types. In almost every place these icons can appear we have to introduce a special case for not just the icon but also for the expected show info functionality. And because you now see a different icon for the copies you start to expect the show info to reflect that. Before you would not have that information and just have the type info and the basic type just looks like a blueprint original. But the special-case code runs on the client and is therefore automatically server and DB friendly, which is hugely important when developing a large-scale single-sharded MMO.
The end result was deployed in Incursion 1.5.
This was a lot work, involved a lot of people in multiple teams and was deployed over three different releases but now you can finally visually tell the difference between BPOs and BPCs.
And now you know how.