OrgrinRT's Learning Log AKA The pitch-in thread for helpful souls

orgrinrt

Chieftain
Joined
Nov 7, 2016
Messages
64
Location
Finland
Hello, Civ Fanatics.

This first post will be a mere introduction, skip to the TL;DR or straight to second post if you're not intrested. This will just be a long, boring bit of text describing how I ended up here.

The whole bit:
Spoiler :

Three days ago I started to wonder how I could make some things happen in regards of modding. First, I wanted to make more mods cross-compatible with each other (mostly custom civs), and that sprung a whole new thing I found myself very excited about.

With gracious help from JFD and Chrisy15 (who were patient enough to assist me with my dumb questions when I literally didn't know a single thing about anything), I took it upon myself to learn to do some customizations.

The problem in the first place was that some custom civs were incompatible with some of other mods I liked to play with, and vice versa, so I needed to fix that. But in addition, I wanted to add to the immersion of each civ for my own enjoyment. I guess this could be called modding, but really, it's more like modmodding or modmodmodding. But you get the picture. I wanted to make some civs I enjoy, more enjoyable (for me) and also more compatible / up-to-date with all the new stuffs with e.g JFD's amazing gameplay mods.

So, I needed to start piecing things together, how the different things work around civ V modding and customization. Worth mentioning is I don't have any background in codings whatsoever (other than the current norm of things pretty much everyone knows, like HTML or CSS), so this really is a big thing for me to start learning.

Now I've always been a bad learner in the sense that I lack the patience needed to succesfully focus on reading longs bits of text. I'm literally stupid that way, with or without the negative connotation that comes with the term. So I needed to just start figuring things out while messing with things.

First thing was, I had to decide where to start. So I took the route of picking a civ I play the most as, and go from there. The pick was Hypereon's amazing Finland (Mannerheim) civ. This resulted in a seemingly infinite series of questions, with which I bugged a bunch of people out here in the forums (big thanks to everyone helping!). Eventually the questions started to arise more frequently than it was physically possible for me to keep asking around for, so I had to really start a long process of trial-and-error and extensive comparing with a big bunch of stuff that you guys have done that work and succesfully achieve what they're supposed to.

Well, out of that rose the mess of a modmod I made to "enhance" and "update" the mentioned civ from Hypereon. What it really does, is clumsily (succesfully, nevertheless) add in the features of current versions of JFD's gameplay mods, add a variety of unique stuff to it, including new sounds, a big array of unique Finnish names and lists and whatnot to accompany it all. The aim was to do this to a bunch of other civs, but I felt that I need to perfect this one first, and then apply the complete "package" to the civs I love.

I simply wanted to do more. I believe this is how most people end up with modding. I got the fever, I had to learn more and do more.

So, this is the what and why for this thread. I wanted to start updating my learning process and write up some random mumblings along the road, in case someone wants to pitch-in to help. Basically, it really started to mess with my head to be a constant pain in the ass for you guys who actually create amazing things. I didn't want to just be the guy who keeps bugging you.

I expect nothing from this, so if nobody pitches in, that's perfectly fine. This is a road I have to walk myself anyway, so it really isn't necessary. But I thought I'd give the opportunity just in case someone's a real naturally helpful soul. Also, it's a way to document my learning process, maybe it'll be a good laugh in time.

I will be posting my progress here. I'm not going to bore anyone with details or some kind of diary type of a post; I'm thinking I'll just write down the problems I have, and the thinkings I'm doing regarding to some things I need done.

Anyway, if you read all the way here, you're definitely not easily bored! You're really lucky in that sense, I truly wish you've embraced that.


TL;DR:
Spoiler :

Three days ago suddenly wanted to do some customizations. Then realized how cool this whole idea of modding is. Wanted to learn how to mod. This thread is a monologue of mine where I think out loud of the things I'm working on / struggling to make work. Didn't want to bother anyone with my dumb questions anymore, hence the thread. Also, it's a way to document my learning process, maybe it'll be a good laugh in time. You guys can pitch in if you have any suggestions, help or pointers for me.


I'm a mess, this thread will most definitely be a mess, and everything I'm currently doing with the modmod thing is probably a huge mess on its own. Please, don't feel obligated to help just because everything's a mess here :D

If you have some time to spare and need something to do, feel free to help me here :^)


BY THE WAY
I recommend that you skip through to the newest post: The ones before it are probably outdated and already figured out/fixed/whatever.
 
Last edited:
Hypereon's Finlands - A Update and Fix Patch (Unofficial)

Changelog/features:

Spoiler :

Audio:
-Added a unique sound to Mannerheim's UB with the feature provided by JFD's CiD
-Changed UnitSelect and UnitOrder sounds to use JFD's CulDiv's Finnish language instead of northern
-Added DoM speech for Mannerheim, delivered by the brilliant Michael Harris / forevertomorrow
-Added a unique sound Kekkonen's UB with feature provided by JFD's CiD
-Added DoM speech for Kekkonen, also delivered by the amazing M. Harris / forevertomorrow

Minor civs:
-Added five Finnish minor civs to the base game
-Added JFD's CulDiv support to them (CULTURE_JFD_NORTHERN)
-Added YNAEMP support for maps where these citie's TSL is
----North Atlantic
----Europe Large
----Earth Giant
----Earth Huge
----Earth Standard
----Earth Greatest
----Europe Giant
----Europe Greatest
----Eastern Europe
----Europe North West Standard
----Europe North Sea Huge
----North Atlantic Huge
----Mongol Steppe Huge


Mod support:

-Ynaemp
----Updated some of the starting locations that weren't correct (WIP - adding v24 map support soon)
----Fixed an error where the Mannerheim's original support file didn't succesfully take RequestedResource from CIVILIZATION_SWEDEN

