Vikings shouldnt get kjøbmandehavn(Copenhagen)

...and it worked like a charm! :goodjob:

jmerry, you're a real life-saver! :king:

Right now I'm doing both spawning units with a set XP value and/or an promotions list - or simply a level value. There should be some results this weekend.
 
Ok, here it comes! :D I'm pretty much done with the unit spawns so I thought you guys could test them out.

Here's the available arguments, by the way:
Spoiler :
Integer variables:
  • iCiv is the index value of the Civ getting the spawn, as defined in the Consts.py file.
  • iUnitType is the index value of the unit spawned, as defined in the Consts.py file.
  • iNumUnits is a numerical value corresponding with the number of units spawned.
  • iLevel is the unit level. (Default is "1".)
  • iXP is the amount of experience points granted to the unit.
  • iInterval is the interval between spawns - or the relative probability of a spawn on a given turn.
  • iModulo is used to time intervals to specific game turns, but the default value is 0 and iModulo should always be less than iInterval. If set to "-1" the iInterval variable will produce a random outcome instead.
Booleans:
  • bHuman can be used to enable the spawn also for the human player ("True").
  • bPreset is used to determine whether promotions granted by the iLevel variable will be preset by a script ("True") or not ("False")
  • bDomestic determines whether or not a unit must spawn within its own cultural borders ("True") or not ("False").
  • bConscript is used for determining if a non specified unit will be the default defender of the era ("False") or the unit that would be available for drafting with the Nationhood civic ("True").
  • bAttack is used for presetting both promotions and the AI Type (see below) setting of spawning units.
Turples and lists:
  • tCoords is the spawn tile coordinates separated by a comma and within parenthesis.
  • tTL and tBR are a couple of coordinates corresponding to the bottom left corner (tTL) and the upper right corder (tBR) of a defined map area respectively.
  • lPromotions is a list of promotions ID values and every entry must be separated by a comma and all the entries must be inside brackets.
Other:
  • AIType is a bit complex to describe but the standard setting would be "UnitAITypes.NO_UNITAI". I have made it possible to just enter "None" instead as the default setting though.
I've replaced Rhye's makeUnit() function with custom.makeUnit():
Code:
custom.makeUnit(iCiv, tCoords, iUnitType, iNumUnits, iLevel, iXP, lPromotions, AIType)
This isn't a very helpful function, though. Unit level and experience need to be calculated and any preset promotions entered manually. The function also takes no game circumstances into consideration and you will have to use your own triggers and if statements in order to get any real use of it. Because otherwise there will be issues, like accidentally spawning inside enemy territory (and perhaps even dislodging enemy units) or unintentionally awakening collapsed Civs. Or worse.

In order to put some limitations into the spawns I did what Rhye did also, and wrapped the makeUnit() function into another function. In fact, I did these:
Code:
custom.createDomesticUnits(iCiv, bHuman, tCoords, iUnitType, iNumUnits, iLevel, bPreset)

custom.createHostileUnits(iCiv, bHuman, tCoords, iUnitType, iNumUnits, iLevel, bPreset)

custom.createNavy(iCiv, bHuman, tCoords, bDomestic, iUnitType, iNumUnits, iLevel, bPreset)

custom.createGarrison(iCiv, bHuman, tCoords, iUnitType, iNumUnits, iLevel, bConscript)
There is no longer any need to calculate how many XP each unit level corresponds to or to list any promotions. Promotions can be assigned automatically (and will differ between the functions) or the player (human or AI) can chose for himself.

There's also no need to figure out how the AIType variable should be defined, as that is handled by the function. And if the spawning tile is already occupied by an enemy unit, the function will look for an available adjacent tile instead.

custom.createDomesticUnits() is used to spawn units inside of a Civs own cultural borders, and is mostly intended for spawning defensive units.

custom.createHostileUnits() can be used to spawn units outside of a Civs cultural borders, but they still won't spawn inside another Civs cultural borders.

custom.createNavy() is used to spawn naval units and can be restricted to domestic cultural borders only.

