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

Karatekid5

Warlord
Joined
Nov 11, 2012
Messages
197
Location
Pennsylvania
With most of the community deep into Civ 6 it seems a little lonely around here!

So, for my newest custom civ I'm making a rather silly unit that I intend to be able to destroy sea resources (it makes sense in context) if the pillage action is performed while sitting on top of one. Having used FramedArchitecture's old Global Warming mod and seeing it destroy resources via its' coastal flood mechanics, I was able to find some code that it uses to do so. DestroyResource seems to be a variable, but I'm unsure of how to change this or make LUA code fire off on the pillage action.

The code from FramedArchitecture's mod:

Code:
if ( DestroyResource ) then
                local player = Players[targetPlot:GetOwner()];
                local pActivePlayer = Players[Game.GetActivePlayer()];
                local iResourceID = targetPlot:GetResourceType(Game.GetActiveTeam())
                
                if ( iResourceID ~= -1 ) then
                    targetPlot:SetResourceType(-1);
                    if ( player == pActivePlayer ) then
                        local text;
                        local heading;
                        text = Locale.ConvertTextKey("TXT_KEY_GW_LOST_RESOURCE", GameInfo.Resources[iResourceID].Description);
                        heading = Locale.ConvertTextKey("TXT_KEY_GW_LOST_RESOURCE_SHORT");
                         pActivePlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
                    end
                end
            end

This is just a snippet but it seems pretty simple. Still, would this work if it checked for an AI civ? and if the AI uses this unit against the player can it display a notification indicating it?

One more thing regarding this unit, it uses the Submarine Granny2 file, and while I can easily set custom birth and selection sounds for it, I was wondering if it was possible to change the movement and attack sounds.

Thanks in advance to anyone that helps!
 
..., but I'm unsure of how to change this or make LUA code fire off on the pillage action.
The main problem you're facing right now is that there does not exist a "hook" (I.e. an event that fires whenever a certain condition is met) for when a tile is pillaged.

There are two ways you can go from here:
1) Use Lua trickery to keep track of tiles with resources which your UU entered, and every turn check if such tile was pillaged. The drawback of this method is that it will have a delay (I.e. the resource is not immediately destroyed upon pillaging), and that it will also destroy resources on tiles that were pillaged that the UU enters, but that were not actually pillaged by the UU.
2) Make your mod depend on the VMC/CP DLL, which adds GameEvents.TileImprovementChanged(...). (I.e. the hook you actually wish to use). The drawback of this is that your mod now relies on a DLL mod, of which only one can be active at the same time. (This may not necessarily be a problem, especially since the VMC/CP DLL is pretty much the most used DLL out there.)

This is just a snippet but it seems pretty simple. Still, would this work if it checked for an AI civ?
If you're using GameEvents then yes. Be cautious if you're starting to use Events though; they may not always fire for the AI, when a tile is not visible, when quick combat is enabled, etc.

and if the AI uses this unit against the player can it display a notification indicating it?
Yes, this is certainly possible. The code for it is pretty much already in the snippet you provided. Adding some extra pPlayer:IsHuman()-checks will do the trick.

One more thing regarding this unit, it uses the Submarine Granny2 file, and while I can easily set custom birth and selection sounds for it, I was wondering if it was possible to change the movement and attack sounds.
Unfortunately I know little of 3D modding, but I can recall that this should be possible. It's just that I don't know how (and where I read this) exactly...

With most of the community deep into Civ 6 it seems a little lonely around here!
Some of us are still around here ;)
 
Some of us are still around here ;)

And that makes me very happy! :hug: As a Civ 5 player who hasn't really enjoyed Civ 6, it's nice to see that I'm not being left behind!


There are two ways you can go from here:
1) Use Lua trickery to keep track of tiles with resources which your UU entered, and every turn check if such tile was pillaged. The drawback of this method is that it will have a delay (I.e. the resource is not immediately destroyed upon pillaging), and that it will also destroy resources on tiles that were pillaged that the UU enters, but that were not actually pillaged by the UU.
2) Make your mod depend on the VMC/CP DLL, which adds GameEvents.TileImprovementChanged(...). (I.e. the hook you actually wish to use). The drawback of this is that your mod now relies on a DLL mod, of which only one can be active at the same time. (This may not necessarily be a problem, especially since the VMC/CP DLL is pretty much the most used DLL out there.)

