Zero Cost Production

This should be fairly easy to implement, as it already works in a sense. I'd like to be able to produce and place a unit without having a cost attached. The use case stems uniquely from having unit upgrades that should not require resources to achieve. This is actually a replacement for this feature request on the old forum and on Github, as I think this is a more elegant solution to my proposed use case anyways. My proposed code for this would be as follows:

Now, this code does work with the engine in its current state. I can successfully purchase the zero cost unit and place it—with the engine accurately removing no resources for the purchase, but it throws all sorts of errors that make it untenable to implement with the current code. So my request is more along the lines of removing those errors and supporting a use of the code like that.

I would be incredibly grateful to have this feature supported, as it forms the basis of an entirely new economic and production system that I am cooking up.

You cannot make a trigger that places as many "inf2" as the number of "inf1" in a territory.

To do that directly, you would need to make like 1,000 triggers per territory per player, from 1 to 1,000, plus another trigger per territory to remove the "upgraded" unit, and that would only cover as long as it never happens that more than 1,000 of the same units are in a same territory.

But you can do it by having the units produce their "upgrades" (via unit option), and, then, you just need a single trigger to removed the producing units (look at the "recruit" in House of Absburg), but this is sort of a workaround mix of unit options and triggers (works fine tho).

This proposal is a quite patchy too, to be honest, because in the moment something costs 0, then you will just spam the button buying like 9999 of those units, since anyways you don't care to overbuy them. So, if a unit costs 0, it would be rather more reasonable that you are not prompted to buy it at all, but can just place as many as you want during the placement phase (or, with what you can do now, just make a trigger giving the player an enormous quantity of units, then place what you will; tho that's a bit dirty).

@cernel But if the purchase phase would limit you not purchasing more than you can place (something like the current warning that you are purchasing too much, yet not allowing you to do so), then it would make good sense having the purchase of cost 0 units available (still, if there is only 1 cost 0 units available, it would make more sense that you automatically get as many of that unit as the unused units purchase max allowance).

@redrum What Cernel said about doing the triggers. I'm not aware of any way that this could be accomplished through triggers.

For the sake of this example, we shall say that veteran infantry consumes a supply unit and an infantry to be built. Therefore, they should cost no PUs, as the cost is completely made up of the cost of the two combined units. Also, veteran infantry can only be placed under certain conditions in this example, so there will be no spamming of veteran infantry.

Here is the full error text after attempting to build one zero PU cost unit with the above XML productionRule.

TripleA engine version 1.9.0.0.10053
Loading map: the_pact_of_steel, from: /Users/username/triplea/downloadedMaps/the_pact_of_steel/map
Loading resources from the following paths: [/Users/username/triplea/downloadedMaps/the_pact_of_steel/map, /Applications/TripleA.app/Contents/java/app/assets]
Loading map: the_pact_of_steel, from: /Users/username/triplea/downloadedMaps/the_pact_of_steel/map
Loading resources from the following paths: [/Users/username/triplea/downloadedMaps/the_pact_of_steel/map, /Applications/TripleA.app/Contents/java/app/assets]
Error: Max cant be less than min. Current Min: 0, Current Max: 2147483647, New Max: -2147483648
java.lang.IllegalArgumentException: Max cant be less than min. Current Min: 0, Current Max: 2147483647, New Max: -2147483648
at games.strategy.ui.IntTextField.setMax(IntTextField.java:85)
at games.strategy.ui.ScrollableTextField.setMax(ScrollableTextField.java:130)
at games.strategy.triplea.ui.ProductionPanel$Rule.setMax(ProductionPanel.java:345)
at games.strategy.triplea.ui.ProductionPanel.calculateLimits(ProductionPanel.java:197)
at games.strategy.triplea.ui.ProductionPanel$Rule$1.changedValue(ProductionPanel.java:354)
at games.strategy.ui.ScrollableTextField.notifyListeners(ScrollableTextField.java:190)
at games.strategy.ui.ScrollableTextField.lambda$new$0(ScrollableTextField.java:112)
at games.strategy.ui.IntTextField.notifyListeners(IntTextField.java:179)
at games.strategy.ui.IntTextField.access$300(IntTextField.java:18)
at games.strategy.ui.IntTextField$IntegerDocument.insertString(IntTextField.java:142)
at javax.swing.text.AbstractDocument.replace(AbstractDocument.java:669)
at javax.swing.text.JTextComponent.setText(JTextComponent.java:1669)
at games.strategy.ui.IntTextField.setValue(IntTextField.java:79)
at games.strategy.ui.ScrollableTextField$1.actionPerformed(ScrollableTextField.java:53)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
at java.awt.Component.processMouseEvent(Component.java:6533)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6298)
at java.awt.Container.processEvent(Container.java:2237)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2295)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4889)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4526)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4467)
at java.awt.Container.dispatchEventImpl(Container.java:2281)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:90)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.awt.EventQueue$4.run(EventQueue.java:729)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:109)
at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:190)
at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
at java.security.AccessController.doPrivileged(Native Method)
at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
at java.awt.Dialog.show(Dialog.java:1084)
at java.awt.Component.show(Component.java:1671)
at java.awt.Component.setVisible(Component.java:1623)
at java.awt.Window.setVisible(Window.java:1014)
at java.awt.Dialog.setVisible(Dialog.java:1005)
at games.strategy.triplea.ui.ProductionPanel.show(ProductionPanel.java:104)
at games.strategy.triplea.ui.TabbedProductionPanel.getProduction(TabbedProductionPanel.java:41)
at games.strategy.triplea.ui.PurchasePanel$1.actionPerformed(PurchasePanel.java:119)
at games.strategy.triplea.ui.PurchasePanel.lambda$waitForPurchase$2(PurchasePanel.java:106)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

