Any way to get the unit-create event to work as expected?

Skajaquada

Crazy Engineer
Joined
Mar 4, 2007
Messages
134
I've been playing around with this a little and though it really seem like one of the most important events it's behavior is very strange. I'm just doing something very simple where I want to change a part of the name based on what Civilization built the unit. In the FireTuner all the prints show that it's working with print-outs for the civilization-type, original name and new name. However in-game, with all the units that a civilization started with the part of the unit-name always seem to correspond with one of the civilization's special name.

I've tried doing some things like assigning an array with every built unit's ID and true or fase if it've already been built so it doesn't fire again for the same unit, however this problem really seem to go beyond all reason... Is there anything I'm missing? It really feels like variables I use to build the new unit-name are pointers or something that later gets updated but they're just normal "local namePart"-variables in LUA inside the function for the event.

One work-around I tried that failed was to check if player is human with the "isHuman"-function but then it never ran, or rather it was never true. I tried that as the event doesn't seem to run for the computer-player anyway. If there's something there I'm missing as well I'd appreciate the help as that seem like the most reasonable way.
 
SerialEventUnitCreated works for this. It triggers for all players, whever a unit is created. Unfortunately, it ALSO triggers whenever a unit embarks, disembarks, rebases, etc. So in my mod, I got around this in a fairly simple way:

> Add a "Rookie" promotion. In my mod, this promotion is -25% to combat but +100% XP rate. For this to work you don't actually need the promotion to have any effect, but I like the idea of a slightly weaker unit that gains double experience from its first fight.
> In SerialEventUnitCreated, if a unit doesn't have the Rookie promotion AND its creation turn equals the current turn number, do whatever you wanted to do on unit creation, and then give the Rookie promotion.
(That's Units:GetGameTurnCreated() == Game:GetGameTurn() for the turn check.)
If you enter SerialEventUnitCreated and either the unit already has the Rookie promotion OR the turn numbers don't match, then it's one of those other triggers (like embarkation) and you should do nothing.
> Then, just add a mechanism to remove the Rookie promotion. I personally remove it at the end of a unit's first combat, although the EndCombatSim event that does this adds its own limitations (no Quick Combat or Strategic View). You could, instead, just remove it after a certain number of turns have passed if you wanted to avoid those issues. Or, you could remove it whenever the unit moved, which it can't do on its first turn anyway.
 
yep, "marking" the unit with a temporary promo seems the easier way.

I wonder if we can just compare the number of remaining moves with the max move combined with the turncreated ?

Anyway, because I needed an units table for other things in my WW2 mods, I added a key here and check if the unit already exist before registering it.

I've tried doing some things like assigning an array with every built unit's ID and true or fase if it've already been built so it doesn't fire again for the same unit, however this problem really seem to go beyond all reason...

but your key can't be only the unit ID, this is unique only for a player, units from different player could have the same ID.
 
I wonder if we can just compare the number of remaining moves with the max move combined with the turncreated ?

This'd break if you had any promotions, policies, wonders, etc. that added additional movement points. There are quite a few of these. You could probably work out a way to resolve this, but at that point your overhead has exceeded the other methods. The temporary promotion really seems to be the easiest way to resolve it, which is why I did it that way.

With the method I said, the only problem would be if you had a unit that could attack, continue moving after the attack, and then immediately embark before the end of the turn. That could be avoided by simply making the Rookie promotion disappear at the end of the turn instead of after the first combat, but IMO it's not really necessary. After all, the AI won't generally do something like that, and the player can just be told not to do that. If a player wants to cheat by getting the effect twice, well, he can just open up FireTuner and cheat more directly.

but your key can't be only the unit ID, this is unique only for a player, units from different player could have the same ID.

Correct. This is why the GetUnitFromID function has to be indexed by player. IDs, by themselves, aren't unique.

This whole thing can really be resolved by the devs giving us a GOOD OnUnitCreated GameEvent, instead of forcing us to use SerialEventUnitCreated with all of its UI-affiliated flakiness. But until they do, we make do with what works.
 
SerialEventUnitCreated was the one I meant and have been using. I was testing some more last night and it just seem to be a problem with the free units a civilization can start with.

It's really interesting actually, according to the print-outs it's not like it's making the event fire again. I've also got keys for unts like "if general start new army-counter" and those seem to be completely ignored the time the units get their new name the second time. It's probably just something with the set-name function.

Unless I'm missing something there with the free units it's probably best to just skip that event on the first turn and loop through all the players and units either on the first turn or just directly in the LUA-file if the units are registered then. Otherwise I'll just save all the naming for the second turn.

Also the check I did just for the unit-ID if it had been created was probably what caused only the human-player to appear to get it's new unit-names.

EDIT: Finally got it, I was using the SendRenameUnit-function I found in the manual rename-unit LUA-file. There's no using of it in the code but in the Wiki there's a function called SetName for the unit-object that seem to work much better. Anyway, SendRenameUnit only takes in a unit-ID and name so it was probably automatically using the active-player or something, at-least that was what my tests during the week seemed to indicate when I tried to init the free units somehow.
 
The problem with a new unit promotion is Firaxis did not appear to provide us with a "replace unit" function either. Is there a way to replace a unit other than creating a new unit and deleting the old one? To check for that circumstance I currently have a global "ReplacingUnit" variable which bypasses the call to LuaEvents.NewUnit.
 
Is there a way to replace a unit other than creating a new unit and deleting the old one?

Isn't that how the game itself handle units upgrades ?
 
Isn't that how the game itself handle units upgrades ?

Yes. There's a way to use the Unit:Convert() function to do exactly this, copying all characteristics of an old unit to a new one. I haven't been able to get it to work right, in my own mod, but others have used it. And yes, it's how the game already handles unit upgrades, although I don't know offhand if any Lua files have this sort of functionality. (UnitPanel handles the UI for it, but it appears to let the engine do the actual work.)
 
I didn't know about Unit:Convert() :D

Using a custom function in my own mod, but again it's backed by a table to track all units.

hmmm, from the wiki :
unit:Convert(<Unit> pUnit)

which convert which ? :think:

do you know of a mod using it ?
 
do you know of a mod using it ?

No, I just remember seeing a discussion of it in this forum way back when. The idea was that you'd create newUnit using the usual Init() function, do newUnit:Convert(oldUnit), then kill the oldUnit. The part that I keep getting stuck on is pulling the identifiers for the newly spawned unit (since the Init function doesn't return that in a usable form). But it's not a high priority for my mod, so I've never put any real effort into working the kinks out of it.
 
