[TOTPP] Lua Scenario Template

If you're curious about what the limits are, I'd suggest running an experiment. There's a pretty basic events file attached to this post, so you could run an experiment, doubling the size of the state table until something breaks.
I ended up getting curious about this, so I tried testing with these events:
Code:
--Events for Classic Rome

-- this will print to the lua console
print "You should see this in lua console if this worked"

-- The require command allows the access of code written in another file
-- At the moment, the lua console will only look in the lua directory,
-- not in the directory where Events.lua is located
local civlua = require "civlua"
local func = require "functions"
-- The assignment tells how we access the required functions.
-- e.g. to use functionInCivlua(), we write civlua.functionInCivlua()


-- This implements the justOnce function.  Not part of lesson 2
local state = {}
local justOnce = function (key, f) civlua.justOnce(civlua.property(state, key), f)
end

local startExponent = 16



-- These are necessary to 'save' and 'load' the data associated with the justOnce function.
--  Not Part of lesson 2.
civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer) 
    civ.ui.text("state loaded")
    if state.exponent then
        for i=1,2^state.exponent do
            if i ~= state[i] then
                error("key: "..tostring(i).." value: "..tostring(state[i]))
            end
        end
        civ.ui.text("state verified")
    end
    state.exponent = state.exponent or startExponent
    state.exponent = state.exponent + 1
    for i=1,2^state.exponent do
        state[i] = i
    end
    civ.ui.text(state.exponent)
end)
 civ.scen.onSave(function () return civlua.serialize(state) end)

By saving and then loading the newly saved game, the amount of data saved in the state table doubled each time (and verified against what they should be). 2^21 entries seems to work fine, but 2^22 didn't work one time (the game seems to have not added the table). However, another time, after closing the game and opening it again, 2^22 was saved (for 98 MB of data, compared to .1MB for the underlying game), and 2^23 didn't work (and was taking noticeable time saving the game).

There was also a time when I got an error suggesting that the 'buffer' was nil, and that persisted regardless of which saved game I tried to load until I closed the whole program and started it again.
 
I've been working on a system for storing data for different items. It is very similar to the existing flag and counter modules, except that you also associate a game object with it, such as a unit or city. What I've actually done is write a generic module builder, so by providing a few basic functions a data module can be built for almost any kind of game data.

I think I've got all the functions ready, so now I'm about to start testing. I intend to test this much more thoroughly than I usually test stuff, since this has the potential to create delayed and tricky bugs, so I'm not sure when it will be released. If there is some sort of useful feature that you think I missed, now would be a good time to let me know.

Here is the documentation template. <item> would be replaced with unit/city/whatever.

Code:
-- This is a template for data modules created from the supplementalData module

--  This module is used to associate data with individual <item>s.  Functions are used to
--  get the data and to change it, so you don't have to interact and maintain the underlying
--  tables directly.  These functions also check the data that you input, so that errors are
--  caught quickly rather than being saved in tables only to surface later.
--
--  This module provides "flags", which store boolean values, "counters", which store number
--  values, and "phrases", which store string values.  There is also some basic "generic"
--  data storage, which can store "state savable" data, and so is more flexible, but also has
--  fewer error checks and built in features.  
--
--  These flags, counters, and phrases have names, which are strings.  To access a piece of data,
--  you will need to have the <item> it is associated with, as well as the name, and whether the
--  data is a flag, counter, or phrase.  Then, you can use one of the provided functions
--  to interact with that data.
--
--  By default, all data is nil.  However, flags, counters, and phrases will not return nil
--  when you get their values (generic, however, will).  Instead, when you define a
--  data entry, you will define a default value which is to be returned if the data is nil.
--  Functions are provided to check if the underlying value is actually nil, and to reset
--  the data to nil if you need to.  A data value stops being nil when you assign it an
--  actual value.
--
--  You can set data to update periodically, during onTurn, onTribeTurnBegin, or onTribeTurnEnd
--  events.  Flags and Phrases can be reset to nil automatically, while counters and generic data
--  can be assigned update parameters or functions.  Usually, updates are only performed on
--  data which is already non-nil, but you can choose to update all if that is needed for your
--  functionality.  The update time can also be set to 'custom', which will only update the
--  data on specific function call.
--
--