JFD's gameplay mods:
-Added support for JFD's CiD's province feature
-Added unique Finnish names for EVERY possible province _DESC_ (including feminine)
-Added support for JFD's CiD's Colonial cities
-Added unique Finnish name list for colonial city names (pool of 15 names)
-Added/updated support for JFD's CiD leader flavors (all the current ones)
-Updated JFD's CulDiv support to include all the new columns (including the Finnish unitDialogueTag)
-Updated JFD's CulDiv soundtrack tag to the current version (works better/correctly now)
-Removed JFD's ExCe's revolutionaryCitizensToSpawn support (the feature isn't anymore)
-Updated support for ExCe
-Added JFD's and Pouakai's mercenary leader flavor
-Added support for JFD's RtP piety features (incl. leader flavors etc.)
-Added support for JFD's new sovereignity features
----Added unique Finnish titles for legislature and office title
----Added unique (and accurate) list of Finnish HeadOfGovernment (pool of 25 names)
----Added unique Finnish titles for EVERY possible government type _TITLE_GOVERNMENT, _TITLE_LEADER (and republic's _TITLE_LEADER_LATE)
-Added unique Finnish great people names for new great people included in JFD's gameplay mods
-Added unque Finnish political party names (WIP...not sure if they are working correctly as of now)
-Added unique leader traits AND preferences (lists of 17 each) to JFD's Reforms

Unique Cultural Influence:
-Fixed Mannerheim's UCI (now it works)
-Added UCI support for Kekkonen

Great People:
-Added a list of Finnish great people (currently 170 names, will add more in time)
-Also supports JFD's and VP's(diplomat?) new GP
-Added a new module; Unique Great People (thanks to LeeS and Chrisy15 for helping with it!)




To do:

Spoiler :

Audio:
-Add more UnitOrder and UnitSelect sounds in Finnish (possibly not only update but completely make a new pool)

Minor Cities:
-Civilopedia entries for the cities (I believe these can be added, right?

Great People:
-Figure a way to transfer the unique work to the new unique GP
-Add unique works of art for each great artist, musician and writer (uh oh!)
-Civilopedia entries for the great people



Notes:
Spoiler :

Eventhough I got the blessing from Hypereon, I'm not comfortable on sharing this package until I can figure out the great people.




Unique Era Unitsounds

To Do:

Spoiler :

Audio:
-Make collections of 1-3 new unit sounds appropriate for the eras the current ones don't fit in

Others:
-Figure out how to do the whole thing in the first place :smoke:


Thoughts:

Spoiler :

I'm thinking the script would have to get the current era the player's in on the round start, and I'd have to add a new column in the database where the UnitSelect and UnitOrder sounds are placed in with an integer value. I guess that 3 eratypes would be enough. It would then check the integer value and assign the appropriate soundtag for the civ for the current roudnd. Ideally this mod would fetch the unitsound's type from JFD's CulDiv's UnitDialogueTag, and I'd just have to add the new sounds in this mod to the tables JFD uses for his cultural unitsounds. I'm not completely sure how this would be done though.

The other way would be to trigger the change when a new era begins, and store a CurrentEraSoundTag with the appropriate value until a new era begins again and changes the value. Not completely sure which way would be easier to do and more lightweight. I'm thinking even this way still needs to read the CurrentEraSoundTag each round to know which sounds to order the game to play. Unless it could be made into a more of a "setting" that applies at all times.

The hard part will definitely be the lua coding since i don't know a thing about it yet. We'll see if this is doable or not.


Edits/Updates(newest at bottom):
  • 27.11.16: Updated Hypereon's Finland's changelog and to-do lists
  • 27.11.16: Updated Hypereon's Finland's changelod and to-do lists (UGP module is alive!)
  • 29.11.16: A whole lot of things, including getting Kekkonen all up-to-date, fixing a bunch of errors and such, adding support to UCI, adding more YNAEMP maps' TSL to minorcivs, added 2 more minorcivs, etc. see the complete list above
 
Last edited:
Allright, time for the first actual post here.

The problem with me not having any background in coding, is that lua scares me. I have no idea what functions are and generally no clue of what's what. And I've come to a point where mere sql's just don't do it for me anymore. So basically, with the help of many of you guys, I learned how to understand and edit stuff in the database. But now I need to really make things happen, instead of just adding in / updating / removing stuff.

Here's the thing I'm after: I want to have the civilization only spawn great people appropriate for the civ. Now I'm full aware of LeeS's mod to make civ-specific GG and GA names, but honestly, I wasn't able to find a easy way to make it work with all the names. He does also mention the problem with great writer's, musicians and all the great people with unique great works. That's not a nut I want to crack for now, I just want this modmod to internally do the things LeeS's mod does, but also with other great people (ignoring the great works problem). (just a quick EDIT here; this is actually a no-issue for me, since I'd be creating individual new works of art for each great person. I'm dumb!)

First, I tried to see how his mod works, and if it would be possible for me to run it inside my modmod. He had included some instructions on how to do this, but I quickly realized the thing's a little too heavy for my needs. For what I want done, the whole TSL thing is probably an overkill, and I've already read up a bit on the saveutils to understand how I could use that for the small needs I'd have.

The thing is though, I have zero knowledge on coding, as said numerous times before. Lua is seriously intimidating. I've now spent a whole day trying to understand the inner thinkings of the language, and I think I've got some things figured out. I think I understand the logic of how it goes through things, but I just don't know the names for all the appropriate words to use such as what local means, what's the exact format of calling a function etc.

So, I started to work on a logic-tree (if that's a word) of how the script would work, according to my limited thinking. This is what I currently have:

Spoiler :

Code:
--------------------------------------------------------------
-- Planning bit
--------------------------------------------------------------
--The grand scheme:
------When great person is born
------IF playerciv = CIVILIZATION_FINNS or
------IF AIciv = CIVILIZATION_FINNS
------fetch *unit_name* from Unit_UniqueNames in column UniqueName
------require CivilizationType CIVILIZATION_FINNS associated with the UniqueName
------somehow attach that to the great person born (maybe there's a ready API to use for that?)
---------------------------------------------------------------
--To do that, we need to:
----determine (on game load)
-------if CIVILIZATION_FINNS exists in the game
-------if exists, is it player or AI (inconsequential now, but could do expanding on this in future?)
----determine (on round start)
-------if a new great person is born
-------if is, what is the UnitType
-----------fetch a name from UniqueName namepool with associated UnitType AND the requirement of CIVILIZATION_FINNS
-----------withdraw that name from the pool until expended
----determine (during the round)
-------if great person was expended
-------if was, return the name in the UniqueName namepool
-------if great person is granted/born during round
-------if is, apply same logic as when it would be born on round start
----------------------------------------------------------------




--take CIVILIZATION_FINNS and somehow use something to
----check if that is player's civ
------if it isn't, then ignore
------if it is, then
--------SetPersistantProperty("playerGP", CIVILIZATION_FINNS)
--------------------------------------------------------------


It's a mess, I know, especially since I'm not at all sure if that's a valid presentation of how the things would run. But if I didn't at least begin to try and figure it out, I'd never actually try and figure it out, and I thought this'd be a great starting point to start and figure out how lua works (and I guess, most of the generic coding languages as well follow a similiar logic?)

Anyway, I'll leave this here for now, I have some other things to do. Sorry to cut this in the middle of the story, I'll continue with it at some other time. Meanwhile, if you have any pointers, I'd be more than glad to receive them!


EDIT:

By the way, with this one, I already have done the initial steps of adding CivilizationType to the Units_UniqueUnits table as a new column, and I've done a list of CIVILIZATION_FINNS specific names there. I did this following LeeS's logic, so enormous thanks to him for explaining some of the things in his mod's thread!

So what I have, is a SQL file to add the new column, a SQL file to define (and insert) the new names (including CivilizationType) into the database, and an XML file that writes into language_en_US the actual readable strings for the names.

Not sure at all if this is what I should've done, if someone thinks it should have been done some other way, please do post a frustrated post :smoke:


EDIT2:

Made some changes in the planning bit. I guess it makes more sense now. I wasn't really sure how these things work when I initially made it. I still don't, but it's starting to flesh out a bit.

Here's the new one:
Spoiler :

Code:
--------------------------------------------------------------
-- Planning bit
--------------------------------------------------------------

--------------------------------------------------------------
--The grand scheme:
------When great person is born
------IF playerciv = CIVILIZATION_FINNS or
------IF AIciv = CIVILIZATION_FINNS
------fetch *unit_name* from Unit_UniqueNames in column UniqueName
------require CivilizationType CIVILIZATION_FINNS associated with the UniqueName
--------------------------------------------------------------

--------------------------------------------------------------
--To do that, we need to:
----determine (on game load)
-------if CIVILIZATION_FINNS exists in the game
-------if exists, is it player or AI (inconsequential now, but could do expanding on this in future?)
-------store this data? (is it needed though?)*
----determine (on round start)
-------if a new great person is born
-------if is, what is the UnitType
-----------fetch a name from UniqueName namepool with associated UnitType AND the requirement of CIVILIZATION_FINNS
-----------withdraw that name from the pool until expended
----determine (during the round)
-------if great person was expended
-------if was, return the name in the UniqueName namepool
-------if great person is granted/born during round
-------if is, apply same logic as when it would be born on round start
--------------------------------------------------------------

--------------------------------------------------------------
--*check (on game load)  if CIVILIZATION_FINNS exists in the table Civilizations in the first place. If so, then
----check if persistant property has either true, if so
----end
----If not, then
----check (on game load) if that civ is in the current game
------check if the civ is AI. If so, then
--------SetPersistanProperty("FinnInGameAI", true)
--------If it isn't AI, then
--------check if the civ is player. If so, then
----------SetPersistantProperty("FinnInGamePlayer"), true)
--if not, then just end (though this mod probably wouldn't be activated unless it existed :^D)
--------------------------------------------------------------


I was wondering if it was possible to make the great person born event a trigger instead of checking if there's a great person during round start and etc.

There probably is a way to do that instead, I'd think that'd save some resources and make it more light actually.
 
Last edited:
Hmm. Actually following LeeS's logic, maybe the name change could be done through a dummy promotion. Give every great person born the dummy promotion, and if the promotion itself could be made to change the unit name. I'd imagine this should be possible, since LeeS used a dummy promotion for something at least.
So maybe if there exists an event for an unit being spawned, it could then check if the unit
  • IsUnitClass, bool, iUnitClassType (this is from whoward's API bits for his DLL, isn't that incorporated in the CP?)
Then just make a string of OR's for every great person UnitClassType. Thought since I'm such a noob with these things, I have no idea if the "i" in the beginning of UnitClassType means integer or ID. Or if it's more complex than that. Either way, it'd probably refer to the UnitClassType ID anyway, right? So it would be rather easy to just check if in the event of a unit being spawned, if it is a great person and then gives it the promotion. If there's a way to do it, that is :D

The whole concept is based on the assumption that there's no other easy way to change unit's name, other than making it a part of a promotion. I'm not even sure if that's possible/easy, but seeing LeeS used that method, I'm leaning towards there being a reason for him using that.

EDIT:

Uhh, I'm not sure if anyone can relate to the feeling of frustration you get when you know exactly what you need to do but don't have any means to do it. Nor any immediate-future prospects of gaining the means.

This is definitely something someone could assist me on. If anyone's noticing this thread and also extremely generous in his/her distribution of time and effort, that is. :smoke:


EDIT2:

I'm thinking I'll just try to get the first thing done first, then think about the further complications.

Spoiler :
Code:
--------------------------------------------------------------
--*check (on game load)  if CIVILIZATION_FINNS exists in the table Civilizations in the first place. If so, then
----check if persistant property has either true, if so
----end
----If not, then
----check (on game load) if that civ is in the current game
------check if the civ is AI. If so, then
--------SetPersistanProperty("FinnInGameAI", true)
--------If it isn't AI, then
--------check if the civ is player. If so, then
----------SetPersistantProperty("FinnInGamePlayer"), true)
--if not, then just end (though this mod probably wouldn't be activated unless it existed :^D)
--------------------------------------------------------------

So I'll be trying to convert this kind of logic to actual strip of code. Let's see how this goes.

EDIT3:

Yeah, for starters, I've definitely completely misunderstood how the saveutils work. That's definitely not the way they should be used, is it :D I'm also starting to think I don't really need to store that data, since it's probably rather simple to check. I'll just make the FinnInGamePlayer to be the same as the little strip of code it requires to check it, and writing the true isn't really going to be that much waste of space.

As you can all now publicly see, I'm completely lost with this whole coding thing. Feeling rather embarassed.
 
Last edited:
Okay, so as it turns out, the whole planning bit is utterly useless. I need to understand how things work before I can even plan things. So, I'll just start from the beginning and start adding things as I go.

It probably still has to start with a check if the civilization is in the game. Otherwise I suspect it'll load the scripts because it isn't told not to, and then it'll get super sad when it can't find the CIVILIZATION_FINNS it needs for the changes from the gameinfo.

So, I guess we'll start from there. I took this bit from Hypereon's civ's function file, because it already has to check if the civ is in the game. Sorry if this isn't something people accept around here, but why write again something that is already written? On top of that, I'd definitely mess it completely and it'd take ages for me to figure out how to do it. This way, I can shave off tons of time and just focus on failing on the other bits.

Code:
local civilizationID = GameInfoTypes["CIVILIZATION_FINNS"]

function Hyp_IsCivilizationActiveAI(civilizationID)  --Thanks JFD
    for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
        local slotStatus = PreGame.GetSlotStatus(iSlot)
        if (slotStatus == SlotStatus["SS_COMPUTER"]) then
            if PreGame.GetCivilization(iSlot) == civilizationID then
                return true
            end
        end
    end

    return false
end

if Hyp_IsCivilizationActivePlayer(civilizationID) then
end

function Hyp_IsCivilizationActivePlayer(civilizationID)  --Thanks JFD
    for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
        local slotStatus = PreGame.GetSlotStatus(iSlot)
        if (slotStatus == SlotStatus["SS_TAKEN"] then
            if PreGame.GetCivilization(iSlot) == civilizationID then
                return true
            end
        end
    end

    return false
end

if Hyp_IsCivilizationActive(civilizationID) then
end

I'm still unsure if those two really need to be seperated, but I'll leave it like that until I know for sure that any of the parts of this bit don't need to use the extra info. I'll just combine them with a "or" if that's the case. Also, seems like JFD helped out Hypereon as well with this bit. Eventually, not sure if I need to credit both of them if I ever end up sharing this modmod, I don't know the standards of this forum. Hopefully I'm not offending anyone by just taking this part right in.


EDIT:

Allright, after some extensive thinking and frustration I've come up with this small bit that I finally feel like might be what I'm looking for.

Code:
local UnitType = unit:GetUnitType();

Events.SerialEventUnitCreated.Add(
    if(UnitType == IsUnitClass(true, 51); then -- Artist
        --give promotion etc

This makes use of the mentioned whoward's API, and I've obviously never used an API shortening before, so not sure at all if that is formatted correctly. Also the UnitType definition, I wasn't sure at all if it was just GetUnitType(); or with the unit: part, but I then searched through a bit of the gameasset's lua files and in almost all cases it had the unit: prefix, so even though I have no idea what it does, I decided to add it in just in case :D Oh the joy of doing things you have no idea about. Well, at least I'm trying. Anyway, just from the context I think it defines where the GetUnitType gets the actual unittype from. In my head I sorted it so that the unit: prefix makes it get the unittype of the unit in question. Which would be what I want seeing as it's inside the UnitCreated event. Also that event line is a result of some very quick searches on the game's included API. I'm not sure if SerialEvent is what I need here, but it was the only EventUnitCreated or similiar I was able to find.
 
Last edited:
Okay, I'm gonna be very honest here. This feels extremely embarrassing. I'm very tempted to remove every post from this thread, but I find it imperative to stay with the original purpose and reason for this thread.

Anyway, I guess I'm getting somewhere. I have no idea if it's going to work, but at least it looks like a strip of code. The problem with not understanding it beforehand really makes it all the more frightening. The idea is to learn as I go, so I hope nobody judges me for just searching APIs and randomly copypasting them in a order I think is right, with calls in the middle that I think are right.

Here's where we are now:
Code:
local UnitType = unit:GetUnitType();
local SetUnitName = Unit:Setname();

Events.SerialEventUnitCreated.Add(
    if(UnitType == IsUnitClass(true, 51); then -- Artist
        SetUnitName(--here we'd probably need to run a function that goes and fetches a random name from the Unit_UniqueNames table from column
                    --UnitName AND when picking a random name, only include names in the pool that have CivilizationType CIVILIZATION_FINNS

As is probably obvious, I'm first trying to do it without the dummy promotions. I'm still feeling LeeS used them for a reason, so I'm not all that hopeful that this approach works, but I'm just kind of flowing and trying to feel it. Set all presumptions aside and work it out.
 
1. There's a "Modiquete" thread somewhere that sets out guidelines for crediting, but stealing code is pretty standard practice so don't worry about that :p

2. WHoward has a thread in the SDK/Lua subforum that has spreadsheets with every Lua method and GameEvent we can use; this gives you a pretty good idea of what you've got.

The GameEvent UnitSetXY fires whenever the x or y co-ordinates of a unit changes; this includes when the unit is placed on the map. As such, you can use this event to tell when a Great Person has been born. Vague code (from memory - don't expect perfection):

Code:
local iPromotionUnitBorn = GameInfoTypes["PROMOTION_C15_UNIT_BORN"]
function C15_GPBorn  (playerID, unitID, iX, iY)
    local pPlayer = Players [playerID]
    If pPlayer:GetCivilizationType () == civilizationID then
        local pUnit = pPlayer:GetUnitByID(unitID)
        if (pUnit:GetUnitClassType() == GameInfoTypes["UNITCLASS_SCIENTIST"] or pUnit:GetUnitClassType() == GameInfoTypes["UNITCLASS_ENGINEER"] or pUnit:GetUnitClassType() == GameInfoTypes["UNITCLASS_MERCHANT"]) and not pUnit:IsHasPromotion(iPromotionUnitBorn) then
            pUnit:SetName (However LeeS gets his names)
            pUnit:SetHasPromotion(iPromotionUnitBorn)
        end
    end
end

How nice of the forum to tell me I've been ninja'd :p
 
Last edited:
Okay, I'm gonna be very honest here. This feels extremely embarrassing. I'm very tempted to remove every post from this thread, but I find it imperative to stay with the original purpose and reason for this thread.

Anyway, I guess I'm getting somewhere. I have no idea if it's going to work, but at least it looks like a strip of code. The problem with not understanding it beforehand really makes it all the more frightening. The idea is to learn as I go, so I hope nobody judges me for just searching APIs and randomly copypasting them in a order I think is right, with calls in the middle that I think are right.

Here's where we are now:
Code:
local UnitType = unit:GetUnitType();
local SetUnitName = Unit:Setname();

Events.SerialEventUnitCreated.Add(
    if(UnitType == IsUnitClass(true, 51); then -- Artist
        SetUnitName(--here we'd probably need to run a function that goes and fetches a random name from the Unit_UniqueNames table from column
                    --UnitName AND when picking a random name, only include names in the pool that have CivilizationType CIVILIZATION_FINNS

As is probably obvious, I'm first trying to do it without the dummy promotions. I'm still feeling LeeS used them for a reason, so I'm not all that hopeful that this approach works, but I'm just kind of flowing and trying to feel it. Set all presumptions aside and work it out.

Generally I'd say it's bad practice to make the mod require a modded DLL, especially when it shouldn't be necessary. If you do use a modded DLL, then I'm pretty sure that the CP has a GreatPersonBorn event anyway :p

Events.SerialEventUnitCreated is a bad event, because it fires at times you wouldn't expect and whatnot. I'd recommend taking similar precautions to what I did with the dummy promotion, to make the code only run if the unit didn't have the promotion (which I was meant to then give to the unit - dammit)

Changing GWAM would require deleting and replacing the unit afaik (although the GW is bound to the Unit_UniqueName...) so you'll probably have to delete and replace the unit until you get a valid one - aka not particularly viable :/
 
Promotions have nothing to do with naming the unit. Promotions added by any of my mods to great people are merely markers to indicate to the code that the unit has already been processed, or to give the great people specialized abilties (in the case of Scipio's Rome).

In Civilization-Appropriate Great General & Admiral Names
Code:
pUnit:SetName(sUnitName)
sets the unit's in-game name by sending the TXT_KEY_SOMETHING tag name for the desired Great Person Name to the Unit's "Name" that is displayed in the unit-panel. The game then translates the TXT_KEY_SOMETHING to the correct actual text that will be shown in-game based on the player's selected language. Selection of the correct TX_KEY_SOMETHING to use for a particular unit is done previously in the code.

This line of code updates the in-game mouse-over tooltip for the unit's name.
Code:
Events.UnitNameChanged( pUnit:GetOwner(), pUnit:GetID() );

Using Machiavelli25's excellent SerialEventUnitCreatedGood gets around all the issues with the game "re-creating" the same unit over and over again. It is essentially the same thing @Chrisy15 does with UnitSetXY but the SerialEventUnitCreatedGood fires far less often as a game engine event than does UnitSetXY. UnitSetXY fires for every move of every unit ever.

You cannot rename a Great Writer, for example, and have the Great Writer's correct Great Work follow along for the new name: you get a Great Writer with a new Great Person Name but the original Great Work correct for the original Great Writer is still applied to this renamed Great Writer. It requires a custom DLL in order to make the new Great Person Name given the Great Writer to cause the Great Work assigned to the unit to be altered to match the new name.
 
1. There's a "Modiquete" thread somewhere that sets out guidelines for crediting, but stealing code is pretty standard practice so don't worry about that :p

Yes, well the IsCivilizationActive function was actually made by Whoward, so :p
 
Pretty sure, one of BNW's patch was made by Whoward entirely in GameEvents and Firaxis just took it and released it as their own.

On another note, I probably haven't thoroughly read it, but why so much code for one simple aesthetic change?
 
1. There's a "Modiquete" thread somewhere that sets out guidelines for crediting, but stealing code is pretty standard practice so don't worry about that :p

2. WHoward has a thread in the SDK/Lua subforum that has spreadsheets with every Lua method and GameEvent we can use; this gives you a pretty good idea of what you've got.

The GameEvent UnitSetXY fires whenever the x or y co-ordinates of a unit changes; this includes when the unit is placed on the map. As such, you can use this event to tell when a Great Person has been born. Vague code (from memory - don't expect perfection):

Code:
local iPromotionUnitBorn = GameInfoTypes["PROMOTION_C15_UNIT_BORN"]
function C15_GPBorn  (playerID, unitID, iX, iY)
local pPlayer = Players [playerID]
If pPlayer:GetCivilizationType () == civilizationID then
local pUnit = pPlayer:GetUnitByID(unitID)
if (pUnit:GetUnitClassType() == GameInfoTypes["UNITCLASS_SCIENTIST"] or pUnit:GetUnitClassType() == GameInfoTypes["UNITCLASS_ENGINEER"] or pUnit:GetUnitClassType() == GameInfoTypes["UNITCLASS_MERCHANT"]) and not pUnit:IsHasPromotion(iPromotionUnitBorn) then
pUnit:SetName (However LeeS gets his names)
pUnit:SetHasPromotion(iPromotionUnitBorn)
end
end

How nice of the forum to tell me I've been ninja'd :p

I looked the modiquette up and I think I get it now. It makes sense.

I also went to look up whoward's list, but then on another thread I stumbled upon some .xml files that assumably hold similiar lists. Not sure if those actually are the lists you were referring to, but I'll go and eye it around for a while to see what exactly can be done (and be amazed, probably).

I'm trying to phrase that bit of code in my head and I'm having a hard time putting the two and two together. I'll get back to this after I reply to the other posts.

Generally I'd say it's bad practice to make the mod require a modded DLL, especially when it shouldn't be necessary. If you do use a modded DLL, then I'm pretty sure that the CP has a GreatPersonBorn event anyway :p

Events.SerialEventUnitCreated is a bad event, because it fires at times you wouldn't expect and whatnot. I'd recommend taking similar precautions to what I did with the dummy promotion, to make the code only run if the unit didn't have the promotion (which I was meant to then give to the unit - dammit)

Changing GWAM would require deleting and replacing the unit afaik (although the GW is bound to the Unit_UniqueName...) so you'll probably have to delete and replace the unit until you get a valid one - aka not particularly viable :/

Oh right, actually, that'd make it more compatible all around. I was initially thinking in terms of my own gameplay, since at first I was just doing this thing for my own use and I use CP. Having a hard time shaking off that intuition!

Actually I just now got the bit of code you gave one post earlier, so nevermind that. Took me a while to understand that the promotion is there to differiate it from the random triggerings you mentioned. (EDIT: I'll actually try and see the bit from machiavelli instead of using this method, but thanks for the pointer anyway, I'd have never realized something like that would have been needed!)

Promotions have nothing to do with naming the unit. Promotions added by any of my mods to great people are merely markers to indicate to the code that the unit has already been processed, or to give the great people specialized abilties (in the case of Scipio's Rome).

In Civilization-Appropriate Great General & Admiral Names
Code:
pUnit:SetName(sUnitName)
sets the unit's in-game name by sending the TXT_KEY_SOMETHING tag name for the desired Great Person Name to the Unit's "Name" that is displayed in the unit-panel. The game then translates the TXT_KEY_SOMETHING to the correct actual text that will be shown in-game based on the player's selected language. Selection of the correct TX_KEY_SOMETHING to use for a particular unit is done previously in the code.

This line of code updates the in-game mouse-over tooltip for the unit's name.
Code:
Events.UnitNameChanged( pUnit:GetOwner(), pUnit:GetID() );

Using Machiavelli25's excellent SerialEventUnitCreatedGood gets around all the issues with the game "re-creating" the same unit over and over again. It is essentially the same thing @Chrisy15 does with UnitSetXY but the SerialEventUnitCreatedGood fires far less often as a game engine event than does UnitSetXY. UnitSetXY fires for every move of every unit ever.

You cannot rename a Great Writer, for example, and have the Great Writer's correct Great Work follow along for the new name: you get a Great Writer with a new Great Person Name but the original Great Work correct for the original Great Writer is still applied to this renamed Great Writer. It requires a custom DLL in order to make the new Great Person Name given the Great Writer to cause the Great Work assigned to the unit to be altered to match the new name.

Uh oh, I definitely misunderstood how the mod works then. To be fair though, I had (and still have) no idea what I'm doing, so initially when I checked the mod's folders I just considered how many folders and files there were and focused on trying to first figure out how the TSL was set up. I gave up with trying to integrate it after I just simply wasn't able to make sense of the TSL files, so the only thing I really registered in my mind was the fact that there was a file with a dummy promotion in it. Now that I think of it, it's pretty unexplainable why my intuition told me it was for naming the units :D Curious is the mind of a human.

I'll try and give your mod another look, see how you made use of machiavelli's bit, if it's okay with you. For some reason, I'm now feeling slightly ashamed, or "naked" if you will, being so helpless and just going about stealing other peoples' lines and helps. Oh well, everyone starts somewhere, don't they?

Also, the great works is a shame, I read it when you mentioned it. Seems like I misunderstood the whole concept yet again - I wasn't thinking about it in the sense that the GP born was merely renamed after it was born, I was for some reason mistaking the process to actually having a individual great person born. Now that I put it in words, it sounds funny, but such is the way the less knowledgeable mind thinks. Could there be some weird alternative way to handle the GW moving? I mean if it was easy, it would have been created by now, but just asking for the off chance.

Overall, it's going to take a long while before I really get the hang of things with lua. But my most heartfelt thanks to each of you jumping in to help me or correct me! This community is weirdly nostalgic in the sense that the whole amazing show that runs around here (and I've only taken part in or followed the modding side of this forum) greatly resembles the way online communities used to be in the early 2000's. I say used to be, because every other forum so far I've joined in has had either a huge lack of activity or very divided userbase to the extent that it gets hostile. Here I haven't witnessed either, so that's really amazing from every single one of you!

But I'll try and continue this journey amongst online manuals for a few seconds here, it's evident that this really requires at least some kind of understanding on how coding works.
 
Last edited:
WH's Excel Sheets (think they're in the attachment at the bottom; swear the thread looks different to how it used to...)
 
Code:
local iPromotionUnitBorn = GameInfoTypes["PROMOTION_C15_UNIT_BORN"]
local civilizationID = GameInfoTypes["CIVILIZATION_FINNS"]
local tGreatPeopleClassesToProcess = {[GameInfoTypes["UNITCLASS_SCIENTIST"]] = true,
	[GameInfoTypes["UNITCLASS_ENGINEER"]] = true,
	[GameInfoTypes["UNITCLASS_MERCHANT"]] = true}

-- JFD_IsCivilisationActive
function JFD_IsCivilisationActive(civilisationID)
	for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
		local slotStatus = PreGame.GetSlotStatus(iSlot)
		if (slotStatus == SlotStatus["SS_TAKEN"] or slotStatus == SlotStatus["SS_COMPUTER"]) then
			if PreGame.GetCivilization(iSlot) == civilisationID then
				return true
			end
		end
	end
	return false
end

-- JFD_GetRandom
function JFD_GetRandom(lower, upper)
    return Game.Rand((upper + 1) - lower, "") + lower
end

function GetNameForGreatPerson(pUnit)
	if pUnit ~= nil then
		local sUnitType = GameInfo.Units[pUnit:GetUnitType()].Type
		local tCorrectUnitNames = {}
		for row in GameInfo.Unit_UniqueNames("UnitType='" .. sUnitType .. "'") do
			table.insert(tCorrectUnitNames, row.UniqueName)
		end
		if #tCorrectUnitNames > 0 then
			local sSelection = tCorrectUnitNames[1]
			if #tCorrectUnitNames > 1 then
				sSelection = tCorrectUnitNames[JFD_GetRandom(1, #tCorrectUnitNames)]
			end
			return sSelection
		end
	end
	return "Georgie"
end
function C15_GPBorn(playerID, unitID, iX, iY)
	local pPlayer = Players [playerID]
	if pPlayer:GetCivilizationType() == civilizationID then
		local pUnit = pPlayer:GetUnitByID(unitID)
		if pUnit ~= nil then
			if tGreatPeopleClassesToProcess[pUnit:GetUnitClassType()] and not pUnit:IsHasPromotion(iPromotionUnitBorn) then
				local sSelectedName = GetNameForGreatPerson(pUnit)
				pUnit:SetName(sSelectedName)
				Events.UnitNameChanged( pUnit:GetOwner(), pUnit:GetID() );
				pUnit:SetHasPromotion(iPromotionUnitBorn)
			end
		end
	end
end
if JFD_IsCivilisationActive(civilizationID) then
	GameEvents.UnitSetXY.Add(C15_GPBorn)
end
But all you'd be doing is replacing one randomly-generated unit name for another, which the game already handles for Great People so long as there are valid entries in the Unit_UniqueNames xml-table for the correct sort of UNIT_SOMETHING.

Or you'd be renaming everybody for the Finns to "Georgie" if they are a Scientist, Engineer, or Merchant, and the function called GetNameForGreatPerson cannot find any valid unique names. Note that the way this function is written it does not look to see if there is already a unit named "Madame Currie", for example, for a Great Scientist. Nor does it track those units that have been already born and expended.

Spoiler :
it feels like such a vacation to whomp up some sample code for a game that has everything working and implemented
 
Last edited:
Code:
local iPromotionUnitBorn = GameInfoTypes["PROMOTION_C15_UNIT_BORN"]
local civilizationID = GameInfoTypes["CIVILIZATION_FINNS"]
local tGreatPeopleClassesToProcess = {[GameInfoTypes["UNITCLASS_SCIENTIST"]] = true,
    [GameInfoTypes["UNITCLASS_ENGINEER"]] = true,
    [GameInfoTypes["UNITCLASS_MERCHANT"]] = true}

-- JFD_IsCivilisationActive
function JFD_IsCivilisationActive(civilisationID)
    for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
        local slotStatus = PreGame.GetSlotStatus(iSlot)
        if (slotStatus == SlotStatus["SS_TAKEN"] or slotStatus == SlotStatus["SS_COMPUTER"]) then
            if PreGame.GetCivilization(iSlot) == civilisationID then
                return true
            end
        end
    end
    return false
end

-- JFD_GetRandom
function JFD_GetRandom(lower, upper)
    return Game.Rand((upper + 1) - lower, "") + lower
end

function GetNameForGreatPerson(pUnit)
    if pUnit ~= nil then
        local sUnitType = GameInfo.Units[pUnit:GetUnitType()].Type
        local tCorrectUnitNames = {}
        for row in GameInfo.Unit_UniqueNames("UnitType='" .. sUnitType .. "'") do
            table.insert(tCorrectUnitNames, row.UniqueName)
        end
        if #tCorrectUnitNames > 0 then
            local sSelection = tCorrectUnitNames[1]
            if #tCorrectUnitNames > 1 then
                sSelection = tCorrectUnitNames[JFD_GetRandom(1, #tCorrectUnitNames)]
            end
            return sSelection
        end
    end
    return "Georgie"
end
function C15_GPBorn(playerID, unitID, iX, iY)
    local pPlayer = Players [playerID]
    if pPlayer:GetCivilizationType() == civilizationID then
        local pUnit = pPlayer:GetUnitByID(unitID)
        if pUnit ~= nil then
            if tGreatPeopleClassesToProcess[pUnit:GetUnitClassType()] and not pUnit:IsHasPromotion(iPromotionUnitBorn) then
                local sSelectedName = GetNameForGreatPerson(pUnit)
                pUnit:SetName(sSelectedName)
                Events.UnitNameChanged( pUnit:GetOwner(), pUnit:GetID() );
                pUnit:SetHasPromotion(iPromotionUnitBorn)
            end
        end
    end
end
if JFD_IsCivilisationActive(civilizationID)
    GameEvents.UnitSetXY.Add(C15_GPBorn)
end
But all you'd be doing is replacing one randomly-generated unit name for another, which the game already handles for Great People so long as there are valid entries in the Unit_UniqueNames xml-table for the correct sort of UNIT_SOMETHING.

Or you'd be renaming everybody for the Finns to "Georgie" if they are a Scientist, Engineer, or Merchant, and the function called GetNameForGreatPerson cannot find any valid unique names. Note that the way this function is written it does not look to see if there is already a unit named "Madame Currie", for example, for a Great Scientist. Nor does it track those units that have been already born and expended.

Spoiler :
it feels like such a vacation to whomp up some sample code for a game that has everything working and implemented

Wow, you wrote all that in this time? I mean, it took me near 40 minutes to just figure out the ~5 lines I mentioned a few posts back :D

As a learning experience I'm going to take that bit and add comments to every line to explain what I think it does, maybe thinking about each line with focus and then putting it in words helps a bit with the learning. It'd be much appreciated if you (or someone else who happens about) could check it out at some point and see if I'm getting things right, just to break wrong thinking processes so that they don't develop. If I've learned something during this yet so short life, it's that learning out of bad/wrong habits is near-impossible. So I'd love to get it right with this in the first try.

Anyway, here's the things:
Spoiler :


Code:
local iPromotionUnitBorn = GameInfoTypes["PROMOTION_C15_UNIT_BORN"]
-----------------this defines what iPromotionUnitBorn is, right? local makes it so that it's only recognized within this file? the definition just refers to PROMOTION_C15_UNIT_BORN in GameInfoTypes

local civilizationID = GameInfoTypes["CIVILIZATION_FINNS"]
-----------------same thing, right? for these definitions to work, the part inside [] needs to be in the GameInfoTypes, right? Is this the kind of thing that doesn't break the thing eventhough those are missing? e.g it doesn't infinitely search for it in GameInfoTypes, or make a loop or something?

local tGreatPeopleClassesToProcess = {[GameInfoTypes["UNITCLASS_SCIENTIST"]] = true,
    [GameInfoTypes["UNITCLASS_ENGINEER"]] = true,
    [GameInfoTypes["UNITCLASS_MERCHANT"]] = true}
-----------------This does the same thing, but makes it a hash, right? This makes them all "the same", or equal, under tGreatPeopleClassesToProcess. Would it have the same effect to do the same with "and" in between of the GameInfoTypes without the brackets?

-- JFD_IsCivilisationActive
function JFD_IsCivilisationActive(civilisationID)
-----------------starts the function bit. I do not know what the part inside () actually does in general sense in lua language, but I'm guessing that it does the whole JFD_IsCivilisationActive code with civilisationID as the object of the question "is civ active".

    for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
    -----------------no idea what "i" here represents. ID? It takes slot 0, major civ max slot amount -1, and slot 1, the "for" makes the next iSlot reference to be this list? Like a very extremely local define? If the next bit only had iSlot without this line, would it then get the slot status to all of the available slots?
 
        local slotStatus = PreGame.GetSlotStatus(iSlot)
        -----------------defines slotStatus to be PreGame.GetSlotStatus(the earlier defined iSlot). I guess the pregame means that it assumes that during the actual game there could be changes, and specifically only gets the status on the starting slots? GetSlotStatus presumably checks whether the slot is occupied by AI or human?
    
        if (slotStatus == SlotStatus["SS_TAKEN"] or slotStatus == SlotStatus["SS_COMPUTER"]) then
        -----------------only does the next bit if the slotstatus has "SS_TAKEN" or "SS_COMPUTER" as its value, I think
    
            if PreGame.GetCivilization(iSlot) == civilisationID then
            -----------------only goes forward to the next bit if either slot 0, GameDefines.MAX_MAJOR_CIVS-1 or 1 is CIVILIZATION_FINNS. Still a little bit unknown to me why these slots are checked specifically. I'm thinking I must not understand what the iSlot thing does above (the "for" part) and I'm just simply lost here
    
                return true
                ------------------the whole function returns true
            
            end
        end
    end
    return false
    -----------------if the requirements for the if statements don't go through, this function returns false
 
end

-- JFD_GetRandom
function JFD_GetRandom(lower, upper)
    return Game.Rand((upper + 1) - lower, "") + lower
end
-----------------Presumably this just a RNG?

function GetNameForGreatPerson(pUnit)
-----------------starts the function called GetNameForGreatPerson. I'm a little unsure what the () part means, but it's pUnit. I don't know what the "p" in this means. Or why it's there.

    if pUnit ~= nil then
    -----------------if pUnit returns no value, then the function moves onward
 
        local sUnitType = GameInfo.Units[pUnit:GetUnitType()].Type
        -----------------defines sUnitType to be whatever's pUnit's type
    
        local tCorrectUnitNames = {}
        -----------------I'm guessing this should reference to the table where the appropriate names are
    
        for row in GameInfo.Unit_UniqueNames("UnitType='" .. sUnitType .. "'") do
        -----------------This prepares for the next action in the function. I'm guessing it finds the line with pUnit's type in it from the table Unit_UniqueNames
    
            table.insert(tCorrectUnitNames, row.UniqueName)
            -----------------Inserts stuff from the defined correct names table row UniqueName into spot defined in earlier line
        
        end
    
        if #tCorrectUnitNames > 0 then
        -----------------only continues if there actually are names in the appropriate name list
    
            local sSelection = tCorrectUnitNames[1]
            -----------------I'm guessing the [1] means it only gets one in the line that's about to come
        
            if #tCorrectUnitNames > 1 then
            -----------------only continues if there's more than one in the list
        
                sSelection = tCorrectUnitNames[JFD_GetRandom(1, #tCorrectUnitNames)]
                -----------------finds the one name from the name list using the RNG. The () part specifies that it randomizes everything between 1 and the total amount of the names
            
            end
            return sSelection
            -----------------if there only was one name, it returns that as the value
        
        end
    end
    return "Georgie"
    -----------------if there were no names, it gives Georgie (btw, at this point I'm starting to feel very good for not giving up; I think I'm starting to get this a little bit!)
 
end
function C15_GPBorn(playerID, unitID, iX, iY)
-----------------I'm still struggling to understand what the things between () describe. There was talking in the thread about the X and Y coordinates, so I'm guessing from that context it has something to do with changing states (changing the coordinates) or something. I hope that someone is reading this and willing to explain this a bit to me. Although I'll be spending a lot of time reading online manuals durin the upcoming months, so eventually I'll get it anyway.

    local pPlayer = Players [playerID]
    -----------------basically just ensures that the player referred to in the future is the one in the players list?
 
    if pPlayer:GetCivilizationType() == civilizationID then
    -----------------only continues if the player is CIVILIZATION_FINNS
 
        local pUnit = pPlayer:GetUnitByID(unitID)
        -----------------makes pUnit to be a UnitID, with GetUnitByID (which is assumably a API thing, not entirely sure what it does though)
    
        if pUnit ~= nil then
        -----------------if pUnit returns no value at this point, continue
    
            if tGreatPeopleClassesToProcess[pUnit:GetUnitClassType()] and not pUnit:IsHasPromotion(iPromotionUnitBorn) then
            -----------------uses the definition in the start of this script. checks if pUnit is one of those types, and doesn't have the dummy promotion. If conditions are met, continues
        
                local sSelectedName = GetNameForGreatPerson(pUnit)
                -----------------defines sSelectedName to be the value from the earlier function
            
                pUnit:SetName(sSelectedName)
                -----------------applies the selected name to the pUnit
            
                Events.UnitNameChanged( pUnit:GetOwner(), pUnit:GetID() );
                -----------------basically just fires the event of name change so that other scripts can use that? this is just a wild guess.
            
                pUnit:SetHasPromotion(iPromotionUnitBorn)
                -----------------after name change, gives the unit the dummy promotion so that the unit's excluded from being renamed again when moving (though this only came up in the thread, I wouldn't understand the moving part just from this code alone, the coordinates are only mentioned inside the () in the C15_GPBorn function, and I don't know what effect the () has)
            end
        end
    end
end
if JFD_IsCivilisationActive(civilizationID)
-----------------this basically just adds the condition that CIVILIZATION_FINNS needs to be in the game, uses the function above
    GameEvents.UnitSetXY.Add(C15_GPBorn)
    -----------------this I assume is the thing that fires the whole thing? if a unit sets X and Y (moves, is spawned etc), it fires the C15_GPBorn function (which then fires the GetNameForGreatPerson function). I'm thinking that the "Add" tells the whole script to run the function defined inside (). If there was no "Add", it would then tell the system that such event happened? I could be wrong here, but either way this doesn't look to work the same way as the earlier Events. line. So I'm wrong on one of them, and I don't know which one. But I'm assuming this really is the trigger for the whole thing, so I think I got the Events.UnitNameChanged wrong?
end


The above bit is definitely a very boring read, and I expect no one to waste their time in reading it and helping me. Though if you or someone does, that'd be very amazing!

I started figuring it out immediately after your post, so it definitely did take a long time for me to figure out even such a relatively short bit. Some 2 hours I think. Granted, I wouldn't have made it at all without "some" coffee and "a few" smokes :smoke:

I'm definitely not good at this.

EDIT: I'm taking it that the spoiler bit is a reference to CIV VI :D?
 
Allright, today's been a busy day and I didn't get anything done in regards of lua. On top of that, I'm still very much struggling with understanding it. This is going to take a long time for me it seems.

Anyhow, the DOM narrative is now done for Hyperion's Finland, and I'll include it in the modmod tomorrow. It's pretty neat, delivered by the brilliant Michael Harris / forevertomorrow. Eventhough it's only the DOM narrative, this definitely sends the cold shivers run through my spine every time (sorry if this idiom doesn't translate that well into english) I hear it. It's going to make playing Mannerheim that much more amazing!

Anyhow, I'll include all the credits in detail once I get around to do all the actually hard things I need done. Which might take forever. But I'll be adding a bunch of DOMs with Michael's amazing professionalism into the mix once I know which modcivs will be included in this immersion package. Which really means, which civmakers don't get offended by me making alterations here and there and adding a few things :D

EDIT:

By the way, if you or someone you know is doing a civ and needs a DOM narrative, I definitely recommend Michael's services. He has a fiverr gig I believe, which is incredibly low priced compared to the quality he provides. :thumbsup: https://www.fiverr.com/forevertomor...e&funnel=abd5ebfb-a711-4e17-9535-ca2ebc389dca

Here's a sample of his great voice:
 
Last edited:
Hmm. I've been doing some reading on how especially the functions work, and I think I'm starting to get it.

Code:
function(something, something, iX, iY)

The mystery to me was, what those things inside the brackets mean or do.

Well, I think I got it now: Those are variables that will be specified every time the function is called, right?

For example, in the bit LeeS and chrisy15 provided, the part

Code:
   GameEvents.UnitSetXY.Add(C15_GPBorn)

Actually presumably adds these variables it needs from the game event.

Code:
function C15_GPBorn(playerID, unitID, iX, iY)

So every time a gamevent UnitSetXY happens, it adds the variables into the function. That's why the function does all the stuff with playerID and unitID; I'm guessing this fires, as LeeS said earlier, every time the game event happens. So it checks if playerID is the civilizationID defined earlier in the code, and if unitID is the one also defined earlier (the great people). What the function itself doesn't alter, are the coordinates, but I guess they are included because the game event has those variables and that's the only way to make the function work properly?

I mean, it sounds so obvious (if I got it right here in the first place) that I feel extremely stupid for not understanding this earlier. So every time the function is called, it needs values for each of these variables, and that's basically what the bracket bit is there for. In a sense, it "feeds" the values to the function that change in every different occasion the function is ran.

It does feel rather exciting to begin to understand these things a little better. I'm full aware I might still have gotten it wrong here, but at least I think this makes sense. I believe these mumblings must sound so weird to someone who knows how the code works, but for me, this is a big step forward.

With this understanding alone, I think I can soon start to move and actually continue working with the modmod.


EDIT:

Oh how pleasing 'tis to educate oneself indeed.

So I think I've got the hang of local statements as well. They are always attached to the chunk they're written in, so a local define inside a function or an if statement is always local to that, and don't matter anywhere else. If a local define is done at the start of the code, it's local to the whole chunk of that code, the whole file in this case.

At least I think I got it :D This is truly an adventure to me. So much new stuff.


EDIT2:

Aight, piecing more stuff together. In for loops the x1, x2, 1 means the starting point, the ending point and the increment. Meaning that for example this bit

Code:
for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do

Instead of what I originally thought to be 0 AND x AND 1, it's actually starting with 0 and going all the way to MAX_MAJOR_CIVS-1 and in increments of one.

Feeling extremely stupid right now.
 
Last edited:
There's something that the general lua guides and tutorials don't touch on, and that's the game-specific tinkering with the database.

I see that in every lua script so far the method of doing that is by using something called GameInfo. I searched through the files and couldn't find a table or a database with that name, however, I did find a folder with that name in both game's core files and in extensions core files.

What I'm struggling with, is how that works really. I see lines like

Code:
        local tCorrectUnitNames = {}
        for row in GameInfo.Unit_UniqueNames("UnitType='" .. sUnitType .. "'") do
            table.insert(tCorrectUnitNames, row.UniqueName)
        end

I now understand that it collects the names as an table, but eventhough I can use and understand this bit just by understanding that and how the for loop works, I still lack understanding on how to use that GameInfo reference. I'd like to learn how to use that, so I can do my own stuff. I know for a fact that I could just imitate how others use it, but I think it's better that I actually learn what it does and how it works to really understand it.

Initially, I'm thinking it's a path of a kind, with dots seperating the points in the path, but I can't really figure out how it works since there's nothing called GameInfo I could find that would help me understand it.

Anyone? :sad:
 
You've got the right idea with functions - good job there

GameInfo isn't a table; it's a tool that's used to select a table. The table is chosen afterwards: GameInfo.Units, for example. To iterate through the table, you then use

Code:
for row in GameInfo.Table () do

"row" is now the row of the table; using row.Column will get you the data for that column's square in the table.

If you ever just want to get the ID of a row (which is what you use to compare with functions like GetUnitType()), then you can usually instead use GameInfoTypes["UNIT_X"].

Now we get to stuff that I know less about...

table.insert looks to be used to add a value into a square; the first value is the name, and the second is the square you add it to (row.column).

...I'll go look at the examples I've got on my PC. I need to stop doing tech support on my phone...
 
I'm gonna cheat and steal people's functions to explain this :p

Code:
function GetStrongestMilitaryUnit(pPlayer, bIgnoreResources, ...) -- Sukri
    local tUnit = {["ID"] = GameInfoTypes.UNIT_WARRIOR, ["Combat"] = -1} -- 1
    for iKey, sCombatType in pairs({...}) do -- 2
        for row in GameInfo.Units("CombatClass = \'" .. sCombatType .. "\'") do -- 3
            if pPlayer:CanTrain(row.ID, bIgnoreResources) and row.Combat > tUnit.Combat then -- 4
                tUnit = row -- 5
            end
        end
    end
    return tUnit.ID -- 6
end

local unitID = GetStrongestMilitaryUnit(player, false, "UNITCOMBAT_NAVALMELEE")

1. Here we are creating a table called tUnit. Lua tables have 2 components; a "Key" (k) and a "Value" (v). The Key is the row ID, and is defined in the square brackets. The Value is then the data held about the Key. The Value can be a table, as shown here:

Code:
local tTableOfUnitDataToReturn = { Experience = 0, Promotions = {}}

2. This is starting to get outside my understanding, but we'll go with it :p
pairs() is a function used to iterate through a Lua table. A more conventional example would be
Code:
for k, v in pairs(tTable) do
        print(k, v)
end

And so if this was tTable:

Code:
local tExample2 = {}
tExample2[0] = 36
tExample2[1] = 94
tExample2[2] = 48

Then Firetuner would print:

Code:
1  94
2  48
0  36

Because pairs also doesn't go through tables in sequence. If you want to iterate through a table in sequence, you'd use ipairs, although this doesn't work if your table starts with k being 0, or has any non-sequential k value:

Code:
local tExample2 = {}
tExample2[0] = 36 -- ipairs wouldn't print anything here, because it'd find the first key to not be 1 and decide it's reached the end
tExample2[1] = 94
tExample2[2] = 48

local tExample3 = {}
tExample3[1] = 94
tExample3[2] = 48 -- ipairs would stop here, because it has reached tExample3[3] which doesn't exist. Since it views this as it being tExample3[null], it stops because it believes that it has reached the end of the table (because here, the nonexistent tExample3[5] is also tExample3[null]).
tExample3[4] = 45

Now, the example is weird because "..." isn't a table, but a single value. Presumably this is because a table can be passed through "...", and as such this tells us that pairs will "iterate" through a single value as well as a table. TIL.

[MEGA_LATE_EDIT] Turns out that what allows it to iterate through the string is the fact that there are some curly brackets hidden in that pairs() function - so whatever is passed into "..." is then inserted into a one-key-long table regardless. Only noticed this 36 hours later :p

3. Now we're back to GameInfo. As I suspected, the value in brackets is used as a query; it'll only select values that fufil the contents of the brackets. The two full stops (..) are what Lua uses to append strings, and so the code views the contents as:
Code:
"CombatClass = 'UNITCOMBAT_NAVAL_MELEE'"

(the \' is there because the string requires apostrophes there for the statement, but Lua (presumably) would get confused if you just left them as normal. That's a guess, tbh, but eh)

So this iterator will go through every row in Units that has a CombatClass of UNITCOMBAT_NAVAL_MELEE - aka naval melee ships.

4. Here you can see how you interact with the row. Earlier I called "row" the ID of the row; this was bad wording. "row" is a pointer pointing to the row; think of it like a pencil that you're holding as you read your way down the lines of a book, so that you don't lose track of where you are if you glance away. You then use row.column to get the data from that column for row. This line shows various logic comparisons with the values found.

5. This then overwrites the entirety of tUnit with the new data. The old ID and Combat keys are thrown out the window, and instead you've got the pointer pointing at row saved there instead. This makes a change that will be reflected by the iterator, so that when it makes the next tUnit.Combat comparison it'll be taking the .Combat value from our old row, rather than from the hardcoded values from before. This is why it's fine to use GameInfoTypes["UNIT_WARRIOR"] there despite the fact that we're looking for a naval unit, because the default values in tUnit are designed to be overwritten.

6. This line then returns the ID of whatever's ended up in tUnit. This is then used later to define unitID, because the return has "replaced" the function with the returned value, meaning that it's treated as

Code:
unitID = tUnit.ID

...That's a weird one to explain, so say if I've done it badly.

-----------------------------------------------------------------------------------------------

That was surprisingly fun. Please say if I've only confused you further, or if I've failed to answer your question :p
 
Last edited:
Back
Top Bottom