Boudicca's Rebellion - Creation Thread

Ok, well, it's not the end of the world I guess to write it like this:

Spoiler :

Code:
if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})  --This is to enhance replay value - the units will spawn near the battle but not clear exactly where
    civ.ui.text("The rash Qunitus Petillius Cerialis, commander of the Legio IX Hispana, has fallen in battle, swept aside by the incredible and swelling forces of the Iceni.  Emboldened by their success, more tribesmen flock to the banner!")
    civlua.createUnit(object.uIceniSwordsmen, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=8,randomize=true, veteran=false})
    end 
   
    if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})
    civlua.createUnit(object.uIceniNobles, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=4,randomize=true, veteran=false})
    end 
   
    if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,3,{0,1,2,3,9})
    civlua.createUnit(object.uIceniSpearmen, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=12,randomize=true, veteran=false})
    end 
   
    if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,3,{0,1,2,3,9})
    civlua.createUnit(object.uIceniSkirm, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})
    end 
   
    if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,3,{0,1,2,3,9})
    civlua.createUnit(object.uIceniSkirm, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})
    end 
   
    if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})
    civlua.createUnit(object.uIceniArchers, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})
    end
   
    if loser.type == object.uCerialis then 
    local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})
    civlua.createUnit(object.uIceniArchers, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})
    end


which "splashes" all over the place.

Another thing - you may have mentioned this and sorry if you did - but where would I store the new state files? I want to have a state.BoudiccaKilled where these events won't fire once she's taken out. Since I'm not supposed to modify the actual events file, I'm not totally certain where this belongs (meaning each part - where do I store the initialization, where do I store the onLoad etc).
 
Another thing - you may have mentioned this and sorry if you did - but where would I store the new state files? I want to have a state.BoudiccaKilled where these events won't fire once she's taken out. Since I'm not supposed to modify the actual events file, I'm not totally certain where this belongs (meaning each part - where do I store the initialization, where do I store the onLoad etc)

You have a couple options.

First, and preferred, is to use the flag.lua and counter.lua modules (flag = require("flag"), counter = require("counter")).

Somewhere in the files (not within a function), you will need to have, for each flag or counter the line

counter.define("myCounterKey",initialVal)
flag.define("myFlagKey",initialBool)

(The actual initialization is taken care of behind the scenes)

You can then use functions like flag.value, flag.setTrue, counter.add, etc. These functions are in the flag and counter files, but I'll write up the documentation for shortly.

The second option is to use the function gen.getState(), which will return the state table (actually a table in the state table, so you don't have to worry about overwriting another module's state keys).

You can use gen.getState() anywhere you would have used state in OTR.

Ok, well, it's not the end of the world I guess to write it like this:

You could have written it as
Code:
if loser.type == object.uCerialis then
   local randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})  --This is to enhance replay value - the units will spawn near the battle but not clear exactly where
    civ.ui.text("The rash Qunitus Petillius Cerialis, commander of the Legio IX Hispana, has fallen in battle, swept aside by the incredible and swelling forces of the Iceni.  Emboldened by their success, more tribesmen flock to the banner!")
    civlua.createUnit(object.uIceniSwordsmen, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=8,randomize=true, veteran=false})

    randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})
    civlua.createUnit(object.uIceniNobles, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=4,randomize=true, veteran=false})

    randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,3,{0,1,2,3,9})
    civlua.createUnit(object.uIceniSpearmen, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=12,randomize=true, veteran=false})

    randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,3,{0,1,2,3,9})
    civlua.createUnit(object.uIceniSkirm, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})

    randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,3,{0,1,2,3,9})
    civlua.createUnit(object.uIceniSkirm, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})

    randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})
    civlua.createUnit(object.uIceniArchers, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})

    randomTile = gen.getRandomNearbyUnoccupiedTile(loser.location,4,{0,1,2,3,9})
    civlua.createUnit(object.uIceniArchers, object.tIceni, {{randomTile.x,randomTile.y,randomTile.z}}, {count=2,randomize=true, veteran=false})