--      Note that flag, counter, and phrase names must be distinct, even between data types.
--      That is, flag names must be different from counter names and phrase names, etc.

--  <item>Data.defineFlag(flagName,defaultValue=false,resetTime="never")
--      Defines a flag for <item> data, which keeps boolean values
--      flagName = string
--          the name of the flag
--      defaultValue = boolean
--          If the underlying stored value is nil, this is the value the flag takes on
--      resetTime = "never", "onTurn", "onTribeTurnBegin", "onTribeTurnEnd", "custom",
--          Gives the time when the flag's stored value is reset to nil
--          "never" means never reset automatically
--          "onTurn" means at the very beginning of the onTurn event (before all other code)
--          "onTribeTurnBegin" means at the very start of the onTribeTurnBegin event for the owner (before all other code)
--          "onTribeTurnEnd" means at the very end of the onTribeTurnEnd event for the owner (after all other code)
--          if <item> has no owner, onTribeTurnBegin and onTribeTurnEnd updates happen
--          during the onTurn update
--          "custom" means the update must be programmed in manually using <item>Data.update
--          

--  <item>Data.defineModuleFlag(moduleName,flagName,defaultValue=false,resetTime="never")

--      Note: The optional parameter of moduleName prevents name conflicts
--      for flags used in a module/library.  

--  <item>Data.flagGetValue(<item>,flagName) --> boolean
--  <item>Data.flagGetValue(<item>,flagName,moduleName=nil) --> boolean
--      returns the value associated with the <item>'s flag of flagName
--      if the value is nil, the default specified during the definition is returned

--  <item>Data.flagSetTrue(<item>,flagName) --> void
--  <item>Data.flagSetTrue(<item>,flagName,moduleName=nil) --> void
--      sets the value associated with the <item>'s flag of flagName to True

--  <item>Data.flagSetFalse(<item>,flagName) --> void
--  <item>Data.flagSetFalse(<item>,flagName,moduleName=nil) --> void
--      sets the value associated with the <item>'s flag of flagName to false

--  <item>Data.flagReset(<item>,flagName) --> void
--  <item>Data.flagReset(<item>,flagName,moduleName=nil) --> void
--      sets the value associated with the <item>'s flag of flagName to nil,
--      (meaning that it returns the default value set when it was defined)

--  <item>Data.flagIsNil(<item>,flagName) --> boolean
--  <item>Data.flagIsNil(<item>,flagName,moduleName=nil) --> boolean
--      returns true if the underlying value of <item>'s flagName flag is nil
--      (including if all keys are nil)
--      and false otherwise