custom.createGarrison() works the same as before, but note that the unit level now has to be set also. (Promotions will always be preset.)

But this isn't all, as you will need to be able to spawn units on random tiles - just like with Rhye's spawnUnits() function. So I made my own:
Code:
custom.spawnUnits(iCiv, bHuman, tTL, tBR, bDomestic, iUnitType, iNumUnits, iLevel, bPreset, bAttack)
Also, you'd wanna spawn units at intervals - or even on random turns:
Code:
custom.spawnLandUnits(iCiv, bHuman, tTL, tBR, bDomestic, iInterval, iModulo, iUnitType, iNumUnits, iLevel, bAttack)

custom.spawnSeaUnits(iCiv, bHuman, tTL, tBR, bDomestic, iInterval, iModulo, iUnitType, iNumUnits, iLevel, bAttack)

custom.spawnBarbs(iCiv, tTL, tBR, iInterval, iModulo, iUnitType, iNumUnits, iLevel)
The last one can be used to replace Rhye's spawnUnit() function for the Barbarian and other minor spawns - or you could add your own with additional settings. (You can set iCiv to "-1" for a random Minor Civ - just like with the city spawn function.)

Note that all promotions will be preset by the functions above, but also that you can differentiate between offensive and defensive promotions. If you don't need to define an entire area but wanna use these functions to spawn units on one defined tile only, you can just leave the tBR blank (like this: "()") and it will work just like the "createUnit" functions mentioned earlier.

The interval spawning is achieved with this little function I made:
Code:
custom.turnTrigger(iInterval, iModulo)
If you put it into an if statement you can have any events trigger at intervals - or randomly, see above for the specifics.

And I've used this function to execute the human player check associated with the bHuman boolean. It also checks if the Civ in question is alive:
Code:
custom.checkPlayer(iCiv, bHuman)
Both of these functions return a boolean (True/False) so its really easy to use them in an if statement. Like:
Code:
if (custom.turnTrigger(10, -1) and custom.checkPlayer(con.iJapan, False)):
This example would trigger with a 10% probability at any given turn - as long as Japan is still around and is not controlled by the human player. The functions could be used separately also, or with a different set of arguments - of course.

Also, I made this function to make up for the lack of support for including lists of promotions:
Code:
custom.promoteUnits(iCiv, bHuman, tCoords, iXP, lPromotions, bForce, bNew)
The function will give XP and/or the promotions listed to any and all units on the designated tile, unless restricted by the iCiv, bHuman or bNew variables. (If bNew = "True" the function will only deal with units created on the same game turn as it is used.) Since not all promotions would normally be available to all unit types, the bForce boolean can be used to override this.

Unit level will be set automatically as the XP is used for promotions, and the list of promotions is separate from the XP setting, by the way.

In this update is also included a few additional, easy-to-use functions for flipping stuff:
Code:
custom.invadeCoreArea(iInvader, bHuman, iVictim)

custom.reclaimNormalArea(iCiv, bHuman)

custom.expandBroaderArea(iCiv, bHuman, bMinorOnly)
The first one will give a Civ's Core Are to another Civ (making the first one collapse shortly). The second one will give a Civ everything inside its Normal Area. And the third one makes a Civ expand into all of its Broader Area. The settings should be self- explanatory.

I might also do some additional unit spawn functions, like for entire stacks of units of various types. Such a function would basically just take the number of units spawned and the actual composition of the stack would be set with booleans. Like if there is to be any siege units present, or if its an offensive stack. Stuff like that. The actual unit types, their exact numbers (within the value entered) and promotions would be determined by what Techs the Civ receiving the spawn has.

Other than that I'm gearing up for a proper release with some additional stuff. I'll post everything in the modmod forum, but in the meanwhile I'm awaiting the results of your testing and also looking forward to any critique or suggestions that you might have. :king:

edit: Also a big thanks to jmerry for getting me back on the right track when I was stuck! :goodjob:
 

Attachments

  • Custom.zip
    5.7 KB · Views: 61
double post... :rolleyes:
 
Looking forward to it.
Really? :eek:

You could always take a look at the attached code right now and tell me if you can spot any obvious improvements. I'm just a beginner at this myself...

I'm guessing there is a lot that could be included, like a function for collapsing a Civ on que, or for changing the diplomatic status between Civs, or for revealing map tiles, or... What would you like to see?

I might need some help creating easy-to-use functions for messages and pop-ups, by the way... It reminds me about what my next project will be. :D

Right now I'm thinking about renaming the released file CustomUtils.py and also attaching a file called Scenario.py as a template for the scenario maker's own spawns and additions and what not. It would basically have the checkGameTurn() function in it with some tutorial spawns and whatnot, but the would be Python programmer could also try his first own functions in this file. Because it really isn't proper to mess around with Rhye's files when you could have all your additions in a file of your own. (It also makes it easier to export your historical events to the inevitable next version of RFC.)

Thoughts? Suggestions?
 
If you're building a list of promotions, you probably want a list of constants somewhere to base it on. Here are the promotion IDs for Rhye's scenario:
Spoiler :
Code:
iCombat1 = 0
iCombat2 = 1
iCombat3 = 2
iCombat4 = 3
iCombat5 = 4
iCover = 5
iShock = 6
iPinch = 7
iFormation = 8
iCharge = 9
iAmbush = 10
iAmphibious = 11
iMarch = 12
iBlitz = 13
iCommando = 14
iMedic1 = 15
iMedic2 = 16
iGuerilla1 = 17
iGuerilla2 = 18
iGuerilla3 = 19
iWoodsman1 = 20
iWoodsman2 = 21
iWoodsman3 = 22
iCityRaider1 = 23
iCityRaider2 = 24
iCityRaider3 = 25
iCityGarrison1 = 26
iCityGarrison2 = 27
iCityGarrison3 = 28
iDrill1 = 29
iDrill2 = 30
iDrill3 = 31
iDrill4 = 32
iBarrage1 = 33
iBarrage2 = 34
iBarrage3 = 35
iAccuracy = 36
iFlanking1 = 37
iFlanking2 = 38
iSentry = 39
iMobility = 40
iNavigation1 = 41 
iNavigation2 = 42
iLeader = 43
iLeadership = 44
iTactics = 45
iCombat6 = 46
iMorale = 47
iMedic3 = 48
iRange1 = 49
iRange2 = 50
iInterception1 = 51
iInterception2 = 52
iAce = 53
iSelfPreservation1 = 54
iSelfPreservation2 = 55
iSelfPreservation3 = 56
iMercenary = 57
I had those around already since I used them for fixing a bunch of UPs.
 
Truthfully I have lots of modding work at the moment and was hoping someone else would look at it... :mischief:
Well, right now I'm actually studying your code for making the pop-ups happen in the Reformation mod. :king:

I think I'm able to follow the code alright, but I'm not sure why exactly you need to give the pop-up an ID number. (I realize that Rhye has done this also, but I never bothered with it when I made my own...) It will however complicate things manifoldly! :p
 
If you're building a list of promotions, you probably want a list of constants somewhere to base it on. Here are the promotion IDs for Rhye's scenario:
Spoiler :
Code:
iCombat1 = 0
iCombat2 = 1
iCombat3 = 2
iCombat4 = 3
iCombat5 = 4
iCover = 5
iShock = 6
iPinch = 7
iFormation = 8
iCharge = 9
iAmbush = 10
iAmphibious = 11
iMarch = 12
iBlitz = 13
iCommando = 14
iMedic1 = 15
iMedic2 = 16
iGuerilla1 = 17
iGuerilla2 = 18
iGuerilla3 = 19
iWoodsman1 = 20
iWoodsman2 = 21
iWoodsman3 = 22
iCityRaider1 = 23
iCityRaider2 = 24
iCityRaider3 = 25
iCityGarrison1 = 26
iCityGarrison2 = 27
iCityGarrison3 = 28
iDrill1 = 29
iDrill2 = 30
iDrill3 = 31
iDrill4 = 32
iBarrage1 = 33
iBarrage2 = 34
iBarrage3 = 35
iAccuracy = 36
iFlanking1 = 37
iFlanking2 = 38
iSentry = 39
iMobility = 40
iNavigation1 = 41 
iNavigation2 = 42
iLeader = 43
iLeadership = 44
iTactics = 45
iCombat6 = 46
iMorale = 47
iMedic3 = 48
iRange1 = 49
iRange2 = 50
iInterception1 = 51
iInterception2 = 52
iAce = 53
iSelfPreservation1 = 54
iSelfPreservation2 = 55
iSelfPreservation3 = 56
iMercenary = 57
I had those around already since I used them for fixing a bunch of UPs.
Thanks a ton, again! :goodjob:

That list will surely be appended to the Scenario.py file once that one is released! :king: I would have used it for sure while coding the script for presetting promotions, if I've had it. (I just used the values I found in the API instead. Not as convenient to work with, hence all the commenting lines...)
 
Just a basic trivia question, how come the constants are all the same for every aspect of the game? i3000BC=1, iCombat2=1, etc etc

Is there a part of the code that tells the game which section to get #1 from, or am I being really dense?
 
Just a basic trivia question, how come the constants are all the same for every aspect of the game? i3000BC=1, iCombat2=1, etc etc

Is there a part of the code that tells the game which section to get #1 from, or am I being really dense?

The numbers just index entries in lists, like XML code, that the other Python functions look up themselves.
 
Just a basic trivia question, how come the constants are all the same for every aspect of the game? i3000BC=1, iCombat2=1, etc etc

Is there a part of the code that tells the game which section to get #1 from, or am I being really dense?
I'm not entirely sure about what you mean, but all aspects of the game are indexed for the game to reference. Like units (and units types), techs, Civs (and teams), or game turns. And all indexes start with 0 (zero) since the code always counts from 0 (zero).

That is why the first unit created in the game has the index value of 0, the second has ID 1, the third one is #3 and so forth. The same with Civs - the first one is Civ #0, the second one #1 and so on. I also believe that the first game turn is #0, the second #1, and... you get the drift.

What the variables do, is simply hold these values - so that the programmer doesn't have to memorize all the indexes or have them available for reference all the time. These variables are actually called constants ("Consts" - get it?) as their value never changes. (Because the indexes never change.)

Since all the indexes only use integer values (there's no Civ 2.5 or Unit 3.45) these constants start with the letter "i" (as in integer). Rhye could very well have opted for a different approach and named them whatever, as the variable name does nothing in itself.

So you could define the variable WorstCivEver = 10 and use it in your code whenever you refer to Civ #10 instead of iMaya or rather con.iMaya (the eleventh Civ in RFC would be the Mayas). Since the variable would only mean the integer value of ten and nothing else, you could also use it instead of iExecutive1 or iClam or iMongolGer. But that would be very confusion to you in the long run. :rolleyes:
 
I just realized something pretty nifty. :king:

Instead of asking for the game turn when triggering spawns and whatnot, one could use game year instead. This way there would be no need to either figure out what date corresponds to what game turn, or to use any of the defined dates of Rhye's (like i1500AD). :goodjob:

The best solution would to make a custom function that returns a boolean if the game year has come. The same function could also support year intervals. Like:
Code:
custom.triggerYear(argsList)
argsList is a argument that can hold several arguments, in this case it would be either one or two year dates. (One for one turn only, two to create an interval of potentially several turns.)

An example of use in a if statement:
Code:
if (custom.triggerYear([500, 600])):
This line would trigger any code as long as the game year is at least AD 500 and at the most AD 600. Only having it trigger once on the game turn closest to AD 500 would just be:
Code:
if (custom.triggerYear([500])):
I just have to figure out a way to ensure that the function will always trigger even if no game turn corresponds exactly to the game year - and that it will only trigger once. But I think I just might get it to work.

Also, I'm thinking about including this function to the other functions for spawning stuff and whatnot. That way there would be no need to use nearly as many if statements while making scenarios. You enter these dates into the functions directly, kinda how spawning cities work right now.
 
The constants aren't the only way of doing things; there are other mods that use things like "getInfoTypeForString" to get those indices from the names, while the game is running. That makes it easier to move things around in the XML files, but it comes at the cost of some speed. Constants that are defined once and then used repeatedly need to be maintained by the modder, but they're somewhat faster. Also, it's probably a good idea to stick with a single design philosophy, and use constants for similar purposes when adding new features.

On the "game year" function: that seems like something the Epic/Marathon people might well have written already. Why not ask them?
 
On the "game year" function: that seems like something the Epic/Marathon people might well have written already. Why not ask them?
I managed to find everything I needed in the API and it was pretty much just a matter of getting the logic of it right. Math is not my friend. :rolleyes: I do believe that I managed to get it right, eventually, but I pretty much had to resort to pen-and-pencil before I figured it out. :lol:

I don't have time to test anything tonight, but I'll try to confirm my solution tomorrow.

Work - what is it good for? :p
 
I'm not entirely sure about what you mean, but all aspects of the game are indexed for the game to reference. Like units (and units types), techs, Civs (and teams), or game turns. And all indexes start with 0 (zero) since the code always counts from 0 (zero).
Oh I just meant why are the integers the same. Lets use a fictional example, such as gameturn. Like when a function asks for gameturn "1", how can it be sure that "1" is 3000BC, and not Combat2 ;)