Both options look tempting. While I have little experience with Civ 5 LUA (scripting for every game is wildly different ><), it would be nice to not require a custom DLL, especially since I plan to release this and I don't know how many community patch users will want to have a Civ in their game that's based on these. But at the same time using the DLL means less scripting involved and a more accurate ability. I still have to dig around the code more (what I found only displays the notification, not the routine for actually destroying the resource), but which of the two options do you think would be better? And for a human player check does there need to be a variable in the parenthesis? Or just True/False?


Unfortunately I know little of 3D modding, but I can recall that this should be possible. It's just that I don't know how (and where I read this) exactly...

No worries! I did find the Audio 3D sounds that the Submarine uses in the game's files, but I'm just not sure how to attach my modified scripts to the unit itself.
 
Hey, people are still modding the original Star Wars Battlefront 2. So as long as people still have an interest in the game there will always mods made for Civ V!

While using the CP DLL accomplishes exactly what you want, you have to remember that the end user will not always be interested in having to use another mod just to use yours (unless your mod is specifically tailored for it, or course), especially if said mod edits the DLL. For Troller's non-DLL solution, you could reword your unit's ability and make mention that it must end its turn on a pillaged resource to destroy it. That would at the very least make it seem like an intentional balance rather than a bug. Of course, you could potentially implement both using lua to check if the CP is active or not. In the end, it's all up to you.

The drawback of this method is that [...] it will also destroy resources on tiles that were pillaged that the UU enters, but that were not actually pillaged by the UU.
Wouldn't it be possible to check if the tile is already pillaged when the unit enters said tile to avoid this problem?

And for a human player check does there need to be a variable in the parenthesis?
Nope, IsHuman() is a simple boolean test that takes no parameters. Although the former is outdated, you can usually just check the modiki or search the forums for reference on how certain lua methods work.
 
Hey, people are still modding the original Star Wars Battlefront 2. So as long as people still have an interest in the game there will always mods made for Civ V!
Remember Super Mario 64, Super Mario World, etc.? Those things never seem to age either ;)

While using the CP DLL accomplishes exactly what you want, you have to remember that the end user will not always be interested in having to use another mod just to use yours (unless your mod is specifically tailored for it, or course), especially if said mod edits the DLL.
I also forgot to mention that Mac and Linux users will not be able to use your mod either. They cannot use any DLL mod unfortunately.

For Troller's non-DLL solution, you could reword your unit's ability and make mention that it must end its turn on a pillaged resource to destroy it. That would at the very least make it seem like an intentional balance rather than a bug. Of course, you could potentially implement both using lua to check if the CP is active or not. In the end, it's all up to you.
I consider civ5 modding to be a game of tradeoffs. There are many things that are straight up not possible to do, but with some minor tweaks a whole new world opens up.
I'd personally go for the first suggestion, since it requires more planning ("will my unit die if I leave it here?"), and you may not always wish to destroy a resource.

Wouldn't it be possible to check if the tile is already pillaged when the unit enters said tile to avoid this problem?
Hmm, that would probably work.

...Although the former is outdated, you can usually just check the modiki or search the forums for reference on how certain lua methods work.
I highly recommend the Excel sheets linked at the bottom of the first post in this thread: https://forums.civfanatics.com/threads/bnw-lua-api-reference.558353/
I've extended the Lua methods sheet by methods introduced in the VMC/CP DLL (by straight up looking into its source code), which I'll attach to this post too. (I've also marked some methods as broken, since I couldn't get them to not crash when used)

EDIT: Just realized I read that a bit wrong. These sheets don't explain how to use the methods, it just states what parameters they take and what values they return.
 

Attachments

  • Lua Methods - incl VMC v87.xlsx
    146 KB · Views: 101
Nope, IsHuman() is a simple boolean test that takes no parameters. Although the former is outdated, you can usually just check the modiki or search the forums for reference on how certain lua methods work.

Sounds good! My first attempt at this is probably gonna fail completely but I'll do what I can to try and make it work!


Remember Super Mario 64, Super Mario World, etc.? Those things never seem to age either ;)

Former Super Mario World ROMhacker here! :goodjob:


I consider civ5 modding to be a game of tradeoffs. There are many things that are straight up not possible to do, but with some minor tweaks a whole new world opens up.
I'd personally go for the first suggestion, since it requires more planning ("will my unit die if I leave it here?"), and you may not always wish to destroy a resource.