-- <item>Data.defineCounter(counterName,defaultValue=0,minValue=-math.huge,maxValue=math.huge,update="none",updateTime="never",updateParameter=nil,nonInteger=nil)
--      Defines a counter for <item> data, which keeps numerical values
--      counterName = string
--          the name of the counter
--      defaultValue = number
--          If the underlying stored value is nil, this is the value the counter takes on
--      minValue = number
--          This is the smallest number the counter can be.  If anything would set the counter below this number,
--          the counter is set to this number instead
--      maxValue = number
--          This is the largest number the counter can be.  If anything would set the counter above this number,
--          the counter is set to this number instead
--      update = "none", "increment", "set", "reset","function", "incrementAll", "setAll", "functionAll"
--          This is the kind of update the counter receives each turn
--          "none" means no update
--          "increment" means that the updateParameter is added to the current value of the counter (subject to maxValue and minValue) ,
--              but only if the counter isn't currently nil
--          "incrementAll" same as increment, but is also applied to <item>s with nil as the underlying value of the counter
--          "set" means the counter is set to the updateParameter, but only applies if the counter isn't currently nil
--          "setAll" same as "set", but is also applied to <item>s with nil as the underlying value of the counter
--          "reset" sets the underlying counter value to nil
--          "function" sets the underlying counter value to the result of updateParameter(formerUnderlyingValue,<item>ID) (subject to maxValue and minValue), only for underlying values which are not nil
--          "functionAll" sets the underlying counter value to the result of updateParameter(formerUnderlyingValue,<item>ID) (subject to maxValue and minValue), even for nil underlying values
--      updateTime = "never", "onTurn", "onTribeTurnBegin", "onTribeTurnEnd", "custom"
--          Gives the time when the counter update happens
--          "never" means no update
--          "onTurn" means at the very beginning of the onTurn event (before all other code)
--          "onTribeTurnBegin" means at the very start of the onTribeTurnBegin event for the owner (before all other code)
--          "onTribeTurnEnd" means at the very end of the onTribeTurnEnd event for the owner (after all other code)
--          if <item> has no owner, onTribeTurnBegin and onTribeTurnEnd updates happen
--          during the onTurn update
--          "custom" means the update must be programmed in manually using <item>Data.update
--      updateParameter = number, nil, or function
--          if update is "increment","incrementAll", "set", "setAll" then this must be a number
--          if update is "none" or "reset", this is ignored and can be nil
--          if update is "function", this is a function(numberOrNil) -> numberOrNil
--      nonInteger = bool or nil
--          if true, the counter can take on non-integer values
--          if false, the value is rounded using math.floor(initialValue+0.5)
--          if nil, an error is thrown when the counter is set to a non-integer value


--  <item>Data.defineModuleCounter(moduleName,counterName,defaultValue=0,minValue=-math.huge,maxValue=math.huge,update="none",updateTime="never",updateParameter=nil,nonInteger=nil)


--  <item>Data.counterGetValue(<item>,counterName) --> number
--  <item>Data.counterGetValue(<item>,counterName, moduleName=nil) --> number
--      returns the value associated with the <item>'s counter of counterName
--      if the value is nil, the default specified during the definition is returned


--  <item>Data.counterSetValue(<item>,counterName,value) --> number
--  <item>Data.counterSetValue(<item>,counterName,value,moduleName=nil) --> number
--      sets the value of the <item>'s counterName to the specified value
--      if this value is outside the counter's defined maxValue and minValue,
--      those values are then applied
--      returns the value the counter was set to


--  <item>Data.counterAdd(<item>,counterName,increment,minValue=-math.huge,maxValue=math.huge) --> number
--  <item>Data.counterAdd(<item>,counterName,increment,minValue=-math.huge,maxValue=math.huge,moduleName=nil) --> number
--      adds the increment to the <item>'s counterName current value, but substituting minValue or maxValue
--      if the result is out of the range.  Then, the minimum and maximum values specified
--      when the counter was defined are applied (i.e. the minValue and maxValue here do not
--      override the defined min and max values)
--      returns the value the counter was set to


--  <item>Data.counterSubtract(<item>,counterName,increment,minValue=-math.huge,maxValue=math.huge) --> number
--  <item>Data.counterSubtract(<item>,counterName,increment,minValue=-math.huge,maxValue=math.huge,moduleName=nil) --> number
--      subtracts the increment to the <item>'s current value, but substituting minValue or maxValue
--      if the result is out of the range.  Then, the minimum and maximum values specified
--      when the counter was defined are applied (i.e. the minValue and maxValue here do not
--      override the defined min and max values)
--      returns the value the counter was set to


--  <item>Data.counterSetWithin(<item>,counterName,minValue=-math.huge,maxValue=math.huge) --> number
--  <item>Data.counterSetWithin(<item>,counterName,minValue=-math.huge,maxValue=math.huge,moduleName=nil) --> number
--      Sets the counter's current value within the minValue and maxValue specified
--      (This does not change the overall max and min set when defining the counter)
--      returns the value the counter was set to


