More than one percentage population bonus?

Creation, discussion, and balancing of game content such as techs, buildings, ship parts.

Moderators: Oberlus, Committer, Oberlus, Committer

Post Reply
Message
Author
User avatar
Oberlus
Cosmic Dragon
Posts: 2606
Joined: Mon Apr 10, 2017 4:25 pm

More than one percentage population bonus?

#1 Post by Oberlus » Sat May 23, 2020 3:34 pm

THE CONTEXT
(Geoff, you can skip this part and go to THE PROBLEM)

Stemming from this post.

I am tinkering with population bonuses, with the aim of making environments (and terraforming) more relevant mid-to-late game.
Vezzra, don't worry, I will not say "pleeeease, merge this into release!" :lol: .

I'd like to have two kinds of pop-boost techs: some than improve the "habitable space" (currently Sub. Hab, Orb. Hab. and N-Dim. Str) and some that increase species "population density" by improving tolerance to environments (Sym. Bio., Xeno. Gen.. Xeno. Hyb. and Cyborgs). The idea is that the former increase population in already habitable planets and that the later unlocks habitability in inhospitable environments. Tall empires would focus (first) on the former and wide empires on the latter.

An option could be to have dedicated techs to unlock environments in a hard way. That is, techs that are mandatory to have in order to live in worse environments. This could work like "Planetarey Ecology is required to apply other bonuses in adequate planets; Xeno. Gen. to unlock poor planets; Xeno. Hyb. to unlock hostile planets". This would make that even being self-sustaining (or having three specials) would not let such species to live in a poor planet until they get Xeno. Gen., but then they would get relatively great pop. stats in poor planets. I don't like this approach because (well balanced) it is nice to offer the player different ways to get to those environments.

The option I like is about using percentage population bonuses. This would make that the "habitable space" techs would give a +X% population boost, which would not help in environments that are not yet habitable, and would have a greater impact of planets with greater population. I like this much more than making (e.g.) Orb. Hab. give +3 to good, +2 to adeq. and +1 to the rest, which is the other way to achieve what I am looking for.

Before I put in some tables with maths for argumentation of why I think this is better and to discuss it, I need to solve the inherent problem with percentage boosts and FOCS, which is the objective of this thread.



THE PROBLEM

There is a single percentage boost to population, the species population trait. I think there is a reason for that: effects in FOCS are applied sequentially, following the order imposed by the set priorities, and applying more than one multiplicative effect complicates calculations a bit and quickly gets into lots of decimals.
I bet players like to be able to forecast the results of getting a new tech. FreeOrion does that pretty well in most cases:
(+1 +2) +1 +1 +2 = 7 is rather simple.
(+1 +2)*1.25 +1 +2 = 6.75 is not so simple but tolerable (although UI will show us 6.7 or 6.8).

Now, if one wants to apply more than one multiplicative effect:
(+1 +2)*1.25*1.25 +2 = 6.6875 is not simple, and I dislike so many decimals (and it will be shown as 6.7).

A way to avoid such decimals and keep calculations simple and readily understood is to not concatenate multiplications, make them apply to the "base" value, simultaneously instead of sequentially:
3 + 3*0.25 + 3*0.25 +2 = 6.5

Say the first 0.25 is from Good Population trait, and the second one is from Subterranean Habitation. If you set two different effects, one for each, with different priorities, when the first one is applied the first "3*0.25" is added to Value and the second one will operate over 3.75 instead of 3. Thus the decimals explode and the calculations are harder to do in your head.
If you do some FOCS hacking to include all the multiplicative effects in the same effect, so you do 3 + 3*0.5 + 2 to get the desired 6.5, the problem is then you can only set a single accounting label. Sighs...

AFAICT, there is no way to do this without changing backend to add new capabilities to meters' calculation.
The way I imagine is having an intermediate temporal value (meter?) that gets all the updates from the BEFORE_SCALING_PRIORITY effects (the "3" in the examples above), like this:

IntermediateValue = Value // starting value, usually 0.
IntermediateValue = Value +1
IntermediateValue = Value +2 // Now we got the 3 we need.
Value = IntermediateValue // Update Value and keep IntermediateValue to apply over it the multiplicative effects.
Value = Value + IntermediateValue*0.25 // Good Population
Value = Value + IntermediateValue*0.25 // Sub. Hab.
Value = Value +2 // Value is 6.5 as desired.


Can this be done in a different way without messing with backend?

If not, would this kind of change be acceptable for master?

User avatar
Oberlus
Cosmic Dragon
Posts: 2606
Joined: Mon Apr 10, 2017 4:25 pm

Re: More than one percentage population bonus?

#2 Post by Oberlus » Sun May 24, 2020 2:05 pm

Since this is about avoiding decimals I could also use roundings, but this is also about making calculations in your head easy, so I'm not kind to that option. Plus it seems rounding functions aren't implemented?


I'm guessing this could be done with rather complex/long FOCS code, by giving different priorities to each multiplicative effect and adding to the later ones "if" conditions to account for the earlier ones' effects.

Is there a way (condition) to query if a species have a trait as Bad/Good Population, such as OwnerHasTech?
I bet there isn't and it isn't easy to implement.

Since the only trait that would matter here is Bad/Good Population, and everything else would be techs, this could be circunvented by making the trait effect the one with latest priority, so that it is the one querying for other techs.

So it could be like this:


Sub. Hab.

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_1]]
            effects = SetTargetPopulation value = Value + 0.25*abs(Value)
Orb. Hab.

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_2]]
            effects = SetTargetPopulation value = Value + 0.25*abs( Value - 0.2 * Value * (Statistic If Condition = OwnerHasTech name = "GRO_SUBTER_HAB") )
N-Dim. Str.

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_3]]
            effects = [
                SetTargetConstruction value = Value + 10
                SetTargetPopulation value = Value + 0.25*abs( Value - 0.2 * Value * (Statistic If Condition = OwnerHasTech name = "GRO_SUBTER_HAB")  
                                                                    - 0.2 * Value * (Statistic If Condition = OwnerHasTech name = "CON_ORBITAL_HAB") )
            ]
Good Population

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_LAST]]
            effects = SetTargetPopulation value = Value + 0.25*abs( Value - 0.2 * Value * (Statistic If Condition = OwnerHasTech name = "GRO_SUBTER_HAB") 
                                                                          - 0.2 * Value * (Statistic If Condition = OwnerHasTech name = "CON_ORBITAL_HAB")
                                                                          - 0.2 * Value * (Statistic If Condition = OwnerHasTech name = "CON_NDIM_STRC")  )
Formulas for more than one effect to be subtracted have not been checked, only the one for Orb. Hab. are ensure to work as intended.

Ophiuchus
Programmer
Posts: 1481
Joined: Tue Sep 30, 2014 10:01 am
Location: Wall IV

Re: More than one percentage population bonus?

#3 Post by Ophiuchus » Sun May 24, 2020 4:34 pm

Oberlus wrote:
Sun May 24, 2020 2:05 pm
Since this is about avoiding decimals I could also use roundings, but this is also about making calculations in your head easy, so I'm not kind to that option. Plus it seems rounding functions aren't implemented?
Not sure about this one.
Oberlus wrote:
Sun May 24, 2020 2:05 pm
I'm guessing this could be done with rather complex/long FOCS code, by giving different priorities to each multiplicative effect and adding to the later ones "if" conditions to account for the earlier ones' effects.
Yes, you can. You can split effects into partial effects and write the effects to coincide. Its just very verbose and not very intuitive/hard to maintain. You cant really find out afterwards which effects triggered (besides using a stackinggroup).
Oberlus wrote:
Sun May 24, 2020 2:05 pm
Is there a way (condition) to query if a species have a trait as Bad/Good Population, such as OwnerHasTech?
I bet there isn't and it isn't easy to implement.
You can query the tag list - it should be consistent with the effects (else its a species bug).

