Add counter in built unit-name like 2nd Squad, 1st Company

Skajaquada

Crazy Engineer
Joined
Mar 4, 2007
Messages
134
I was thinking about something meaningful half-doable thing to do in the LUA-files. Would this be impossible? I was thinking for every unit you build (maybe "same" unit or a category of units) would add a counter in their names like "Infantry, 1st Squad, 1st Company", "Infantry, 2nd Squad, 1st Company" and then to make it slightly more challanging a "Captain" unit would reset the Squad-counter and increase the Company-counter.

There's a row in SetUnitName.lua to manually set the unit's name that could be a start:

Code:
Network.SendRenameUnit(pUnit:GetID(), Controls.EditUnitName:GetText());

The question is just when to set it, I was thinking some onBuilt-event would be nice. Anyone seen one? I was looking for files with "build" in them but didn't find anything. There's the UnitMemberOverlay.lua that could be important. I'm saying that because I'm not sure what it does. Though it's in the UI-folder so it's probably just something with the UI ;)

Otherwise I suppose there are the UnitList.lua and UnitPanel.lua. I was going to say we could add an additional field in the unit-tables in the database for squad and company and simply alter those files to show them. Then make UDFs to update those fields on inserts. However the unit-tables don't actually contain built units but definitions of units. Right?

Any thoughts? I was looking for similar mods for Civ 4 and saw someone had made some random-name mod but I don't think that helps.
 
Weird that it would be done via a network method, rather than a Unit method... but anyway...

I rather suspect there might be a GameEvent for it, given which ones there are, but there's no GameEvent documentation but what we've been able to infer from DLC scenario files... I think it's unlikely that there's on in the Event namespace simply because they're mostly UI-oriented. There is Events.SerialEventUnitCreated, but no-one's documented it yet. You could play with it to find out if it's fired when a Unit becomes 'known' to the player or whenever any unit is created.
 
Alternatively, you could do it per-player-turn using GameEvents, use SaveUtils to flag units that have already been renamed, and iterate through each player's units on their turn. However, I think the player-turn GameEvent fires on turn end, so it might be weird.
 
UnitFlagManager.lua has a SerialEventUnitCreated listener which has the following argumets:
Code:
OnUnitCreated( playerID, unitID, hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible )
There's also a Unit:SetName() function which seems much more reasonable than the Network method assuming it works.
 
I might just be tired (I was reading SetXY as "sexy" a couple of times) but there's no method there to get the unit-object from the ID. Or is there a constructor for that they haven't listed? Anyway, if there isn't I can't use Unit:SetName() in the SerialEventUnitCreated. However this worked:

Code:
function MyOnUnitCreated( playerID, unitID, hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible )
    if( Players[ playerID ] == nil or
        Players[ playerID ]:GetUnitByID( unitID ) == nil or
        Players[ playerID ]:GetUnitByID( unitID ):IsDead() )
    then
        return;
    end
    
	Network.SendRenameUnit(unitID, "  Unit Created: " .. tostring( playerID ) .. " " .. tostring( unitID ) .. " " .. fogState );

end
Events.SerialEventUnitCreated.Add( MyOnUnitCreated );

:goodjob:

I don't really need the unit-object as "tostring (unitID)" shows the unit-name. I can just build the new name from that. It's also fun that you can read civilization and unit-type. I'll be sure to have character's name show up when you build "them" in my WH40K-mod, like "Luna Wolves, Captain, 1st Company -> Abbadon" :)

I guess that's that, now I need to read up on the LUA-syntax to make this actually do something worthwhile. Anyway, have you seen the Network being used for something else? When I read that line of code I figured it was to send the unit-name out over the network to update them in multiplayer-games, or is that something else?

Otherwise I don't mind sending the unit-names over the network. There's also that saying if you see a fence in the forest find out why someone set it up before you tare it down :)

EDIT: Duh, first row in that code-snippet gets the Unit from the ID :p

I had to try however this didn't work:

Code:
Players[ playerID ]:GetUnitByID( unitID ):setName("  Unit Created: " .. tostring( playerID ) .. " " .. tostring( unitID ) .. " " .. fogState);
 
Anyway, have you seen the Network being used for something else? When I read that line of code I figured it was to send the unit-name out over the network to update them in multiplayer-games, or is that something else?

Otherwise I don't mind sending the unit-names over the network. There's also that saying if you see a fence in the forest find out why someone set it up before you tare it down :)
I don't know much about the Network methods as they are completely missing from the wiki and I've never really needed to look at any of the places where the vanilla files use them. It just seems like overkill to me for this usage since AFAIK, mods aren't available in multiplayer and this would be a mod. But I can't really disagree with the sentiment that if that's how the manual name setting is done, that's how a mod should do it too.

