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.
 
Hi Whoward, quick question regarding a bit of your code.

In your CustomMods.h file, there are many instances of where you append something to the end of your preprocessor defines:

Code:
// Permits ships to enter coastal forts/citadels in friendly lands
#define MOD_GLOBAL_PASSABLE_FORTS                   [B]gCustomMods.isGLOBAL_PASSABLE_FORTS()
[/B]

Is the bolded code the part that checks whether we set the option to "ON" (a '1') in the table CustomModOptions?
 
GLOBAL_PASSABLE_FORTS is not a good example of how the code is setup, see BUILDINGS_PRO_RATA_PURCHASE for a more self-explanatory use

Code:
#if defined(MOD_BUILDINGS_PRO_RATA_PURCHASE)
  int iProductionNeeded = getProductionNeeded(eBuilding);

  if (MOD_BUILDINGS_PRO_RATA_PURCHASE) {
    // Deduct any current production towards this building
    int iProductionToDate = m_pCityBuildings->GetBuildingProduction(eBuilding);
    iProductionNeeded -= (iProductionToDate * gCustomMods.getOption("BUILDINGS_PRO_RATA_PURCHASE_DEPRECIATION", 80)) / 100;
  }
	
  int iCost = GetPurchaseCostFromProduction(iProductionNeeded);
#else
  int iCost = GetPurchaseCostFromProduction(getProductionNeeded(eBuilding));
#endif
 
I'm guessing you're referring to MOD_GLOBAL_PASSABLE_FORTS as a poor example in the context that the stuff is strewn across an additional 4-5 files instead of just CvCity.cpp for MOD_BUILDINGS_PRO_RATA_PURCHASE, but the part that I do not fully understand is still the same thing:

Code:
#define MOD_BUILDINGS_PRO_RATA_PURCHASE [U] [B]gCustomMods.isBUILDINGS_PRO_RATA_PURCHASE()[/B][/U]


What is the g.CustomMods.is[Mod ComponentName]() part? Is it safe to delete (just the bolded/underline part)? I'm guessing checking something on whether to enable the preprocessor directive line?
 
Unless you understand the complete system within CustomMods.cpp, I'd avoid deleting anything!
 
Well, say I want to merge the MOD_GLOBAL_PASSABLE_FORTS with my own DLL. I would like to have it work without going into the CustomModOptions table

Code:
<Row Class="6" Name="GLOBAL_PASSABLE_FORTS" Value="1" DbUpdates="1"/>
<Row Class="6" Name="GLOBAL_PASSABLE_FORTS_ANY" Value="1"/>

in the sense that the option will always be turned "ON" so long as the preprocessor directive is defined.

From what I can gather, the code in just checks whether the improvement has "m_bMakesPassable" set to true, and performs the appropriate logic in CvUnit/CvPlot.

So in that sense if I keep everything except for gCustomMods.isGLOBAL_PASSABLE_FORTS() (in CustomMods.h) and the stuff in CustomMods.cpp, the feature would still be functional (assumes the Improvements table has the MakesPassable column set to 1 for the appropriate rows).

Edit: Nevermind, tested to work after merging (some initial strange interaction going on with GLOBAL_PASSABLE_MODS_ANY being treated as a boolean in the source code though).
 
Hi Whoward, if you have a moment, I have 4 questions:

1) Why in the promotion "PROMOTION_CAN_CROSS_MOUNTAINS" both the tags 'IgnoreTerrainDamage' and 'IgnoreFeatureDamage' are set to true? (Mountain is a Terrain, so why 'IgnoreFeatureDamage'?)

2) 'IgnoreTerrainDamage' and 'IgnoreFeatureDamage' apply always to all terrains and all features?
For example: if I set Desert terrain to give damage when a unit end is turn there and then a unit adopt "PROMOTION_CAN_CROSS_MOUNTAINS" (with 'IgnoreTerrainDamage' true) the unit will no longer receive damage by desert?

Does the answer to this question apply to 'ExtraFeatureDamage' and 'ExtraFeatureDamage' too?

3) Can I use 'TERRAIN_MOUNTAIN' with the table 'UnitPromotions_Terrains' (and with all other tables who involve a Terrain) to give units attack, defense or movement bonus/penalty for Mountains ?

4) I apologize if this is not the right thread to ask but, speaking of promotions, if you may, could you explain to me (or point me where it has already been explained) how the AI choose promotions (since there are no flavors, what is the mechanic behind its choice) ?

Thank you in advance, Ulixes.
 
I'll need to dig into the code base to answer 1 thru 3, but 4 is simple - it's hard-coded in CvUnit::AI_promotionValue()
 
I'll need to dig into the code base to answer 1 thru 3, but 4 is simple - it's hard-coded in CvUnit::AI_promotionValue()

Thank you for your time, I'll wait.

About promotions, just a quick info: If I create new promotions (many with the same tags of vanilla ones) can I count on the AI to choose them ?
In other words, the AI, when choosing promotions, consider the single tags or is it bound to the names of vanilla promotions?
 
1) Probably something I was playing with and never used (glaciers/ice sheets spring to mind as you can place ice on land)

2) IgnoreTerrain/FeatureDamage applies to all terrain/feature types with a TurnDamge entry, but does NOT apply to any terrain/feature type with an ExtraTurnDamage entry.

So if Marsh has TurnDamage=10 and ExtraTurnDamage=5, all units will take 15 points of damage, if they end their turn there, except those with an IgnoreTerrainDamage promotion which will only take 5 points of damage.

3) No. Mountains aren't actually a terrain but a plot type. The turn damage code knows this and fakes up mountains as a terrain type before the database lookups
 
the AI, when choosing promotions, consider the single tags or is it bound to the names of vanilla promotions?

The AI doesn't care what a promotion is called, it uses the effects - that is, the (existing) column names (eg HillsDefense, OpenDefense, RoughAttack).
 
it says incompatible version (i have bnw and g&k, and the patch stated on the description, but says incorrect patch or something)
 
After ctrl+f -ing some of this threads pages and not finding anything i would like to submit a possible bug:

When used in conjunction with the Enhanced User Interface all tile yield icons turn into the food icon.

it works fine with the vanilla bnw singleplayer and multiplayer, just playing with this specific .dll mod produces this inconvenience therefore i assume that the .dll handles tile yields differently than the base game

newest game version pulled from steam (civ 5 complete)
newest EUI version pulled from the EUI thread
newest .dll version pulled from the pick'n'mix website

I've already posted this issue in the EUI thread, including screenshots:
http://forums.civfanatics.com/showpost.php?p=13676916&postcount=1478
Spoiler :
I hope i did not mess this up.
 
Hi, I have a quick question (sry if it has been asked b4),

Is there a way to increase the maximum acquire plot distance? I believe it is currently hardcoded at 5, so is there an option to increase it?

Thanks
 
Is there a way to increase the maximum acquire plot distance? I believe it is currently hardcoded at 5

Short answer: No

Long answer: Yes. But you would need to recode the DLL to some other arbitrary value and for every 1 tile change there is an exponential increase in processing time (and hence delay) between turns.
 
Top Bottom