[TOTPP] Lua Scenario Template

@Prof. Garfield Thanks very much, I have it now.

When you have a moment, can you please take a look at the applyWonderBonus()function in generalLibrary.lua? I think there's a bug in the "check if expired" section. Specifically, on every iteration of that loop, tribe:hasTech(wonder.expires) is repeatedly referencing the tribe that was passed in as a parameter. I think you meant to write civ.getTribe(i):hasTech(wonder.expires) or alternatively, civ.hasTech(civ.getTribe(i), wonder.expires).

I also noticed that your loop starts with 0 (barbarians). In my testing, I'm seeing inconsistent behavior on that front. If you give a wonder-expiring tech that has never been researched to the barbarians, the in-game popup appears announcing that this cancels the effect of the wonder. But it doesn't actually do so! It looks to me like the wonder continues to function until a non-barbarian tribe acquires the tech. So I'd propose that this loop should really start with 1 instead of 0.

Finally, this function seems to be very unusual in that it's a local function only, which is not assigned into the gen table. Is there a reason for this? I'm working on the Lua code for the combat logic, and trying to incorporate existing General Library functions rather than duplicate them as helper functions in the combat logic file. applyWonderBonus() has the logic I need (or it will with the changes mentioned above) but isn't actually accessible.

Lest you think I'm being too critical of your work, I should mention that in figuring all this out, I realized that my own Medieval Millennium code to handle this concept also has a bug (but a different one). :lol: My issue is that I tried to shortcut checking civ.hasTech() for every active tribe by simply checking tech.researched or more precisely wonder.expires.researched. Unfortunately this isn't perfectly equivalent. This field is set to true as soon as any tribe (barbarians or not) acquires that tech, either by research or event. Furthermore, if an event takes away the tech from every tribe, tech.researched remains true -- it doesn't get reset back to false. So for both of these reasons, it isn't equivalent to whether or not any non-barbarian active tribe currently possesses the tech, which is apparently the rule when it comes to wonder expiration. (It works just fine to reactivate a wonder by taking away the wonder-expiring tech from all tribes, despite tech.researched remaining true.)
 
When you have a moment, can you please take a look at the applyWonderBonus()function in generalLibrary.lua? I think there's a bug in the "check if expired" section. Specifically, on every iteration of that loop, tribe:hasTech(wonder.expires) is repeatedly referencing the tribe that was passed in as a parameter. I think you meant to write civ.getTribe(i):hasTech(wonder.expires) or alternatively, civ.hasTech(civ.getTribe(i), wonder.expires).

Yes, that is a mistake. I bet that when I tested that I just gave the tech to the active tribe, and so never noticed.

I also noticed that your loop starts with 0 (barbarians). In my testing, I'm seeing inconsistent behavior on that front. If you give a wonder-expiring tech that has never been researched to the barbarians, the in-game popup appears announcing that this cancels the effect of the wonder. But it doesn't actually do so! It looks to me like the wonder continues to function until a non-barbarian tribe acquires the tech. So I'd propose that this loop should really start with 1 instead of 0.

I believe that I wrote that code to support calculating the movement allowance of a damaged unit (gen.maxMoves takes wonders into account for sea units, but the sample code in the thread does not), so I was focused on testing for unit movement.

If you notice any errors in any code I've written, please correct them. Leave a note that you've tested the barbarian thing, so that someone doesn't come back later and wonder why barbs are excluded (Make sure to test for when COSMIC2's BarbResearch is set to 1, a v0.16 addition). If you need something that feels like it should be in the module, feel free to add it as well (and leave a note that you wrote it if you care about such things).

At the moment, I'm overhauling the way the Template is linked together, so I can't easily update the repository until I've finished that and tested that all the event types fire properly. Make any changes you need, and I'll use difference software to merge the changes into the General Library when I'm ready.