--  <item>Data.counterIsAtLeast(<item>,counterName,threshold) --> bool
--  <item>Data.counterIsAtLeast(<item>,counterName,threshold,moduleName=nil) --> bool
--      returns true if the <item>'s counterName is at least the threshold
--      and false otherwise


--  <item>Data.counterIsAtMost(<item>,counterName,threshold) --> bool
--  <item>Data.counterIsAtMost(<item>,counterName,threshold,moduleName=nil) --> bool
--      returns true if the <item>'s counterName is at most the threshold
--      and false otherwise


--  <item>Data.counterReset(<item>,counterName) --> void
--  <item>Data.counterReset(<item>,counterName,moduleName=nil) --> void
--      sets the value associated with the <item>'s counterName to nil
--      (meaning that it returns the default value set when it was defined)


--  <item>Data.counterIsNil(<item>,counterName) --> boolean
--  <item>Data.counterIsNil(<item>,counterName,moduleName=nil) --> boolean
--      returns true if the underlying value of <item>'s counterName counter is nil
--      and false otherwise


-- <item>Data.definePhrase(phraseName,defaultValue="",resetTime="never")
--      Defines a phrase for <item> data, which keeps string values
--      phraseName = string
--          the name of the phrase
--      defaultValue = string
--          If the underlying stored value is nil, this is the value the phrase takes on
--      resetTime = "never", "onTurn", "onTribeTurnBegin", "onTribeTurnEnd", "custom"
--          Gives the time when the phrase's stored value is reset to nil
--          "never" means never reset automatically
--          "onTurn" means at the very beginning of the onTurn event (before all other code)
--          "onTribeTurnBegin" means at the very start of the onTribeTurnBegin event for the owner (before all other code)
--          "onTribeTurnEnd" means at the very end of the onTribeTurnEnd event for the owner (after all other code)
--          if <item> has no owner, onTribeTurnBegin and onTribeTurnEnd updates happen
--          during the onTurn update
--          "custom" means the update must be programmed in manually using <item>Data.update


--  <item>Data.defineModulePhrase(moduleName,phraseName,defaultValue="",resetTime="never")


--  <item>Data.phraseGetValue(<item>,phraseName) --> string
--  <item>Data.phraseGetValue(<item>,phraseName,moduleName=nil) --> string
--      returns the value associated with the <item>'s phrase of phraseName
--      if the value is nil, the default specified during the definition is returned


--  <item>Data.phraseSetValue(<item>,phraseName,value) --> void
--  <item>Data.phraseSetValue(<item>,phraseName,value,moduleName) --> void
--      sets the value associated with <item>'s phraseName to value


--  <item>Data.phraseReset(<item>,phraseName) --> void
--  <item>Data.phraseReset(<item>,phraseName,moduleName) --> void
--      sets the value associated with the <item>'s phraseName to nil
--      (meaning that it returns the default value set when it was defined)


--  <item>Data.phraseIsNil(<item>,phraseName) --> boolean
--  <item>Data.phraseIsNil(<item>,phraseName,moduleName=nil) --> boolean
--      returns true if the underlying value of <item>'s phraseName phrase is nil
--      and false otherwise


-- <item>Data.defineGeneric(dataName,updateTime="never",updateAll=false,updateFunction=nil)
--      defines a generic entry for <item> data, and can keep any item that is
--      "state savable" (since it must be saved in the state table)
--      An item is "state savable" if it is either
--      nil
--      a number
--      a string
--      a boolean
--      a table with keys that are numbers or strings
--        and with values that are also state savable
--      "generic" data doesn't have the same guards against misuse 
--      that the other <item>Data types have, but it is more flexible
--      gen.isStateSavable(item) may be useful to you
--      updateTime = "never", "onTurn", "onTribeTurnBegin", "onTribeTurnEnd", "custom"
--          updateTime defines when the updateFunction is executed
--          if <item> has no owner, onTribeTurnBegin and onTribeTurnEnd updates happen
--          during the onTurn update
--      updateAll = nil or boolean
--          if true, the update function is applied to all <item>, not just those with
--          non-nil values for this generic data
--      updateFunction = function(value,<item>ID) --> value
--          takes the existing value for <item>'s generic data under dataName
--          and the <item>'s ID number, and produces a new
--          value for the generic data under dataName


