Natural batteries and crafted batteries would need different classes, as the crafted ones have to have that additional code @Maalteromm mentioned. I guess you could make all batteries the crafted kind, that might work.
Here is what I'm reading in the previous posts:
1. @scifiwriterguy wants to avoid changing the fabricator code. I assume his approach requires new battery objects.
2. @Maalteromm *does* want to change the fabricator code. Zero changes to batteries.
Assuming I'm understanding what @Maalteromm is suggesting, there are NEVER any changes to the batteries in his approach. Rather, there is only a change to the fabricator itself. The fabricator reads the charge of the batteries and transfers that charge to the output battery. All batteries in the game already have a charge. It's just that the fabricator doesn't consider the charge of the battery used to construct an item. There are no changes to batteries. All batteries are exactly the same. The changed code is only in the fabricator. Again, I think @scifiwriterguy wants to avoid changing the fabricator while @Maalteromm *does* want to change the fabricator. I agree wholeheartedly with @Maalteromm's approach.
Actually, I'm not advocating any new objects at all. Sorry if it came off that way.
My suggestion was to remove batteries completely from the fabrication recipes. Players then fabricate a battery separately and install it in whatever device. The same base object that batteries ever were, only now no longer part of the finished fabrication routine. We've no need to create a new object class. (As a side bonus, it also introduces players to the battery replacement mechanic in the game. Build your new tool, stick a battery in it.)
Changing code is a problem. Always. Whenever you start messing with code that works, you run the very serious risk of ending up with bugs you never had and, really, never needed to have. As a result, you only mess with your code when it doesn't do the job it needs to do or it's become bloated. The fabricator code works. Adding new variables to push around values that, in the end, don't need to be pushed around is just inviting headaches. It's a lot easier, simpler, and safer to update an object or case table to remove a result than it is to muck around with the function itself to play musical chairs with a variable.
When you're at alpha build stage, sure, code editing is part and parcel with creating the product. But when you're in a nearly finished state, going back and changing function calls that are fundamental to the operating of the software as a whole is irresponsibly dangerous except in the most severe circumstances. (And this is experience talking - having to build a machine language compiler is a powerful learning experience on the comparative value of code editing.)
Simpler solutions are nearly always better. Faster, cheaper, and safer.
I actually agreed with the opinion on removing the batteries from the fabrication recipes. To me it is a requirement for the new item to work, so I was indifferent to its presence on the fabrication process. I'm coming around to the other opinion now, after @Jacke comment.
However I still disagree on the coding issue. We are talking about software and whatever change made to it will alter it code on one level or another. Removing the batteries will require to change the recipe on the fabricator, the crafted item which will spawn without a battery and probably the UI of the fabricator for that item.
Every time you alter code there's a chance something will go wary, but it is not like the end of the world really.
There is version control.
Which ever solution for the recharge exploit, I would like UWE to move on this soon and make a proper and complete solution. It's aggravating that a significant challenge like recharging batteries can be bypassed by a pile of Titanium and a Fabricator.
I think that removing batteries from the build requirement is probably the most simple solution, but it introduces an extra step in crafting and that can have a serious effect on how new players view the game. To me, having a battery already in the tool really smooths out the crafting experience, but something does need to be done about this potential exploit.
I think the most elegant solution would be to transfer the amount of charge in the battery used to create the tool to the final product. But I also agree that this is probably more complicated to implement, and thus has a greater chance of introducing bugs.
It's a complicated issue and I'm sure it comes up in the devs' meetings. We'll just have to wait and see how they decide handle it, then help them test the change.
However I still disagree on the coding issue. We are talking about software and whatever change made to it will alter it code on one level or another. Removing the batteries will require to change the recipe on the fabricator, the crafted item which will spawn without a battery and probably the UI of the fabricator for that item.
Every time you alter code there's a chance something will go wary, but it is not like the end of the world really.
There is version control.
I think we're getting jammed by a semantics disconnect.
Most of the time, when people talk about "changing the code" they mean changing how functions are executed - what commands applied using which variables and when. Processing steps. However, for us, removing the battery from the completed item doesn't involve changing the functions involved in the process at all or even the structure of the function calls; that code remains untouched. All that needs to be changed - depending on how UWE built their system - is going into a case table or a where list and removing a line from the state list. So, rather than creating obj_laser_cutter and obj_battery, it's only returning obj_laser_cutter. You don't change how the function operates, just what it's told to drop into the player's inventory. It's why case tables are so popular in complex programming like compilers or games; editing those is extremely straightforward and, unless you reference an object that doesn't exist, totally safe.
(Of course, referencing a phantom is always a bad idea.)
By leaving the actual functions and their calls unaffected, we reduce the risk of introducing new bugs dramatically. Now, naturally, a lot of this is guesswork - UWE isn't going to be letting us crawl through their source - but assuming they haven't walked completely away from standard good programming practices, we're operating under reasonable assumptions.
...UWE isn't going to be letting us crawl through their source...
Do you remember the line from the movie Independence Day where the President tells that one guy Area 51 doesn't exist, and then the spy tells him... "Strictly speaking, that's not entirely true..."--because Area 51 really does exist?
So, let's just say your comment about UWE not letting us crawl through their source isn't strictly true...
...UWE isn't going to be letting us crawl through their source...
Do you remember the line from the movie Independence Day where the President tells that one guy Area 51 doesn't exist, and then the spy tells him... "Strictly speaking, that's not entirely true..."--because Area 51 really does exist?
So, let's just say your comment about UWE not letting us crawl through their source isn't strictly true...
Okay but when do we get to save the planet with a Macbook and a nuke?
However I still disagree on the coding issue. We are talking about software and whatever change made to it will alter it code on one level or another. Removing the batteries will require to change the recipe on the fabricator, the crafted item which will spawn without a battery and probably the UI of the fabricator for that item.
Every time you alter code there's a chance something will go wary, but it is not like the end of the world really.
There is version control.
I think we're getting jammed by a semantics disconnect.
Most of the time, when people talk about "changing the code" they mean changing how functions are executed - what commands applied using which variables and when. Processing steps. However, for us, removing the battery from the completed item doesn't involve changing the functions involved in the process at all or even the structure of the function calls; that code remains untouched. All that needs to be changed - depending on how UWE built their system - is going into a case table or a where list and removing a line from the state list. So, rather than creating obj_laser_cutter and obj_battery, it's only returning obj_laser_cutter. You don't change how the function operates, just what it's told to drop into the player's inventory. It's why case tables are so popular in complex programming like compilers or games; editing those is extremely straightforward and, unless you reference an object that doesn't exist, totally safe.
(Of course, referencing a phantom is always a bad idea.)
By leaving the actual functions and their calls unaffected, we reduce the risk of introducing new bugs dramatically. Now, naturally, a lot of this is guesswork - UWE isn't going to be letting us crawl through their source - but assuming they haven't walked completely away from standard good programming practices, we're operating under reasonable assumptions.
Probably, I'm not very familiar with formal programming terminology and english is not my native language.
However in your example you're only addressing the created items. We shouldn't only focus on what's being returned by those functions, but also on what is being removed by them and their requirements description on the fabricator and PDA UI. Said function will need to stop deleting a battery from the player inventory, the UI will need to be adjusted accordingly. You will also need to add a hint/tutorial to teach those slow players who don't realize they need to load the new item with a battery.
Those changes need to be balanced against such a small change in code which will not require changing anything else.
I'm having a hard time visualizing what bugs could arise from that, because to me programming is a very logical and straightforward field. I'm still an amateur, but whenever I see a bug it's usually human mistake (it's practically always human, what I mean is that in my experience it usually is the programmers fault and not the language/compiler. I understand that others might have differing experiences).
I can't properly process how saving one object attribute value, with a name that's not used anywhere else in the code, and later updating the new object with this value can break the game.
So here's a thought on that one: maybe the Fabricator charges the battery automagically in order to give a fully functioning item. May just be intended.
So here's a thought on that one: maybe the Fabricator charges the battery automagically in order to give a fully functioning item. May just be intended.
Besides it is hurts immersion as it is. @scifiwriterguy elegantly put it:
It could be handwaved by saying that the fabricator charges the battery during the process, but that just reeks of weak retconning and actually kinda makes it worse.
The only way it could make sense is if you pulled the missing juice from your base / Cyclops / Lifepod available energy pool, and that would open a whole new can of worms coding-side, I'm sure.
However in your example you're only addressing the created items. We shouldn't only focus on what's being returned by those functions, but also on what is being removed by them and their requirements description on the fabricator and PDA UI. Said function will need to stop deleting a battery from the player inventory, the UI will need to be adjusted accordingly. You will also need to add a hint/tutorial to teach those slow players who don't realize they need to load the new item with a battery.
Those changes need to be balanced against such a small change in code which will not require changing anything else.
No UI changes are necessary; the UI only provides a visual representation of the player's inventory table. When an item is added or removed from the table, the UI changes its representation. All UIs built by a competent developer are dynamic; having to program each and every possible use case would be a colossal waste of time. So, when the function removes, say obj_copper_wire from the inventory table, the UI interprets the change and no longer draws the icon in inventory. Similarly, if no instruction to remove an item is executed, then the UI doesn't have to do anything. In this case, by removing the battery from the list of items used to create the tool, the command to remove the battery from inventory is no longer sent, and the player's inventory retains the battery. The UI draws the icon in inventory like it had been all along. In terms of compute cycles, it's even cheaper than what had to happen before.
As for showing which ingredients are needed, this is almost certainly also dynamic. The UI looks at the text in the code and draws the appropriate tooltips. (A great example is Stellaris; so long as the right variables are referenced and values given, the UI parses it all into pretty, human-readable words by itself. Any changes automatically update the tooltip because the system is reading the data and running it through an interpreter.) Unless the programmers are real busywork junkies, they went dynamic, too.
I'm having a hard time visualizing what bugs could arise from that, because to me programming is a very logical and straightforward field. I'm still an amateur, but whenever I see a bug it's usually human mistake (it's practically always human, what I mean is that in my experience it usually is the programmers fault and not the language/compiler. I understand that others might have differing experiences).
I can't properly process how saving one object attribute value, with a name that's not used anywhere else in the code, and later updating the new object with this value can break the game.
In theory, it won't break the game; in practice of implementing that function, though, there are lots of opportunities for breakage. You're absolutely right: all bugs come from a programmer either misunderstanding what s/he is supposed to be doing or just making a plain old mistake. From misnaming a variable to forgetting an ampersand (or, heaven help you, a semicolon), all bugs come down to fallible humans. Thus one of the fundamental principles of programming is to reduce the number of opportunities for mistakes. This means simplifying your code as much as possible while still completing your goals.
(There's a lot of simple speak coming up. This is not disrespect or contempt for your programming skills; it's so the non-programmers can follow along.)
I'm sure you're aware that function types are not equal. The fabrication function within the game's code is almost certainly a void. (This means that it doesn't receive any data; it's just told "go" and that's it.) The main loop checks to see if the player has the necessary inputs and calls the function. The function isn't "given" the objects because it's all theater; software functions don't need the raw materials. With the right console commands, you can probably execute the fabrication function right off the command line.
There are a few ways to handle this. A switch table would work, but it'd be pretty clunky. An std array makes more sense, but a lot of programmers don't like them because they quickly become difficult to understand once you start having a lot of field data. (Fine if it's only the processes shuffling it around, but if humans need to read and tweak, it gets messy, mainly because nobody believes in using comments anymore.) Or you can get really creative with data structures - it all depends on what Unity allows and can do. In any case, the function looks up the entry for whatever it is you're fabricating, removes the objects listed as to be removed from the player's inventory, and adds the object you wanted. Easy as uno, dos, tres.
But the key element here is that the fabrication function nearly certainly not actually doing anything with object data. It's not being passed data on the objects used to create the product in-game, nor is it configured to. All it knows to do is to delete however many objects with these object IDs, then create one of this object ID. (Or two.) So, you need to swap a function from a void to, most likely an int (if you're a masochist, a float) so you can start passing that specific state data - the battery charge - over to the function. Okay, big deal, so we do that! Not so easy; you need to make absolutely certain that you go through your code and find every single instance where you're calling that function and make the necessary changes. Going from a void to a non-void function means that you need to set every one of your function calls to be calling an int rather than a void and to be passing it the correct number of variables or it's going to crash on you. (Actually, it'll just fail to compile. Still vexing.)
So we do that. We go through all the code and update all of our function calls. Now the function is being handed the data, but it was never configured to do anything with data, so you now need to add in that functionality. Whoops, hold on - we were deleting the objects from inventory before we even called the function. Ah, nuts, now we have to go back and make sure we preserve whatever data we want before that part executes, then pass that variable value over. We can't use a local now, it has to be a global var. Okay, so we do that. Then we pass that value to the fabrication function. Now we need to add code to the fabrication function to set the battery charge to whatever our holder variable says (likely not too difficult; each powered object probably has a child variable like laser_cutter.charge or similar, so we just write the passed data to laser_cutter.charge). Oh, except the fabrication function never handled that before, either. Argh! Okay, add that as another passable variable because that value has to come back from the function. Write the variable, place the edited item in the player inventory.
Man, that's a lot of work. We need to:
Change the function type
Reset and error check all of the function calls
Set up variable passes for all those function calls (do this at the same time as #2)
Create a global var to store the charge state of the battery used
Pass the charge state variable to the function
Overwrite a default value with the new charge variable
Pass it back for addition to the player inventory
And some of those steps, particularly #2, are going to take a fair bit of time, and that's assuming there are no mistakes. If there are, the compiler throws errors and it's time for a good old bughunt. We're gonna need coffee.
We've also created a new problem: what if the player has more than one battery in their inventory? How does the system pick? Go with the most charged? The least? Let the user pick? These are new problems because, before now, there's never been a choice involved; the system seems to just yank one out of your inventory more or less at random. Now we need to add logic to tell the software how it's supposed to choose which out of a selection of valid objects is the correct valid object.
Contrariwise, we can just remove batteries from the process altogether. Of course, that's going to take some work, too, namely:
Go into the switch/array/other structure table/DLL CSV/etc and remove the battery by setting the default charge of powered tools to the "no battery present" flag, probably -1. (0-100 are probably used for actual charge percentages.)
?
Profit.
Seriously, that's it. A single coder could make that change for all of the powered tools in about ten minutes.
Happily, the UI already has in place a means to alert the player that they're playing with a dead toy: the pop-up at the bottom of the screen prompting the user to swap batteries. Most of us run into it first while using the seaglide, but it comes up every time a tool runs dry. The UI is already set up to handle the learning curve for the "your tools do not come with batteries" problem, so we don't need to do anything there.
Please don't misunderstand me, though; I'm not saying that creating a tool with the battery charge of whatever battery was used to create it is a bad idea. It'd be quite thorough. But it would also be substantially more work and risk. A basic tenet in programming is to make as few changes as necessary to achieve your goal; fewer changes mean fewer places for things to go wrong (and time and money saved). Since the goal is to close up an exploit, the path of least resistance is to just take batteries out of the equation, so to speak. Both are theoretically doable, but when it comes to commercial programming, reliable simplicity is king.
Thank you for your thoroughly response. I'm learning lots.
I believe unity is object oriented, as such there must be some sort of fabricator base class that all other fabricate calls derive from. Can't they just change this base class with a conditional checking if a battery will be used and let all fabricator calls inherit that? That should make it all much quicker.
For the float thing, I'm not a masochist, just addicted to them. It would use a little more memory and processing, but other than that it wouldn't make much of a difference. I can't fathom how that would stack up if it's made common practice because I'm not used with scenarios where something else can be used. So I intuitively put ints on this little box in my mind where they can only be indexes, timesteps and such.
And for the battery used, it should just go for the highest charged ones.
Happily, the UI already has in place a means to alert the player that they're playing with a dead toy: the pop-up at the bottom of the screen prompting the user to swap batteries. Most of us run into it first while using the seaglide, but it comes up every time a tool runs dry. The UI is already set up to handle the learning curve for the "your tools do not come with batteries" problem, so we don't need to do anything there.
I believe the problem here would arise when the first simple item was built without a battery and the player don't even knows he needs one. Then he would need to stop at the fabricator, find the battery in it, check the recipe and try to farm those items without knowing where or what they are.
I know this is one of the premises of the game, however in this early stage you have to take the slower players by the hand and nudge them in the right direction.
Here @Kurasu solution of providing a few batteries in the pod would be key. Another would be to make the very simple recipes, such as the scanner, not needing batteries. The PDA seem to be getting along fine without'em.
I would like to thank you again Scifiwriterguy. It was very enlightening.
however in this early stage you have to take the slower players by the hand and nudge them in the right direction.
I do not mean any disrespect while referring to "slow players". In fact the brightest person I know is terrible with videogames, by never making habit of playing them just didn't develop that hunch for guessing usual game mechanics.
I believe unity is object oriented, as such there must be some sort of fabricator base class that all other fabricate calls derive from. Can't they just change this base class with a conditional checking if a battery will be used and let all fabricator calls inherit that? That should make it all much quicker.
A good idea, but in this case, it wouldn't work well. If all the recipes had a battery issue, then changing the base class would be the ideal solution - fixes all the inheritors in one shot. But most recipes don't use batteries, which means we'd be stuck assigning a null value of some kind to ensure that the function is still being passed the correct number of variables - in a format and with values that won't break the system. Then the function is going to try to do something with that null, which could also end up doing something unexpectedly peculiar.
(Programming tip: never pass a function junk data. A variable whose contents are random can create some bad results when the value happens to be legitimate for the function but produces an invalid result. Variable control is way up on the list of programming musts. You probably already know this one, but a good practice is to re-zero a variable before writing a value to it; cleans out any junk or overflow that might've ended up in there somehow.)
For the float thing, I'm not a masochist, just addicted to them. It would use a little more memory and processing, but other than that it wouldn't make much of a difference. I can't fathom how that would stack up if it's made common practice because I'm not used with scenarios where something else can be used. So I intuitively put ints on this little box in my mind where they can only be indexes, timesteps and such.
In this day and age, there's really no overriding reason to avoid floats when possible, but there are still some very good ones. Back in the day when memory was actually a constraint - when you didn't have multi-gigabyte, essentially-limitless RAM playgrounds, you had to be very efficient in using it. Thus, floats were only used when you needed high precision; the extra memory a float used was a serious consideration in its choice. It's why precise values in older games just didn't happen or were fudged from integers. In terms of memory, ints are just plain cheaper.
(Same goes for conserving compute cycles - nobody bothers tightening their code anymore because when you have a processor that can complete a few billion calculations per second, it's not a do-or-die thing. Of course, the total complacency of programmers for coding efficiently is why we end up with bloatware that can drag even a power rig to a crawl. Windows is a perfect example: if MS ever bothered to do a proper efficiency analysis and clean and tighten the code, it'd probably use about 30% less memory, take up less space in storage, and load a LOT faster. But they won't because there's no market pressure that forces them to do better. Older coders just bristle at that; making functional, elegant code was a mark of pride back in the day.)
Of course, that's not an issue anymore. You could use nothing but floats and probably not run into any memory issues. But floats can really make a mess if you let them run around unsupervised. (Kinda like kids.) Fundamentally, the difference comes down to this:
Integer - Limited range, high precision at all times
Float - Expansive range, progressively lower precision in higher values
This would seem counterintuitive, but remember that floats have a fixed memory space to work in. Thus, as the whole number gets larger, precision actually goes down because the variable has to reallocate space left of the decimal to store the number. That means that the value to the right of the decimal gets progressively more and more rounded:
1.2345678
10.234568
100.23457
1000.2346
10000.235
(Naturally, in reality, the memory space is larger; this is for illustrative purposes.)
In practical terms, like coding for a battery percentage, a float will work just as well as an int or a fixed. But when you're doing ultra-high-precision calculations (like for a physics simulation), then the imprecision of a float is deadly.
Don't forget, it's float rounding that screwed up the gen-1 Pentium processor. And, worse, it's a cumulative problem. Rounded variables used in calculations cause progressive inaccuracies. Nine times out of ten, nobody notices. The tenth time, though...oh, fun can happen. In programming, it's called relative error. A float deals in relative error; they're all going to be off and not by the same amount. Fixed-point or integer variables also have error, but in those cases it's absolute error - it's going to be off by the exact same amount every time if it's off at all.
At the end of the day, if you really need precision, you use a fixed variable to avoid rounding errors and keep all error an easily-compensated absolute. If you need to keep track of very granular data - like a battery percentage - an int will do the job just fine and prevents calculation weirdness. (They're also faster running through the processor, even if it has a dedicated FPU.) And if you need to keep track of enormous, brain-meltingly large numbers, then floats are going to be pretty much your only option.
I believe the problem here would arise when the first simple item was built without a battery and the player don't even knows he needs one. Then he would need to stop at the fabricator, find the battery in it, check the recipe and try to farm those items without knowing where or what they are.
I know this is one of the premises of the game, however in this early stage you have to take the slower players by the hand and nudge them in the right direction.
A very valid point; new players in particular would need to be made aware. A line could be added to the recipe tooltip (maybe in red for visibility) that the device requires a separately-fabricated battery. (Even in the future, it will still probably say Batteries Not Included on the box.) Or a PDA milestone pop-up could be used as a warning the first time.
Another would be to make the very simple recipes, such as the scanner, not needing batteries. The PDA seem to be getting along fine without'em.
That'd be a little immersion breaking, I fear. A complex, powerful device like the scanner would be expected to chew through batteries. Removing batteries from Tool A but not Tool B usually brings the "why does this need batteries and not that?!" complaints out of the woodwork.
Besides, I'm pretty sure the PDA runs on the survivor's fear and blind rage at how unhelpful it is.
I would like to thank you again Scifiwriterguy. It was very enlightening.
Hey, no worries! Programming is kind of like painting - you might know 90% of what another artist might recommend, but that 10% that's new could save you a massive amount of frustration down the line.
Let me chime in this interesting programming discussion, and provide some info on the battery implementation in the game. (Even though I haven't read everything in detail, there's quite a lot of info here ).
All tools are derived from a so called "PlayerTool" class. If the tool is powered, it has an "EnergyMixin" attached to the gameobject (which AFAIK is added in the unity designer thingy). This EnergyMixin provides some basic power functionality: telling how much charge the tool has, playing sounds when swapping batteries and when the tool powers up/down. It also reacts to EMP charges, but most importantly: it contains a battery slot. Simply said, all powered tools contain a reference to the battery that's in them, they do not store the amount of charge themselves. If the tool needs to know about the charge (for the UI, and whether it can turn on), it simply asks the EnergyMixin, which in turn checks if a battery is in the batteryslot, and if so, returns the charge in the battery. That way you don't need to worry about any of the other code.
Now, how do these batteries end up there in the first place? Well, if you press R, you can swap any compatible battery in. And for crafting: these EnergyMixin classes provide a so called OnCraftEnd method. When crafting an object, the entire component hierarchy is created, and when crafting is finished (and the object is stored in your inventory), this method is called on all components that have it, to tell that they need to do their initialization. For EnergyMixin's, there's a field determining the type of the default battery (since we have multiple battery types, and vehicles use power cells), and if this type is set, that type of battery is created (out of thin air, yes) and stored in the battery slot.
So, in order to remove default batteries from everything, all that needs to be done is remove that line. However, if some game components still require a default battery (wasn't there something with a power cell in the recipe?), it's better to set the default battery type to none. As far as I understood, the values in these public fields are edited in the unity inspector, and stored with the asset/gameobject/however that works. It would take a dev a few minutes to go over all the tools and set that field to none. Even better is to set the default to none, and override it for the types that need it.
Any way I could add to the discussion would, unfortunately, have nothing to do with code and everything to do with player experience.
Making items not require a battery, but starting without a battery in them, should be as simple as changing the crafting recipe, and changing the newly crafted device's battery to read "None".
If you're worried about guiding the player, a line of PDA dialogue could easily be added when you first create a scanner (since they will almost definitely be the first thing someone makes that requires batteries).
"Extra batteries are included in all lifepod survival kits. Simply insert in the empty slot located on the device. Removing to exchange is as simple as the push of a button."
.... which will both lead players to check out the lifekit (where the starting batteries are) *and* have them looking for which button to press (there is a keybound 'replace' button that is visible in the UI settings) without having to break the immersion by having a big "PRESS 'R' TO REPLACE BATTERY" onscreen.
Maybe on crafting your first battery:
"Rechargable batteries are a required power source for nearly all Alterra handheld technology."
... so if someone makes a battery first, they have the idea that it'll be required for a lot of stuff, *and* that they can be recharged, so they aren't as likely to just throw out the dead ones.
As it stands now, with the battery recipe requiring only 1 copper and 2 acid mushrooms, and copper actually being quite thick in sandstone now that lead has been removed, creating batteries is not going to be horrible even in the early game, so I can agree on only having two (repair tool and scanner). I'm swimming in mats early game for once, compared to my earlier starts, and I'm loving it. It actually makes me feel comfortable with running a battery out rather than trying to save the power because they're a PITA to create.
Later on, when a power cell is needed, the player should have dead batteries (probably mostly from the sea glider) they can use to make it; batteries that, at this particular moment, are otherwise useless to them (because if you follow the path, the battery charger comes *after* you get the ability to craft a seamoth). And I don't find it 'not immersive' that the batteries are dead and yet still make a fully powered power cell. And by the time they're ready to make a Cyclops, they should have tons of spare batteries from the vacuum packs to make the 6 required.
Unless you are feeling like the vehicles shouldn't be held to the same "requires batteries" as handheld devices (which, to be honest, I would be perfectly comfortable with; it doesn't feel non-immersive to have them treated differently to me, and the power cells in those aren't being exploited by crafting because of the large number of materials needed to make something).
Comments
I actually agreed with the opinion on removing the batteries from the fabrication recipes. To me it is a requirement for the new item to work, so I was indifferent to its presence on the fabrication process. I'm coming around to the other opinion now, after @Jacke comment.
However I still disagree on the coding issue. We are talking about software and whatever change made to it will alter it code on one level or another. Removing the batteries will require to change the recipe on the fabricator, the crafted item which will spawn without a battery and probably the UI of the fabricator for that item.
Every time you alter code there's a chance something will go wary, but it is not like the end of the world really.
There is version control.
I think the most elegant solution would be to transfer the amount of charge in the battery used to create the tool to the final product. But I also agree that this is probably more complicated to implement, and thus has a greater chance of introducing bugs.
It's a complicated issue and I'm sure it comes up in the devs' meetings. We'll just have to wait and see how they decide handle it, then help them test the change.
I think we're getting jammed by a semantics disconnect.
Most of the time, when people talk about "changing the code" they mean changing how functions are executed - what commands applied using which variables and when. Processing steps. However, for us, removing the battery from the completed item doesn't involve changing the functions involved in the process at all or even the structure of the function calls; that code remains untouched. All that needs to be changed - depending on how UWE built their system - is going into a case table or a where list and removing a line from the state list. So, rather than creating obj_laser_cutter and obj_battery, it's only returning obj_laser_cutter. You don't change how the function operates, just what it's told to drop into the player's inventory. It's why case tables are so popular in complex programming like compilers or games; editing those is extremely straightforward and, unless you reference an object that doesn't exist, totally safe.
(Of course, referencing a phantom is always a bad idea.)
By leaving the actual functions and their calls unaffected, we reduce the risk of introducing new bugs dramatically. Now, naturally, a lot of this is guesswork - UWE isn't going to be letting us crawl through their source - but assuming they haven't walked completely away from standard good programming practices, we're operating under reasonable assumptions.
Do you remember the line from the movie Independence Day where the President tells that one guy Area 51 doesn't exist, and then the spy tells him... "Strictly speaking, that's not entirely true..."--because Area 51 really does exist?
So, let's just say your comment about UWE not letting us crawl through their source isn't strictly true...
Okay but when do we get to save the planet with a Macbook and a nuke?
Probably, I'm not very familiar with formal programming terminology and english is not my native language.
However in your example you're only addressing the created items. We shouldn't only focus on what's being returned by those functions, but also on what is being removed by them and their requirements description on the fabricator and PDA UI. Said function will need to stop deleting a battery from the player inventory, the UI will need to be adjusted accordingly. You will also need to add a hint/tutorial to teach those slow players who don't realize they need to load the new item with a battery.
Those changes need to be balanced against such a small change in code which will not require changing anything else.
I'm having a hard time visualizing what bugs could arise from that, because to me programming is a very logical and straightforward field. I'm still an amateur, but whenever I see a bug it's usually human mistake (it's practically always human, what I mean is that in my experience it usually is the programmers fault and not the language/compiler. I understand that others might have differing experiences).
I can't properly process how saving one object attribute value, with a name that's not used anywhere else in the code, and later updating the new object with this value can break the game.
That is exploitable and the devs are tackling some of these exploits right now, as noted by @garath
https://trello.com/c/7JJZqCVE/6655-address-battery-exploit-in-welder
Besides it is hurts immersion as it is. @scifiwriterguy elegantly put it:
No UI changes are necessary; the UI only provides a visual representation of the player's inventory table. When an item is added or removed from the table, the UI changes its representation. All UIs built by a competent developer are dynamic; having to program each and every possible use case would be a colossal waste of time. So, when the function removes, say obj_copper_wire from the inventory table, the UI interprets the change and no longer draws the icon in inventory. Similarly, if no instruction to remove an item is executed, then the UI doesn't have to do anything. In this case, by removing the battery from the list of items used to create the tool, the command to remove the battery from inventory is no longer sent, and the player's inventory retains the battery. The UI draws the icon in inventory like it had been all along. In terms of compute cycles, it's even cheaper than what had to happen before.
As for showing which ingredients are needed, this is almost certainly also dynamic. The UI looks at the text in the code and draws the appropriate tooltips. (A great example is Stellaris; so long as the right variables are referenced and values given, the UI parses it all into pretty, human-readable words by itself. Any changes automatically update the tooltip because the system is reading the data and running it through an interpreter.) Unless the programmers are real busywork junkies, they went dynamic, too.
In theory, it won't break the game; in practice of implementing that function, though, there are lots of opportunities for breakage. You're absolutely right: all bugs come from a programmer either misunderstanding what s/he is supposed to be doing or just making a plain old mistake. From misnaming a variable to forgetting an ampersand (or, heaven help you, a semicolon), all bugs come down to fallible humans. Thus one of the fundamental principles of programming is to reduce the number of opportunities for mistakes. This means simplifying your code as much as possible while still completing your goals.
(There's a lot of simple speak coming up. This is not disrespect or contempt for your programming skills; it's so the non-programmers can follow along.)
I'm sure you're aware that function types are not equal. The fabrication function within the game's code is almost certainly a void. (This means that it doesn't receive any data; it's just told "go" and that's it.) The main loop checks to see if the player has the necessary inputs and calls the function. The function isn't "given" the objects because it's all theater; software functions don't need the raw materials. With the right console commands, you can probably execute the fabrication function right off the command line.
There are a few ways to handle this. A switch table would work, but it'd be pretty clunky. An std array makes more sense, but a lot of programmers don't like them because they quickly become difficult to understand once you start having a lot of field data. (Fine if it's only the processes shuffling it around, but if humans need to read and tweak, it gets messy, mainly because nobody believes in using comments anymore.) Or you can get really creative with data structures - it all depends on what Unity allows and can do. In any case, the function looks up the entry for whatever it is you're fabricating, removes the objects listed as to be removed from the player's inventory, and adds the object you wanted. Easy as uno, dos, tres.
But the key element here is that the fabrication function nearly certainly not actually doing anything with object data. It's not being passed data on the objects used to create the product in-game, nor is it configured to. All it knows to do is to delete however many objects with these object IDs, then create one of this object ID. (Or two.) So, you need to swap a function from a void to, most likely an int (if you're a masochist, a float) so you can start passing that specific state data - the battery charge - over to the function. Okay, big deal, so we do that! Not so easy; you need to make absolutely certain that you go through your code and find every single instance where you're calling that function and make the necessary changes. Going from a void to a non-void function means that you need to set every one of your function calls to be calling an int rather than a void and to be passing it the correct number of variables or it's going to crash on you. (Actually, it'll just fail to compile. Still vexing.)
So we do that. We go through all the code and update all of our function calls. Now the function is being handed the data, but it was never configured to do anything with data, so you now need to add in that functionality. Whoops, hold on - we were deleting the objects from inventory before we even called the function. Ah, nuts, now we have to go back and make sure we preserve whatever data we want before that part executes, then pass that variable value over. We can't use a local now, it has to be a global var. Okay, so we do that. Then we pass that value to the fabrication function. Now we need to add code to the fabrication function to set the battery charge to whatever our holder variable says (likely not too difficult; each powered object probably has a child variable like laser_cutter.charge or similar, so we just write the passed data to laser_cutter.charge). Oh, except the fabrication function never handled that before, either. Argh! Okay, add that as another passable variable because that value has to come back from the function. Write the variable, place the edited item in the player inventory.
Man, that's a lot of work. We need to:
And some of those steps, particularly #2, are going to take a fair bit of time, and that's assuming there are no mistakes. If there are, the compiler throws errors and it's time for a good old bughunt. We're gonna need coffee.
We've also created a new problem: what if the player has more than one battery in their inventory? How does the system pick? Go with the most charged? The least? Let the user pick? These are new problems because, before now, there's never been a choice involved; the system seems to just yank one out of your inventory more or less at random. Now we need to add logic to tell the software how it's supposed to choose which out of a selection of valid objects is the correct valid object.
Contrariwise, we can just remove batteries from the process altogether. Of course, that's going to take some work, too, namely:
Seriously, that's it. A single coder could make that change for all of the powered tools in about ten minutes.
Happily, the UI already has in place a means to alert the player that they're playing with a dead toy: the pop-up at the bottom of the screen prompting the user to swap batteries. Most of us run into it first while using the seaglide, but it comes up every time a tool runs dry. The UI is already set up to handle the learning curve for the "your tools do not come with batteries" problem, so we don't need to do anything there.
Please don't misunderstand me, though; I'm not saying that creating a tool with the battery charge of whatever battery was used to create it is a bad idea. It'd be quite thorough. But it would also be substantially more work and risk. A basic tenet in programming is to make as few changes as necessary to achieve your goal; fewer changes mean fewer places for things to go wrong (and time and money saved). Since the goal is to close up an exploit, the path of least resistance is to just take batteries out of the equation, so to speak. Both are theoretically doable, but when it comes to commercial programming, reliable simplicity is king.
Thank you for your thoroughly response. I'm learning lots.
I believe unity is object oriented, as such there must be some sort of fabricator base class that all other fabricate calls derive from. Can't they just change this base class with a conditional checking if a battery will be used and let all fabricator calls inherit that? That should make it all much quicker.
For the float thing, I'm not a masochist, just addicted to them. It would use a little more memory and processing, but other than that it wouldn't make much of a difference. I can't fathom how that would stack up if it's made common practice because I'm not used with scenarios where something else can be used. So I intuitively put ints on this little box in my mind where they can only be indexes, timesteps and such.
And for the battery used, it should just go for the highest charged ones.
I believe the problem here would arise when the first simple item was built without a battery and the player don't even knows he needs one. Then he would need to stop at the fabricator, find the battery in it, check the recipe and try to farm those items without knowing where or what they are.
I know this is one of the premises of the game, however in this early stage you have to take the slower players by the hand and nudge them in the right direction.
Here @Kurasu solution of providing a few batteries in the pod would be key. Another would be to make the very simple recipes, such as the scanner, not needing batteries. The PDA seem to be getting along fine without'em.
I would like to thank you again Scifiwriterguy. It was very enlightening.
I do not mean any disrespect while referring to "slow players". In fact the brightest person I know is terrible with videogames, by never making habit of playing them just didn't develop that hunch for guessing usual game mechanics.
A good idea, but in this case, it wouldn't work well. If all the recipes had a battery issue, then changing the base class would be the ideal solution - fixes all the inheritors in one shot. But most recipes don't use batteries, which means we'd be stuck assigning a null value of some kind to ensure that the function is still being passed the correct number of variables - in a format and with values that won't break the system. Then the function is going to try to do something with that null, which could also end up doing something unexpectedly peculiar.
(Programming tip: never pass a function junk data. A variable whose contents are random can create some bad results when the value happens to be legitimate for the function but produces an invalid result. Variable control is way up on the list of programming musts. You probably already know this one, but a good practice is to re-zero a variable before writing a value to it; cleans out any junk or overflow that might've ended up in there somehow.)
In this day and age, there's really no overriding reason to avoid floats when possible, but there are still some very good ones. Back in the day when memory was actually a constraint - when you didn't have multi-gigabyte, essentially-limitless RAM playgrounds, you had to be very efficient in using it. Thus, floats were only used when you needed high precision; the extra memory a float used was a serious consideration in its choice. It's why precise values in older games just didn't happen or were fudged from integers. In terms of memory, ints are just plain cheaper.
(Same goes for conserving compute cycles - nobody bothers tightening their code anymore because when you have a processor that can complete a few billion calculations per second, it's not a do-or-die thing. Of course, the total complacency of programmers for coding efficiently is why we end up with bloatware that can drag even a power rig to a crawl. Windows is a perfect example: if MS ever bothered to do a proper efficiency analysis and clean and tighten the code, it'd probably use about 30% less memory, take up less space in storage, and load a LOT faster. But they won't because there's no market pressure that forces them to do better. Older coders just bristle at that; making functional, elegant code was a mark of pride back in the day.)
Of course, that's not an issue anymore. You could use nothing but floats and probably not run into any memory issues. But floats can really make a mess if you let them run around unsupervised. (Kinda like kids.) Fundamentally, the difference comes down to this:
This would seem counterintuitive, but remember that floats have a fixed memory space to work in. Thus, as the whole number gets larger, precision actually goes down because the variable has to reallocate space left of the decimal to store the number. That means that the value to the right of the decimal gets progressively more and more rounded:
- 1.2345678
- 10.234568
- 100.23457
- 1000.2346
- 10000.235
(Naturally, in reality, the memory space is larger; this is for illustrative purposes.)In practical terms, like coding for a battery percentage, a float will work just as well as an int or a fixed. But when you're doing ultra-high-precision calculations (like for a physics simulation), then the imprecision of a float is deadly.
Don't forget, it's float rounding that screwed up the gen-1 Pentium processor. And, worse, it's a cumulative problem. Rounded variables used in calculations cause progressive inaccuracies. Nine times out of ten, nobody notices. The tenth time, though...oh, fun can happen. In programming, it's called relative error. A float deals in relative error; they're all going to be off and not by the same amount. Fixed-point or integer variables also have error, but in those cases it's absolute error - it's going to be off by the exact same amount every time if it's off at all.
At the end of the day, if you really need precision, you use a fixed variable to avoid rounding errors and keep all error an easily-compensated absolute. If you need to keep track of very granular data - like a battery percentage - an int will do the job just fine and prevents calculation weirdness. (They're also faster running through the processor, even if it has a dedicated FPU.) And if you need to keep track of enormous, brain-meltingly large numbers, then floats are going to be pretty much your only option.
A very valid point; new players in particular would need to be made aware. A line could be added to the recipe tooltip (maybe in red for visibility) that the device requires a separately-fabricated battery. (Even in the future, it will still probably say Batteries Not Included on the box.) Or a PDA milestone pop-up could be used as a warning the first time.
That'd be a little immersion breaking, I fear. A complex, powerful device like the scanner would be expected to chew through batteries. Removing batteries from Tool A but not Tool B usually brings the "why does this need batteries and not that?!" complaints out of the woodwork.
Besides, I'm pretty sure the PDA runs on the survivor's fear and blind rage at how unhelpful it is.
Hey, no worries! Programming is kind of like painting - you might know 90% of what another artist might recommend, but that 10% that's new could save you a massive amount of frustration down the line.
Let me chime in this interesting programming discussion, and provide some info on the battery implementation in the game. (Even though I haven't read everything in detail, there's quite a lot of info here ).
All tools are derived from a so called "PlayerTool" class. If the tool is powered, it has an "EnergyMixin" attached to the gameobject (which AFAIK is added in the unity designer thingy). This EnergyMixin provides some basic power functionality: telling how much charge the tool has, playing sounds when swapping batteries and when the tool powers up/down. It also reacts to EMP charges, but most importantly: it contains a battery slot. Simply said, all powered tools contain a reference to the battery that's in them, they do not store the amount of charge themselves. If the tool needs to know about the charge (for the UI, and whether it can turn on), it simply asks the EnergyMixin, which in turn checks if a battery is in the batteryslot, and if so, returns the charge in the battery. That way you don't need to worry about any of the other code.
Now, how do these batteries end up there in the first place? Well, if you press R, you can swap any compatible battery in. And for crafting: these EnergyMixin classes provide a so called OnCraftEnd method. When crafting an object, the entire component hierarchy is created, and when crafting is finished (and the object is stored in your inventory), this method is called on all components that have it, to tell that they need to do their initialization. For EnergyMixin's, there's a field determining the type of the default battery (since we have multiple battery types, and vehicles use power cells), and if this type is set, that type of battery is created (out of thin air, yes) and stored in the battery slot.
So, in order to remove default batteries from everything, all that needs to be done is remove that line. However, if some game components still require a default battery (wasn't there something with a power cell in the recipe?), it's better to set the default battery type to none. As far as I understood, the values in these public fields are edited in the unity inspector, and stored with the asset/gameobject/however that works. It would take a dev a few minutes to go over all the tools and set that field to none. Even better is to set the default to none, and override it for the types that need it.
Making items not require a battery, but starting without a battery in them, should be as simple as changing the crafting recipe, and changing the newly crafted device's battery to read "None".
If you're worried about guiding the player, a line of PDA dialogue could easily be added when you first create a scanner (since they will almost definitely be the first thing someone makes that requires batteries).
"Extra batteries are included in all lifepod survival kits. Simply insert in the empty slot located on the device. Removing to exchange is as simple as the push of a button."
.... which will both lead players to check out the lifekit (where the starting batteries are) *and* have them looking for which button to press (there is a keybound 'replace' button that is visible in the UI settings) without having to break the immersion by having a big "PRESS 'R' TO REPLACE BATTERY" onscreen.
Maybe on crafting your first battery:
"Rechargable batteries are a required power source for nearly all Alterra handheld technology."
... so if someone makes a battery first, they have the idea that it'll be required for a lot of stuff, *and* that they can be recharged, so they aren't as likely to just throw out the dead ones.
As it stands now, with the battery recipe requiring only 1 copper and 2 acid mushrooms, and copper actually being quite thick in sandstone now that lead has been removed, creating batteries is not going to be horrible even in the early game, so I can agree on only having two (repair tool and scanner). I'm swimming in mats early game for once, compared to my earlier starts, and I'm loving it. It actually makes me feel comfortable with running a battery out rather than trying to save the power because they're a PITA to create.
Later on, when a power cell is needed, the player should have dead batteries (probably mostly from the sea glider) they can use to make it; batteries that, at this particular moment, are otherwise useless to them (because if you follow the path, the battery charger comes *after* you get the ability to craft a seamoth). And I don't find it 'not immersive' that the batteries are dead and yet still make a fully powered power cell. And by the time they're ready to make a Cyclops, they should have tons of spare batteries from the vacuum packs to make the 6 required.
Unless you are feeling like the vehicles shouldn't be held to the same "requires batteries" as handheld devices (which, to be honest, I would be perfectly comfortable with; it doesn't feel non-immersive to have them treated differently to me, and the power cells in those aren't being exploited by crafting because of the large number of materials needed to make something).