[BNW] How to make a unit destroy a resource via pillaging?

  1. Functions subscribed to the PlayerDoTurn hook event need to state a variable name on the function line for the gamecore to pass the ID# of the player currently being processed. You need
    Code:
    function PlecoRuinsResource(iPlayer)
    instead of
    Code:
    function PlecoRuinsResource()
  2. You need to get the player object from the passed player ID# before you can do any "player" methods. Otherwise you get a nil value error. This causes a nil value error because pPlayer has never been defined
    Code:
    pPlayer:GetCivilizationType()
    You need
    Code:
    function PlecoRuinsResource(iPlayer)
    	local pPlayer = Players[iPlayer]
    The second line extracts the player object from the Players table and inserts that into local variable "pPlayer"
  3. The next line has multiple problems:
    • iTriremes has never been defined and is therefore a nil value
    • Syntax "==" is not used to place a value into a variable. "=" is required
    • You've got the order reversed for a "set value" statement. Your code is attempting to place the current value within variable "iTriremes" into the pPlayer:GetUnitClassCount(iUnitClass) method, which you can never do
    • It appears you were attempting to create variable "iTriremes" and set it to the current number of units the player has belonging to the UNITCLASS_TRIREME class of units, but your order is reversed nor did you make "iTriremes" a local variable, which would tend to make whatever value is placed into this variable persist between turns.
    • What you would want would be
      Code:
      local iTriremes = pPlayer:GetUnitClassCount(iTriremeUnitClass)
  4. You've forgotten the needed 'then' keyword here
    Code:
    if iTriremes > 0
  5. You have no properly-defined unit object here, so "pUnit" is a nil value
    Code:
    pUnit:GetID()
    Further, we do not work from a unit's ID# for Unit:Something() methods, we use the defined unit-object.
  6. In order to process through all of a given player's units, Civ5 lua gives us a special command for this
    Code:
    pPlayer:Units()
    "pPlayer" is the previously-defined player object variable. "Units()" in this case is a static object that does not require any arguments inside the "()". So, to run through all the units belonging to "pPlayer", we do as
    Code:
    for pUnit in pPlayer:Units() do
    	local iUnitType = pUnit:GetUnitType()
    	if pUnit:IsCombatUnit() then
    		print("A unit of UnitType " .. iUnitType .. " (" .. Locale.ConvertTextKey(GameInfo.Units[iUnitType].Description)) .. ") belonging to player " .. iPlayer .. " (" .. pPlayer:GetName() .. ") was discovered")
    	end
    end
    • This would print a message into the lua log for every combat unit that was discovered showing the unit's name as it appears in-game as well as the player's lua ID# and their name.
    • "pUnit" is an object variable that holds each unit's object in turn as the loop is conducted through all units belonging to the player. I could just as easily have called this object variable "Cheeseburgers":
      Code:
      for Cheeseburgers in pPlayer:Units() do
      	local iUnitType = Cheeseburgers:GetUnitType()
      	if Cheeseburgers:IsCombatUnit() then
      		print("A unit of UnitType " .. iUnitType .. " (" .. Locale.ConvertTextKey(GameInfo.Units[iUnitType].Description)) .. ") belonging to player " .. iPlayer .. " (" .. pPlayer:GetName() .. ") was discovered")
      	end
      end
  7. An object variable is just that: a variable. We can call it anything we want. But whatever we call it, we must consistently call it the same thing so long as we are using that same variable.
    • "Cheeseburgers" in this case is a temporary object-variable used within the loop. "Cheeseburgers" holds each successive object for each successive unit as the loop through the payer's units is conducted.
    • When we talk about a unit method or a player method between ourselves, we generally shorthand and show things like
      Code:
      Unit:GetPlot()
      In this example "Unit" is signifying that it is a unit-method and that "Unit" is being used as a stand-in for whatever variable-name you are using within your code to define a given unit-object.
  8. Your needed code then becomes
    Code:
    local iCiv = GameInfoTypes.CIVILIZATION_PLECOSTOMUS
    local iSuckerMouth = GameInfoTypes.UNIT_SUCKERMOUTH_SQUAD
    local iTriremeUnitClass = GameInfoTypes.UNITCLASS_TRIREME
    
    function PlecoRuinsResource(iPlayer)
    	local pPlayer = Players[iPlayer]
    	if (pPlayer:GetCivilizationType() == iCiv) and (pPlayer:GetUnitClassCount(iTriremeUnitClass) > 0) then
    		for pUnit in pPlayer:Units() do
    			if (pUnit:GetUnitType() == iSuckerMouth) then
    				local pPlot = pUnit:GetPlot()
    				local iOriginalPlotOwner = pPlot:GetOwner()
    				local iResourceID = pPlot:GetResourceType();
    				if (iResourceID ~= -1) and (iOriginalPlotOwner ~= -1) and (iOriginalPlotOwner ~= iPlayer) then
    					local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
    					--plot resource removal stuff goes here
    
    					if pPlotOwnerPlayer:IsHuman() then
    						local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
    						local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
    						pPlotOwnerPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, pUnit:GetX(), pUnit:GetY());
    					end
    				end
    			end
    		end
    	end
    end
    GameEvents.PlayerDoTurn.Add(PlecoRuinsResource);
    I added some stuff for who owns the plot because you don't want to remove resources from your own plots nor from plots nobody owns because you might want these resources later on

    This chunk of the code will only need to be run if the resource is actually removed:
    Code:
    if pPlotOwnerPlayer:IsHuman() then
    	local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
    	local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
    	pPlotOwnerPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, pUnit:GetX(), pUnit:GetY());
    end
  9. At this point there are some questions that need answering from a code-logic point-of-view:
    • Are you going to remove resources from City-States, even if they are at war with you ? You might want these resources later on.
    • Are you going to remove resources from Allies and Friends, or only players with whom you are currently at war?
    • Are you going to remove strategic and luxury resources? Remember, you might want these later on.
  10. There might be other questions that need answering before any further work on the code could be made. Troller might have a suggestion or two, forex.