I'll likely go with suggestion #1 then since it causes the least amount of complications. I'll also give that spreadsheet a read and see if I can figure this out. Do they contain code for checking resource tiles and their current state? Or is there another mod I can look at for reference? I think having the code check whether or not the unit ends the turn on a pillaged tile would be the most balanced way to do it like Maruis Magnus suggested. It also eliminates the need to keep track of tiles the unit has been to that turn. But won't it also destroy the player's pillaged resources?

I'll post again when I have some code built! :)
 
...Do they contain code for checking resource tiles and their current state?
There are quite a few Plot-methods that check stuff like: get the resource on the plot, get the improvement on the plot, is the improvement on the plot pillaged, etc.

I think having the code check whether or not the unit ends the turn on a pillaged tile would be the most balanced way to do it like Maruis Magnus suggested. It also eliminates the need to keep track of tiles the unit has been to that turn. But won't it also destroy the player's pillaged resources?
A simple check of whether the plot is in the user's lands would prevent that.

Spoiler :

Some methods of interest (all found in the sheet)
Code:
pPlot:GetOwner() --returns the ID of the owner of the plot. -1 if it's neutral land
pPlot:IsImprovementPillaged() --returns true if the improvement on the plot is pillaged. false if there's not. (not sure what happens if there is no improvement on that plot, but probably the latter)
pPlot:GetImprovementType() --returns the ID of the improvement on the plot. Returns -1 if there is no improvement.
pPlot:GetResourceType() --returns the ID of the resource on the plot. -1 if there's no resource
 
Pretty sure the snippet as given won't work for Oil, if the plot is already within the boundary of a city. The game core caches the resource counts as a city acquires tiles, so you need to jump through hoops of un-owning the tile, clearing the resource, then re-owning the tile. Check out how IDE does it - as that's where I borrowed the code for my Units - Herdsmen mod
 
My apologies for the delay! Been busy with work so I haven't had time to work on this. Been looking through the code in the Global Warming mod and the UA LUA file for my Koppai Civ and I am understanding the routines a little more clearly. Here's a first draft that I was able to put together. It's probably still way off the mark but it's nice to have something to start with.

Code:
function PlecoRuinsResource(iPlayer, iUnit)
    local pPlayer = Players[iPlayer]
    local pUnit = pPlayer:GetUnitByID(iUnit)
      if pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_PLECOSTOMUS and pUnit:GetUnitType() == GameInfoTypes.UNIT_SUCKERMOUTH_SQUAD then
           if pPlot:IsImprovementPillaged() == True
                if ( DestroyResource ) then
                local player = Players[targetPlot:GetOwner()];
                local pActivePlayer = Players[Game.GetActivePlayer()];
                local iResourceID = targetPlot:GetResourceType(Game.GetActiveTeam())
              
                if ( iResourceID ~= -1 ) then
                    targetPlot:SetResourceType(-1);
                    if ( player == pActivePlayer ) then
                        local text;
                        local heading;
                        text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
                        heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
                         pActivePlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
                    end
                end
            end

        end
    end

SerialEvents.ImprovementDestroyed.Add(PlecoRuinsResource)
 
