DLL - Various Mod Components

There are two kinds of mission - those that complete instantaneously (promotions, culture bomb, etc) and those that take a number of turns (improvements, routes, etc). While the new Lua custom mission events can handle both types, they have only ever been tested with instantaneous missions.

(Edit: Durational custom missions are described here.)

To create a new instantaneous custom mission we need to define the mission (via XML/SQL) and add the code to handle it (via Lua)

The custom mission definition is an entry in the Missions table and associated text strings. Taking the "Add Beliefs" custom mission from my Morindim civilization as an example

Code:
<GameData>
  <Missions>
    <Row>
      <!-- Unique mission type -->
      <Type>MISSION_MORINDIM_BELIEF</Type>
           
      <!-- See below -->
      <Description>TXT_KEY_MISSION_MORINDIM_BELIEF</Description>
      <Help>TXT_KEY_MISSION_MORINDIM_BELIEF_HELP</Help>
      <DisabledHelp>TXT_KEY_MISSION_MORINDIM_BELIEF_HELP_DISABLED</DisabledHelp>
           
      <!-- Icon to display in the Unit Panel UI - need 45 and 64 variants -->
      <IconAtlas>MORINDIM_ACTIONS_ATLAS</IconAtlas>
      <IconIndex>1</IconIndex>
           
      <!-- Where the mission appears in the Unit Panel UI - <=100 in the secondary stack, >100 in the main stack, >200 above all standard actions -->
      <OrderPriority>201</OrderPriority>
           
      <!-- Is this mission available, should always be 1 for custom missions -->
      <Visible>1</Visible>
           
      <!-- No idea!  Copied straight from an existing mission -->
      <EntityEventType>ENTITY_EVENT_GREAT_EVENT</EntityEventType>
      <Time>25</Time>
    </Row>
  </Missions>

  <Language_en_US>
    <Row Tag="TXT_KEY_MISSION_MORINDIM_BELIEF">
      <!-- Mission name - not actually used by the UI -->
      <Text>Add Beliefs</Text>
    </Row>
    <Row Tag="TXT_KEY_MISSION_MORINDIM_BELIEF_HELP">
      <!-- Tooltip text to display when the unit can perform the mission right now, right here -->
      <Text>This order will consume the Clan Magician and add new beliefs to the Morindim's pantheon.</Text>
    </Row>
    <Row Tag="TXT_KEY_MISSION_MORINDIM_BELIEF_HELP_DISABLED">
      <!-- Tooltip text to display when the unit can not perform the mission right now, right here, but could perform the mission under different circumstances -->
      <Text>The Clan Magician must be within their own territory to add beliefs.</Text>
    </Row>
  </Language_en_US>
</GameData>

For an instantaneous mission we need to add code to handle three of the custom mission events - CustomMissionPossible, CustomMissionStart and CustomMissionCompleted

The required Lua code is pretty much "is this my custom mission", "can the unit ever perform the custom mission", "can the unit perform the custom mission now", and "action the mission".

Continuing the Morindim "Add Beliefs" example, some setup stuff

Code:
-- Utility functions - each function (used in the handlers below) does what it says on the tin!
include("MorindimUtils")

-- The custom mission we're interested in
local iMissionBelief = GameInfoTypes.MISSION_MORINDIM_BELIEF

-- Return value constants needed by CustomMissionStart, do not change these, just use them
local CUSTOM_MISSION_NO_ACTION       = 0
local CUSTOM_MISSION_ACTION          = 1
local CUSTOM_MISSION_DONE            = 2
local CUSTOM_MISSION_ACTION_AND_DONE = 3

Every time the Unit Panel is displayed, it loops all possible actions (missions, builds, promotions, etc) and determines a) if the unit could perform that action and b) if it can perform it right now, right here. The modded DLL sends the Lua GameEvents.CustomMissionPossible() event to ascertain if each custom mission meets these criteria, so we need to hook that event

