1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

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

Discussion in 'Civ5 - Creation & Customization' started by Karatekid5, Aug 12, 2018.

  1. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    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!
     
  2. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    695
    Gender:
    Male
    Location:
    The Netherlands
    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.)

    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.

    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.

    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...

    Some of us are still around here ;)
     
  3. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    Location:
    Pennsylvania
    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!


    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?


    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.
     
  4. MariusMagnus

    MariusMagnus Chieftain

    Joined:
    Mar 12, 2018
    Messages:
    59
    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.

    Wouldn't it be possible to check if the tile is already pillaged when the unit enters said tile to avoid this problem?

    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.
     
  5. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    695
    Gender:
    Male
    Location:
    The Netherlands
    Remember Super Mario 64, Super Mario World, etc.? Those things never seem to age either ;)

    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.

    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.

    Hmm, that would probably 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.
     

    Attached Files:

  6. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    Location:
    Pennsylvania
    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!


    Former Super Mario World ROMhacker here! :goodjob:


    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! :)
     
  7. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    695
    Gender:
    Male
    Location:
    The Netherlands
    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.

    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
    
     
  8. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,161
    Location:
    Near Portsmouth, UK
    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
     
    Troller0001 likes this.
  9. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    Location:
    Pennsylvania
    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)
     
  10. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    695
    Gender:
    Male
    Location:
    The Netherlands
    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: Aug 28, 2018
  11. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,736
    Location:
    Illinois, USA
    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.
     
    Troller0001 likes this.
  12. MariusMagnus

    MariusMagnus Chieftain

    Joined:
    Mar 12, 2018
    Messages:
    59
    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.
     
    Troller0001 likes this.
  13. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,161
    Location:
    Near Portsmouth, UK
    GameEvents.GatherPerTurnReplayStats(iPlayer) pretty much functions as a pseudo GameEvents.PlayerEndTurn(iPlayer) hook
     
    Troller0001 and MariusMagnus like this.
  14. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    Location:
    Pennsylvania
    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: Sep 5, 2018
  15. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    695
    Gender:
    Male
    Location:
    The Netherlands
    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)

    Yes, see this thread by whoward for all file-import-related stuff for pretty much any file you'd use in civ5

    Practise makes perfect ;)

    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.

    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). ;)
     
  16. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,736
    Location:
    Illinois, USA
  17. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    Location:
    Pennsylvania
    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.




    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)
     
  18. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,736
    Location:
    Illinois, USA
    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: Sep 5, 2018
    Troller0001 likes this.
  19. Karatekid5

    Karatekid5 Chieftain

    Joined:
    Nov 11, 2012
    Messages:
    171
    Gender:
    Male
    Location:
    Pennsylvania
    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);
    
     
  20. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    695
    Gender:
    Male
    Location:
    The Netherlands
    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
    
    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).

    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: Sep 7, 2018

Share This Page