Regarding your implementation I think priorities are not meant to be used this way (might be wrong). I am not sure if you intend the effects to be stacking or overriding each other. I think you are trying to override and you are using the immediate value (by using Value) - so that means you need use stackinggroup and the first effect which fires wins.
Any code or patches in anything posted here is released under the CC and GPL licences in use for the FO project.

Furthermore, I propse... we should default to four combat rounds instead of three ...for the good of playerkind.

User avatar
Oberlus
Cosmic Dragon
Posts: 2606
Joined: Mon Apr 10, 2017 4:25 pm

Re: More than one percentage population bonus?

#4 Post by Oberlus » Sun May 24, 2020 5:16 pm

Ophiuchus wrote:
Sun May 24, 2020 4:34 pm
You can query the tag list - it should be consistent with the effects (else its a species bug).
How?
Ophiuchus wrote:
Sun May 24, 2020 4:34 pm
Regarding your implementation I think priorities are not meant to be used this way (might be wrong).
I don't know what you mean. AFAICT, priorities serve to put an order of application for effects.
What my implementation above (which, BTW, is numerically wrong, but I already got the right way) does is to create more than one step for scaling effects, instead of the single one we have now.
Ophiuchus wrote:
Sun May 24, 2020 4:34 pm
I am not sure if you intend the effects to be stacking or overriding each other. I think you are trying to override and you are using the immediate value (by using Value) - so that means you need use stackinggroup and the first effect which fires wins.
I don't want to overrride, I want the multiplicative effects to all work over the value before the first multiplicative effect, like this:

Example for a Good Env. planet with Sub. Hab. (+25%), Sim. Bio. (+1*HabSize), N-Dim. Str (+50%) and 2 specials (+2*HabSize). For clarity, I remove the "*HabSize" next to each integer.
1. Flat modifiers before scaling (multiplicative) effects: Value = +3 +1 = 4.
2. First multiplicative effect (Sub. Hab): Value = 4 + 0.25*Abs(4) = 5.
3. Second multiplicative effect (N-Dim. Str.): Value = 5 + 0.50*Abs(4) = 7
Note that the effect is working over the value before the first multiplicative effect, which was 4 and upped to 5 by the first mult. effect.
4. Flat modifiers after scaling (the 2 specials): Value = 7 + 2 = 9.

At point 3, since Value is already 5 and not 4 due to the first mult. effect, I should recalculate what was the value before that.
Without that, if instead one works over the value right how it was left by the previous effect, we would end up with 7.5 after point 3 and 9.5 after point 4. Not a problem when one starts the multiplicative effects with Value = 4 (*HabSize), but it gets nasty if we work over, e.g., 3 or 5.

Overriding is not an option because a player could research (say) N-Dim. Str. before researching Orb. Hab., and so implementation must not assume that by having N-Dim. Str. implies also Orb. Hab.

User avatar
Oberlus
Cosmic Dragon
Posts: 2606
Joined: Mon Apr 10, 2017 4:25 pm

Re: More than one percentage population bonus?

#5 Post by Oberlus » Sun May 24, 2020 9:10 pm

Sub. Hab.

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_1]]
            effects = SetTargetPopulation value = Value + 1/4*abs(Value)
Orb. Hab.

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_2]]
            effects = SetTargetPopulation value = Value +
                1/4*abs( Value - 1/5*Value*(Statistic If Condition = OwnerHasTech name = "GRO_SUBTER_HAB") )

Having to account for too possible previous effects becomes messy:

N-Dim. Str.

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_3]]
            effects = [
                SetTargetConstruction value = Value + 10
                SetTargetPopulation value = Value + 
                    1/4*abs( Value 
                       // Source has the two previous population scaling techs
                       - 2/6 * Value * (Statistic If Condition = And [
                                OwnerHasTech name = "GRO_SUBTER_HAB"
                                OwnerHasTech name = "CON_ORBITAL_HAB"
                            ]
                        )
                       // Source has only one of the two previous population scaling techs
                        - 1/5*Value*(Statistic If Condition = And [
                                OwnerHasTech name = "GRO_SUBTER_HAB"
                                Not OwnerHasTech name = "CON_ORBITAL_HAB"
                            ]
                        )
                        - 1/5*Value*(Statistic If Condition = And [
                                Not OwnerHasTech name = "GRO_SUBTER_HAB"
                                OwnerHasTech name = "CON_ORBITAL_HAB"
                            ]
                        )
                    )
            ]