Finally, this function seems to be very unusual in that it's a local function only, which is not assigned into the gen table. Is there a reason for this? I'm working on the Lua code for the combat logic, and trying to incorporate existing General Library functions rather than duplicate them as helper functions in the combat logic file. applyWonderBonus() has the logic I need (or it will with the changes mentioned above) but isn't actually accessible.

Based on the comment I left before the function, I meant to make it available in the gen table as well as a local, but never did. I probably read somewhere that local variables were ever so slightly faster than accessing from a table, and figured that it might be used a lot and I wasn't giving up readability or decent error checking by making it local. Go ahead and add it to the gen table. It may make sense to give it a different 'public facing' name, since the function name implies it is doing something, rather than returning a boolean. Perhaps:
Code:
gen.isWonderActiveForTribe = applyWonderBonus

Lest you think I'm being too critical of your work, I should mention that in figuring all this out, I realized that my own Medieval Millennium code to handle this concept also has a bug (but a different one).

I didn't think you were being critical at all. These are legit bugs and oversights. In any case, if my code receives "criticism," that means that someone has at least found it useful enough to use and let me know how it could help them better. Otherwise I'm left wondering if anyone ever cared that I did the work. For a lot of the work I do, I test to a "not obviously broken" standard, not to a "check every corner case" standard, on the basis that testing isn't very fun, and a particular feature may never end up being used anyway. So, a "this is broken" message is good news to me in the sense that someone is using my work, and I didn't have to do the work to find the bug.
 
Thanks for your response. I made the necessary changes to this function in generalLibrary.lua, but I don't seem to be able to submit it to your project in Github. When I navigate to the LuaCore folder, choose "Add file" and then "Upload files", I get a page that says, "Uploads are disabled. File uploads require push access to this repository."

I'm attaching the file here. Are you able to commit this as a single file, without syncing your whole project and the other changes you have underway? Or are you able to make a change to the project configuration that would give me access to do this? I set up a Github account with a username of Knighttime1000 (Knighttime by itself was already taken, grrr!) in case you need that info to provide user-specific access.
 

Attachments

I've made some pretty major changes to the structure of the Lua Scenario Template. If you don't understand a change, it probably won't impact you (but feel free to ask for clarification anyway, just to be sure).

I have added a rushBuySettings.lua file to MechanicsFiles, and an onChooseDefender section to combatSettings.lua (formerly initiateCombat.lua). I combined onChooseDefender and onInitiateCombat into one file, since I think it very likely that those will be changed at the same time.

In parameters.lua, I run the following line
Code:
    civ.scen.compatibility.activateUnitEveryMove = true
to take advantage of TOTPP17's update to the onActivateUnit trigger.

The LuaTriggerEvents directory has been removed. The 'universal trigger events' have been moved to the EventsFiles directory, as have legacyEvents.txt and getLegacyEvents.lua. All these files can be deleted if they are not needed, since I've been using pcall to make many 'require' requests effectively optional. The EventsFiles directory is there in the event that a scenario becomes too large to be properly organized in the consolidatedEvents.lua or discreteEvents.lua files.

The 'context' directories have been removed. With optional 'require,' I have written that functionality into a module if anyone needs it, though I haven't tested it yet. triggerEvents.lua has been removed, since without context functionality, its function can be taken over by events.lua, discreteEvents.lua and consolidatedEvents.lua.

The LuaRulesEvents directory has been renamed MechanicsFiles, since the files in that directory generally deal with game mechanics.

I have used the discrete events module to link many modules to the state table and other trigger events. This should facilitate introducing new modules into existing projects (which before typically required changing events.lua as well). These modules all use an optional require to check for the discreteEventsRegistrar, so the modules can still be taken out of the Template to be used elsewhere if desired.

The keyPressSettings.lua file now calls upon the discrete events to register key press events, so key press events can now be registered in other files if desired.
 
Thanks for your response. I made the necessary changes to this function in generalLibrary.lua, but I don't seem to be able to submit it to your project in Github. When I navigate to the LuaCore folder, choose "Add file" and then "Upload files", I get a page that says, "Uploads are disabled. File uploads require push access to this repository."