-- <item>Data.defineModuleGeneric(moduleName,dataName,updateTime="never",updateFunction=nil)


--  <item>Data.genericGetValue(<item>,keyName) --> value
--  <item>Data.genericGetValue(<item>,keyName,moduleName) --> value
--      returns the value stored by the <item>'s keyName


--  <item>Data.genericSetValue(<item>,keyName,value) --> value
--  <item>Data.genericSetValue(<item>,keyName,value,moduleName) --> value
--      changes the value stored by the <item>'s keyName to value


-- <item>Data.update(<item>,time="custom",tribe=nil,key=nil)
--      updates all of <item>'s data keys that have an updateTime of time, unless
--      key is specified, in which case, update that key only
--      time can be "onTurn", "onTribeTurnBegin","onTribeTurnEnd", "custom"
--      tribe is considered to be the active tribe, relevant for onTribeTurnBegin and onTribeTurnEnd updates
--      if key is specified, that key's update time must be the same as the time specified


-- <item>Data.generalUpdate(time="custom",tribe=nil)
--      updates data keys that have an updateTime of time for all <item>s 
--      time can be "onTurn", "onTribeTurnBegin","onTribeTurnEnd", "custom"
--      tribe is the tribe considered to be the active tribe, 
--      relevant for onTribeTurnBegin and onTribeTurnEnd updates


-- <item>Data.transferData(old<item>,new<item>)
--      associates the <item>Data from the old <item> to the new one
--      (deleting the association with the old one)
--      new<item> can't be nil


-- <item>Data.transferOrDeleteData(old<item>,new<item>)
--      if new<item> is not nil, transfers the data from the
--      old <item> to the new one (deleting the data for the old one)
--      if new<item> is nil, the data is deleted for old<item>


-- <item>Data.deleteData(<item>)
--      deletes the data associated with the <item>


-- <item>Data.validate(<item>)
--      checks that the item is still the same <item> it was before
--      (i.e. that the <item> hasn't been deleted and the ID reused)
--      If it has, eliminate all data for that <item>


-- <item>Data.changeValidationInfo(<item>)
--      replaces existing values of the sameItemCheck with new ones

I didn't mention validation in the template documentation, since it only applies to a couple data types. For units, for example, there is a chance that a unit might be disbanded/deleted/whatever and a new unit created with the same ID before the data "maintenance" notices (and it wasn't caught by events). Hence, the game stores the unit type's ID, and the owner's ID, and checks them before accepting that the data still applies to the unit.

If anyone is interested, here is what is needed to generate a data module.

Code:
-- supplementalData.buildModuleFunctions(moduleName,itemName, isItem, getItemID, getItemFromID,
--          itemIteratorGenerator, sameItemCheck)
--      Builds a supplemental data type module for <item>s
--      moduleName = string
--          the name of the module
--          e.g. unitData
--      itemName = string
--          a name for <item> to be used in error messages
--      isItem = function(anything) --> bool
--          the function to check if something is an <item>
--          e.g. civ.isUnit
--      getItemID = function(<item>) --> integer >= 0
--          converts an <item> to a unique integer >= 0
--          e.g. function(unit) return unit.id end
--      getItemFromID = function(integer) --> <item> or nil
--          takes an integer and retrieves the item corresponding
--          to that ID, or nil if none exists
--          e.g. civ.getUnit
--      itemIteratorGenerator = function() --> iterator
--          a function that returns an iterator over all <item>s
--          e.g. civ.iterateUnits
--      getTribe = function(item) --> tribe or nil
--          For items upgraded onTribeTurnBegin or onTribeTurnEnd,
--          determines which tribe the update will happen for
--          if nil is returned, the item is updated in the onTurn update sequence
--          e.g. function(unit)  return unit.owner end
--      sameItemCheck = function(<item>) --> value1,value2,value3
--          generates 3 state savable values that are unlikely or impossible to be true
--          if the <item> has been destroyed and its id recycled
--          e.g. verifyUnit(unit) return unit.type.id, unit.owner.id, nil end
--
 