:ninja: by Troller
 
Last edited:
First I'd like to thank all of you for your help, both now and in the past. I've never been the best with code work, and while I have a lot to learn this has definitely been a great learning experience!
The code seems to be running without any errors, but resources still aren't being removed and doing a test as a player I am not receiving the proper notifications. The script isn't experiencing any runtime errors in the Lua log. I inserted pPlot:SetResourceType(-1); in the spot labeled "--plot resource removal stuff goes here" since it was mentioned that setting the resource ID on the plot to -1 would be enough to remove it. Maybe I'm using the wrong variable?

  1. At this point there are some questions that need answering from a code-logic point-of-view:
    • Are you going to remove resources from City-States, even if they are at war with you ? You might want these resources later on.
    • Are you going to remove resources from Allies and Friends, or only players with whom you are currently at war?
    • Are you going to remove strategic and luxury resources? Remember, you might want these later on.
  2. There might be other questions that need answering before any further work on the code could be made. Troller might have a suggestion or two, forex.

As funny as it would be to keep it as-is (plecos are known for their destructive behavior), it would be smart to only remove them from players who are at war with the Plecos. I assume there's a routine that allows one to check diplomatic status with the target player, though it's probably more complicated than that.
 
Last edited:
If you are using my exact code but with that one line added you won't see a notification if you are running your CIVILIZATION_PLECOSTOMUS civ as the human player. The other player whose resource went *poof* would have to be the human as I wrote the code. The unit also has to start its turn on a plot that has both a resource and which belongs to a player that is not the one running as the CIVILIZATION_PLECOSTOMUS player.

You won't see a notification anyway since I missed the bit Troller was referring to with regard to your iX and iY variables on the notification line are undefined and therefore nil.

You can change the notification behavior to show to the human when they are running the game as the CIVILIZATION_PLECOSTOMUS player to
Code:
if pPlayer:IsHuman() then
	local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
	local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
	pPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, pUnit:GetX(), pUnit:GetY());
end
 
Last edited:
I know that the notification won't be visible to someone playing as the Pleco Civ. By a player test I meant that I entered a game as a non pleco Civ (chose random and rolled Suleiman) with the Plecos in one of the AI slots, surrounded a sea resource in my territory with mountains (to prevent the unit on top of it from moving off of the tile), switched to the plecos via IGE, dropped a Suckermouth Squad on the tile (to make sure the Plecos owned the unit), then switched back to Suleiman and hit next turn. I apologize for being a bit vague there. No notification popped up after letting a few turns pass. Made the changes you just mentioned to the last line of the notification code and I am still not receiving its' notifications.