I'm attaching the file here. Are you able to commit this as a single file, without syncing your whole project and the other changes you have underway? Or are you able to make a change to the project configuration that would give me access to do this? I set up a Github account with a username of Knighttime1000 (Knighttime by itself was already taken, grrr!) in case you need that info to provide user-specific access.

I'll have to look into changing Github settings. For now, I just merged the two files, and uploaded the merged version with my changes. I've attached the new file. Let me know if you have any trouble with it.
 

Attachments

OK... looks like it should be fine. I'm basically done with the combat logic file, and it's working well for me so far, but I have a little more testing to do with overriding values. I'm guessing (hoping) you'll want to integrate it into the LST somehow, but I'll leave the best way to accomplish that up to you. I'll post it in the other thread that I started with combat logic details when it's ready, and tag you there.
 
Hello Prof.Garfield,

I've a small question about city conquest with LUA. I would like to set the defender of a city to "Anybody" like it is possible in Macro language. How should I change the following code?
Is it sufficient if I only delete the line "defender == object.pRebels"?

Code:
if city == object.cQuetzaltenango and defender == object.pRebels then
    gen.justOnce("Quetzaltenango conquered",function ()
    object.cQuetzaltenango.name = "Guatemala City"
    end)   
end
 
Is it sufficient if I only delete the line "defender == object.pRebels"?

Yes, that is correct.

Code:
if city == object.cQuetzaltenango then
   gen.justOnce("Quetzaltenango conquered",function ()
    object.cQuetzaltenango.name = "Guatemala City"
    end)   
end
 
When I navigate to the LuaCore folder, choose "Add file" and then "Upload files", I get a page that says, "Uploads are disabled. File uploads require push access to this repository."

I've given you push access to the Lua Template repository, so you should be able to use the 'upload file' button if you need to. Generally, however, to suggest a change to a project you would submit a 'pull request' to the project with your change.
 
I've given you push access to the Lua Template repository, so you should be able to use the 'upload file' button if you need to.
Thanks, I received and accepted the invitation. I do want to be clear that I would never push changes into this repository without receiving your blessing and approval beforehand.

Generally, however, to suggest a change to a project you would submit a 'pull request' to the project with your change.
Got it, thanks. I've used a couple of other code collaboration products in the past, however GitHub seems to use different terminology for some functions and features. I probably should watch a few "how to use GitHub" videos in order to feel more comfortable with the interface and more confident that I'm going to use it the "right" way.
 
I've updated the template to integrate @Knighttime's combat calculator. The interface is in combatSettings.lua. I also added the 'repeatMove' variable for onActivateUnit interfaces.
 
Hello Prof.Garfield,

I've downloaded your latest LUA files because I'm planning to make France and the Protestant League playable in my Reformation scenario too.
Every nation will get it's own events so there will be a huge amount of events in a sum.

You've added the consolidated.Events.lua and discreteEvents.lua file in your package so I would like to ask which one I should use?
Would it be possible to create single event files for every nation and link to them in the main event file?
 
You've added the consolidated.Events.lua and discreteEvents.lua file in your package so I would like to ask which one I should use?

You can use whichever one suits you, or both together.

In consolidatedEvents.lua, all the code for a particular execution point goes in the same function, like you would have written it before (either in triggerEvents.lua, or in a dedicated file). In discreteEvents.lua (or any other file that requires the discreteEventsRegistrar.lua module), you can break up your events into smaller parts, and writing

Code:
function discreteEvents.onExecutionPoint(arg1,arg2)
    -- some code
end
will make the discrete events registrar record that code behind the scenes and run it with all the other code registered in that way. However, the order of execution will only be preserved within a single file. That is, if you have discreteEventsA.lua and discreteEventsB.lua that both register discrete events, make sure that there will be no problem if the B code is executed before the A code, or the A code is executed before the B code.