Once again, I was able to close the error notifications and finish purchasing and placing the unit, with all working as needed. The problem isn't that the engine cannot handle this but that it does not know that it can, so to speak.

@Cernel Just like with other games, there would be a warning not to purchase more than you can place. Also, there would be no incentive to purchase 9999 veteran infantry, as they are and always will be free, as long as the prerequisite units are in place.

@theredbaron Maybe I've not been clear. The incentive to purchase 9999 veteran infantry is that you don't care to buy more than you can place, so you will not bother losing time calculating buying exactly what you plan to place, nor take the pointless risk of defective miscalculations. Anyways, tha game would be playable, of course.
In your example, I would still rather suggest that cost 0 units are not purchasable but, during placement, you are simply allowed to place infinite of them.

@RoiEX If you believe this should be classified as a bug, should I open a Github issue? I agree that it's odd, as there isn't even a noticeable change in the user's game experience besides all the error messages.

@Cernel I agree that that is a good solution, but I don't think it is so much better than the one I am proposing that I should ask the devs to code an interface for that sort of setup. If you convince me to get concerned about spamming purchases, I might just make a second purchase phase right before place where you could only purchase the veteran_infantry–exactly how many you need, that would essentially replicate what you are describing.

@theredbaron I'd say this is more of a feature request since its pretty clear there were some intentional limits placed on some of the UI widgets to not allow '0' cost. I can probably take a look at the code to see if adjustments can easily be made to remove any of the errors and allow this. @Cernel Brings up good points that align with what I was getting at. For this particular use case, I'd almost lean towards just giving each player like 100 of the VeteranInfantry to place each turn as essentially if you have a 0 cost unit that is what players will do anyways so just saves them the clicks.

@redrum You are right, as this is something the code is not currently designed to support. Still, I'd appreciate if this option were available. I have experimented with giving each player 100 Veteran Infantry each turn, but to my annoyance, they piled up turn after turn. For other parts of the game, I need "Unplaced units live when not placed" to be true, so this cannot solve the problem of the massive stacks of units in the place queue that are very visually distracting. I think it is important for a player to be able to control their visual space while playing, and this is one element of that.

My initial feature request two years ago was to be able to remove the excess "veteran infantry" that were not placed to alleviate this. Now, however, that I have discovered the option discussed here of zero cost units, I actually prefer this solution. Perhaps down the road an option can be added to limit purchase to the number of units able to be placed (this would make maps like Civil War and TWW easier to play). I'd say they both suffer from a similar problem, as in Civil War, I can purchase many fortifications at their relatively low cost, while only being able to place a handful of them in those territories that have engineers.

Regardless, I am amenable that we fix the problem of purchasing more units than can be placed in the future, though I hope that doesn't hold up this feature. In the end, though, I'm a beggar, so I can't be a chooser

I did a quick test by setting a unit production rule to PU=0 and it worked fine on purchases. It defaults to having the max arrow on the purchase to 10,000 if unit cost is 0 as that was the existing cap.

@redrum I would rather disable the max button than setting it at some value, 10,000 or whatever.

If max button has to be, rather having it adding the same as you get in edit mode (99), that it is basically the same thing as when you buy at no cost.

But I don't like that the max is 99 in edit mode; so I would rather set them both at 100 (also upping the current edit mode max from 99 to 100).

But with purchase you may need to buy more than 100, so maybe having the option of clicking multiple times and keeping adding 100 each time? I think that may be good for edit mode too, as maybe you want to add more than 100 (or currently 99) units.

@cernel Well the current max today is 10,000 so I just matched 0 cost to that for now. Not sure if there is any real reasoning behind that. But if you create a 1 PU unit and give some nation 20,000 PUs then you'll see that the max button goes to 10,000. I tend to agree that 10,000 is a lot and probably 100 makes more sense but not sure if any maps rely on that.

I believe (actually I'm certain, because there are other instances in which it is explained) that 10,000 limit means just infinite; meaning it is not really a cap, but rather a limit that nobody is supposed to actually hit (a "no-limit" limit).

Due to this, I definitely believe it should not be applied to a situation where any users would actually hit it. So, I think it would be more sensible, if feasible, to disable the "max" button for cost 0 (so, people will hit the 10,000 only if they write down a bigger number, that should not happen).

Anyways, as we both realise, the matter, to start with, is rather borderline, as one may argue that a purchase phase is not really "purchase" if you pay 0. I think free stuff would be better handled by a special delegate that allows you to place as much as you want (so, basically buying at cost 0 in the same instant you place), limited only by placement limits.

@cernel I tend to agree that 10,000 is a lot and probably 100 makes more sense but not sure if any maps rely on that.

I think you got me wrong. I would certainly not advise to lower the 10,000 limit for normal purchase (in which you actually pay something), as it is good actual purchases having no other cap but the resources themselves (so 10,000 being there just to mean infinite). I was just talking about the max limit you get in edit mode, for adding units, that is currently set at 99, as that is the only current example of a max that is arbitrarily set over free stuff (if you want to have more than 99, you have to use edit mode again and again).

@redrum Thanks for doing this, it works quite well. I will put it to good use.

@Cernel That sounds like a good solution, to remove (or limit) the max button for zero cost purchases. We shall see if abuse of this feature becomes a problem once a map-maker implements it and we can see how players use it.