Modders Guide to FfH2

Merging your changes in Patch T and a couple things popped out at me:

CvCityAI:

You made this change:
Code:
		int aiValues[NUM_CITY_PLOTS];

//FfH: Modified by Kael 02/05/2009
//		for (iI = 0; iI < NUM_CITY_PLOTS; iI++)
		for (iI = 0; iI < getNumCityPlots(); iI++)
//FfH: End Modify

But if you look at the first line in the code segment, you are still initializing your array to the global define size. Fortunately your global define is 37 (3 ring), so this isn't a PROBLEM, but it is still a waste of memory space.
 
Merging your changes in Patch T and a couple things popped out at me:

CvCityAI:

You made this change:
Code:
		int aiValues[NUM_CITY_PLOTS];

//FfH: Modified by Kael 02/05/2009
//		for (iI = 0; iI < NUM_CITY_PLOTS; iI++)
		for (iI = 0; iI < getNumCityPlots(); iI++)
//FfH: End Modify

But if you look at the first line in the code segment, you are still initializing your array to the global define size. Fortunately your global define is 37 (3 ring), so this isn't a PROBLEM, but it is still a waste of memory space.

Yeah, I tried to change the size of the array and got a ton of compile problems. C++ really doesnt like arrays whose size could change dynamically (probably for good reason). So I left the larger array size.
 
Array indexing in python is annoying me right now.


Most all of the Marnok functions use a random selection from a list by getting a random number based on length of the list, then subtracting 1. Random numbers are 0 to (target -1), so by subtracting 1 you are making it impossible to get the last element of the array, and possible to try for a -1 index item.

Except of course that when I removed the -1 entries I later got a python error for being out of bounds...



So, we have Marnok functions for lairs, and even the selection of your elemental for the Tower of Mastery which seem to require a -1. Yet we ALSO have functions like insane which STILL pick from a list in an array, but do NOT require the -1.


What's the deal? How do you know which you need, or why is it working on some and not others?
 
What would happen if I used a PyPerTurn type function as a <PyRequirement> of a spell instead? How often would that run? Every time the unit moves? Every time a unit moves within range of it? Every time it is selected? Way more often than the processor should could handle?


I was thinking that I'd like Auric Ascended to terraform land and water into temporary snow terrain passively just moving near it. Originally I thought the PyPerTurn tag would be the way to do it, but was wishing that it could instead be applied onMove. (This is partially so that he cannot hide from a land unit with the Godslayer.)

I know having prereqs actually do stuff may be considered bad form, but is there a good reason not to actually use it like this?
 
Array indexing in python is annoying me right now.


Most all of the Marnok functions use a random selection from a list by getting a random number based on length of the list, then subtracting 1. Random numbers are 0 to (target -1), so by subtracting 1 you are making it impossible to get the last element of the array, and possible to try for a -1 index item.

Except of course that when I removed the -1 entries I later got a python error for being out of bounds...



So, we have Marnok functions for lairs, and even the selection of your elemental for the Tower of Mastery which seem to require a -1. Yet we ALSO have functions like insane which STILL pick from a list in an array, but do NOT require the -1.


What's the deal? How do you know which you need, or why is it working on some and not others?

The solution, if there is access to the functions, could be to modify the random generator calls in Marnok functions, so that instead of par example:
FunctionName(RND(101),..,..,..,...) you do a FunctionName(RND(101)+1,...,...,...), so when it gets to substruct the number, it will always be within 0 and 100(if 100 is the target). Of course, there should be also a comment describing why it was done so...
If I did not understand the problem correctly and what I propose is irrelevant, then please forgive me. I would like to understand the problem and help, though.
 
No, the issue is that they DO work with the -1, and seem to require it. But that makes no sense overall. So I am wondering why it does work, and why only in certain cases.




Unrelated question:

Barbarian units can be blocked from targetting our cities by blocking chokepoints with units, ANY units. I see 2 solutions:

First:

Change

if (AI_targetCity())

to