My apologies for the delay! Been busy with work so I haven't had time to work on this. Been looking through the code in the Global Warming mod and the UA LUA file for my Koppai Civ and I am understanding the routines a little more clearly. Here's a first draft that I was able to put together. It's probably still way off the mark but it's nice to have something to start with.
...
First and foremost you seem to be using SerialEvents.ImprovementDestroyed. SerialEvents (unlike GameEvents) have a tendency to not execute when the human player cannot see the affected tile (due to the FoW or just because it occurs somewhere on the map where he's not looking at the point it happens). (which is why they are usually used for graphics/UI-only stuff that do not affect gameplay)
This causes inconsistency in Single Player games and even de-syncs in multiplayer.

As I mentioned in post #2, there unfortunately is no perfect GameEvent-hook outside modded DLLs. Of course your code can be adapted accordingly (as MariusMagnus already kindly did for us) to fit with existing GameEvents.

-----
Now, since there's still plenty to learn from the code you provided above I'll go over it "as if" SerialEvents.ImprovementDestroyed actually worked properly (even though it doesn't!)
Spoiler :

Code:
SerialEvents.ImprovementDestroyed.Add(PlecoRuinsResource)
From what I saw, the correct syntax would be:
Events.SerialEventImprovementDestroyed(PlecoRuinsResource)

Code:
function PlecoRuinsResource(iPlayer, iUnit)
Here you're trying to use parameters that aren't passed by your Event.
The one's you're able to use are:
Code:
function PlecoRuinsResource(iX, iY, someOtherParemeter, someOtherOtherParameter)
IX and iY represent the plot-coordinates of the destroyed improvement. All other information you need (including iPlayer and iUnit) can be derived from these!
(I have no idea what the last two do, but you can simply omit them)

to get pPlayer and pUnit you can do the following:
Code:
local pPlot = Map.GetPlot(iX, iY);
local pUnit;
for i=0,pPlot:GetNumUnits()-1,1 do
  local pPlotUnit = pPlot:GetUnit(i);
  --there can only be one combat unit on a plot at the same time (outside of modded DLLs)
  if pPlotUnit:IsCombatUnit() then
    pUnit = pPlotUnit;
    break;
  end
end
--at this point we can use pUnit
local pPlayer = Players[pUnit:GetOwner()];
EDIT: nil-error fixed as per LeeS's comment. Also removed the IsCargo-check (I wasn't entirely sure if Cargo units would be counted as 'combat units', but apparently they don't)

Code:
 if pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_PLECOSTOMUS and pUnit:GetUnitType() == GameInfoTypes.UNIT_SUCKERMOUTH_SQUAD then
Database calls are expensive, especially when used often. Hence it's better to cache such values that we use often (since they do not change!).

I'd also recommend to add in your own "abbreviation" in the Units, Buildings, Civs, etc. you define. This way, the chance of another modder adding the exact same text for the unit (which causes errors) is WAY lower:
E.g. I (Troller0001) personally use "TRL", Chrisy15 uses "C15", JFD uses well, "JFD", etc. Such abbreviations are usually anywhere from 2 to 5 letters (though anything more can't hurt either)

Caching and abbreviation added:
Code:
--this is at the top of your code
local iCiv = GameInfoTypes.CIVILIZATION_KK5_PLECOSTOMUS --this caches info
local iSuckerMouth = GameInfoTypes.UNIT_KK5_SUCKERMOUTH_SQUAD --this caches info

--this is instead of the line shown in the code-block above
 if pPlayer:GetCivilizationType() == iCiv and pUnit:GetUnitType() == iSuckerMouth then --this line also becomes less daunting to look at

Code:
if pPlot:IsImprovementPillaged() == True
True is not the same as true! True (with a capital letter) is seen as a variable by Lua.
Either change True to true or omit the ==true part entirely.
You're also missing the then.

Code:
if ( DestroyResource ) then
DestroyResource is undefined. You can leave this if-statement out entirely. (Removing this also takes care of an "end" you missed)

Code:
               local player = Players[targetPlot:GetOwner()];
               local pActivePlayer = Players[Game.GetActivePlayer()];
                local iResourceID = targetPlot:GetResourceType(Game.GetActiveTeam())
           
                if ( iResourceID ~= -1 ) then
                    targetPlot:SetResourceType(-1);
                    if ( player == pActivePlayer ) then
                        local text;
                        local heading;
                        text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
                        heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
                         pActivePlayer:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
                    end
                end
targetPlot is not defined. Use pPlot instead (which we defined in our edited code)
There's some optimisation possible in the use of variables.

Full code:
Spoiler :

Code:
print("KarateKid5Lua.lua has been loaded!"); --this is a great way to see if your Lua actually loads into the game if "nothing seems to happen"!

local iCiv = GameInfoTypes.CIVILIZATION_KK5_PLECOSTOMUS --this caches info
local iSuckerMouth = GameInfoTypes.UNIT_KK5_SUCKERMOUTH_SQUAD --this caches info

function PlecoRuinsResource(iX, iY)
    local pPlot = Map.GetPlot(iX, iY);
    local pUnit;
    for i=0,pPlot:GetNumUnits()-1,1 do
      local pPlotUnit = pPlot:GetUnit(i);
      --there can only be one combat unit on a plot at the same time (outside of modded DLLs)
      if pPlotUnit:IsCombatUnit() then
        pUnit = pPlotUnit;
        break;
      end
    end
 
    if pUnit then --while we could safely assume that pUnit is defined, this is just a sanity check for some edge-case scenarios that may occur
        local pPlayer = Players[pUnit:GetOwner()];
     
        if pPlayer:GetCivilizationType() == iCiv and pUnit:GetUnitType() == iSuckerMouth then
            if pPlot:IsImprovementPillaged() then
                local iResourceID = pPlot:GetResourceType(Game.GetActiveTeam());
           
                if iResourceID ~= -1 then
                    pPlot:SetResourceType(-1);
                 
                    local iOwner = pPlot:GetOwner();
                    if iOwner == Game.GetActivePlayer() then
                        local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
                        local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
                        Players[iOwner]:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
                    end
                end
            end
        end
    end
end
Events.SerialEventImprovementDestroyed.Add(PlecoRuinsResource);



-----
EDIT:

Now, it is actually possible to adapt the code in such a way that the resource gets destroyed if you start your turn on a pillaged tile using GameEvents.PlayerDoTurn(iPlayer)
You can use the pPlayer:Units() iterator to loop over all of a player's units.
Many parts of the code above can then be pasted in and you have your working function
-------
EDIT:
As Whoward also mentioned, removing the resource should also be done as in his Herdsmen-mod, though that should be a simple copy-paste.
 
Last edited:
when will a unit be a cargo unit if it is a combat unit ?
Code:
if pPlotUnit:IsCombatUnit() and not pPlotUnit:IsCargo() then
The "and" condition will only be evaluated if the first condition on the line is met, and will have no effect on the code if the unit is not a combat unit. It would be easier I would think to look for the unit-type within the scan of the plot's units. You also have a potential to get a nil value indexing error since you are looping through to the number of units on the plot instead of the number of units on the plot - 1.
 
To build on Troller's point of reworking the code for use with GameEvents.PlayerDoTurn(iPlayer), it may be possible to give the impression of it running at the end of the previous player's turn. Initialize an iPreviousPlayer variable whenever your mod loads (You could simply set it to 0 -- Player 1 -- but I recommend calling Game.GetActivePlayer() and then getting the playerID from that, as games can be saved and then loaded during turn processing) and initialize your pPlayer variable using that instead of the iPlayer one passed by your function. To actually get it working, just add a line at the very end of the function assigning iPlayer's value to iPreviousPlayer. The logic within the function should now affect the previous player.

You'll possibly also want to check that iPlayer does not equal iPreviousPlayer, just in case.
 
GameEvents.GatherPerTurnReplayStats(iPlayer) pretty much functions as a pseudo GameEvents.PlayerEndTurn(iPlayer) hook
 
I gave the finished code Troller created a try, using the GameEvents.PlayerEndTurn hook instead of the non-working hook I had chosen before. According to the LUA log the code itself runs but the ability doesn't seem to work properly. I haven't yet grabbed any of the recommended code from the Herdsman mod, but so far the code hasn't been having any effect. Firstly, I remembered that fishing boats aren't pillaged improvements, they're outright destroyed, just leaving the resource behind. Would it be best to change the check to see if the resource is simply bare?
I also tried placing a land resource in the water via IGE and setting it to pillaged. But the Pleco unit, despite ending its' turn on the resource, did not destroy it. It's probably a matter of grabbing code from the herdsman or choosing a better LUA hook, but what would be the net best course of action to try? I assume setting the tile status to Resource -1 would functionally be enough to delete the resource.
On a side note, is InGameUIAddin the correct insertion setting for this kind of LUA abillity?


As a side project I'm working on another (rather crazy) UA where it's supposed to check the amount of civs met and every 5 turns randomly declare war on one of them. I found one example code I could somewhat understand and I looked through the vanilla DiploList.lua and found some code that gets the amount of Civs met, but I can't quite understand what each line does and it's leaving me scratching my head... I've been having a lot of trouble grasping Civ V's style of LUA. Even the i and p before certain variables confuse me. I do have the basic idea for the proper structure, though:

local iPreWarTurns == 0
local iCiv = [Database civ call]
[call array of major civs currently met]

function RageWarCheck
if iPreWarTurns =>5

if [roll for probability, 50%]
[choose from array]
DeclareWar()
iPreWarTurns = 0

else
end
else
iPreWarTurns = iPreWarTurns + 1
end

GameEvents.PlayerDoTurn.Add(RageWarCheck)
 
Last edited:
I gave the finished code Troller created a try, using the GameEvents.PlayerEndTurn hook instead of the non-working hook I had chosen before. According to the LUA log the code itself runs but the ability doesn't seem to work properly. I haven't yet grabbed any of the recommended code from the Herdsman mod, but so far the code hasn't been having any effect. Firstly, I remembered that fishing boats aren't pillaged improvements, they're outright destroyed, just leaving the resource behind. Would it be best to change the check to see if the resource is simply bare?
It's really hard to say anything about possible errors without the code itself. (The code for the PlayerDoTurn should be a tad bit different from the code I posted).
Also, testing for an unimproved resource could work (but will not work for pillaged improvements unless you check for both). I personally think that the Unit-ability not working on sea resources is fine, since your UU is a land unit (?) anyways.

I also tried placing a land resource in the water via IGE and setting it to pillaged. But the Pleco unit, despite ending its' turn on the resource, did not destroy it. It's probably a matter of grabbing code from the herdsman or choosing a better LUA hook, but what would be the net best course of action to try? I assume setting the tile status to Resource -1 would functionally be enough to delete the resource.
Setting the resource to -1 should be enough yea.
Also, depending on your setup and use of PlayerDoTurn, your UU might actually need to start it's turn on a resource to destroy it. (Then again, this is just a guess since I can't see your code)

On a side note, is InGameUIAddin the correct insertion setting for this kind of LUA abillity?
Yes, see this thread by whoward for all file-import-related stuff for pretty much any file you'd use in civ5

As a side project I'm working on another (rather crazy) UA where it's supposed to check the amount of civs met and every 5 turns randomly declare war on one of them. I found one example code I could somewhat understand and I looked through the vanilla DiploList.lua and found some code that gets the amount of Civs met, but I can't quite understand what each line does and it's leaving me scratching my head... I've been having a lot of trouble grasping Civ V's style of LUA.
Practise makes perfect ;)

Even the i and p before certain variables confuse me.
The i and p (as well as the s and t) reference Hungarian Notation, which is commonly used in civ modding. Since Lua does not care what kind of data you put in a variable (E.g. I can put a string in a variable that previously held an integer), Hungarian notation allows the coder to quickly see what kind of data the variable holds. This is no guarantee if you code improperly of course, but once you grasp the basics code becomes a lot clearer.
iPlayer, iNumResources, etc. -> i stands for Integer (which can be an ID)
pPlayer, pUnit, etc. -> p stands for pointer. Having a pointer to an instance allows you to use the functions (as defined in the civ5 API) on those instances. E.g. You can use :GetCivilizationType() on a pPlayer pointer.
sPlayer, sUnit -> s stands for String. E.g sPlayer may contain the player's name, etc.
tUnits, -> t stands for Table. E.g. a table consisting of unitIDs, buildingIDs, city Names, etc.

Code:
I do have the basic idea for the proper structure, though:

local iPreWarTurns == 0 --while this method would work if there is 1 player of your Civ on the map, what about if there are 2 or more players of your civ active at the same time? (HINT: consider using a table)
local iCiv = [Database civ call]
[call array of major civs currently met] --you'll want to move this into the RageWarCheck-function since this array can change between one turn and another

function RageWarCheck --you forgot your brackets and parameters -> function RageWarCheck(iPlayer)
  if iPreWarTurns =>5
      
        if [roll for probability, 50%]
              [choose from array] --HINT: use Game.Rand(...)
              DeclareWar() --I'm assuming this function has been defined somewhere else
              iPreWarTurns = 0
       
          else --this else can be removed; it does absolutely nothing here since there is no code between this 'else' and the next 'end'
          end
  else
   iPreWarTurns = iPreWarTurns + 1
 end
end --you were missing this 'end'
GameEvents.PlayerDoTurn.Add(RageWarCheck)

You can also use [CODE][/CODE]-blocks to preserve indentation of your code

P.s. Lua is not an acronym, it comes from the Portuguese word for "moon". Hence people usually say Lua instead of LUA (which also avoids confusion with Leader-Unique-Ability for Civ6). ;)
 