The good news is that the resource destruction is working now! Apparently I made a massive blunder and forgot that I had given the Suckermouth Squad an internal name of "UNIT_PLECOSTOMUS_SCHOOL". Either way I'm glad the most important part of the code now works!
 
Last edited:
if you're using
Code:
	pPlotOwnerPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, pUnit:GetX(), pUnit:GetY());
Change to
Code:
pPlotOwnerPlayer:AddNotification(NotificationTypes["NOTIFICATION_GENERIC"], text, heading)
I looked up an old mod of mine where I know the generic notification was working. I am not using an XY location for the notification and it works just fine.
 
Thank you so much, that worked perfectly! The notification now shows up!
Now, if I wanted to make it so that the resource destruction only happens when a Civ is at war with the plecos, would I utilize the "Team:IsAtWar(TeamID index)" in an if statement?

Also, now running into another problem unfortunately. For some reason it won't read a properly set up TXT Key. It is the text for the short version of the notification (It simply shows up as TXT_KEY_PLECO DESTRUCTION_HEADER).

Code:
local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_HEADER");

<Row Tag="TXT_KEY_PLECO DESTRUCTION_HEADER">
           <Text>You got Pleco'd!</Text>
     </Row>

The TXT Key is defined in an XML that contains the rest of the Civ's text, which all works properly.
 
Thank you so much, that worked perfectly! The notification now shows up!
Now, if I wanted to make it so that the resource destruction only happens when a Civ is at war with the plecos, would I utilize the "Team:IsAtWar(TeamID index)" in an if statement?

Also, now running into another problem unfortunately. For some reason it won't read a properly set up TXT Key. It is the text for the short version of the notification (It simply shows up as TXT_KEY_PLECO DESTRUCTION_HEADER).

Code:
local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_HEADER");

<Row Tag="TXT_KEY_PLECO DESTRUCTION_HEADER">
           <Text>You got Pleco'd!</Text>
     </Row>

The TXT Key is defined in an XML that contains the rest of the Civ's text, which all works properly.
You can't have spaces in your tags, that's why it's not working properly.
I.e. Change it to:
<Row Tag="TXT_KEY_PLECO_DESTRUCTION_HEADER">
<Text>You got Pleco'd!</Text>
</Row>


if you're using
Code:
    pPlotOwnerPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, pUnit:GetX(), pUnit:GetY());
Change to
Code:
pPlotOwnerPlayer:AddNotification(NotificationTypes["NOTIFICATION_GENERIC"], text, heading)
I looked up an old mod of mine where I know the generic notification was working. I am not using an XY location for the notification and it works just fine.
In an old unreleased and dead mod of mine I was able to use the X,Y-values for Notifications for Resources, Buildings, Improvements, and possibly some more that I can't remember.
Since Pleco is able to destroy a resource, it could be nice to have the resourceIcon show up in the notification (instead of the generic exclamation mark):
Code:
--pPlayer, sDescription, sTitle, iPlotX, iPlotY, and iResourceID are all defined above
pPlayer:AddNotification(NotificationTypes["NOTIFICATION_DISCOVERED_BONUS_RESOURCE"], sDescription, sTitle, iPlotX, iPlotY, iResourceID, -1)
--I suspect that the last argument (-1) could simply be omitted
 
Last edited:
As I recall this worked without troubles
Code:
local iDeer = GameInfoTypes.RESOURCE_DEER
local iNotification = NotificationTypes.NOTIFICATION_DISCOVERED_BONUS_RESOURCE
.....
pPlayer:AddNotification(iNotification, Locale.ConvertTextKey("TXT_KEY_NOTIFICATION_FOUND_RESOURCE","TXT_KEY_RESOURCE_DEER"), Locale.ConvertTextKey("TXT_KEY_NOTIFICATION_SUMMARY_FOUND_RESOURCE","TXT_KEY_RESOURCE_DEER"),pPlot:GetX(),pPlot:GetY(),iDeer)
The final argument did not seem to be needed as I recall.
 
I could've sworn that I typed an underscore and not a space, thank you for finding that! While I wasn't able to get the resource pic to appear on the notification based on LeeS's code, I was able to get the coordinates working properly, which is the most important part!
All that's left is to add a check for war status. Would I use Team:IsAtWar(TeamID index) for that?
 