if (AI_targetCity(MOVE_THROUGH_ENEMY | MOVE_IGNORE_DANGER))


The function allows you to pass in flags, but from what I see, nothing DOES pass any in for unit moves really. However, the actual path generation function is in the exe (which sucks since I would like to teach the AI to consider AutoAcquire Promotions when moving...)


The other option would be: CvUnitAI::AI_solveBlockageProblem

This function is not called by the Barbarians, and I haven't had time to completely pick it apart and see if it does what I think it ought to just yet. But it would seem that it is intended to clear chokepoint blocks, along with declaring war if neccessary to pass through someone's land.
 
No, the issue is that they DO work with the -1, and seem to require it. But that makes no sense overall. So I am wondering why it does work, and why only in certain cases.

If they do work and the arrays are [0 - (number-1)](typical C/C++ array indexing), the functions must use some kind of 1 based calculation and then transform to the 0 base calculation of the C/C++ array. I also had to do similar tricks, when needed to get 1 based input and manipulate a 0 based array. If it is not commented fully, it is a pain to understand when and why it happens. I comment heavilly such code, because it is very confusing when you forgot about the reason that made you do it this way. Even though the random namber is in the range 0-(number -1), the actual number given by the functions could be one higher that it should, so it has to be reduced afterwards.

Unrelated question:

Barbarian units can be blocked from targetting our cities by blocking chokepoints with units, ANY units. I see 2 solutions:

First:

Change

if (AI_targetCity())

to

if (AI_targetCity(MOVE_THROUGH_ENEMY | MOVE_IGNORE_DANGER))


The function allows you to pass in flags, but from what I see, nothing DOES pass any in for unit moves really. However, the actual path generation function is in the exe (which sucks since I would like to teach the AI to consider AutoAcquire Promotions when moving...)


The other option would be: CvUnitAI::AI_solveBlockageProblem

This function is not called by the Barbarians, and I haven't had time to completely pick it apart and see if it does what I think it ought to just yet. But it would seem that it is intended to clear chokepoint blocks, along with declaring war if neccessary to pass through someone's land.

You could write a function, that takes the same parameters as the one in the .exe. Then, instead of calling the one in the .exe directly, you change the code to call your function. You do whatever work you want before you call the function in the exe, then you manipulate whatever you want, after the function in the .exe returns, and finally you return to the caller.
Example: You program a CvUnitAI::Pathwrapper() that would be called instead of the pathgeneration function. In it you could have
{
//work done before calling the path generator
returnvalue=CalltothePathGenerator();
//work done after calling the path generator, possibly manipulating the return value if needed.
}

I do not know if it is doable, since I do not have the source code. But it is a suggestion that may help.
 
You can't effectively write a wrapper because nobody outside of Firaxis has the sourcecode for the EXE, and re-writing the pathing algorithms would be a massive PITA.

This is the point of writting such a function. You will still call the Fireaxis code, for the pathing, but get some things done before and after the call, depending on the parameters you define, knowing that the caller is trying to create a path. Provided that you know the name of the function that is called, of course. I would assume that the path is returned to the caller after it s created, somehow, right? You would have then access to the proposed path, and manipulate it as you deem propper.

Another solution could be, since the problem of targetting through chockpoints is difficult, to make the AI to target such chockpoints. A human player is not likely to leave control of any chockpoints to the AI, so, I guess it is a good tactic to include chockpoint control in their list of objectives to consider.
 
Is there a particular reason why the "act as city" functionality of forts was removed in FFH? I just recently added it back in, and I'm curious if I'm inviting some horrible bug/crash/man eating velociraptor as a result. :)
 
It doesn't cause a crash or anything, but it makes the AI workers build forts over all the mana resources. They seem to really want to collect raw mana, and Kael really seems to want to stop that, even though it doesn't really do anything.


Oh, another reason was that Kael got tired of people reporting ships moving on land tiles with forts on them as bugs.
 