It's really hard to say anything about possible errors without the code itself. (The code for the PlayerDoTurn should be a tad bit different from the code I posted).
Also, testing for an unimproved resource could work (but will not work for pillaged improvements unless you check for both). I personally think that the Unit-ability not working on sea resources is fine, since your UU is a land unit (?) anyways.


Setting the resource to -1 should be enough yea.
Also, depending on your setup and use of PlayerDoTurn, your UU might actually need to start it's turn on a resource to destroy it. (Then again, this is just a guess since I can't see your code)

Here is the current code I am using:

Spoiler :

Code:
-- Code by Troller0001

print("The plecos have invaded another river!");

local iCiv = GameInfoTypes.CIVILIZATION_PLECOSTOMUS --this caches info
local iSuckerMouth = GameInfoTypes.UNIT_SUCKERMOUTH_SQUAD --this caches info

function PlecoRuinsResource(iX, iY)
    local pPlot = Map.GetPlot(iX, iY);
    local pUnit;
    for i=0,pPlot:GetNumUnits()-1,1 do
      local pPlotUnit = pPlot:GetUnit(i);
      --there can only be one combat unit on a plot at the same time (outside of modded DLLs)
      if pPlotUnit:IsCombatUnit() then
        pUnit = pPlotUnit;
        break;
      end
    end
 
    if pUnit then --while we could safely assume that pUnit is defined, this is just a sanity check for some edge-case scenarios that may occur
        local pPlayer = Players[pUnit:GetOwner()];
  
        if pPlayer:GetCivilizationType() == iCiv and pUnit:GetUnitType() == iSuckerMouth then
            if pPlot:IsImprovementPillaged() then
                local iResourceID = pPlot:GetResourceType(Game.GetActiveTeam());
        
                if iResourceID ~= -1 then
                    pPlot:SetResourceType(-1);
              
                    local iOwner = pPlot:GetOwner();
                    if iOwner == Game.GetActivePlayer() then
                        local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
                        local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
                        Players[iOwner]:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
                    end
                end
            end
        end
    end