continued... said:
EDIT: Duh, first row in that code-snippet gets the Unit from the ID :p

I had to try however this didn't work:

Code:
Players[ playerID ]:GetUnitByID( unitID ):setName("  Unit Created: " .. tostring( playerID ) .. " " .. tostring( unitID ) .. " " .. fogState);
Yeah, that level of Lua syntax is beyond me. I've always felt that if I have to call a function too many times, I should be storing the return in a variable instead. So I'd probably do something like this:
Code:
function MyOnUnitCreated( playerID, unitID, hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible )
  if Players[ playerID ] ~= nil then
    pUnit = Players[ playerID ]:GetUnitByID( unitID )
    if ( pUnit ~= nil and not pUnit:IsDead() )
      -- Do the unit renaming with either pUnit:SetName() or the Network stuff
    end
  end
end
Events.SerialEventUnitCreated.Add( MyOnUnitCreated )
 
It shouldn't matter with a single line but if you're checking for nil and isDead() it's good. However it should work with a single line, it's even how they're doing it at the top of the function. Anyway, I tried putting the Unit inside a variable but it didn't change anything. However I can get the Unit:GetName() so it's really strange Unit:SetName doesn't work.

The function is coming up nicely. It looks like this now:

Code:
local g_squad_counter = {};
local g_company_counter = {};
function MyOnUnitCreated( playerID, unitID, hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible )
    if( Players[ playerID ] == nil or
        Players[ playerID ]:GetUnitByID( unitID ) == nil or
        Players[ playerID ]:GetUnitByID( unitID ):IsDead() )
    then
        return;
    end
    
	if ( g_squad_counter[tostring( playerID )] ~= nil ) then
		g_squad_counter[tostring( playerID )] = g_squad_counter[tostring( playerID )] + 1;
	else
		g_squad_counter[tostring( playerID )] = 1;
	end

	if ( g_company_counter[tostring( playerID )] ~= nil ) then
		if ( Players[ playerID ]:GetUnitByID( unitID ):GetName() == "Warrior" ) then
			g_company_counter[tostring( playerID )] = g_company_counter[tostring( playerID )] + 1;
			g_squad_counter[tostring( playerID )] = 1;
		end
	else
		g_company_counter[tostring( playerID )] = 1;
	end

	Network.SendRenameUnit(unitID, "Squad: " .. g_squad_counter[tostring( playerID )] .. ", Company: " .. g_company_counter[tostring( playerID )] );
	print( "  Unit Created: " .. Players[ playerID ]:GetUnitByID( unitID ):GetName() .. ", Squad: " .. g_squad_counter[tostring( playerID )] .. ", Company: " .. g_company_counter[tostring( playerID )] );
	
end
Events.SerialEventUnitCreated.Add( MyOnUnitCreated );

There's just one really strange problem. When I start a game the first Warrior have "Squad: 1, Company: 2" in it's name, then the next turn it changes it to "Squad:2, Company: 1", which is strange... The first Settler have "Squad: 1, Company: 1" so I get that the first Warrior becomes the "captain" and resets the squad-counter. The thing that's strange is that it fires that SerialEventUnitCreated for the first Warrior again the second turn. It says "Incorrect type for pointer argument. Using NULL instead." a couple of times in the Live Tuner so I might do something.

Another thing is that the SendRenameUnit puts the real unit-name in parentheses. Any thoughts on the formatting? Now it looks like this:

Squad: 1, Company:1 (Scout)
Squad: 2, Company:1 (Scout)
Squad: 3, Company:1 (Scout)
Squad: 1, Company:2 (Warrior)
Squad: 2, Company:2 (Scout)

I was thinking of writing a little function tomorrow to set the "st" and "th" right so it'll look like this:

1st Squad, 1st Company (Scout)
2nd Squad, 1st Company (Scout)
3rd Squad, 1st Company (Scout)
1st Squad, 2nd Company (Warrior)
2nd Squad, 2nd Company (Scout)

Good? Yes? No? I was thinking the "captain" wouldn't get a squad too but the title "Captain". I'll just set the squad-counter to nil so the next unit will get the 1st Squad. It also wasn't so clear with having Warrior as the "captain" but the point of the structure of these if-statements is that the "first" captain is the captain of the first company. Though I'm not sure how it otherwise could've been done. It feels a little strange with a Company without a captain the first couple of turns.

After that I think the next step is making some nice arrays (sorry "tables" :) ) to contain exclude- and captain-lists. Anything else?
 
Regarding Unit:SetName(), it's possible that the function is broken. I thought I had used it recently, but am not 100% sure.

For naming, using 1st, 2nd, etc. seems nicer. There was a similar mod for Civ4 and I believe they used that kind of naming scheme.

No idea on the event firing twice, although a lot of the things in the Events.* list fire more often than you expect because they are tied to the UI.
 