Would it be possible to create single event files for every nation and link to them in the main event file?

I have a module context.lua in the Lua core, which will allow you to do what you want. Here's the description:
Code:
-- The context module is meant to facilitate the introduction
-- of code that should only be run in certain situations,
-- but is impractical to check for those situations every time
--
-- For example, you may want to have substantially different
-- events based on whether the scenario is being played
-- as single player or multiplayer.
--
-- First, we choose directory names where we're going to store
-- the separate code, for example SinglePlayer, and Multiplayer
--
-- Next, we must somewhere register our context checker function.
-- The context checker function determines which directory will
-- be checked for code.  If that function returns nil (as the
-- default version always does), then no context code is retrieved.
--
-- context.registerSelectContext(function()
--      if flag.value("multiplayerVersion") then
--          return "Multiplayer"
--      else
--          return "SinglePlayer"
--      end
-- end)
--
--  At some point, we will want to use some context dependant code.
--  To do so, we call:
--
--  context.aFileName.someFunction(arg)
--
--  At this point, context will run the selectContextFn (registered above)
--  and get a context directory name.  Suppose it is Multiplayer.
--
--  If Multiplayer\aFileName.lua does not exist, then a table with an 
--  empty function for every key is returned, so no computation is done
--  and nil is returned.
--  If  the file does exist, then someFunction is run from Multiplayer\aFileName.lua
--
--  -- require, helpers etc.
--  local prefix = {}
--  function prefix.someFunction(arg)
--      -- some code
--  end
--  -- possibly other code
--  return prefix
--
--  at this point, if someFunction doesn't exist in the file, there will be an error
I don't think I got around to testing this module yet, but any fixes should simply involve replacing the entire context.lua file. Since someone actually needs this, I'll get to work on it.

If any of this was confusing, let me know, and I'll try to clear it up.
 
I've downloaded your latest LUA files because I'm planning to make France and the Protestant League playable in my Reformation scenario too.
Every nation will get it's own events so there will be a huge amount of events in a sum.

The context.lua file in LuaCore needs updating. I've attached the file, but I've also updated the repository if you haven't downloaded the template yet.

I also have a small 'context' example (in the repository under the LuaDocumentation folder also), which has some simple key press events to change the context and to make different messages depending on the context.

Extract the zip contents into the main scenario folder, and add the line
Code:
local contextExample = require("contextExample")
to events.lua or some file that is required by events.lua (e.g. discreteEvents.lua).
 

Attachments

I just added a pathfinding module 'aStarCiv.lua' to the LuaCore. This has been used in Cold War, and works there.

The aStarCiv.aStar function takes a handful of functions as part of its arguments to determine the exact details of how pathfinding should work in the particular case. I think that I never put it in the Template since I was planning on adding some generic tools. I wrote one for finding a sea path (aStarCiv.findSeaPath), which should serve as a basic example. If anyone needs generic path finding, let me know and I'll add it to the module. (Also, if you need something fairly specific, let me know, and I'll help you write that as well.)