Hi @Prof. Garfield , so with the LST when my naval units attack a loaded sea transport, the ground units defend first as the sample function provided is to make planes defend first on air protected stacks.

1664151430353.png


I have added the following to combatSettings.lua (basically returning a huge negative number) but it seems not to work as land units keep defending first:

SQL:
local function defenderValueModifier(defender,tile,attacker)
    -- added to ensure land units on sea transports don't defend!
    if defender.type.domain == domain.ground and tile.baseTerrain == object.bSea then
        return -1e8
    else
    -- default
        return 0
    end
end

I declared the table of domains copied from civlua:

SQL:
local domain = {ground = 0, air = 1, sea = 2}

Any suggestion please?

Thanks,

Pablo
 
@Pablostuka

Right after the basic definition of defenderValueModifier, there is this section of code which overwrites the function if simpleSettings.fightersAttackAirFirst is enabled:

Code:
if simpleSettings.fightersAttackAirFirst then
    local function tileHasCarrierUnit(tile)
        for unit in tile.units do
            if gen.isCarryAir(unit.type) then
                return true
            end
        end
        return false
    end
    defenderValueModifier = function(defender,tile,attacker)
        if gen.isAttackAir(attacker.type) and defender.type.domain == 1 
            and defender.type.range >= 2 and
            not gen.hasAirbase(tile) and not tile.city and
            not tileHasCarrierUnit(tile) then
            return 1e8
        else
            return 0
        end
    end
end

Try eliminating/commenting out this code (or adding your modification to it). If that doesn't fix the problem, then let me know.

That said, this is a mistake in the template, the default onChooseDefender should make sea transports defend before their cargo. I'll have to make a fix.
 
Try eliminating/commenting out this code (or adding your modification to it). If that doesn't fix the problem, then let me know.

That said, this is a mistake in the template, the default onChooseDefender should make sea transports defend before their cargo. I'll have to make a fix.
Silly me, that worked thanks! I didn't realise the function itself was being redefined and my fighters flag was true :hammer2: It's a bit odd for me to have the power to dynamically redefine functions but I'll get used to it! :)

BTW if you are going to fix that, it would be also good to include some code that destroys all the land units if their sea transport is destroyed, just as the default TOTPP behaviour. Right now the stack of land units remain once the transport is sunk:

1664179278870.png
 
BTW if you are going to fix that, it would be also good to include some code that destroys all the land units if their sea transport is destroyed, just as the default TOTPP behaviour. Right now the stack of land units remain once the transport is sunk:
Probably some default code onUnitKilled to ensure TOPP NoStacksKill behaviour is not overriden:
1664187588209.png
 
BTW if you are going to fix that, it would be also good to include some code that destroys all the land units if their sea transport is destroyed, just as the default TOTPP behaviour. Right now the stack of land units remain once the transport is sunk:
I'm guessing that unit.carriedBy isn't set to the transport for the units in that stack. In any case, it would still make sense to write some failsafe code to destroy land units when the last transport is sunk.
 
I updated the traits module to fix a bug with traits.hasAnyTrait. As far as I know, @Pablostuka is the only one using this at the moment, but if anyone else is, they should download the updated file.
 
Supplemental data has been added to the template. Right now, there are modules for city data, unit data, and tile data. However, the supplemental data module would make adding data for tribes or techs or whatever pretty trivial, if anyone wants that. My testing file has ~800 lines of code, so this should be in good condition. Of course, something may very well have slipped through the cracks, so let me know if there are any problems.

I had the range for land and sea feature enabled by default, so I disabled it. It would only matter if you had a rules.txt with a non-zero range for a land or sea unit.

