[TOTPP] Lua Scenario Template

There's no limit as far as I know to the number of improvements as far as I know. I don't think anyone has ever stress tested it but I don't see 3 being an issue.
 
All the relevent object.lua refs are in place - I'm just making sure that I can have three improvements.

Sure, you can make as many improvements mandatory as you would like.
 
I just updated the template repository.

The beforeProduction event has been implemented and linked to the template, as has onCalculateCityYield.

The General Library now has functions to calculate a city's production from the land (which was necessary to have in order to allow terrain production changes to happen within the cityYield event). If you need these, they are

Code:
-- gen.getTileProduction(tile,city) --> integer (food), integer(shields), integer(trade)
-- returns the tile production values, presuming that the city
-- given is the one working the tile
-- That is to say, returns the values that would be seen on the tile in the city window
-- Doesn't check if that city is actually working the tile

-- gen.computeBaseProduction(city)-->integer(food), integer(shields), integer(trade)
-- Computes the resources harvested by the city from the terrain
-- includes superhighway/supermarket/railroad bonus, but not factories/powerplants

I'm pretty sure I captured everything, but if you find an anomaly, please let me know. I never knew that a city square will count as having a mine if there is a mineBonus, but the irrigationBouns is 0. I only discovered this while testing on Imperialism. I think in the base game that would only come up for mountain terrain, and in Classic/MGE, I'm pretty sure the mountain had a food bonus of 1, even though it couldn't be irrigated.

I also added a "persistentRandom" functionality, which will allow you to use the same random number on multiple occasions.
Code:
-- gen.persistentRandom(key) --> number between 0 and 1
-- checks the persistentRandom table (within the state table)
-- for a value associated with key. If it exits, the value is
-- returned.  If it does not exist, a random number between
-- 0 and 1 is generated, stored in the table under the key,
-- and also returned
-- example of use: WWII scenario with seasons
-- You may want to have some games where the 1941 spring starts
-- in April, and other games where it starts in May.  When
-- determining whether to load winter or summer terrain stats during
-- 1941, you would use gen.persistentRandom("EarlySpring1941") < 0.5
-- as part of the season check in April, and load summer if the value is less than 0.5
-- and winter otherwise.  This way, each when each player starts their
-- game that month, they will all either get winter or summer terrain

-- gen.clearPersistentRandom(key) --> void
-- sets the value associated with the key in the
-- persistentRandom table.  This could either be for reuse of the key,
-- or to prevent the key from staying in the state table indefinitely

-- gen.getPersistentRandomTable() --> table
-- returns the persistentRandom table
I was doing some work on a 'seasons' module, and realised that something like this could be useful to add some variety in when the seasons change. I'm sure there are other uses for it as well.

I also made it so that if your scenario's folder name includes "AltLuaCore", the game will look for LuaCore files in
Code:
<Your TOT Dir>\Template\LuaCore
instead of
Code:
<Your Scenario Directory>\LuaCore

If you want, you can have work in progress scenarios refer to a Template folder in your Test of Time directory for updates to the Lua Core, and then just copy the versions that are current at the time you release. This was mostly for allowing me to have testing scenarios all refer to the same lua core, rather than fixing one file and copying it around. I figure AltLuaCore is unlikely to be a natural part of a scenario folder name.
 
Further updates to the template.

I've added in (in a basic form) the initiateCombat event, and commented out references to the combatResolution event. The files are still there (with a note about the deprecation), so if anyone really wants to use combatResolution, they can, as long as they uncomment the relevant code in events.lua.

I also deprecated (commented out references to in events.lua) the current "attack bonus" module, the one that uses the "Napoleonic" system of adding extra attack values when a unit is activated. The initiateCombat event should serve as a better way to achieve combat bonuses. I don't think anyone is using the attackBonus module, but I can put it back in if someone is.

I discovered that the combat log isn't actually recording combat at the moment. This probably affects @CurtSibling and @civ2units , but I don't know who else is building off the template. The fix is to change triggerEvents.lua, make sure to have the following references for log

Code:
local log = require("log")