It's strange is what it is, even without my function is seems to give that NULL-error in the Live Tuner. It was also strange that it didn't reset the squad-counter. It might be something with units that are pre-built. I'll look into it more tomorrow.

Here're my functions now:

Code:
local g_squad_counter = {};
local g_company_counter = {};
local g_officer_counter = {};
local g_is_init = {};
-------------------------------------------------
-- Ordinal help function
-------------------------------------------------
function get_ordinal( number )
	if ( math.abs( number ) % 100 < 21 and math.abs( number ) % 100 > 4 ) then
		return number .. "th";
	end
	local tmp = math.abs( number ) % 10;
	if ( tmp < 1 ) then
		return number .. "th";
	end
	if ( tmp < 2 ) then
		return number .. "st";
	end
	if ( tmp < 3 ) then
		return number .. "nd";
	end
	if ( tmp < 4 ) then
		return number .. "rd";
	end
	return number .. "th";
end
-------------------------------------------------
-- Change Unit Name On Created
-------------------------------------------------
function MyOnUnitCreated( playerID, unitID, hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible )
	if( Players[ playerID ] == nil or
        Players[ playerID ]:GetUnitByID( unitID ) == nil or
        Players[ playerID ]:GetUnitByID( unitID ):IsDead() or 
		not Players[ playerID ]:GetUnitByID( unitID ):IsCanAttack() )
    then
        return;
    end

	-- Init stuff
	local new_name = "";
	if ( g_is_init[tostring( playerID )] == nil ) then
		g_company_counter[tostring( playerID )] = 1;
		g_squad_counter[tostring( playerID )] = 1;
		g_officer_counter[tostring( playerID )] = 0;
	end

	-- Check if unit is "officer"
	if ( Players[ playerID ]:GetUnitByID( unitID ):GetName() == "Scout" ) then
		-- Reset Company if it've got at-least 1 Squad and 1 "officer"
		if ( g_squad_counter[tostring( playerID )] > 1 and g_officer_counter[tostring( playerID )] > 0) then
			g_company_counter[tostring( playerID )] = g_company_counter[tostring( playerID )] + 1;
			g_officer_counter[tostring( playerID )] = 0;
			g_squad_counter[tostring( playerID )] = 1;
		end
		-- Set "officer" as Lieutenant if Company already have a Captain
		if ( g_officer_counter[tostring( playerID )] > 0 ) then
			new_name = get_ordinal( g_officer_counter[tostring( playerID )] ) .. " Lieutenant, " .. get_ordinal( g_company_counter[tostring( playerID )] ) .. " Company";
			g_officer_counter[tostring( playerID )] = g_officer_counter[tostring( playerID )] + 1;
		else
			new_name = " Captain, " .. Ordinal( g_company_counter[tostring( playerID )] ) .. " Company";
			g_officer_counter[tostring( playerID )] = g_officer_counter[tostring( playerID )] + 1;
		end
	else
		new_name = get_ordinal( g_squad_counter[tostring( playerID )] ) .. " Squad, " .. get_ordinal( g_company_counter[tostring( playerID )] ) .. " Company";
		g_squad_counter[tostring( playerID )] = g_squad_counter[tostring( playerID )] + 1;
	end

	Network.SendRenameUnit(unitID, new_name );
	print( "New name: " .. new_name );

end
Events.SerialEventUnitCreated.Add( MyOnUnitCreated );

I made it so consecutive "captains" are listed as "1st Leutenant", "2nd Leutenant" etc. if the company doesn't contain a single squad so you don't get a bunch of companies with just a captain and nothing else. I also think I made it so that the first captain built doesn't increase the Company-counter so every following captain will preceed it's company.

I also abort the function if Unit:IsCanAttack is not true so Settlers and Workers aren't drafed into a company. It still feels like some things are missing... Instead of checking "Scout" as captain I was thinking of either doing a list with captain-units though I rather check for something else. It would be fun with some "Officer"-promotion to make this script a little more independant. Though it might be best to just have two lists, one with officers and one with squad-names, since this will just be about the visual stuff tieing it to promotions will not be good.

Then I was thinking of doing something with the unit-types, maybe see if I can get tank-types or air-types. What you call a tank-company anyway? At-least they wouldn't be squads so I'll have to do something about that.

EDIT: If anyone finds this searching for a problem with that event fiering twice. A "workaround" was simply checking for "Game.GetGameTurn() == 1" and then return the function. It's incredible strange but skipping the first turn 0 didn't work... The second turn my Warrior got renamed to "2nd Squad, 1st Company" which makes no sense, then the first Scout gets the same name. However skipping the second turn 1 actually produces nice results. Then the pre-built units don't change name and there really is a minimal chance anything is built the second turn of a game
 
Back
Top Bottom