end
GameEvents.PlayerDoTurn.Add(PlecoRuinsResource);



and the problem is that this unit is a Trireme replacement, so it will only be interacting with sea resources. Though, since fishing boats are destroyed and not pillaged (I feel like an idiot for not remembering this when I made the thread), we would only need to check for the presence of a resource.



The i and p (as well as the s and t) reference Hungarian Notation, which is commonly used in civ modding. Since Lua does not care what kind of data you put in a variable (E.g. I can put a string in a variable that previously held an integer), Hungarian notation allows the coder to quickly see what kind of data the variable holds. This is no guarantee if you code improperly of course, but once you grasp the basics code becomes a lot clearer.
iPlayer, iNumResources, etc. -> i stands for Integer (which can be an ID)
pPlayer, pUnit, etc. -> p stands for pointer. Having a pointer to an instance allows you to use the functions (as defined in the civ5 API) on those instances. E.g. You can use :GetCivilizationType() on a pPlayer pointer.
sPlayer, sUnit -> s stands for String. E.g sPlayer may contain the player's name, etc.
tUnits, -> t stands for Table. E.g. a table consisting of unitIDs, buildingIDs, city Names, etc.


Thank you for the information! I couldn't find this on any of the Wiki pages I checked.


As for the other, crazier ability, I've made progress on the code, all I need to do is figure out how to actually build the array of met civs itself so that I can use Game.Rand to pull one at random. The closest function I was able to find on the wiki was GetNumMinorCivsMet(), which is not quite what I want. Also, regarding your comments in the code, DeclareWar() does seem to be a real function, I've seen it mentioned and utilized in other threads during my research.