end
 
Here is the documentation for flags and counters. If anything is unclear, please let me know. It does assume some familiarity with the different lua data types.
 

Attachments

  • FlagsAndCounters.txt
    5.7 KB · Views: 37
Is my understanding of this correct?

If you intend only to reference a flag within a single file, it might make sense to define it within that file. Alternatively, you might find it convenient to define all your flags in a particular file, perhaps object.lua for example.

It would probably not be a bad idea to do this in this template/example scenario.

The command to define a flag is
flag.define("myFlagKey",initialBool)

So, in object.lua, would I just write:

flag.define("BoudiccaKilled", false)
flag.define("SeutoniousKilled", false)
flag.define("NorthernFrontierGarrisoned",true)

and then in, say, the unitKilled

if if loser.type == object.uSuetonius then
flag.setTrue("SeutoniousKilled") --do I need the quotation marks?
end

Am I following?

I'm used to the state version where I'd need

state.BoudiccaKilled = state.BoudiccaKilled or false

In both the initializer and also in onLoad - this isn't necessary with your flags module? i.e. it is not ephemeral game to game?
 
So, in object.lua, would I just write:

flag.define("BoudiccaKilled", false)
flag.define("SeutoniousKilled", false)
flag.define("NorthernFrontierGarrisoned",true)

and then in, say, the unitKilled

if if loser.type == object.uSuetonius then
flag.setTrue("SeutoniousKilled") --do I need the quotation marks?
end

Am I following?