Code:
-- start is a tile object or table of such objects (if multiple places can act as the 'start')
-- goal is a tile object or table of such objects (if any one of them is a valid goal)
-- heuristic(startTile,endTileOrTable) --> number
--      an estimate of the cost from startTile to endTile
--      if the goal is specified as a table of tiles, heuristic should be able to
--      accommodate that and accept a table of tiles as an argument
--      does NOT have to accommodate a table for startTile, even if multiple
--      starting locations are valid
--  neighbours(tile,start,goal)--> table of {neighbouringTile,cost from tile to neighbouring tile}
--      returns the neighbouring tiles of a tile, and the cost to get to them
--      start and goal are included parameters (in case, for example, you want to limit how far away the path can take
--  pathCost(orderedTableOfTiles)-->number
--  stopLookingHeuristicCost
--      If the best estimated cost (fScore) exceeds this cost, stop looking and return false
--      default is math.huge (explore all possibilities)
function aStarCiv.aStar(start,goal,heuristic,neighbours,pathCost,stopLookingHeuristicCost) --> cost,pathTable
 
Here are some text instructions for converting a scenario to Lua, and setting up the can build code generator. This video has instructions as well. You will need the most recent version of TOTPP, since parts of the template expect that.

Part A: Add the Lua Template
  1. Make a backup copy of your scenario folder, just in case.
  2. If you have an events.txt file for the old style "macro" or "legacy" events in your scenario folder, rename it to legacyEvents.txt.
  3. Download the LuaTemplate code. Go to https://github.com/ProfGarfield/LuaTemplate, click on green 'code' button, select download zip option.
  4. Copy the contents of this template into your scenario folder. Do not include the LuaTemplate-main folder. That is, you should be copying 6 folders and a couple other files into your scenario folder.
  5. If you have legacy events, move legacyEvents.txt from the main scenario folder into the EventsFiles folder, replacing the existing file.
  6. If your legacyEvents.txt file is in good order, you should be able to load your existing scenario and play it without issue.
Part B: Create 'object' files that other programs will reference.

We will be creating two files here: object.lua, and object.js. The object.lua file is a file that associates a wide variety of programming objects with human readable names, so that code is easier to write and follow. The file object.js provides this same information to the html file that will be run in the web browser to generate the production restrictions code.

1. (optional) If units, improvements, wonders, or advances in your rules.txt file have the same name, rename them with more descriptive names. Save and re-load your game. (In the video, I do this between instructions 6 and 7)

2. Enable cheat mode, press CTRL+SHIFT+F3 to open the Lua Console.

3. Press Load Script button. Navigate to the directory of the scenario you are working on. By default, you will probably be in <Test of Time Directory>\lua. Once in the scenario directory, open the Scripts directory. Choose the file makeObject.lua, and open that file. This will create a our object.lua file.

4. Open your file explorer, and navigate to the directory of the scenario you're working on. Open the LuaParameterFiles directory.

5. In LuaParameterFiles, you should find a file object.lua and a file XXXXXXXXXXobject.lua. The latter file was just created, and the numerical prefix is a number generated based on the current date and time (so that nothing is overwritten accidentally). Delete the existing object.lua file, and rename XXXXXXXXXobject.lua as object.lua.

6. (Optional) Search your new object.lua file for underscores (_). If multiple items have the same name, every one after the first will have underscores appended to the end of the name to distinguish them. You may wish to change these names, to make referencing them easier. If you choose to do this later, you can simply add another name for the same item as well. E.g.
Code:
object.uInfantry___ = civ.getUnitType(51)
Code:
 object.uInfantry___ = civ.getUnitType(51)
object.uInfantryFrench = civ.getUnitType(51)

7. Open the console again, press Load Script, navigate to the Scripts folder where you found makeObject.lua (you may already be there), and choose the makeObjectJS.lua file.

8. In your file explorer, navigate to the directory of the scenario you are working on, then to the Scripts directory.

9. Delete object.js and rename XXXXXXXXXXobject.js as object.js.

10. Open the file autoCanBuild.html in your web browser (this will probably be the default action for clicking on this kind of file). Check that the 'Choose an Item' drop down menu at the top of the page has the units, improvements, and wonders of your game. If so, then everything probably worked.

11. (Optional) If you changed the names of stuff in the rules.txt file, you can now change them back.
Part C: Generating Code

1. In your file explorer, navigate to your scenario folder. Open the MechanicsFiles directory. Then, open canBuildSettings.lua in your preferred text editor.
2. Scroll down the file. The top of the file has documentation. If you're only using the code generator, you won't need to worry about this. Part way down the file there are the following lines:
Code:
local unitTypeBuild = {}
local improvementBuild = {}
local wonderBuild = {}
local addBuildConditions = canBuildFunctions.makeAddBuildConditions(unitTypeBuild,improvementBuild,wonderBuild)
-- addBuildConditions(item,buildabilityParameters)
--      adds the buildabilityParameters to the appropriate table and index for item.  If the item
--      already has buildabilityParameters registered, the new set of parameters are automatically
--      added to the alternateParameters table
-- addBuildConditions(tableOfItems,buildabilityParameters)
--      for each item in the table, register the buildability parameters as above
Then, there is a sizable empty gap, followed by
Code:
-- canBuildFunctions.hideProcessingList() -- uncomment if you don't want the list of stuff being processed printed in the console
canBuildFunctions.supplyUnitTypeParameters(unitTypeBuild)
canBuildFunctions.supplyImprovementParameters(improvementBuild)
canBuildFunctions.supplyWonderParameters(wonderBuild)
Place all your generated code within this gap.

3. To choose a production item to generate restrictions for, change the selection in the drop down menu beside "Choose an item:" at the top of the left hand side.
Note: changing the item here does NOT reset the selected restrictions. This is convenient if you wish to give multiple items the same or similar restrictions, but it means you will have to reset everything manually for new situations. You can check that all restrictions have been removed by pressing the "Generate Code" button, and checking that the generated code looks like this:
Code:
addBuildConditions(object.uSampleUnit0, {})
If there is nothing between the { and }, then the item has only the standard Civ II production restrictions.

4. Select the desired restrictions for the unit/improvement/wonder in question. Then, on the bottom of the left hand side, press the "Generate Code" button. Beneath that, some code will be generated. Copy that code, and paste it into canBuildSettings.lua, within the gap identified in instruction 2.
If you create multiple build conditions for the same item, the item will be buildable if ANY of the conditions are met.

5. Any lists you create will not be saved if you close or refresh the page.

6. The "ShowCode" button beside a list (on the right hand side) will generate the code for the corresponding list. If a build condition has the "Use list variable name" selected, the generated code will refer to the list name, rather than re-creating the entire list. The code for that list should be placed before the first reference to it.
Generating code for a list and using the list variable name has a few advantages. First, if multiple conditions refer to the same list, you only need to change the list in one place to change all the conditions. Second, if the list has a good name, it is clear exactly what the list is and means (prussianHomeCities is probably more informative than a list of German cities). Third, if you have to close the code generator, you can just create empty lists with the same names when you start work again.​
 
Just made a modest 'future proofing' update to the template.

events.lua will now attempt to execute the file recentFeature1.lua upon startup. If that file exists, it will move on to recentFeature2.lua, then to recentFeature3.lua and so on in order, until a file doesn't exist. If a file is named recentFeature.lua, a text box will appear suggesting the appropriate name for the file.

What this should mean is that (in combination with the discrete events) I should be able to provide updated template features for scenarios in progress without having to make changes to existing designer modified files (the LuaCore will still be fair game).

I also made events.lua look for exampleFeature.lua, to provide a simple way to distribute example code.
 
I added "supplemental conditions" to the canBuild module. Basically, what this means is that by calling
Code:
canBuildFunctions.registerSupplementalCondition(itemType, function(defaultBuildFunction,city,item) --> bool)
You can add a function that must be true in order for the item to be built (if it is true, the city must still meet all other conditions).

The purpose is to allow modules to make adjustments to the buildability settings without relying on the designer to add them to canBuildSettings.lua. For example, I'm about to write a strategic bombing module, and it might make sense to forbid construction of a new factory if there is no place to put it (e.g. all nearby squares are water or used by other cities' factories already. I expect design will be smoother if by default the designer doesn't have to worry about what to do if there is no place for the new factory.

I've also included functionality to override these "supplemental conditions" both on an individual basis and for everything at once. The code generator has also been updated to include this override.
 
Hello @Prof. Garfield,
I've downloaded your LUA template files and tried to use it for a new scenario. When I'm loading the savefile the LUA console displays the following error messages:
upload_2021-12-30_13-31-57.png


May I ask you for checking this please? I didn't change anything on the template files
 
Back
Top Bottom