Code:
function triggerEvents.unitDefeated(loser,winner,aggressor,victim,loserLocation,winnerVetStatus,loserVetStatus)
    context[getContext()]["unitDefeated"](loser,winner,aggressor,victim,loserLocation,winnerVetStatus,loserVetStatus)
    universal["unitDefeated"](loser,winner,aggressor,victim,loserLocation,winnerVetStatus,loserVetStatus)
    legacy.doUnitKilledEvents(loser,winner)
    log.onUnitKilled(winner,loser)
end
(This function is already in the file, you just need to add the log.onUnitKilled(winner,loser) line.)

If you want to use this version of the log, you should be able to copy your existing contents of LuaParameterFiles, LuaRulesEvents, and LuaTriggerEvents directories, along with your other scenario files. Don't copy over events.lua, and if you have been putting events into triggerEvents.lua (rather than using the UniversalTriggerEvents directory files), you will have to copy over the event contents from the old file to the new one manually. Let me know if you need help with this.
 
I'm using an attack bonus module in the scenario I sent you I believe.

If I were to write the template so that your existing attackBonusSettings.lua file provided the same attack bonus (except that you couldn't move units off the 'commander' and still have the bonus), would that be fine? Or, do you (or anyone else) need the feature that the bonus appears in the civlopedia and can also be 'carried' off the square with the leader that created the bonus?

Since the attack bonus functionality is being used in a scenario currently being developed, I'll try to get the functionality re-implemented tomorrow.

For anyone else using attack bonuses, is anyone not using the 'simple' version (that is, did they use the 'categoryAttackBonus')?
 
My attack bonus is Guderian and some others giving bonuses to panzers that can move up to 8 spaces with roads, so I don't know that it would work well if he had to be on the same tile unfortunately.
 
My attack bonus is Guderian and some others giving bonuses to panzers that can move up to 8 spaces with roads, so I don't know that it would work well if he had to be on the same tile unfortunately.

OK, if you have a situation where you specifically want units to be able to leave the tile and still retain the bonus, you're probably not alone and I'll restore the functionality to the template on the next update. All it is is uncommenting a couple lines.
 
Just wondering:

I'm using city co-ords for unit locations in "CanBuildSettings" - But how do I employ Lua to use the map landmass number? (in this case, zone 4)

The cities in the Canadian landmass can train Canadian troops - I'd like newly-settled cities in zone 4 to also be able to build them. Any tips?
 
Just wondering:

I'm using city co-ords for unit locations in "CanBuildSettings" - But how do I employ Lua to use the map landmass number? (in this case, zone 4)

The cities in the Canadian landmass can train Canadian troops - I'd like newly-settled cities in zone 4 to also be able to build them. Any tips?

You can submit functions into the 'location' parameter the same way you submit city coordinates (you can even use both at the same time). Your function should take a tileObject as an argument, and return true if the location can build the item, and false if not.

In your case, you want to define a function like this
Code:
-- isCanada(tile)-->bool
-- returns true if the tile is in landmass 4 (Canada), false Otherwise
local function isCanada(tile)
    if tile.landmass == 4 then
        return true
    else
        return false
    end
end
In fact, since you're making a single check, and == returns true or false, you can write the code as
Code:
local function isCanada(tile)
    return tile.landmass == 4
end

If you don't want to dedicate landmasses to each country, you can use gen.inPolygon instead
Code:
--#gen.inPolygon(tile,tableOfCoordinates)-->bool
-- the table of coordinates defines the corners of the
-- polygon.  Returns true if the tile is within the
-- polygon defined by the table of coordinates, and
-- false otherwise.  Checking that the map is correct
-- must be done separately
-- the entry:
-- tableOfCoordinates.doesNotCrossThisX
-- sets an x coordinate that the polygon does not
-- cross.  If absent, 0 is used,
-- meaning the polygon shouldn't cross the date line

Code:
local canadaPolygon = {--Table of coordinate defining the Canada Polygon}
local function inCanada(tile)
    return gen.inPolygon(tile,canadaPolygon)
end
 
Can you help me understand what I'm doing wrong here on the canBuildSettings?

This works:

improvementBuild[object.iCivilianPopulation.id] = {location=britishHomeCities}


This does not work:
improvementBuild[object.iCivilianPopulation.id] = {location=britishHomeCities,germanHomeCites}

This also does not work (even though I would have thought it would):
improvementBuild[object.iCivilianPopulation.id] = {location={britishHomeCities,germanHomeCites}}

Instead I get this error:

Code:
...f Time\Scenario\Hinge of Fate\LuaCore\generalLibrary.lua:2471: bad argument #1 to 'getTile' (number expected, got civ.city)
stack traceback:
    [C]: in function 'civ.getTile'
    ...f Time\Scenario\Hinge of Fate\LuaCore\generalLibrary.lua:2471: in function 'generalLibrary.getTileId'
    D:\Test of Time\Scenario\Hinge of Fate\LuaCore\canBuild.lua:180: in upvalue 'makeNewLocationParameters'
    D:\Test of Time\Scenario\Hinge of Fate\LuaCore\canBuild.lua:207: in local 'postProcessParameters'
    D:\Test of Time\Scenario\Hinge of Fate\LuaCore\canBuild.lua:240: in upvalue 'postProcessParameterTable'
    D:\Test of Time\Scenario\Hinge of Fate\LuaCore\canBuild.lua:326: in function 'canBuild.supplyImprovementParameters'
    ...enario\Hinge of Fate\LuaRulesEvents\canBuildSettings.lua:342: in main chunk
    [C]: in function 'require'
    D:\Test of Time\Scenario\Hinge of Fate\events.lua:35: in main chunk

I'm basically trying to set things up so that only certain cities (British and German) can build these two improvements because I have them tied into the strategic bombing system and I don't want to go through and add coordinates for every city on the map. Any ideas?
 
Can you help me understand what I'm doing wrong here on the canBuildSettings?

This works:

improvementBuild[object.iCivilianPopulation.id] = {location=britishHomeCities}


This does not work:
improvementBuild[object.iCivilianPopulation.id] = {location=britishHomeCities,germanHomeCites}

This also does not work (even though I would have thought it would):
improvementBuild[object.iCivilianPopulation.id] = {location={britishHomeCities,germanHomeCites}}

Instead I get this error:

Code:
This also does not work (even though I would have thought it would):
improvementBuild[object.iCivilianPopulation.id] = {location={britishHomeCities,germanHomeCites}}

britishHomeCities and germanHomeCities are both tables of coordinates already. The location parameter assignment expects a table of coordinates, not a table of (table of coordinates). You will have to merge the two tables into a single list.

Code:
local britishAndGermanHomeCities = {}
for __,loc in pairs(britishHomeCities) do
    britishAndGermanHomeCities[#britishAndGermanHomeCities+1] = loc
end
for __,loc in pairs(germanHomeCities) do
    britishAndGermanHomeCities[#britishAndGermanHomeCities+1] = loc
end

Code:
improvementBuild[object.iCivilianPopulation.id] = {location=britishAndGermanHomeCities}
 
I restored the the attack bonus to the template. (I won't be writing the replacement for initiateCombat today, however.)

I also added to a table merging function to the General Library, since @JPetroski 's situation is probably not unique.
Code:
-- gen.mergeTableValues(table,table,...) --> table
--  accepts an arbitrary number of tables as
--  arguments and returns a table with all
--  the values from all the tables.
--  Table keys are lost, and replaced by
--  integers starting at 1.
--  Duplicate values will appear multiple times
 
I just updated the template repository.

The beforeProduction event has been implemented and linked to the template, as has onCalculateCityYield.

The General Library now has functions to calculate a city's production from the land (which was necessary to have in order to allow terrain production changes to happen within the cityYield event).
...
I'm pretty sure I captured everything, but if you find an anomaly, please let me know. I never knew that a city square will count as having a mine if there is a mineBonus, but the irrigationBouns is 0. I only discovered this while testing on Imperialism. I think in the base game that would only come up for mountain terrain, and in Classic/MGE, I'm pretty sure the mountain had a food bonus of 1, even though it couldn't be irrigated.
Believe it or not, I was working on the same functionality at the same time! So naturally I had to take a look at what you posted to see how it compared. You found a few corner cases like the city square one that I didn't realize, and you added support for a lot of non-standard ocean tile improvements. Very thorough job, congratulations.

The only thing that I didn't find in your code was handling for pollution. That reduces the yield of a tile by 50%, and I think the reduction is rounded down, so the yield is rounded up. I'm not 100% sure about the timing, but I think this occurs as the very last adjustment for each tile element (food, shields, and trade). Let me know if I'm wrong though!
 
Believe it or not, I was working on the same functionality at the same time! So naturally I had to take a look at what you posted to see how it compared. You found a few corner cases like the city square one that I didn't realize, and you added support for a lot of non-standard ocean tile improvements. Very thorough job, congratulations.

The only thing that I didn't find in your code was handling for pollution. That reduces the yield of a tile by 50%, and I think the reduction is rounded down, so the yield is rounded up. I'm not 100% sure about the timing, but I think this occurs as the very last adjustment for each tile element (food, shields, and trade). Let me know if I'm wrong though!

:wallbash: You're right. I completely forgot about pollution. I'll have to update that soon.
 
Updated the General Library and events.lua to change how globals do and do not work.

The actual Global table (_G) is still disabled for adding indices and accessing indices that don't already exist. However, there are 2 global tables that you can put things in console and _global. The console table has existed for a while, and _global I just added.

If you want to use global variables, instead of writing myGlobalVariable, you write _global.myGlobalVariable. _global.state provides access to the state table (actually a table in the state table, so you don't accidentally overwrite some other part of state).

I've made the warnings for trying to use the state table more informative, explaining the common errors that lead to them:

Code:
myVariable = "seventeen"
...drive_c\Test of Time\Template\LuaCore\generalLibrary.lua:2820: 
You appear to have forgotten to put 'local' before 'myVariable' the first time you used it.
If you really did mean to make a global variable, write:
_global.myVariable
If you are trying to define a variable in the console, use the command:
console.restoreGlobal()
to restore access to global variables (locals don't work well in the console)

Code:
> civ.ui.text(myVariable)
...drive_c\Test of Time\Template\LuaCore\generalLibrary.lua:2791: 
The variable name 'myVariable' doesn't match any available local variables.
Consider the following possibilities:
Is 'myVariable' misspelled?
Was 'myVariable' misspelled on the line where it was defined?
(That is, was 'local myVariable' misspelled?)
Was 'local myVariable' defined inside a lower level code block?
For example:
if x > 3 then
    local myVariable = 3
else
    local myVariable = x
end
print(myVariable)
If so, define 'myVariable' before the code block:
local myVariable = nil -- add this line
if x > 3 then
    myVariable = 3 -- remove local from this line
else
    myVariable = x -- remove local from this line
end
print(myVariable)
If you really did mean to access a global variable, write:
_global.myVariable
If you are trying to work in the console, use the command:
console.restoreGlobal()
to restore access to global variables (locals don't work well in the console)
I hope these errors are more understandable. Let me know if you think they should be modified further, or are too long.
 
Small update to events.lua in the template.
These commands now run the code for the corresponding event, so you can test the event without having to cycle through a turn.
Code:
console.onTurn()
console.beforeProduction()
console.afterProduction()
Code:
console.commands()
prints the keys in the console table. Some are functions like the commands above, others give you access to certain modules like flag and counter, where you might want to use commands.

The Legacy Events negotiation settings aren't working properly (they've given me a lot of trouble for some reason). For now, just use Lua. Its not that hard in this case. Here's an example:
Code:
local legacy = require("legacyEventEngine")
local negotiationSettings = {}
local object = require("object")
function negotiationSettings.negotiation(talker,listener)
   -- Britain can't initiate conversation with anyone
    if talker == object.tBritain then
        return false
    end
    -- No one can initiate conversation with britain
    if listener == object.tBritain then
        return false
    end
    -- Germany can't send an envoy to USSR
    if talker == object.tGermany and listener == object.tUSSR then
        return false
    end
   
    -- all other cases, they can talk, so return true
    return true
end
return negotiationSettings
 
Back
Top Bottom