Hi @Prof. Garfield , so with the LST when my naval units attack a loaded sea transport, the ground units defend first as the sample function provided is to make planes defend first on air protected stacks.

[IMG width="145px" alt="1664151430353.png"]https://forums.civfanatics.com/attachments/1664151430353-png.640190/[/IMG]

I have added the following to combatSettings.lua (basically returning a huge negative number) but it seems not to work as land units keep defending first:
This has been fixed in the template. I also scrapped the confusing re-definition of defenderValueModifier. I don't know why I did it the way it was before.

BTW if you are going to fix that, it would be also good to include some code that destroys all the land units if their sea transport is destroyed, just as the default TOTPP behaviour. Right now the stack of land units remain once the transport is sunk:
I started looking into this, and for a little while, I was getting the bug of the land units not being destroyed, even if they had a valid carriedBy field. Then the bug just stopped, and I have no idea why. I will add a bit of failsafe code so that land units aren't stranded at sea, but I wanted to be done with supplemental data today.
 
I updated the template so that most files have a 'versionNumber' variable at the top of the file, as well as a 'fileModified' boolean. Some code has also been added elsewhere in those files to make the rest of this work.

You can specify that a minimum version of a particular file is required by appending :minVersion(number) to the require line. E.g.
Code:
local gen = require("generalLibrary"):minVersion(1)
If your version of the file is out of date, an error will be produced on load, telling you to update. In addition, if fileModified has been set to true, a warning will be added that you've modified the file.
Code:
C:\Test of Time\Template\LuaCore\generalLibrary.lua:5917: The LuaCore\generalLibrary.lua file is out of date.  It is version 0, but one of your other files needs version 1 or later.  You should download the most recent version of the Lua Scenario Template, and replace LuaCore\generalLibrary.lua with the updated version.
IMPORTANT WARNING: it appears you've changed this file for your scenario.  Replacing this file will remove those changes.  You will have to reimplement them in the new version of the file.
stack traceback:
    [C]: in function 'error'
    C:\Test of Time\Template\LuaCore\generalLibrary.lua:5917: in function 'generalLibrary.minVersion'
    C:\Test of Time\Template\events.lua:152: in main chunk

There is a similar mechanism that will just print stuff to the console :recommendedVersion(number).
Code:
local simpleSettings = require(simpleSettings):recommendedVersion(2)
This is for cases where an outdated file doesn't break anything, but does limit features. In the case of simpleSettings, settings in the table are nil by default, so if they aren't defined in the file, it doesn't matter. However, it does mean you're missing out on some potentially useful features.

There is also a script file updateInfo (in the CTRL+SHIFT+F4) menu, which will keep track of updates. If there is a new feature, it will print the recommended text (or tell you that there is a file that you are missing completely). If there is a bugfix, that will use the minVersion method instead.

I'm hoping that this will allow someone to add/update a file in their scenario in order to get a new feature, and then be able to follow the line of error messages until they've updated everything necessary. If they use the fileModified boolean, they'll be warned about losing customisations to files only when they've actually been made.

If a file doesn't have the minVersion/recommendedVersion methods, the error will look something like this:
SQL:
[keyPressSettings.lua]
[28]    local keyboard = require("keyboard"):minVersion(1)
Code:
..est of Time\Template\MechanicsFiles\keyPressSettings.lua:28: attempt to call a nil value (method 'minVersion')
stack traceback:
    ...est of Time\Template\MechanicsFiles\keyPressSettings.lua:28: in main chunk
    [C]: in function 'require'
    C:\Test of Time\Template\events.lua:111: in upvalue 'requireIfAvailable'
    C:\Test of Time\Template\events.lua:122: in local 'attemptToRun'
    C:\Test of Time\Template\events.lua:169: in main chunk
It is an attempt to call a nil value on the require line.
 
That's interesting but I don't really see why it would be needed unless I'm misunderstanding the whole purpose of it. Isn't the designer supposed to know and include the right versions of the scripts they need and release their scenario with whatever version they worked with? Even if it's not the most recent codebase in your repository, whatever they used should work with their design, is that right?