Code:
function OnCustomMissionPossible(iPlayer, iUnit, iMission, iData1, iData2, _, _, iPlotX, iPlotY, bTestVisible)
  -- iPlayer - the player this event is for
  -- iUnit - the unit of that player this event is for
  -- iMission - the custom mission id
  -- iData1, iData2 - additional mission data that can be sent via Lua (usually not used)
  -- _, _ - two parameters that are always 0 and -1 so can be ignored
  -- iPlotX, iPlotY - where the mission is to occur
  -- bTestVisible - true = could the unit perform the action, false = can the unit perform the action now

  -- Is this the Morindim player (told you the utility functions do what they say on the tin!) and the mission we're interested in?
  if ([COLOR="DarkOrchid"]IsMorindimPlayer(iPlayer) and iMission == iMissionBelief[/COLOR]) then
    local pPlayer = Players[iPlayer]
    local pMagicianUnit = pPlayer:GetUnitByID(iUnit)

    -- Can this particular unit type for this player perform this mission?  In our case only Clan Magicians can
    if ([COLOR="SeaGreen"]IsClanMagician(pMagicianUnit)[/COLOR]) then
      -- So we know the unit could perform the mission, but does it meet all the circumstantial conditions, ie, is the unit within our territory?
      if ([COLOR="Orange"]pMagicianUnit:GetPlot():GetOwner() ~= iPlayer[/COLOR]) then
        -- Circumstantial conditions NOT met (not in our territory), return the value of bTestVisible
        return bTestVisible
      end

      -- All conditions met, so we can perform the action
      return true
    end
  end

  -- The unit can't perform the mission - wrong player, wrong unit type, whatever - return false
  return false
end
GameEvents.CustomMissionPossible.Add(OnCustomMissionPossible)

When the player clicks the mission button in the Unit Panel, the DLL sends the GameEvents.CustomMissionStart event, so we need to hook that event to actually perform the custom mission

Code:
function OnCustomMissionStart(iPlayer, iUnit, iMission, iData1, iData2, iFlags, iTurn)
  -- iPlayer - the player this event is for
  -- iUnit - the unit of that player this event is for
  -- iMission - the custom mission id
  -- iData1, iData2, iFlags - additional mission data that can be sent via Lua (usually not used)
  -- iTurn - the turn the mission started (not used for instantaneous missions as it will always be the current turn)

  -- Is this the Morindim player and the mission we're interested in?
  if ([COLOR="darkorchid"]IsMorindimPlayer(iPlayer) and iMission == iMissionBelief[/COLOR]) then
    -- We don't need to check that the unit can actually perform the mission now, as we couldn't have clicked the mission button if it can't!
     
    -- Action the mission - in this example we just fire a LuaEvent and let some other code worry about bringing up the Choose Beliefs UI and removing the unit
    [COLOR="magenta"]LuaEvents.MorindimChooseBeliefs(iPlayer, iUnit, 2)[/COLOR]

    -- Instantaneous missions MUST ALWAYS return the CUSTOM_MISSION_NO_ACTION outcome after executing the mission code
    return CUSTOM_MISSION_NO_ACTION
  end

  -- The mission wasn't for us, return the default outcome
  return CUSTOM_MISSION_NO_ACTION
end
GameEvents.CustomMissionStart.Add(OnCustomMissionStart)

Finally we need to handle the GameEvents.CustomMissionCompleted event sent by the DLL after the mission has completed.

Code:
function OnCustomMissionCompleted(iPlayer, iUnit, iMission, iData1, iData2, iFlags, iTurn)
  -- iPlayer - the player this event is for
  -- iUnit - the unit of that player this event is for
  -- iMission - the custom mission id
  -- iData1, iData2, iFlags - additional mission data that can be sent via Lua (usually not used)
  -- iTurn - the turn the mission started (not used for instantaneous missions as it will always be the current turn)

  -- Is this the Morindim player and the mission we're interested in?
  if ([COLOR="darkorchid"]IsMorindimPlayer(iPlayer) and iMission == iMissionBelief[/COLOR]) then
    -- We don't need to check that the unit can actually perform the mission, as we couldn't have clicked the mission button if it can't!

    -- Instantaneous missions will never do anything here (as we did all the work in CustomMissionStart)
     
    -- Return true as this is our mission
    return true
  end

  -- The mission wasn't for us, return false
  return false
end
GameEvents.CustomMissionCompleted.Add(OnCustomMissionCompleted)