Code:
local iPreWarTurns = 0
local iCiv = CIVILIZATION_ONIONFALLS

function RageWarCheck(iPlayer)
[call array of major civs currently met]
  if iPreWarTurns =>5
      if pPlayer:GetCivilizationType() == iCiv
        if Game.Rand(2, "War Probability") >= 2
              iWarTarget = [choose from array]
              if iWarTarget:IsAlive()
                pTeam:DeclareWar(iWarTarget, True)
                local WarMessage = "OH NO, A WAR STARTED!"
                Events.GameplayAlertMessage(WarMessage)
                iPreWarTurns = 0
              end
          end
  else
   iPreWarTurns = iPreWarTurns + 1
 end
end
GameEvents.PlayerDoTurn.Add(RageWarCheck)
 
  1. GameEvents.PlayerDoTurn does not provide two arguments to functions subscribed to it. It only provides the PlayerID # for the player currently taking their turn. So this line
    Code:
    pPlot = Map.GetPlot(iX, iY);
    results in variable pPlot being assigned a value of "nil". You can not "index" (ie, do plot methods on) a nil value -- attempting to do so causes a fatal run-time error for the function
  2. This statement is not true:
    Code:
    --there can only be one combat unit on a plot at the same time (outside of modded DLLs)
    There can be multiple combat units on the same tile. DOMAIN_LAND, DOMAIN_SEA, DOMAIN_AIR
  3. This will only give a valid resource ID # if the active player (ie, the human player) can "see" the resource
    Code:
    local iResourceID = pPlot:GetResourceType(Game.GetActiveTeam());
    If you use plain
    Code:
    local iResourceID = pPlot:GetResourceType();
    you will get the resource that is on the plot regardless of whether the human player has the correct tech, etc., to see the resource. Oil = nuff said.
  4. In Single Player games this
    Code:
    if iOwner == Game.GetActivePlayer() then
    is the same as saying
    Code:
    if iOwner == 0 then