Yes, and yes, you do need quotation marks for "SeutoniousKilled", since it is a string. (I suppose you could put certain flag keys into a table, and use keys to that table, but I don't see the point.)

I'm used to the state version where I'd need

state.BoudiccaKilled = state.BoudiccaKilled or false

In both the initializer and also in onLoad - this isn't necessary with your flags module? i.e. it is not ephemeral game to game?

That's already done behind the scenes in the flag and counter modules. The define function puts all the keys into a list, and then there are initialization functions that are run in appropriate locations.
 
So could context 1 and context 2 be used to make the events less clunky/large if one wanted to, say, place all the Iceni Rebellion events in Context 1, and then all the Caledonian Incursions in Context 2?

In other words:

As both context files have the same lua events files with the same triggers - if there is a unitKilled in context1 and a unitkilled event in context 2, and a unitkilled event in the main triggerevents, will ALL three fire/be found?

If so, this could be amazingly useful for larger scenarios to help keep things organized.

Edit - I just tested it with a simple text and this appears to be the case but if I'm wrong let me know! (I only tested one context however, not both).
 
Last edited:
So could context 1 and context 2 be used to make the events less clunky/large if one wanted to, say, place all the Iceni Rebellion events in Context 1, and then all the Caledonian Incursions in Context 2?

In other words:

As both context files have the same lua events files with the same triggers - if there is a unitKilled in context1 and a unitkilled event in context 2, and a unitkilled event in the main triggerevents, will ALL three fire/be found?

If so, this could be amazingly useful for larger scenarios to help keep things organized.

Edit - I just tested it with a simple text and this appears to be the case but if I'm wrong let me know! (I only tested one context however, not both).

No, the idea of the Context Events is that only one group of them fire in a given game. Using Your Cold War scenario as an example, you have some events that should only take place if China is the only human player, and other events that should take place only if the USSR/Pro East are the only human player. The getContext function in triggerEvents.lua would determine which of these events to run.

An alternative use, which I was suggesting for this scenario, is to vary the likelihood of events between games. I'll move away from Roman Britain, and into the 1930s. Suppose the player takes the role of Hitler, trying to expand Germany's influence, through a combination of diplomacy, bluff, and threats. Britain has 2 possible leaders, Churchill and Chamberlain, but Hitler doesn't know who is in power (in practice, he would be uncertain of the personality of the leader rather than the name, but it is easier to use name for this explanation). If Chamberlain is the leader, Hitler can be more aggressive with a relatively low risk of starting a war before he is ready. If Churchill is the leader, Hitler has an elevated chance of a premature war if he pushes too much. For any given action, there is a chance of appeasement, sabre rattling, or war, but in any given situation, Chamberlain is more likely to be peaceful. If Hitler notices that there is a lot of appeasement, he may guess he is dealing with Chamberlain, and become more aggressive, while if he sees a lot of credible sabre rattling, he might think he is dealing with Churchill, and the correct action might be to back down a bit. In this case, the context would be chosen at the beginning of the game and saved in the state table, but it would be different each game.

If you simply want to split some event types into multiple files, that is just a matter of creating some new files, and 'requiring' them into the existing ones. (Not all the subfolders are in the require path check, so you might have to include the subfolder as part of the require line. I.e.
Code:
 local extraUnitKilled = require("UniversalTriggerEvents\\extraUnitKilledEvents")
 
I spent my lunch break to take a stab at starting the designer readme. I figure I'd share it now so that stylistic changes can be made early on in the process, but here's what I was thinking. I'm not sold on this being the definitive first four pages (it might make sense to have a higher level / one sentence overview of all the files first) but these were the first four that I wrote. Feedback, especially from those not named @Prof. Garfield or @Knighttime :) would be very helpful as I'm writing this for those who feel a little overwhelmed by lua and those two gents certainly aren't!
 

Attachments

  • Designer Readme.pdf
    233.9 KB · Views: 35
I like that kind of initiative to make lua coding less ugly and more abordable for neophyts.
Yet, I'm afraid theses first explanations makes it somehow harder.

In my opinion, one shall begin with small usable exemples with the console to play with (see Prof.Garfield's begin with Lua : a print, a simple unit creation, few small exemple one could duplicate in the console, and further personalize in the console)

Then, one shall go on with the structure of a simple Lua file :
1/ Call for libraries (what are libraries ?)
2/ Variable déclaration
3/ Functions declaration
4/The civ reactives functions (onTurn, onUnitKilled, onCityTaken ...) and their main caracteristics

Then dig in them, explain advantages of multiple files, additional libraries, basic code architectures, etc...

Anyway, that's great
 
Yeah it's just a first stab at explaining two sections that probably should be more towards the middle of the project than the front, but we must start somewhere!

Anyway, the objective of this isn't to teach someone how to code - @Prof. Garfield has already made extensive efforts to help with that. This is more designed to teach someone how to copy and paste code already written for them in the template into their own work, and to understand where to put it. You're kind of a coder first, I'd argue, as opposed to a designer first, as you have pretty extensive knowledge of that. I'm trying to help folks who have zero coding knowledge and don't want to acquire much coding knowledge to still be able to get a workable creation out there that is better than they could do with the macro language.

For example, of the 4 things you mentioned, #4 is probably the only thing I'd include. The others I'd say is kind of beyond the scope of this designer guide and might be better in basic lua lessons (which exist) or the technical guide.
 
Thanks for explaining more clearly your aim, thus clearing my misundertanding.

Ps : In informatics, it is often said the thing programmers most do is Copy/Paste :thumbsup:
 
Last edited:
I like that kind of initiative to make lua coding less ugly and more abordable for neophyts.
Yet, I'm afraid theses first explanations makes it somehow harder.

In my opinion, one shall begin with small usable exemples with the console to play with (see Prof.Garfield's begin with Lua : a print, a simple unit creation, few small exemple one could duplicate in the console, and further personalize in the console)

Then, one shall go on with the structure of a simple Lua file :
1/ Call for libraries (what are libraries ?)
2/ Variable déclaration
3/ Functions declaration
4/The civ reactives functions (onTurn, onUnitKilled, onCityTaken ...) and their main caracteristics

Then dig in them, explain advantages of multiple files, additional libraries, basic code architectures, etc...

Anyway, that's great

I have been considering re-writing (or doing a video series instead) my Lua Lessons using the template and the resources it provides as a starting point. We'll see how things go.


(what are libraries ?)

In regular English usage, a library is a place where books (and other similar materials) are kept, and members of the library can borrow those books to read. In Canada, the French term is bibliothèque, not sure if they use a different term in France.

In programming, a library refers to code that has been made available for other programs to use. So, for example, the 'load a game' window in Civ II looks slightly different depending on the operating system that you are using, since Civ II asks the operating system for the 'choose a file' window, rather than drawing one itself. So, just as one can use a library book without owning that book, one can use code from a library without writing that code oneself.

Library does have a bit of a connotation of a lot of relatively small pieces of code for a lot of use cases, so we'll also refer to lua files providing certain functionality as 'modules.'
 
As this is going to be tough to test it's best if I just ask to get it right...

Code:
local function enoughCohortsInWales()
    return minCohortsInWales <= cohortsInRegion(inWales)
end
local function enoughCohortsInScotland()
    return minCohortsInScotland <= cohortsInRegion(inScotland)
end

could I reference these in afterProduction in this way? I didn't see a reference to a boolean so I'm not sure I can.

Code:
if turn >= 2 and math.random(1, 4) == 4 and tribe == object.tIceni and not enoughCohortsInWales() then
--bad stuff happens
end
 
Where have you stashed "dialogue" options? I'm getting this error

The Global table can't accept values for indices that don't already exist. Key value is: dialog

which is telling me that I need a "local _____ = require("_______")

I didn't see any reference to it in "text." How do I unlock this please?
 
could I reference these in afterProduction in this way? I didn't see a reference to a boolean so I'm not sure I can.

Yes. computing a<b or a>=b provides a boolean, which is then returned by the larger function. So as long as you've defined these within the same file (or used require suitably), then it will work.

Where have you stashed "dialogue" options? I'm getting this error

The Global table can't accept values for indices that don't already exist. Key value is: dialog

which is telling me that I need a "local _____ = require("_______")

I didn't see any reference to it in "text." How do I unlock this please?

I'm not sure what you're trying to do.

If you want to create a dialog box manually, the code, I believe, is
Code:
local myDialog = civ.ui.createDialog()

Unless you have some very specific requirements, it will probably be easier to use text.simple, text.menu, and text.displayNextOpportunity rather than creating your own dialog boxes explicitly.

Did you define 'dialog' as a local variable somewhere in your file? If not, that will give you the global error. You will also get the global error if you've defined your local variable 'below' the place where you use it next. For example

Code:
local function myFunction(myArgument)
    if myArgument < 5 then
        local a = 10
    else
        local a = 20 
    end
    print(a)
end

This would give an error, since locals are restricted to 'lower' blocks of code (as a rule of thumb, if you follow normal indentation practices, locals will be available only until you return to something with the same level of indentation).

You would fix this function as follows:

Code:
local function myFunction(myArgument)
    local a = nil
    if myArgument < 5 then
        a = 10
    else
        a = 20 
    end
    print(a)
end

If this doesn't answer your question, provide me the file that you're trying to put the dialog in.
 
Unless you have some very specific requirements, it will probably be easier to use text.simple, text.menu, and text.displayNextOpportunity rather than creating your own dialog boxes explicitly.

It's just a different way of doing things but it works, so if this will be the standard, this is what I'll use! I was able to manage to get it to work. Thanks!
 
Can you help with this complicated sequence of events for Watling Street? I have already completed the first half where Seutonius' activation near London will change the map and alert him that he needs to high-tail it out of there:

if unit.owner == object.tRomans and unit.type == object.uSuetonius and (unit.location.x >= 68 and unit.location.y >= 104 and unit.location.z == 0) then
gen.justOnce("Retreat to Watling Street",function ()
civ.ui.text("As Seutonius arrives closer to eastern Britannia, he can see pillowing smoke rising before him! Desperate refugees from nearby towns clog the roads!")
civ.ui.text("It is clear that the rebellion is far greater than he ever imagined! If the Romans are caught in the open, they'll certainly be cut down.")
civ.ui.text("Evacuation is impossible. The coward Catus took the last of the ships with him. Seutonius' only option is to find ground that will offer him an advantage.")
civ.ui.text("You'll note that the landscape now has a few terrain types called 'battleground.' They have the mark of the skulls that will soon litter them. Find a suitable one where you will make your stand, assemble ALL forces you can muster, and press '3' to call the Iceni to battle!")
object.lWatlingStreet1.terrainType = 14
object.lWatlingStreet2.terrainType = 14
object.lWatlingStreet3.terrainType = 14
end)
end
(I may put in more spawning rebels at this point too)

What I need from you is a key press event (I was thinking '3' but whatever you think is best) that:

If the unit is object.uSeutonius and if the unit's location is terrain type 14, and if 3 is pressed then:

A menu pops up:

"Last Stand at Watling Street." - Menu title

"It's the end of the line. We have retreated as far as we can, but if we keep running, we'll have nowhere left to run to. We can choose to stand and fight here, but doing so will expend all of our units' movement points, and bring the raging hordes nearby. Imperator, what are your orders?

Option 1 = "Better to die like proud Romans!"
Option 2 = "No, let's find more suitable ground!"

If Option 1, then:

1. All the Roman units across the map should have their movespent
2. All units that belong to object.tIceni should be teleported 1-3 squares EAST of the Roman position, generally - but only to terrain types 1 or 2
3. These units should NOT have their movespent
3. We need to create a wall of object.uWagon owned by object.tGods 4-5 squares next to the Britons (ideally, honestly, it should be a large square that just essentially traps both groups in - so maybe a square 10x10 or something? Whatever measurements it would need to be

If Option 2, then return

In the meanwhile, I'm going to work through the main events that are "needed" in the scenario and then will post it up and we can take a look at what events need an example, and build those. I have been playing with this template on another project too and it is very intuitive so I'm interested in getting this exemplar scenario up and running so we don't delay the template's release too much. I think it'll be a big help.

Thanks,
 
I'm going to attempt to utilize the code you helped with for Wales and Scotland but as I haven't used this before in any scenario I'm a little unclear of what to do.

Would I basically expand on these two functions:

Code:
local function enoughCohortsInWales()
    return minCohortsInWales <= cohortsInRegion(inWales)
end
local function enoughCohortsInScotland()
    return minCohortsInScotland <= cohortsInRegion(inScotland)
end

By say, doing something like:

local function enoughCohortsInWales()
return minCohortsInWales <= cohortsInRegion(inWales)
civlua.createUnit(Etc.)
civ.ui.txt("Etc.")
end

And then plug in "enoughCohortsInWales()" at the end of say, function triggerEvents.afterProduction(turn,tribe)?

Or am I missing the boat?
 
You would use them in this way
Code:
function triggerEvents.afterProduction(turn,tribe)
   if enoughCohortsInWales() then
        -- do something good since there are enough cohorts in Wales
        civlua.createUnit(...)
        civ.ui.text("Etc.")
    else
        -- do something bad, since there aren't enough cohorts in wales
    end
    if not enoughCohortsInScotland() then
        -- do something bad, since there aren't enough cohorts in Scotland
    end
    -- rest of afterProduction
end
 
There are a couple changes to the core files. I discovered by accident that diplomacy.lua has a bunch of globals that have to be turned into locals. I started the process, then realized it was extensive, so stopped for now. I wrote a function for text.lua to write lists (in table form) as strings, although I didn't really need it for this application. I think I've written stuff like that at least twice before, so it made sense to solve the problem now.

I've tested this as much as I could with the files I have at present.

put
local watlingStreet = require("watlingStreet) in your keypress file, and invoke
watlingStreet.keyPressEvent() at a suitable point. (might make sense just to make it a k press event)

I think I've documented this file pretty well, let me know if there is something that is unclear. There are some parameters you may want to change.

EDIT: I had to add a line object.tGods = object.tTheGods in my object file. I'm not sure what you have at present, but I think object.tGods is used only once or twice, so find and replace should be feasible.
 

Attachments

  • waltingStreet+core.zip
    21.2 KB · Views: 31
Top Bottom