However I now realise the function is geared to look up gameturns, and THEN asks for #1... not the other way around. I'm not the cleverest programmer ever, it seems :p
 
Just an update:

I created another class of functions in Custom.py called "Spawn". If you don't wanna use an endless array of if statements for your spawns and historical events and whatnot, you could call on the Custom functions through Spawn() instead. So instead of doing:
Code:
if (iGameTurn >= con.i800AD and iGameTurn <= con.i1000AD):
        if (custom.checkPlayer(con.iChina, False)):
                custom.createDomesticUnits(con.iChina, (100, 44), con.iArcher, 1, 1, True)
...you would achieve the same thing by using the Spawn() class:
Code:
Spawn(800, 1001, True, con.iChina, False).createDomesticUnits((100, 44), con.iArcher, 1, 1, True)
A few things of note:

1. This would mean that the "active Civ check" (whether or not the Civ receiving the spawn is the human player and whether or not the human player is eligible for the spawn - or whether or not the Civ is still alive) would be removed from all the functions in the Custom() class (called with the custom. prefix). So you would either have to call on the custom.checkPlayer() function with an if statement (example #1 above) or use the Spawn() class instead (example #2).

2. If the third argument (boolean) in Spawn() is set to "True" (as above) it is possible to trigger the spawn with the year dates instead of the actual game turns. (Otherwise the first and second argument would have been "201" and "221" respectively - the game turns corresponding to the dates.)

3. The iCiv argument is only entered once (fourth argument in Spawn()) when using Spawn() - the second one (in createDomesticUnits()) would be redundant.

The important thing is that all functions (well, at least the basic ones or the ones with the most features) would still be available through Custom() like before, but you'd get all these built in features if you use the Spawn() class instead. But these features would also still be available if you want to use them through the Custom() class instead. (Perhaps for doing multiple spawns for the same Civ on the same date, or something.) So it would still be flexible enough.
 
Ok, here it comes! :D I'm pretty much done with the unit spawns so I thought you guys could test them out.

I'll help test too :)

I would have responded a few days ago, but my computer COMPLETELY messed up and I had to reboot it, which took a while to figure out. I'm still reinstalling things.

:badcomp: :badcomp: :badcomp: :badcomp: :badcomp: :badcomp: :badcomp: :badcomp:
 
Just another update: I'm currently taking the time to actually learn some of the finer points of Python programming, but also planning the next generation of my custom functions. As an example, the functions for spawning cities and units will belong to two different "classes", and there will be another class for triggering these spawns. The preliminary library (or API) looks something like this:
Spoiler :
Code:
Trigger()
    .year(iYear)
    .turn(iTurn)
    .yearInterval(iYear1, iYear2)
    .turnInterval(iTurn1, iTurn2)
    .randomTurn(iInterval)
    .intervalTurn(iInterval, iModulo)
    .player(iCiv)
    .checkplayer(bHuman, bDead)
    .isTrigger()
    .spawnCity()
    .spawnUnit(iUnitType)

City(iCiv)
    .cityPlot(iX, iY)
    .checkCityPlot(bDomestic, bForeign, bForce)
    .setCityName(name)
    .setCulture(iCityCulture, iPlotCulture)
    .setReligions(lReligions, bErase)
    .setBuildings(lBuildings)
    .foundCity(iPopulation)
    .garrison(bConscript)

Unit(iCiv, iUnitType)
    .plot(iX, iY)
    .area(tTL, tBR, bLand, bDomestic, bForeign)
    .checkPlot(bDomestic, bForeign, bAdjacent)
    .setUnitLevel(iLevel, iXP)
    .setPromotions(lPromotions)
    .setAI(AIType)
    .makeUnit(iNumUnits)
This is the current way of spawning units (exemplified by the custom.createHostileUnits() function covered in the earlier post) on a specified turn in Barbs.py (or any other .py file with the checkGameTurn() function):
Code:
custom = CustomUtils.Custom()
if iGameTurn = 101:
        custom.createHostileUnits(iCiv, bHuman, tCoords, iUnitType, iNumUnits, iLevel, bPreset)
About the same result could be achieved with the new setup but without any if statement (or checkGameTurn() function):
Code:
spawn = CustomUtils.Trigger()
spawn.turn(101).player(iCiv).checkPlayer([COLOR="Red"]True[/COLOR], [COLOR="Red"]False[/COLOR]).spawnUnit(iUnitType).plot(iX, iY).checkPlot([COLOR="Red"]False[/COLOR], [COLOR="Red"]False[/COLOR], [COLOR="Red"]True[/COLOR]).setUnitLevel(iLevel, iXP).setAI(UnitAITypes.UNITAI_ATTACK).makeUnit(iNumUnits)
There are less lines but more code to type, but some of those boolean settings (red) would be preset and not necessary to include (unless you don't wanna use the default setting, of course).

The upside would not only be more settings available but also that you scenario makers wouldn't have to be restricted to a handful or so pre-defined functions. Using the API you could make your own spawns as complicated or as basic as you choose. (The setAI() function wouldn't be necessary in most cases, for example.)

You could also use the Trigger class separately from the spawns in an if statement - or combine them yourself in code:
Code:
trigger = CustomUtils.Trigger()
spawnUnit = CustomUtils.Unit
if trigger.turn(iTurn).player(iCiv).checkPlayer().isTrigger:
        spawnUnit(iCiv, iUnitType).plot(iX, iY).checkPlot().setUnitLevel(iLevel, iXP).setAI(UnitAITypes.UNITAI_ATTACK).makeUnit(iNumUnits)
(Note that I've hid the arguments for checkPlayer() and checkPlot() as the default settings in the example above would be preset.)

I realize it might seem a bit confusing at this point, but it would all be well documented on release. :king:
 
Just a little update for anyone using the Custom module (also known as the Custom.py file). Thanks to Killerkebab's rigorous testing I was able to find two bugs in the code, and those have now been corrected. A new and corrected version is attached.

The first issue had to do with spawning units with city flips. A error in the code prevented this from happening and produced an Python exception potentially disabling other Python code running on that turn. :blush:

The second issue turned out to be that you can't spawn cities on Jungle tiles - probably because the receiving Civ is lacking the appropriate Tech that allows it to clear the Jungle. Now all features (as they are called) are erased from a tile before any city spawning, so this issue should not arise with Forest or Swamp tiles either - no matter Techs.

This could very well be the last update to the Custom module, as I'm still gearing up to redo the whole thing with some more streamlined code and a whole lot more flexibility, as described in previous posts. The issue now is to found the time and the energy to get this off the ground, as I'm kinda short on both... Well, maybe someday? :rolleyes:
 

Attachments

  • Custom.zip
    5.8 KB · Views: 75
Top Bottom