Also, in your new code, "True" does not equal "true". Boolean true is not capitalized.

And you need to state your mathematical comparison characters in the correct order. >= but not => (http://lua-users.org/wiki/ExpressionsTutorial)

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

I forgot to mention that due to conceptual errors you will need to redraft completely your PlecoRuinsResource function that was posted.

  1. Check that the correct player is being processed
  2. iterate through all the player's units looking for your specific unit-type
  3. when you find one of your specific unit's, get it's plot from the Unit:GetPLot() method and also check that I am not misremembering the method name and syntax for Unit:GetPlot()
  4. do your plot stuff for that unit's plot. You will not need to scan through all the units on the plot looking for whether your unit is present since you've already done so in step #2 for each of your special units.
You can further optimize processing by not doing anything for the player if the UnitClassCount for your unit's Class is zero,
 
Last edited:
I followed your instructions and gave this a shot, reworking the code. I'm unsure if I did step 2 correctly, as I couldn't find a function that cycles through the player's units (the closest I could find was getting the number of units the player has, which wouldn't return the correct information). However, I was able to structure out the rest of the code and included a function that gets Unit IDs. I even was able to include your optimization idea regarding the check for Unit Class Count. Either way I assume there's still more to do with this to make it correct.

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

function PlecoRuinsResource()
     
     if pPlayer:GetCivilizationType() == iCiv then
        pPlayer:GetUnitClassCount(iTriremeUnitClass) == iTriremes

       if iTriremes > 0

       pUnit:GetID()

         if pUnit == iSuckerMouth then
            Unit:GetPlot(iSuckerMouth)  
            local iResourceID = pPlot:GetResourceType();         
                if iResourceID ~= -1 then
                    pPlot:SetResourceType(-1);
                 
                    local iOwner = pPlot:GetOwner();
                    if iOwner == Game.GetActivePlayer() then
                        local text = Locale.ConvertTextKey("TXT_KEY_PLECO_DESTRUCTION", GameInfo.Resources[iResourceID].Description);
                        local heading = Locale.ConvertTextKey("TXT_KEY_PLECO DESTRUCTION_SHORT");
                        Players[iOwner]:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
                    end
                end
            end
        end
   end
GameEvents.PlayerDoTurn.Add(PlecoRuinsResource);
 
II'm unsure if I did step 2 correctly, as I couldn't find a function that cycles through the player's units (the closest I could find was getting the number of units the player has, which wouldn't return the correct information).
pPlayer:Units() returns an iterator that allows you to loop over that player's units. E.g:
Code:
--pPlayer has been defined somewhere above
for pUnit in pPlayer:Units() do
  --you can use pUnit here. E.g.
  print(pUnit:GetName().." is owned by "..pPlayer:GetName());
end

However, I was able to structure out the rest of the code and included a function that gets Unit IDs.
pUnit:GetID() returns the ID of the given unit (pUnit). It does not serve any purpose in your code (since it is not stored anyways. Even if it were, you wouldn't need it).

Code:
Players[iOwner]:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, iX, iY);
You also use iX and iY here, which are not defined in your code. You can however do as follows:
Code:
Players[iOwner]:AddNotification(NotificationTypes.NOTIFICATION_GENERIC, text, heading, pUnit:GetX(), pUnit:GetY());

You were almost there. Just these little things were missing.

EDIT: Some more things I noticed:
Code:
pPlayer:GetUnitClassCount(iTriremeUnitClass) == iTriremes
== is used to compare things. = is used to assign data.
It should therefore be as follows:
Code:
iTriremes = pPlayer:GetUnitClassCount(iTriremeUnitClass)


Code:
         if pUnit == iSuckerMouth then
Here, you try to compare a pointer to a unitType, which will not work. Use this instead:
Code:
if pUnit:GetUnitType() == iSuckerMouth then


Code:
            Unit:GetPlot(iSuckerMouth)
You made a typo here and are trying to pass a parameter to :GetPlot() that it does not accept/want. You also forgot to store the data in the pPlot-variable.
Code:
pPlot = pUnit:GetPlot()


Code:
if iTriremes > 0
You're missing a then here.
 
Last edited:
Top Bottom