Unless the player who downloads the scenario is the one responsible to update the code, but what for? :crazyeye:
 
That's interesting but I don't really see why it would be needed unless I'm misunderstanding the whole purpose of it. Isn't the designer supposed to know and include the right versions of the scripts they need and release their scenario with whatever version they worked with? Even if it's not the most recent codebase in your repository, whatever they used should work with their design, is that right?

Unless the player who downloads the scenario is the one responsible to update the code, but what for? :crazyeye:
I thought the way you did at first. The issue is that I might develop something a scenario designer find useful after they start working. Given how long scenario development takes, this is actually rather likely. I've updated scenario code bases a few time already. Even if I'm the one still doing the updating, this system will make it much easier.
 
I thought the way you did at first. The issue is that I might develop something a scenario designer find useful after they start working. Given how long scenario development takes, this is actually rather likely. I've updated scenario code bases a few time already. Even if I'm the one still doing the updating, this system will make it much easier.
Yes but then how the designers themselves will be aware of which versions of your modules they need? Aren't they the ones setting the versions in the require lines when importing modules in their scripts?

Sorry for the questions, just trying to understand. Thanks
 
Yes but then how the designers themselves will be aware of which versions of your modules they need? Aren't they the ones setting the versions in the require lines when importing modules in their scripts?
You don't have to set a version number when requiring a file. You can just use a regular require line. However, if you want to use minVersion, you can just open the file you're requiring and see the version number at the very top of the file. Then you'll be sure that you've guaranteeing a version of the file that has all the functions you use.

Maybe an example of how I expect this to work will help. (It won't actually work for this example, since anyone upgrading won't have versions in place.)

Someone has a problem that the unitData module will solve. So, I send them the unitData.lua file, and the supplementaryData.lua file that it depends on. However, I forget that I wrote gen.validateFunctionArgument at the same time that I wrote supplementaryData.lua. (If I expected it to only be useful for that module, I wouldn't have put it in the General Library.) The General Library is a module that has been around a long time, so an older version already exists in the scenario. In the old system, the require line will work properly and the general library will load, and everything will be fine until gen.validateFunctionArgument is actually called by the code somewhere, and the Lua Console complains about trying to call a nil value. In the new system, I have a minVersion method on the require line, so the error will be generated during the file loading phase, and will have a helpful error message explaining that a more recent version is needed.

I guess the minVersion functionality is mostly useful for me, since I'm the only one writing code without a known end user.
 
aah ok ok got it now, yeah it makes sense for you to ensure designers use the latest code. Yeah.

I was thinking..."but why? I'm the one who is grabbing the latest codebase myself, why would set up a minimum version required if it's me who's writing the code and ensuring the LST is up to date?" :D
 
Hello @Prof. Garfield,

currently I'm using flags in 'onCityTaken.lua' if I would like to say lua that a certain city is conquered by the player. For example:
Code:
if city == object.cArcadia and object.pOttomanEmpire.isHuman == true and flag.value("OttomanByzantineEmpire") == true and defender == object.pIndependentNations then
    gen.justOnce("Arcadia conquered (Ottoman)",function ()
    flag.setTrue("OttomanArcadiaConquered")
    end)
end

Would it be possible, that lua could check in 'events.onTurn' if a certain city is under control of the player or not?
For example I'm using this line and would like to check lua if Hamburg is under control of the player instead of using flags, how should the code look like for working correct (in case if this would be possible)?
Code:
if turn >=42 and turn <= 91 and object.pSpanishHabsburgEmpire.isHuman == true and flag.value("HamburgProtestantYes") == false and math.random()<0.12 then...
 
You can always use
Code:
city.owner
to check who is the current owner of a city
Code:
if turn >=42 and turn <= 91 and object.pSpanishHabsburgEmpire.isHuman == true and object.cHamburg.owner == object.pProtestants and math.random()<0.12 then
 
Top Bottom