Ah, thanks! I'll make sure to put an SDK workaround in for that, although at the moment I've been tempted to turn off the AI ability to make forts period. They've been building them everywhere and basically killing their tech rates post construction.
 
Array indexing in python is annoying me right now.

Most all of the Marnok functions use a random selection from a list by getting a random number based on length of the list, then subtracting 1. Random numbers are 0 to (target -1), so by subtracting 1 you are making it impossible to get the last element of the array, and possible to try for a -1 index item.

Except of course that when I removed the -1 entries I later got a python error for being out of bounds...
<rant>
List indexing! Not arrays, lists! I suppose it is the C++ sickness...
</rant>

I'm assuming you are talking about lines like this:

Code:
sMonster = lList[CyGame().getSorenRandNum(len(lList), "Pick Monster")-1]

Are you sure CyGame().getSorenRandNum(n) selects it's return value from the range [0,n)? Because your problems suggest [0,n] to me. Id est any of: 0,1,2,3,...,n-2, n-1, n .

You actually want a number in the range [0,n), that is: not including n.

So a the simple fix is deduct 1 from [0,n] and get [-1,n-1], and occasionally getting -1 as the index. One might think that's a problem, and it is but not a very visible one. Negative indices is allowed in python lists, they start from the end of the list. So -1 is the last element, -2 is the last but one etcetera. Only when you get to the element before the first one do you get an exception. ( This is very useful, so long as the list is used as intended. )

So apart from getting the last element twice as often as you want, there is no problem.


If you right about CyGame().getSorenRandNum(n) returning from the range [0,n), then deducting 1 doesn't skip the last element, since -1 is the last element. However, you shouldn't be getting any IndexError either.

My best guess is that CyGame().getSorenRandNum(n) includes n in it's possible return values and that the call to doTraitInsane discards any exceptions raised. That would mean that sometimes you can get the insane event, but no change in traits. Now that I think about it, that does seem to happen often, doesn't it?

Anyway, if I'm right correct code should deduct 1 from the upper bound, like so:
Code:
sMonster = lList[CyGame().getSorenRandNum(len(lList)-1, "Pick Monster")]

If you subtract from the return value, you get doubled up on the last result. If you don't subtract at all, sometimes you get IndexError, which may be masked by unhelpful code.
 
Useful to know that you can go into the negs on a list and still have it work in an interesting fashion.

Your explanation makes it sensible that the -1 at the very least isn't causing any errors itself, but I am still completely lost as to why I had an indexing error when I removed the -1 signs. I am dead certain that random return [0,n), because I have been logging the every random number that the game generates for quite a while now so have a large enough of a sample that it really ought to have provided me with at least 1 n result if it was possible. (for example, in my current randomlogger.log I have 499 checks against a 0-15 random, of which none returned a value of 15. All numbers ranging from 0-14 have between 25 and 41 results each. Also, for 211 checks on a random 0-4 I have 52/47/62/50/0).
 
So, what it seems to be the issue here is, what number len(lList) returns?
Does it return a 1-based number of elements or a 0-based number of elements.
Out of my experience, the len function returns the number of elements in an array or list. So for a 15 item list it would return 15. But if you want to get the 15th element, you must ask for lList[14].

I think this explains it perfectly right. a -1 is usually taken as 0 in lists, but a maxelement+1 depents on current implementation. My lists wrap to the maxelement, but other's list may cause an indexing error.
 
Yes, Thunder_Gr, but the len(list) is used in the random numer function, which returns a 0 to N-1 number, which should already solve the -1.
 
What is going to solve this issue is to see if the last element can be returned with the random generator. Which means, we either need the code of the random generator, or a test to see if, using the above code, the last element of the list is ever selected. If it is not, but when the -1 is removed, it causes indexing errors, then perhaps, in these particular lists, the last element is not supposed to be ever accessible.

EDIT: in additions, some lists can return size 1 even if they are empty. In these lists, the last element is always empty, and not ment to be selected. Thus you actually need a len(lList)-2 to access the last valid element.
 
Back
Top Bottom