1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

CivEffects

Discussion in 'Civ4Col - Medieval: Conquests' started by Nightinggale, Feb 12, 2015.

  1. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    Well, the way I handle things like this is to learn form my mistakes. Perhaps even make a note of it, so next time I am writing code I will make sure I don't do this. Or, if I had your knowledge I may rewrite everything so I don't have to worry about it ;)
     
  2. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    I renamed BonusTechCategories into TechCategoryCostModifiers and made it work as the new name indicates, which is to modify the cost of a tech. If you set it to -10 and the tech cost 100 ideas to research, then CvPlayer::getCostToResearch() will return 90 instead of 100 (if tech is of the chosen tech category).
    I also added a modifier for handicap to that function meaning the AI will now get a multiplier for difficulty level. It was scaled for game speed, but not difficulty. I borrowed the setting for father points, but maybe we should add a new int to handicap XML for this purpose.


    There is a problem though. I decided to store modifiers as doubles as it is easy to stack multiple bonuses correctly (vanilla fails to do this) and all it has to do is to multiply it to the number it has to modify.

    There is a catch though, which is lack of precision. If the multiplier starts at 1 (100%) and we multiply it by 1.1 (+10%) and remove the 10% bonus again, it might not go back to 1. I added code to take this into account and make it round up in case it ends up at 0.9999. However adding and removing 50 times and we have a problem. Also the rounding issue might be CPU specific, meaning we could end up with a game, which doesn't behave identically on all computers, which is a network killer (I still want to fix network games).

    I looked into using ints and see if there is something online about using ints for decimal numbers. There is and it's called "binary coded decimal". The financial sector is using it because it has great accuracy (they don't want to have rounding errors for some reason;)). However it doesn't look performance friendly and just switching to a system like that is not enough to make our calculations entirely consistent (well it is, except for the case where a player joins a network game during the game, which I would like to work too).

    Solution:
    I want to make a new class. This class has a list of ints. You can add to that list with ::add(int). At some point you would like to get the resulting int, which can then be obtained by ::get(). It can then be used to do something like this: (not the greatest example, but it should tell the idea).

    Spoiler :
    Code:
    int CvPlayer::calculateAdvancedStartPoints()
    {
    	[S]int iPoints = GC.getCivilizationInfo(getCivilizationType()).getAdvancedStartPoints();[/S]
    	[B]PercentageClass adder;
    	adder.add(GC.getCivilizationInfo(getCivilizationType()).getAdvancedStartPoints());[/B]
    
    	if (NO_WORLDSIZE != GC.getInitCore().getWorldSize())
    	{
    		[S]iPoints *= GC.getWorldInfo(GC.getInitCore().getWorldSize()).getAdvancedStartPointsMod();[/S]
    		[S]iPoints /= 100;[/S]
    		[B]adder.add(GC.getWorldInfo(GC.getInitCore().getWorldSize()).getAdvancedStartPointsMod());[/B]
    	}
    
    	if (NO_GAMESPEED != GC.getInitCore().getGameSpeed())
    	{
    		[S]iPoints *= GC.getGameSpeedInfo(GC.getInitCore().getGameSpeed()).getGrowthPercent();[/S]
    		[S]iPoints /= 100;[/S]
    		[B]adder.add(GC.getGameSpeedInfo(GC.getInitCore().getGameSpeed()).getGrowthPercent());[/B]
    	}
    
    	if (GC.getGameINLINE().isOption(GAMEOPTION_ADVANCED_START))
    	{
    		iPoints += GC.getGameINLINE().getNumAdvancedStartPoints();
    		[B]oops, didn't plan for simply adding instead of multiplying[/B]
    	}
    
    	[S]iPoints *= GC.getHandicapInfo(getHandicapType()).getAdvancedStartPointsMod();[/S]
    	[S]iPoints /= 100;[/S]
    	[B]adder.add(GC.getHandicapInfo(getHandicapType()).getAdvancedStartPointsMod());[/B]
    
    	if (!isHuman())
    	{
    		[S]iPoints *= GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIAdvancedStartPercent();[/S]
    		[S]iPoints /= 100;[/S]
    		[B]adder.add(GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIAdvancedStartPercent());[/B]
    	}
    
    	[S]return iPoints;[/S]
    	[B]return adder.get();[/B]
    }


    Internally get() should sort the list and then multiply the numbers together in a way, which will not cause overflows, but at the same time has as little rounding issues as possible. More importantly it should always return the same given the same numbers, regardless of order (hence the sort).

    Next up is making a class, which makes the work of the current JIT arrays in the player cache. However it should have a vector of this new class (be it 1D or 2D). That way it can recalculate with great accuracy every time a CivEffect is added or removed and it will ALWAYS give the same result for a specific set of CivEffects, regardless of history (no inaccuracy inherited from lost CivEffects or anything like that). This cache will need a JIT array for caching the output though or it will slow down the game. Yeah, a cache for the cache :crazyeye:

    This will use more memory than the current setup, but we should be able to survive that and it will be worth it to regain consistent calculations.

    Now I just need a proper name for this new class.
     
  3. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    OK, I changed all modifiers to use ints instead of double. That way we can be sure they are always behaving identically to the same input. I also added some optimization, like if one of the multipliers is 0, it will return 0 without actually do a bunch of multiplications and divisions. It will also ignore requests to multiply with 100/100. The latter is likely really useful for CivEffects since 100 or +0% is the default.

    I updated calculation costs of research costs. The display now uses the same function as the code itself. This mean the GUI will no longer ignore the modifiers. While we don't actually have any modifiers in XML yet, we do have gamespeed. I also added modifier for the AI based on difficulty. Both gamespeed and difficulty should have their dedicated research modifier tags.

    I removed a modifier for research cost for humans as it made no sense and was used by the AI as well. If we want to make humans and AI pay different research cost, it should be done with the AI modifier in handicap XML (the difficulty level).

    Now it's back to implement the rest of the cache. less than 30% to go and as I can see, the only remaining coding issue is unit promotions. When it can gain free promotions from unitclass, unitcombatclass and profession, then it gets a bit complex to figure out which free promotions the unit has, especially since all 3 categories can be invalidated by gaining a CivEffect.
     
  4. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
  5. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    Moving along slowly as I run into other issues than just implementing the cache.

    I ensured that all cache numbers have access functions (no direct calls, except for some AI code). All access functions now assert if they are called before the cache is set. Fixed the bugs this addition revealed.

    I changed the way the AI values civics. The value contribution from yields, which will not be allowed will be 0. Right now the AI will pick a civic if it gives +200 stone production even if stone production isn't enabled. In time I think we should make an AI CivEffect value function (since it can then be used for all types using CivEffect). It should be able to value removal as well since it should give an educated guess at which civic is best for the AI. This however appears to be a major task of it's own and for now I will just make it ignore yields, which aren't allowed if the civic is added.

    I thought I had implemented functions for 3/4 of the code, but I have only covered 74.7%. No 3/4 done party :(
     
  6. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    Well, if it was a hand grenade or horseshoe that would be close enough, so :banana:
     
  7. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    I changed ExtraYieldThresholds. Before (vanilla?) it though all civics and found the lowest number, which is higher than 0. If a plot is producing equal or more than this number, it adds +1 production.

    Now those bonuses stack. If you get a CivEffect, which sets 2, it gives +1 if the plot is producing 2 or more.
    If another CivEffect has 5 set for the same yield, it will stack and become +1 if production is 2, 3 or 4 and +2 if it is 5 or more.
    InfoArray isn't designed for it and I haven't tested it, but now that I think about it, I suspect you can set both 2 and 5 in the same CivEffect and it should work. That could be really interesting.

    I came up with the idea to add an additional number in XML, which is the production bonus. Right now it is assumed to be +1, but allowing -1 as well could be equally interesting. I coded this to not add bonuses below 0 though, but cancelling each other out could open up a huge number of options and combos. It could also allow +1 for 2+ and -1 for 4+, which effectively gives +1 if production is 2 or 3, but not more than that.

    Cheater. Partying before the rest of us. I have now reached 77%, but that's besides the point :p

    :band:[party]
    :dance::dance::dance::dance:
     
  8. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    Looking at the code to calculate the bonus yields for connected missions and trading posts, I was like.... oh dear. The code works, but we should be able to do way better than that. It recalculates everything without using any cache at all each time something is modified, including number of connected cities. Also it only works with actively selected civics, not CivEffects.

    I'm now considering redesigning and rewriting the entire code for this. Ideally it should cache the number of missions and trading posts in the plotgroup and the yield code in CvCity then adds CvPlayer::getMissionBonus(yield)*CvPlotGroup.getNumMissions(). (not the actual function names). That way we can update those two caches individually and the city will be updated next time it reads how many yields it produce. No need to loop cities for cache updates (though maybe we could benefit from doing that anyway).

    Major change proposal
    Thinking about this, I would say that this bonus is overpowered. Imagine playing on a gigantic map. The plotgroup becomes gigantic. You make 50 missions and you have 10 cities. Gain a civic, which gives +1 food for connected missions (offering food to the chosen people or whatever). That will give you 50*10 = 500 food each turn, which is 2.5 units/turn. Add a building, which reduce population cost and we are closing in on 3 new units/turn. Make half of those missionaries to gain even more food and found a bunch of 1 population cities and you quickly end up with 65*15= 975 food/turn or around 5 units/turn :crazyeye:

    I like the idea though, but the plotgroup should find "the best suited city" to place the yields in, meaning 50 missions provides 50 food (for the +1 food case). I'm not sure what the best suited city should be, but likely something like biggest church, biggest marked, capitol... stuff like that.

    Is this a change we can agree on?
     
  9. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    Yes, I agree. I remember asking you to look at that code cause I knew there was a better setup than what I did. I just put that together to test the idea. It needs a bunch of work still as you say as it's an unfinished idea and not very tested. In my play testing I didn't even check any of that out, so it needs better in game documentation for one.
     
  10. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    Funny, I don't remember you saying that. However now I added it to the todo list in the first post (it has grown to 18 lines, yikes:eek:)

    Since this is a new implementation and possibly a decent sized one too, I have decided to just ignore this for the time being. I keep encountering stuff, which can be improved and I end up coding all sorts of stuff all over the place. Some of it quite important for CivEffect functionality (such as the data structure for it) and some of it is a don't care for the overall implementation of CivEffects (such as connected missions). I have 20 tags to go (where I just decided to ignore 2 of them) and I really want get this up and running soon.

    If I look at the progress, I have added the tags from traits into civics, sorted tags into groups to make it easier to find tags with specific functionality and rewritten the cache for it (well the last is not even done yet).

    The original plan was to merge traits into civics and I haven't even started touched traits yet. I also have to move the files into a new XML directory, which I haven't done, nor have I split any existing files.

    Overall I am approaching the first step in the original plan. Everything I have done until now have been addon tasks I encountered because code and XML structure was insufficient to do what I planned. I really want to code what I planned to code, not all the tasks the code throws at me. In the interest of finishing this, I will ignore any tag, which isn't quick to implement. The only exception being free promotions. Allowing more complex XML setup will result in a more complex DLL code, and it's such a major part of the game that I will not simply break all free promotions.
     
  11. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    I changed how the father point bonus works. They now modify the cost of a FF. Since this leads to a problem with multiple players on a team, the game will use the best modifier from the members. Best is naturally the lowest.

    Vanilla has a penalty modifier for number of players on a team meaning the more players you have, the more expensive the FF will be. I think we should skip vassals in that calculation.
     
  12. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    I implemented GarrisonUnitModifiers, which I renamed to GarrisonUnitChanges to follow the naming convention. I like the concept of the garrison producing yields, but feeling that it could be overpowered, I added iGarrisonUnitMaxUnitChange. This one is used to set how many units can supply this bonus in a city. Kind of like how CIV units can keep order, but only the first X units can do that and adding 20 more will not help.

    The plan is to add a CivEffect, which is given to all players automatically where XML modders can set the defaults. If people don't want a limit, then they can just set it to 1000 there. While I would argue that it's a bad idea, the game will technically be able to handle that, which lives up to my (our?) DLL coding strategy that the DLL should be able to handle whatever weird ideas the XML modders come up with. Some day it might make sense to do something, which seems stupid right now.

    Also I passed the 80% mark for implemented tags :)
     
  13. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    Yay!

    That's a good addition. I really like the garrison producing yields, and setting a limit is a good idea. When we add costs for Military units it will balance itself out more.
     
  14. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    Military cost vs yields produced :think:

    How about a cost modifier for unit upkeep when the units are placed in a city? Something like adding this to CivEffect where reset() will assign an XML value. That way reading from the cache will give you just one number, but it will be the combined values of all CivEffects and static XML data. I already used this approach for data where performance is important, such as route movement cost.

    Stretching this idea further, we can add modifiers for
    1. cities
    2. your (team's) plots
    3. neutral plots
    4. enemy plots
    Order matters as if #2 becomes more expensive as #3, asking for #2 will give you the value of #3.

    We can deal with stuff like that when there actually is a unit upkeep to modify.
     
  15. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    Finished all tags in the production group (except those I intentionally skip because they aren't really implemented in the first place). Now only the unit group remain.

    I updated the code for getting a random amount of yields for killing an animal. Say an animal provides 10 yields and you have +100% bonus. Before it found a random number between 1 and 10 and added 100%, which allows the numbers 2, 4, 6, 8..., but not uneven numbers.

    I changed it to apply the bonus to min and max, making it use all numbers in the range 2 to 20. This change makes an even bigger impact if the bonus isn't 100%, but maybe 20%. It will then give in the range 1 to 12. Where the old code would give 1,2,3...6,7,8, 10, 11, 12. Note that 9 is missing. This is due to int rounding.

    I made the code failsafe. Regardless of input, the output of the random number calculations will now always be at least 1. Makes little sense if you have a -100% modifier, but while 0 and negative might be prone to CTD, 1 is perfectly safe and we have one less place to crash the game.
     
  16. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    So units will cost more when "working" in Cities? Depends on what we decide those units are doing in order to gain the bonus. If they are just hanging out and providing a "royal" presence then they are doing less than perhaps units out in the field. We could add the effect for sure in the XML and then play test it to see how it works.
     
  17. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    No, the other way around. #1 is the cheapest while #4 is the most expensive. Naturally supplies for your troops will be way more expensive in enemy land than inside your own cities. Imagine having to move your troops into enemy land for financial reasons :crazyeye:

    This is also why I wrote that a modifier can't be higher than the modifiers below it. If XML makes a silly mistake like that, it will auto correct itself and you know that regardless of XML values, it will always be the cheapest option to put your troops in your cities. Maybe there will not be a difference between your cities and your own plots, but at least your cities can't be more expensive than any other plots in the game.
     
  18. Lib.Spi't

    Lib.Spi't Overlord of the Wasteland

    Joined:
    Feb 12, 2009
    Messages:
    3,707
    Location:
    UK
    Is that not taking away some of the moddability of the system?

    Making the assumption that cities must always be the cheapest for any mod, takes away the possibility of making a mod where the opposite is true, where actually there is a strategy to be played out where the whole concept is reversed.

    you could perhaps have an assert that asks if it is supposed to be less/more expensive, but making it so automatically, makes it by nature less moddable.

    Now if it is an issue that the math would cause the game to explode, that is another matter, but if it is simply a preference assumption, isn't that bad?
     
  19. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,354
    :think:

    There is no technical reason for that change (unlike the hardcoded hunting yield being at least 1 to avoid crash due to divide by 0). However it is near impossible for modders to predict the combined effects of all possible CivEffect combos, which I why I came up with this approach to assist.

    I guess we could have an on/off switch in globalDefineALT. The XML modder can then decide if the mod should handle each variable individually or as a prioritized list. There are absolutely no technical reasons preventing this setup, not even performance.
     
  20. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    Ah, ok, I totally misunderstood what you where saying. Yeah, that all sounds great.
     

Share This Page