IMPORTANT NOTE: The AI uses none of this code. If you want the AI to perform your custom missions you will need to add code (usually at the start of the AI's turn) to determine if the AI controlled unit can perform the mission, if it is appropriate to perform the mission, and then to action the outcome of the mission directly.

Hello. I am working on a Mod which allows naval units to pillage coastal land tiles like Civ6. Once player select the pillage button, available pillage targets (adjacent land tiles that have improvement) should be highlighted, so that player can choose the target tile. It should be similar to this. ( https://forums.civfanatics.com/threads/allowing-air-units-to-pillage-tiles.631140/ ) But I don't know how to use your mod to create a custom mission that requires player to select a target. When I look inside the dll codes, I find those encoded range missions (move to, range attack, etc) use iData1 and iData2 to record the position target plot. But it doesn't work for custom missions as I tested.
 
As per the response in the thread you linked to, you'll need to solve the "how to put up a range strike arrow" and the code for that is in Lua IIRC

Edit: Digging into this some more, you're going to either need a LOT of Lua, or mod the CvUnit::CanRangeStrikeAt() method to not require a unit or a city on the target plot
 
Last edited:
Github Bug Report and Located Simple Fix

https://github.com/whoward69/DLL-VMC/issues/3

Hi I'm Tawa, I always found late game tech overflow in my modpack, tech point needed in one tech must smalller than 210,000 and I've found the fix:

The fix is simple: At line 1524 of:
https://github.com/whoward69/DLL-VM...4b/CvGameCoreDLL_Expansion2/CvTechClasses.cpp

iResearchCost = ((iResearchCost * 10000) / iResearchMod);
iResearchCost = iResearchCost * (100 + iMod) / 100;

use double or long temperately calculate iResearchCost should help.

DLL-VMC is really a nice and irreplaceable mod btw.
looking foward to DLL-VMC v90
 
Under what circumstances does this occur? I'm guessing marathon games, but are you also using a mod to extend research times?
 
I use SuperPower mod as main mod in pack, which extends the game by 2 eras and has ~100,000 base tech point at end game to get one tech. after consider the amount of cities it overflows.

Also I use 22 civ in 180 * 110 pangea map and have ~ 200 cities, and always found late game tech overflows. (at the begining of the game no overflow, when atom age, the Future age techs overflows. might because of the city amount.) I've played for a month to get atom age...When I keep playing, the tech that starts to overflow is getting earlier.

Please help, a lot user of this mod is suffering from this overflow, and we need 3 simple code change (no more than 20 characters in all, see below) and compile.
 

Attachments

  • nooverflow.png
    nooverflow.png
    752.4 KB · Views: 177
  • overflow.png
    overflow.png
    725.5 KB · Views: 159
Last edited:
The game speed is the one between normal and marathon, that is default speed of SuperPower. The mod has ~60 new tech ~200new unit and buildings, and 2 new era. I use Extreme large map with map mod, with 2% increase for each city(including :c5puppet:) tech point needed to a new tech.

line 1524:
iResearchCost = ((iResearchCost * 10000) / iResearchMod);

int iMod = GC.getMap().getWorldInfo().GetNumCitiesTechCostMod(); // Default is 40, gets smaller on larger maps
iMod = iMod * m_pPlayer->GetMaxEffectiveCities(/*bIncludePuppets*/ true);
iResearchCost = iResearchCost * (100 + iMod) / 100;

if iResearchCost is 220,000 , iResearchCost will directly overflow at line 1524 when * 10000, also possible overflow at line iResearchCost = iResearchCost * (100 + iMod) / 100;

But these first * then / is temporary, so changing iResearchCost from int to long at first is helpful. When return, cast to int. This change does not affect other part of code.:c5happy:

https://github.com/whoward69/DLL-VMC/pull/4/commits/0d2428f9d31326fed56f164eb623743fbf4c2feb
 
Last edited:
Is it possible to apply this small change into new Version of DLL-VMC with only 20-character source editing? This 20-character source change does not breaks any existing code part, and will not have any compatibility issues.
 
I looked at your push request when I got the notification, but unfortunately it does not follow the required conventions - missing conditional compilation markers (#if, #else, #endif etc) - see my first DLL tutorial if these terms don't mean anything.

I've also been in the middle of completing the Automaton Workers code move to traits, buildings and policies - a not insignificant series of code changes and associated mods to write/test.

V90 of the DLL is now on my web site and the code is on GitHub
 
Last edited:
wmm..Thank you for your kindness:c5happy::c5happy:
I've fully tested v90 and found my kind of fix is not useful, the tech points needed is still negative and still 1 turn to get tech, then I edit cost in CIV5Technologies.xml and I can found this negative tech point bug in non-modded games, the tech points needed must < ~210,000 otherwise negative. (Also I cleanned my cache folder every time).

this "tech points needed" and "how many turns needed to get tech" in techtree UI is not only visually negative, but also practically negative, i.e. I can get this tech in 1 turn as the UI says.

I also found "tech points needed" in techtree UI doesn't related to city amount, but "how many turns needed to get tech" do.

So, is there any clue to fix this problem? maybe some changes in DLL or/and XML/LUA are needed?
at line 1480 I found another * 10000 >-<
 
Try the attached. If it fixes the issue I'll release it as V91

See below
 
Last edited:
After Test the new version, unfortunately the tech still overflow and I can still get the tech in 1 turn as the UI says.
In CIV5Technologies.xml I changed Archery tech to cost 70000 and in marathon games the overflow happens.

I think the number overflow happens somewhere in a chain of int: calculating, function calling, UI displaying etc.. Now the Last step UI displaying is overflow and I can infer that there is overflow in the former step. all of the numbers in the chain should be long. Maybe my analysis is helpful for your debugging.
 
After Test the new version, unfortunately the tech still overflow and I can still get the tech in 1 turn as the UI says.
In CIV5Technologies.xml I changed Archery tech to cost 70000 and in marathon games the overflow happens.
try using less tech cost coefficient
This can help (MB 999+ not 99999999999)
I had this
 
After Test the new version, unfortunately the tech still overflow and I can still get the tech in 1 turn as the UI says.

Not what I'm seeing, see attached which are unmodded/modded games with stdandard map + standard speed vs huge map + marathon speed. As you can see, the display is as expected.

Check the version of the DLL in CustomMods.log, it should be "Version 91 - Build Aug 23 2019 13:05:15"
 

Attachments

  • Unmod_StdMap_StdSpeed.jpg
    Unmod_StdMap_StdSpeed.jpg
    35.7 KB · Views: 212
  • Unmod_HugeMap_MarSpeed.jpg
    Unmod_HugeMap_MarSpeed.jpg
    41.6 KB · Views: 215
  • Modded_StdMap_StdSpeed.jpg
    Modded_StdMap_StdSpeed.jpg
    48.9 KB · Views: 219
  • Modded_HugeMap_MarSpeed.jpg
    Modded_HugeMap_MarSpeed.jpg
    47 KB · Views: 223
I apologize that I use wrong dll last time. This time I replace dll file with new one and got a huge step forward:
the science needed is not overflow now, and the turn needed to get the tech still overflow, which is differ from your 4th attached. And I can still get the tech as the UI says.

I changed Archery tech to cost 70000 and tested in marathon huge games.
I also found the log shows Version 91 - Build Aug 23 2019 11:21:01, at log line 284:

CvTechClasses.cpp[1526]: CvPlayerTechs::GetResearchCost - Base cost for tech 3 is 273000
CvTechClasses.cpp[1531]: CvPlayerTechs::GetResearchCost - with player mod is 27300000
CvTechClasses.cpp[1537]: CvPlayerTechs::GetResearchCost - with cities mod is 27846000
CvTechClasses.cpp[1545]: CvPlayerTechs::GetResearchCost - with fudge is 278460
CvTechClasses.cpp[1547]: CvPlayerTechs::GetResearchCost - final is 278460
these numbers are pretty perfect:c5happy:
 

Attachments

  • turnoverflow.png
    turnoverflow.png
    130 KB · Views: 176
  • CustomModslog.zip
    10.8 KB · Views: 114
Ah, I attached the "only half baked" solution … oops

V91 now on my web site and GitHub with both fixes in place
 
I do not understand how it works, so it may sound incomprehensible to you ...

Sorry, but I have no idea what you're trying to do.
 
Sorry, but I have no idea what you're trying to do.
Unfortunately it will come to a standstill

Updated:
It is not surprising that you could not help me. Even after reading all the changes from the creator, not a word is mentioned about it. I think the matter is something else

I was thinking of somehow changing the values INSIDE DLL file

This code helps solve the problem, but I would like to see no AI shutdown completely
<Update>
<Where Name="AI_SMART_V3"/>
<Set Value="0"/>
</Update>

That is, my idea is not compatible with modifications to improve AI.

If you do not know, then I hope there is a person who will bring the matter to mind :(

Thanks anyway for your reply


I won’t spam and will stand here separately
Have you ever seen this "picture"?
-130 gold per turn?
 
Last edited:
With new v91 , this time I got another huge step forward: The UI is all correct. No overflow in tech point needed and turn needed.
But I can still get the tech in 1 turn >_< , while UI says I need 69615 turn.
 

Attachments

  • seemsperfect.png
    seemsperfect.png
    121.9 KB · Views: 187
  • oneturnafter1.png
    oneturnafter1.png
    110.5 KB · Views: 149
  • oneturnafter.png
    oneturnafter.png
    146.8 KB · Views: 159
But I can still get the tech in 1 turn >_< , while UI says I need 69615 turn.

Sorry, not understanding. Can you give me the simple steps to follow (eg edit XML so archery costs 77000, start game with these options, do these steps, see this outcome, etc) to reproduce this.
 
Sorry, not understanding. Can you give me the simple steps to follow (eg edit XML so archery costs 77000, start game with these options, do these steps, see this outcome, etc) to reproduce this.

Um, I changed Archery tech to cost 70000 and tested in marathon huge map games with 12 civs. level 4 AI. Pangea map.
I then build the first city and open tech tree and saw archery tech need 278460 science and 69615 turns.
To this point every thing seems fine, and I click archery tech to research it , move my unit , build some buildings and click next turn.
Then, I get archery in the next turn.
Also get 70000 extra tech points to research any other tech in tech tree.
 
Top Bottom