I've downloaded your latest template files and copied them into my scenario folder. I didn't change anything, just loaded a savegame and got the following error message:
Unfortunatelly I don't understand the error message so I would like to ask you for fixing this.
Biplane, no, 1, 2.,0, 0a,1d, 1h,1f, 1,0, 3, no, 000000000000001
Envoy, no, 0, 1.,0, 0a,1d, 1h,1f, 1,0, 6, no, 000000000000000
The problem is that I didn't think you could have 'no' for the expires field (second column). Set these to nil and try again. Elsewhere in the rules, 'no' and 'nil' mean different things for techs. I'll have to think about how to handle that kind of error.
I copied the file changeRules.lua from my old scenario into my new folder and it works with the 'no' settings.
A second error message appeared indicating that was something wrong with the seasonSettings.lua file:
I replaced the new file with the old seasonSettings.lua file and now everything works.
I copied the file changeRules.lua from my old scenario into my new folder and it works with the 'no' settings.
A second error message appeared indicating that was something wrong with the seasonSettings.lua file:
It was the exact same problem as before, this time with 'no' instead of 'nil' in the ENDWONDER section of rules.txt.
What is happening is that the changeRules.lua module is creating a table of objects with the same (in most circumstances) keys as unitType/Improvement/Wonder/etc. objects, but with values read from the rules.txt file. Errors are being caused because some of my code reads 'no' as false instead of nil. When 'no' is used in a prereq column, it means the item is excluded from the game, but, since Lua doesn't recognise the difference between 'no' and 'nil' in the rules.txt file, I record it as false instead, so other code can tell the difference.
I wasn't expecting to have 'no' in any expires column in rules.txt. Is there a reason you are using it over nil? In particular, does it make a difference in game? Or, is it just for making the rules a bit more readable? That may impact how I address the issue.
That's fine. If you're not interested in changing seasons or large portions of the game rules, then you don't need seasonSettings.lua or changeRules.lua.
If you put 'no' in the prerequisite column, then the wonder/improvement/unitType is excluded from the game (and the civilopedia). What I'm asking is if placing 'no' instead of nil/whatever in an expires column makes a difference in the game in some way. If it does, then I probably want to be able to record it as false instead of nil. If it doesn't, then I will have to make my code interpret 'no' in an expires column as nil. Neither of these are difficult to do, I'm just not sure which one I should do.
If you don't want the item to show up in the civilopedia, then setting prerequisite to 'no' is required.
The issue I'm having here is that Lua doesn't distinguish between 'no' and 'nil' entries in the rules.txt, just recording them both as nil. One of the features of the changeRules.lua module is that it records what the 'current' rules are, so other code can refer to them. Most of the time, I use the same keys and values as Lua uses for the actual items. One exception is that I record a 'no' entry in a prerequisite as false instead of nil, so that programs can make the distinction. I'm trying to find out if I should do the same with techs in the expires column or not, and, due to a quirk of how my code is currently written, not having made that decision causes an error (since I didn't anticipate this issue).
It will then be seen in civilopedia as a consequence howether (maybe not if tech is a no, no thought ?), as said @Prof. Garfield .
That's a trick I'm using in scenarios so units not to be built are still visibles in civilopedia indeed.
Simplier, you may also set said wonders to be unbuildable with lua.
If you put 'no' in the prerequisite column, then the wonder/improvement/unitType is excluded from the game (and the civilopedia). What I'm asking is if placing 'no' instead of nil/whatever in an expires column makes a difference in the game in some way. If it does, then I probably want to be able to record it as false instead of nil. If it doesn't, then I will have to make my code interpret 'no' in an expires column as nil. Neither of these are difficult to do, I'm just not sure which one I should do.
If you don't want the item to show up in the civilopedia, then setting prerequisite to 'no' is required.
The issue I'm having here is that Lua doesn't distinguish between 'no' and 'nil' entries in the rules.txt, just recording them both as nil. One of the features of the changeRules.lua module is that it records what the 'current' rules are, so other code can refer to them. Most of the time, I use the same keys and values as Lua uses for the actual items. One exception is that I record a 'no' entry in a prerequisite as false instead of nil, so that programs can make the distinction. I'm trying to find out if I should do the same with techs in the expires column or not, and, due to a quirk of how my code is currently written, not having made that decision causes an error (since I didn't anticipate this issue).
If you set 'no' in the expires column for an unit for example, the unit will be still buildable. Removing the unit from the game only works in the prerequisite column.
For the units you can use @UNITS_ADVANCED and specifiy which unit can be build or not by which civilization. I used it but there is no need to use 'no' in the prerequisite column for hiding units from the game.
It will then be seen in civilopedia as a consequence howether (maybe not if tech is a no, no thought ?), as said @Prof. Garfield .
That's a trick I'm using in scenarios so units not to be built are still visibles in civilopedia indeed.
Simplier, you may also set said wonders to be unbuildable with lua.
Currently I'm using @Prof. Garfield code generator for restricting buildable improvements for every civilization. This tool is very good and luckily for me easy to understand.
I've found a new bug. When I moved my treasure galleon to Cadiz, the lua console showed me the following error message:
I'm using the treasure galleons as a treasure unit which the player has to sail to Cadiz to get a reward. The unit will be automatically disbanded when arriving in Cadiz.
Could you check this please?
I've found a new bug. When I moved my treasure galleon to Cadiz, the lua console showed me the following error message:
[/URL]
I'm using the treasure galleons as a treasure unit which the player has to sail to Cadiz to get a reward. The unit will be automatically disbanded when arriving in Cadiz.
Could you check this please?
I think this will fix it. Unfortunately, I didn't test the fix, since I'm in the middle of doing other work on the template (so an error is generated when I start my testing game).
Basically, the 'update the leader' code happens after the ship is deleted, causing an error in the "check nearby tiles for leaders" code. Let me know if you still have problems.
The General Library now has a stack data type. I'm not sure how useful it will be to others directly, but it made the 'menuRecord' much easier to program and think about.
Spoiler:
Code:
-- stack data type
-- stack.push(value) --> void
-- adds value to the 'top' of the stack
-- stack.pop() --> value
-- removes value from the 'top' of the stack and returns the value
-- popping an empty stack returns nil
-- stack[i] --> value
-- returns the value of ith item from the 'top' of the stack
-- (the stack remains unchanged)
-- stack[1] is the top item of the stack (the same item stack.pop would remove)
-- if the stack doesn't have an ith item, return nil
-- stack.size --> integer
-- returns the number of items in the stack
-- gen.newEmptyStack() --> stack
-- creates a stack with no values
-- gen.newStack(table = {}) --> stack
-- generates a new stack, with integer
-- values in the table pushed onto the stack
-- starting from the smallest integer value
-- (smallest value will be at the bottom of the stack)
-- All other keys (including non-integer keys) and values are ignored
-- gen.isStack(item) --> boolean
-- returns true if the item is a stack (created by gen.newStack/newEmptyStack)
-- and false otherwise
text.substitute now has a lot of new functionality.
1. You are no longer limited to integer keys in the substitution table. You can now use string keys, by enclosing them in [].
Code:
text.substitute("This is a %STRING[type] unit."{type="Settler"})
2. There are now more automatic substitutions. For example, %MONEY will use text.money to convert a number to a money representation. %NAME will get the 'name' field of the item (and even try .type.name in the case of unit objects).
3. There is a new file textSettings.lua, in which you can add your own custom substitution tags. There is an example %CAPITAL created there.
4. There are now simple if/else substitutions available (and custom substitutions can be created in textSettings.lua)
Code:
text.substitute("The 'number' key is %?ZERO[number]{zero}{not zero}.",{number=3})
[CODE]
5. There is a 'dictionary' in textSettings.lua, where you can register singular and plural versions of words (e.g. unitType names), as well as whether that word takes 'a' or 'an' in front of it. The %#<someWord>#[key] sequence will check if the value for 'key' is 1 or not 1, and check the dictionary if there is a singular/plural pair for the word, and choose the correct one. (If the word is not in the dictionary, it is unchanged.)
[CODE]
text.substitute("We have chosen %STRING[quantity] %#%NAME[type]#[quantity].",{quantity=2, type = engineerUnit})
6. The %@<someWord>@ places either 'a' or 'an' in front of someWord, first checking the dictionary for the answer, and, failing that, placing 'an' if the word starts with a vowel, and a otherwise.
Code:
text.substitute("We have the opportunity to hire %@%NAME[type]@.",{type = archerUnit})
You can search text.lua for console.testSubstitution for some examples that I used as testing code.
text.setMoney (which is called in parameters.lua) now has the option to add a second argument for when it is called on exactly 1 money.
You can now register the Units.bmp (or some other similarly structured file, I guess) using text.registerUnitsImage(filename). This will let you call text.unitTypeImage(unitTypeOrID) to get the image of a particular unit type whenever you want it. For example, if you're confirming the choice of a unit type in a menu.
text.menu now has the option to specify the width (and height) of the menu. (I think @civ2units has been manually making menus at least in part to get better control over how they look.)
Code:
-- text.menu(menuTable,menuText,menuTitle="",canCancel=false,imageInfo=nil,dimensions={width=nil,height=nil},menuPage=1) --> integer,integer
-- (last 4 arguments can be in any order or omitted)
And now, the major addition, which prompted a lot of these other additions: the "menuRecord."
The menuRecord is a data structure that holds all the functionality of a menu within it. I originally called it a 'menu container', but I was afraid that people wouldn't realise that changes made later in the code affect the menu assigned earlier (in the same way that changing a unitType's attack changes the value in all instances of the code).
The menuRecord has a key to store a table that is the menu, or a function to generate the menu at the time it is called. Another key lets you assign a function to 'filter' options, or change the order in which the options appear, by assigning 'weights' to them. Additionally, you can assign a function to automatically choose a menu option, instead of asking the person, which is useful if the AI player will 'use' the menu. Moreover, you can simply assign weights to various options, and the record will automatically choose a result at random, with higher probability going to higher weight choices. There are also options for stuff like the menu text, title, and image.
Perhaps most important, you can assign other menuRecords as the 'nextMenu' for any choice you want. This was the idea behind creating this in the first place, since I thought it would be easier to make nested menus. I'm not sure if I really made it easier, but it could just be that it has been a while since I made such complicated menus.
Spoiler:
Code:
-- menuRecord(callingArgument=nil,history=gen.newEmptyStack(),menuRecordHistory = gen.newEmptyStack()) --> menuChoice, history,menuRecordHistory
-- A menuRecord either shows a menu to the player, or makes
-- a choice automatically (usually if the AI must 'use' the menu)
-- The behaviour of the menu is governed by the keys of the
-- menuRecord
-- history is a "stack" (data type) with previous choices recorded
-- callingArgument can be any value
-- menuRecordHistory is a "stack" of previous menuRecords called
--
--
-- menuGenerator =
-- {[integer] = menuOptionTable}
-- or
-- function(callingArgument,history) --> {[integer] = menuOptionTable}
--
-- where, the menuOptionTable =
-- {choice=menuChoice, optionName=string,
-- nextMenu = nil or menuRecord or integer>0, noFilter = boolOrNil,
-- <userDefKey> = <userUsefulValue>}
--
--
-- The menuGenerator is a function that takes the existing history of the
-- menu, and the callingArgument, and generates a table of menu options,
-- or it is already a table of menu options (called fullMenuTable below)
--
-- When the menuRecord is called, the optionName is shown to the player,
-- and the choice is the first returned value, unless nextMenu is a menuRecord.
-- If nextMenu is a menuRecord, then the choice value is pushed onto the history stack,
-- and the menu returns the result of nextMenuRecord(history,callingArgument)
-- (if choice needs multiple pieces of information, use a table)
-- If nextMenu is a number, go back in the history and menuRecordHistory that many
-- entries and revisit that menu (history and menuRecordHistory have most recent
-- additions removed). 1 means go to previous menu, 2 to 2nd previous menu, etc.
-- If number exceeds stack size, go to first menu (and any 'history' fed into the initial menu).
--
-- The designer can also define extra keys (<userDefKey>) and values to facilitate
-- the other functions in the menuRecord
--
--
-- default: {[1] = {choice=nil, optionName = "Default Choice", noFilter = true}}
--
-- canCancel = boolean
-- if canCancel is true, the menu has a 'cancel' option, which returns nil
--
-- default = false
--
-- menuFilter = function(menuOptionTable,callingArgument,history,fullMenuTable) --> bool or number
--
-- When the player is shown a menu, this function is applied to all
-- entries in the menuTable produced by menuGenerator
-- (except those with noFilter, which count as returning true)
-- if false, the choice is excluded from the menu
-- if true, the item is included in the menu, at the same place
-- if number, all items with number weights are ordered so that
-- the biggest weights are first
--
-- default: nil (leave the generated menu unchanged)
--
-- menuText = string or function(callingArgument,history) --> string
-- provides the text for the menu
-- recentHistory = history[1] if history[1] is a table,
-- {[1] = history[1]} if history[1] is a value
-- apply text.substitute to recentHistory
--
-- default: ""
--
-- menuTitle = string or function(callingArgument,history) --> string
-- provides the text for the menu
-- recentHistory = history[1] if history[1] is a table,
-- {[1] = history[1]} if history[1] is a value
-- apply text.substitute to recentHistory
--
-- default: ""
--
-- autoChoice = function(callingArgument,history,fullMenuTable) --> boolean or menuOptionTable
--
-- if false, the menu is shown to the player
-- if true, the choice is made using autoChoiceWeights below
-- if menuOptionTable, that option is chosen
--
-- default: function(a,b,c) return false end
-- (don't choose anything)
--
-- autoChoiceWeights = function(menuOptionTable,callingArgument,history,fullMenuTable) --> bool or number
--
-- generates weights for an automatic choice. boolean weights (even true) are
-- never chosen, so the menuFilter choices can be reused.
-- weights less than 0 are never chosen.
--
-- default: nil (show a message to the player explaining this was supposed to be automatic,
-- and ask if they want a menu or an error)
--
-- menuImage = nil or imageObject or function(callingArgument,history) -->nil or imageObject
-- The image that will be shown in the menu text box
--
-- default: nil (no image)
--
-- menuName = string
-- a name for the menu in error messages
-- default: "Unknown Menu"
--
-- postProcessor = function(callingArgument,choice,history,menuRecordHistory) --> choiceValueReturnedByMenu
-- Allows for some processing of the data before going to the next menu,
-- or returning the choice (it does not apply if nextMenu has an integer value, since
-- in that case you're going back to a previous menu state)
-- This function is performed after the choice has been added to the history,
-- and after the current menuRecord has been added to the menuRecordHistory
-- If there is no nextMenu, the result of this function is returned as the choice
-- (the default function simply returns the choice)
-- If you wish to change the choice as recorded in the history, you must pop
-- the recorded choice from the history, and push the modified version.
--
-- default: function(callingArgument,choice,history,menuRecordHistory) return choice end
--
--
--
--
-- It is important to note that menuRecords work like unitTypeObjects or tmprovementObjects,
-- that is, changing the value of a key changes how that object works in EVERYWHERE
-- in your code, including in places where you've already assigned that menuRecord
-- as a value.
--
-- In particular, you can assign menuRecordTwo as the value for nextMenu in
-- a choice (or choices) for menuRecordOne, and fill in the details of
-- menuRecordTwo later in your code.
--
Example/testing code starts around line 2653, and 3137 (probably easier). Lines to allow the tests to be called from the console are: console.sampleMenu(tribeID), console.menuAutoTest(tribeID), console.hireMercTest().
As always, let me know if you need help, or find a bug, or whatever.
I've updated the discrete events so that it can be used in both these ways to assign discrete events:
existing method:
Code:
function discreteEvents.onTurn(turn)
civ.ui.text("It is turn "..tostring(turn)..".")
end
new method
Code:
discreteEvents.onTurn(function(turn)
civ.ui.text("It is turn "..tostring(turn)..".")
end)
The reason for this is that I've been preparing the template to work with Visual Studio Code and the LuaLS extension (install). These programs provide some documentation/hints while programming:
Giving a list of possible functions in the civ module:
Giving documentation for a function when hovering the cursor over it:
And, it can give some information (and search function names) for stuff I haven't done "proper" documentation on. (It looks for comments directly above the function call.)
It also has a decent code analyser, which can warn about things like accidental globals (typically misspellings) and code with syntax errors. I added the new way of using discrete events, because the old way triggered warnings from the analyser, which is fair, since it is a very unusual way of doing things.
When I start writing the "How To" guide, I intend to show coding with these tools.
This extension also provides the ability to generate documentation in markdown, so I won't have to do that it separately for use in a web page (at least for a lot of stuff).
Thus far, I've documented the Lua Function Reference into civ.lua and totpp.lua, which are found in the LuaDocumentation directory.
@Prof. Garfield Is it possible to have the Communism tech granted, or researched, to reduce the unhappiness reduction of Cathedrals by one, as I believe it does, and then remove the tech, and have that penalty removed, too, or is the penalty engrained in the .scn files after the tech is researched, whether you retain it or not?
@Prof. Garfield Is it possible to have the Communism tech granted, or researched, to reduce the unhappiness reduction of Cathedrals by one, as I believe it does, and then remove the tech, and have that penalty removed, too, or is the penalty engrained in the .scn files after the tech is researched, whether you retain it or not?
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.