The part that I keep getting stuck on is pulling the identifiers for the newly spawned unit (since the Init function doesn't return that in a usable form).

I'm not sure what you mean by "identifiers," but init returns a object instance of the unit class (or whatever it's called in lua).
PHP:
function UnitClass_Replace(oldUnit, unitClass)
	MapModData.VEM.ReplacingUnit = true
	local newUnit = Players[oldUnit:GetOwner()]:InitUnitClass(unitClass, oldUnit:GetPlot(), oldUnit:GetExperience())
	MapModData.VEM.ReplacingUnit = false
	for promoInfo in GameInfo.UnitPromotions() do
		if oldUnit:IsHasPromotion(promoInfo.ID) and not promoInfo.LostWithUpgrade then
			newUnit:SetHasPromotion(promoInfo.ID, true)
		end
	end
	newUnit:SetEmbarked(oldUnit:IsEmbarked())
	newUnit:SetDamage(oldUnit:GetDamage())
	newUnit:SetLevel(oldUnit:GetLevel())
	newUnit:SetPromotionReady(newUnit:GetExperience() >= newUnit:ExperienceNeeded())
	newUnit:FinishMoves()
	oldUnit:Kill()
	return newUnit
end

function PlayerClass.InitUnitClass(player, unitClassType, plot, exp)
	local newUnit = player:InitUnit( player:GetUniqueUnitID(unitClassType), plot:GetX(), plot:GetY() )
	if exp then
		newUnit:ChangeExperience(exp)
	end
	return newUnit
end

function PlayerClass.GetUniqueUnitID(player, classType)
	local civType = GameInfo.Civilizations[player:GetCivilizationType()].Type
	local unitType = GameInfo.UnitClasses[classType].DefaultUnit
	if civType ~= "CIVILIZATION_MINOR" and civType ~= "CIVILIZATION_BARBARIAN" then
		local query = string.format("CivilizationType = '%s' AND UnitClassType = '%s'", civType, classType)
		for itemInfo in GameInfo.Civilization_UnitClassOverrides(query) do
			unitType = itemInfo.UnitType
			break
		end
	end
	return GameInfo.Units[unitType].ID
end
 
Many moons back, when I looked at this, Unit:Convert() didn't (seem) to work, so like most, I wrote my own.

The one issue with rolling your own is that there is no (easy) way to allow for "only one goody hut upgrade per unit"

So if you build unit A, it finds a goody hut and upgrades, when you convert it to B, B can also be upgraded via a goody hut
 
I have this code in my mod and it works:

Code:
		for unit in player:Units() do
			if unit:GetUnitType() == WORKERS_ID then
				local newUnit = player:InitUnit(SLAVES_ID, unit:GetX(), unit:GetY())
				newUnit:Convert(unit)
			end
		end

No need to kill the old unit. Convert takes care of that. I had assumed that Convert would transfer over all promotions, experience, etc, because that's the way it worked in Civ4. However, I guess I don't know that since my workers don't have any promotions or experience.
 
Back
Top Bottom