Yes, that will be the method needed to determine if a player is at war with another. You'll need to get the TeamID# for both players, and then for one of them you will need to use the TeamID# to get the team object
Code:
Teams[TeamID1]:IsAtWar(TeamID2)
 
Decided to give it a try and was unable to get it to detect war status. Can one grab a Team ID from an i variable? The target player's ID is stored to the value so I'd assume it can check the target player for Team ID.

Code:
           if (pUnit:GetUnitType() == iSuckerMouth) then
               local pPlot = pUnit:GetPlot()
               local iOriginalPlotOwner = pPlot:GetOwner()
               local iResourceID = pPlot:GetResourceType()
               local iPlotX = pPlot:GetX()
               local iPlotY = pPlot:GetY()
               local Plecos = pPlayer:GetID()
               local PlecoTarget = iOriginalPlotOwner:GetID();
               if (iResourceID ~= -1) and (iOriginalPlotOwner ~= -1) and (iOriginalPlotOwner ~= iPlayer) and Teams[Plecos]:IsAtWar(PlecoTarget) then
                   local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                   pPlot:SetResourceType(-1)
                   pPlot:SetImprovementType(-1);
 
iOriginalPlotOwner is an integer value for a player's ID #. You cannot use it to do object methods.
You don't need to do this
Code:
local Plecos = pPlayer:GetID()
Since you already have the ID# of this player. It is the iPlayer value sent from the gamecore to the function PlecoRuinsResource(iPlayer).

SetImprovementType(-1) needs to come before SetResourceType(-1)

Code:
local iCiv = GameInfoTypes.CIVILIZATION_PLECOSTOMUS
local iSuckerMouth = GameInfoTypes.UNIT_SUCKERMOUTH_SQUAD
local iTriremeUnitClass = GameInfoTypes.UNITCLASS_TRIREME

function PlecoRuinsResource(iPlayer)
	local pPlayer = Players[iPlayer]
	if (pPlayer:GetCivilizationType() == iCiv) and (pPlayer:GetUnitClassCount(iTriremeUnitClass) > 0) then
		local pPlayerTeam = Teams[pPlayer:GetTeam()]
		for pUnit in pPlayer:Units() do
			if (pUnit:GetUnitType() == iSuckerMouth) then
				local pPlot = pUnit:GetPlot()
				local iOriginalPlotOwner = pPlot:GetOwner()
				local iResourceID = pPlot:GetResourceType();
				if (iResourceID ~= -1) and (iOriginalPlotOwner ~= -1) and (iOriginalPlotOwner ~= iPlayer) then
					local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
					if pPlayerTeam:IsAtWar(pPlotOwnerPlayer:GetTeam()) then
						--plot resource removal stuff goes here
						local iPlotOwningCityID = pPlot:GetCityPurchaseID()
						local iPlotOwnershipDuration = pPlot:GetOwnershipDuration()
						pPlot:SetOwner(-1)
						pPlot:SetImprovementType(-1);
						pPlot:SetResourceType(-1)
						pPlot:SetOwner(iOriginalPlotOwner, iPlotOwningCityID, true, true);
						pPlot:SetOwnershipDuration(iPlotOwnershipDuration)
						if pPlotOwnerPlayer:IsHuman() then
							local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
							local heading = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION_SHORT");
							pPlotOwnerPlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading);
						end
					end
				end
			end
		end
	end
end
GameEvents.PlayerDoTurn.Add(PlecoRuinsResource);
 
I did the order of which of the plot resource removal commands needs to come in what order from memory. I might have mis-remembered which order is needed, so you might have to experiment and jigger them around a bit in the order.
 
I didn't notice any issues with having SetResourceType before SetImprovementType , when I tested it before you made your changes, the improvement and resource disappeared at the same time. What's the reason for switching them? Just out of curiosity! :)

I knew I was over complicating things a bit when I tried adding the team check, so seeing it done correctly greatly helps for the future! Thank you very much for being so helpful with the code, it makes me so happy to see these ideas come to life and to learn more about Civ V's Lua as a whole!
All I need is a way to check for players met and select one at random for that other code, but that can wait. Now that the big hurdle is out of the way I can get back to polishing the mod and adding the finishing touches. Thank you again!
 
For the removal it would probably make no real difference. But for placing and getting the game to properly accept and count a lux or strat resource it is better to follow in a path of place resource, place improvement rather than the other way round. So in removing I've always followed the mirror order.
 
Back
Top Bottom