For three possible previous effects becomes a 130% more messy:

Good Population

Code: Select all

            priority = [[TARGET_POPULATION_SCALING_PRIORITY_LAST]]
            effects = SetTargetPopulation value = Value + 
                1/4*abs( Value
                    // Source has the three population scaling techs
                    - 3/7*Value*( Statistic If Condition = And [
                            OwnerHasTech name = "GRO_SUBTER_HAB"
                            OwnerHasTech name = "CON_ORBITAL_HAB"
                            OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                    // Source has two out of three population scaling techs
                    - 2/6*Value*(Statistic If Condition = And [
                            Not OwnerHasTech name = "GRO_SUBTER_HAB"
                            OwnerHasTech name = "CON_ORBITAL_HAB"
                            OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                    - 2/6*Value*(Statistic If Condition = And [
                            OwnerHasTech name = "GRO_SUBTER_HAB"
                            Not OwnerHasTech name = "CON_ORBITAL_HAB"
                            OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                    - 2/6*Value*(Statistic If Condition = And [
                            OwnerHasTech name = "GRO_SUBTER_HAB"
                            OwnerHasTech name = "CON_ORBITAL_HAB"
                            Not OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                    // Source has one out of three population scaling techs
                    - 1/5*Value*(Statistic If Condition = Or [
                            OwnerHasTech name = "GRO_SUBTER_HAB"
                            Not OwnerHasTech name = "CON_ORBITAL_HAB"
                            Not OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                    - 1/5*Value*(Statistic If Condition = And [
                            Not OwnerHasTech name = "GRO_SUBTER_HAB"
                            OwnerHasTech name = "CON_ORBITAL_HAB"
                            Not OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                    - 1/5*Value*(Statistic If Condition = And [
                            Not OwnerHasTech name = "GRO_SUBTER_HAB"
                            Not OwnerHasTech name = "CON_ORBITAL_HAB"
                            OwnerHasTech name = "CON_NDIM_STRC"
                        ]
                    )
                )



Alternative putting the OwnerHasTech conditions in the activation

Good Population

Code: Select all

        // Source has no population scaling techs
        EffectsGroup
            description = "GOOD_POPULATION_DESC"
            scope = Source
            activation = And [
                Planet
                Not OwnerHasTech name = "GRO_SUBTER_HAB"
                Not OwnerHasTech name = "CON_ORBITAL_HAB"
                Not OwnerHasTech name = "CON_NDIM_STRC"
            ]
            priority = [[TARGET_POPULATION_SCALING_PRIORITY_LAST]]
            effects = SetTargetPopulation value = Value +1/4*abs(Value) accountinglabel = "GOOD_POPULATION_LABEL"

        // Source has a single population scaling techs
        EffectsGroup
            description = "GOOD_POPULATION_DESC"
            scope = Source
            activation = And [
                Planet
                Or [
                    And [
                        OwnerHasTech name = "GRO_SUBTER_HAB"
                        Not OwnerHasTech name = "CON_ORBITAL_HAB"
                        Not OwnerHasTech name = "CON_NDIM_STRC"
                    ]
                    And [
                        Not OwnerHasTech name = "GRO_SUBTER_HAB"
                        OwnerHasTech name = "CON_ORBITAL_HAB"
                        Not OwnerHasTech name = "CON_NDIM_STRC"
                    ]
                    And [
                        Not OwnerHasTech name = "GRO_SUBTER_HAB"
                        Not OwnerHasTech name = "CON_ORBITAL_HAB"
                        OwnerHasTech name = "CON_NDIM_STRC"
                    ]
                ]
            ]
            priority = [[TARGET_POPULATION_SCALING_PRIORITY_LAST]]
            effects = SetTargetPopulation value = Value +1/5*abs(Value) accountinglabel = "GOOD_POPULATION_LABEL"

        // Source has two population scaling techs
        EffectsGroup
            description = "GOOD_POPULATION_DESC"
            scope = Source
            activation = And [
                Planet
                Or [
                    And [
                        OwnerHasTech name = "GRO_SUBTER_HAB"
                        OwnerHasTech name = "CON_ORBITAL_HAB"
                        Not OwnerHasTech name = "CON_NDIM_STRC"
                    ]
                    And [
                        OwnerHasTech name = "GRO_SUBTER_HAB"
                        Not OwnerHasTech name = "CON_ORBITAL_HAB"
                        OwnerHasTech name = "CON_NDIM_STRC"
                    ]
                    And [
                        Not OwnerHasTech name = "GRO_SUBTER_HAB"
                        OwnerHasTech name = "CON_ORBITAL_HAB"
                        OwnerHasTech name = "CON_NDIM_STRC"
                    ]
                ]
            ]
            priority = [[TARGET_POPULATION_SCALING_PRIORITY_LAST]]
            effects = SetTargetPopulation value = Value +1/6*abs(Value) accountinglabel = "GOOD_POPULATION_LABEL"

        // Source has the three population scaling techs
        EffectsGroup
            description = "GOOD_POPULATION_DESC"
            scope = Source
            activation = And [
                Planet
                OwnerHasTech name = "GRO_SUBTER_HAB"
                OwnerHasTech name = "CON_ORBITAL_HAB"
                OwnerHasTech name = "CON_NDIM_STRC"
            ]
            priority = [[TARGET_POPULATION_SCALING_PRIORITY_LAST]]
            effects = SetTargetPopulation value = Value +1/7*abs(Value) accountinglabel = "GOOD_POPULATION_LABEL"
Maybe conditions could be nested in a shorter and clearer way...


Is any of the two versions more computationally demanding?
Regarding clarity of code, I don't know which one is less annoying.

Ophiuchus
Programmer
Posts: 1481
Joined: Tue Sep 30, 2014 10:01 am
Location: Wall IV

Re: More than one percentage population bonus?

#6 Post by Ophiuchus » Sun May 24, 2020 10:29 pm

Oberlus wrote:
Sun May 24, 2020 5:16 pm
Ophiuchus wrote:
Sun May 24, 2020 4:34 pm
You can query the tag list - it should be consistent with the effects (else its a species bug).
How?
There is a HasTag condition AFAICR.

The only ways to better support what you want I can think of right now is either adding another meter (for the intermediary result) or doing something equivalent to arranging effects in a tree or grouped stack structure. Also I am not sure what the aim of this is. Just getting rid of "floats" for UI purposes (in that case rounding is much cleaner)? Or do you want to get rid of the stacking effect of multiplication ( 125%*125% = 150% + (25%*125%) > 150% ).

If you want to get rid of the stacking effect currently you have to model the previous effects in the current effect like you said and did.

You choose to "remove" effects which already happened. If your basic flat effects are few you are probably better off reconstructing the base for the multiplicative layer. Still this sounds too complicated.

Priority would not matter in this case and the effects of the first multiplicative layer would look this:

Code: Select all

            effects = SetTargetPopulation value = Value + 0.25*abs([[TARGET_POPULATION_INCLUDING_BASIC_FIXED_BONUS]])
This wont save you in the second multiplicative layer though (least ugly solution depending on the number of effects in the different levels).

Note also that the main problem with your proposed scheme is extensibility. What if more effects get added? If you add an effect in master you have to touch a lot of files. If you write a "plugin" you basically have to clone and rewrite a lot of stuff.
Any code or patches in anything posted here is released under the CC and GPL licences in use for the FO project.

Furthermore, I propse... we should default to four combat rounds instead of three ...for the good of playerkind.

User avatar
Oberlus
Cosmic Dragon
Posts: 2606
Joined: Mon Apr 10, 2017 4:25 pm

Re: More than one percentage population bonus?

#7 Post by Oberlus » Mon May 25, 2020 9:40 am

Ophiuchus wrote:
Sun May 24, 2020 10:29 pm
The only ways to better support what you want I can think of right now is either adding another meter (for the intermediary result)
That's what I think would be best, as explained in the OP:
- That intermediate meter, not to be shown to the player, would contain the Value of TargetPopulation meter after the pre-scalling effects but before the scaling effects themselves.
- The scaling effects would use that intermediate meter when applied to TargetPopulation, so that each successive scaling effect is not affected by the others, and each one would have its own accounting label to show to the player.
- Further post-scalling effects would work then over TargetPopulation again.
Ophiuchus wrote:
Sun May 24, 2020 10:29 pm
Also I am not sure what the aim of this is.
To be able to have more than one scalling effect.
I'm not pleased with the idea of having N-Dim. Str. to add +4*HabSize to Good env. and +1*HabSize to Hostile (for the purpose of making good env. much better than hostile late game, to help tall strategies and encourage terraforming).
I want it to be able to give great boosts to good environments and no boost at all to currently inhabitable environments. That's exactly what I would get with a scaling effect: negative or null habitabilities would stay that way, high habitabilities would get a sizeable boost.
I don't want scaling effects to apply one over the other to avoid long decimals (UI), hard mental calculations (KISS) and exponential population growth (balance).

If the intermediate value (backend change, I'm still waiting for core devs to answer to OP) is not gonna happen, then I shall stick to additive effects, or limit myself to maybe a single extra scaling effect that might be upgradeable.
If your basic flat effects are few you are probably better off reconstructing the base for the multiplicative layer. Still this sounds too complicated.
Oh, right, I'll try that too and see how it ends up.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12641
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: More than one percentage population bonus?

#8 Post by Geoff the Medio » Mon May 25, 2020 11:00 am

Oberlus wrote:
Sun May 24, 2020 2:05 pm
Plus it seems rounding functions aren't implemented?
There are floor and ceil and round operations implemented, amongst others. Just wrap a number expression in ceil(...), floor(...), etc. and it should work...

https://github.com/freeorion/freeorion/ ... pp#L40-L46

I think if you want additive combinations of multiplication factors, it should be implemented as a single effect, rather than multiple separate multiply effects that compound with eachother when applied sequentially. I don't have a good solution for the accounting label in that case, though. Having only a single multiplicative scaling effect is also a reasonable solution, I think... I'm not a big fan of multiplicative effects after arbitrary other additive effects anyway... If something says it should give +5, but then after it there is a +20% scaling applied, the +5 becomes +6. I'd rather have "+5 (or +6 for high-population species)" in a single effect's description. (That limits how many different scaling levels can be practically implemented, though maybe that's not really a bad thing either?)

Adding arbitrary intermediate memory / storage would start to turn FOCS into a general-purpose scripting / programming language; ie. declaring variables and later referencing them... That's not really how it's designed / intended. Adding a separate hidden meters for tracking such values would be a major complication and is also not something I want to do.

Ophiuchus
Programmer
Posts: 1481
Joined: Tue Sep 30, 2014 10:01 am
Location: Wall IV

Re: More than one percentage population bonus?

#9 Post by Ophiuchus » Tue May 26, 2020 10:24 pm

Geoff the Medio wrote:
Mon May 25, 2020 11:00 am
Oberlus wrote:
Sun May 24, 2020 2:05 pm
...
Adding a separate hidden meters for tracking such values would be a major complication and is also not something I want to do.
If it is worth the complexity, adding a non-hidden dimension (symbolized by a meter) to population calculation might make more sense than a hidden one.
Any code or patches in anything posted here is released under the CC and GPL licences in use for the FO project.

Furthermore, I propse... we should default to four combat rounds instead of three ...for the good of playerkind.

Post Reply