The Spanish Civil War v4 for ToT + ToTPP + Lua - Development Thread [ON HOLD]

Bonjour @Dadais !
It looks very much like my "stuff" table for units in the TES scenario.
I need to check it out, I don't have so much free time so when I do I focus on working on SCW, so I'm missing many scenarios!
Why didnt you use directly unitsID as the key of your table and added an integer ?
That's a very good question. The unitId is not the key in my tables for the following two reasons:

1) Not all units already exist on scenario start, but I want to have all entries predefined in my tables to help my planning. So for these the uId is initially set to 0, and once created at some point via events, updated later with the actual unit id. The ids are saved in the state table as counters, for persistence, and overwritten to the table onLoad().

2) My espionage module works retrieving random information. If spies are to produce a report on an enemy ship/commander, I want to display the info of a random ship/commander retrieved from my tables. The best way I thought about meeting this requirement is to generate a random integer between 0 (the 1st key) and the no. of elements in the table-1, that's why these sequential integers are the keys of the table as I ensure I get a valid key that exists. See code on how I do this when spying on an enemy ship (for commanders it will be pretty much the same):

1662891414098.png


Also this code ensures a ship not created yet is not picked up, by checking their uId == 0 in a while loop.

I'm all ears if you have better ideas!
 
I need to check it out, I don't have so much free time so when I do I focus on working on SCW, so I'm missing many scenarios!
No worry, the v2 including this feature isn't uploaded at the moment ;) .
There's still a little lua to do :) .

My espionage module works retrieving random information. If spies are to produce a report on an enemy ship/commander, I want to display the info of a random ship/commander retrieved from my tables. The best way I thought about meeting this requirement is to generate a random integer between 0 (the 1st key) and the no. of elements in the table-1, that's why these sequential integers are the keys of the table as I ensure I get a valid key that exists.
While the first point isn't much an issue, this one is indeed an important valid one where this choice of independant keys makes it way easier to code !

Don't worry, some of my questions are also for me to evolve on my code architectures thoughts ;). So, thanks to you !
 
While the first point isn't much an issue, this one is indeed an important valid one where this choice of independant keys makes it way easier to code !

Don't worry, some of my questions are also for me to evolve on my code architectures thoughts ;). So, thanks to you !
Glad I can be of help!

Ok, so today i was able to progress more with the commanders...and their traits! Loads of coding today, tables and tables of tables :D These commanders will have different traits that will affect combat for units under their influence...

The espionage module now can also spy and provide the location of the enemy commanders. I have issues when adding the new lines for the traits, but I'll resolve it later (tried \n, \n\n and ^ and none jump a line :D).

1662918379588.png


Enough Civ2 for the weekend!

Pablo
 
The espionage module now can also spy and provide the location of the enemy commanders. I have issues when adding the new lines for the traits, but I'll resolve it later (tried \n, \n\n and ^ and none jump a line :D).
Try \n^ for a line break. I'm pretty sure all the code I've written interprets that as the newline. If you're using civ.ui.text, you'll need to use apply the func.splitlines function to the message.
Code:
local func = require("functions")
local message = "line1\n^line2"
civ.ui.text(func.splitlines(message))
 
Try \n^ for a line break. I'm pretty sure all the code I've written interprets that as the newline. If you're using civ.ui.text, you'll need to use apply the func.splitlines function to the message.
Code:
local func = require("functions")
local message = "line1\n^line2"
civ.ui.text(func.splitlines(message))
Hi, I printed the traits as part of text.substitute as I had a variable with a bunch of text with the newlines all inside a substitution STRING%n , then all wrapped on text.simple. I'll include the splitlines call, thanks for the suggestion, I was a bit tired to spend more time troubleshooting but wanted to show a sneak peek here 😁
 
Hi, I printed the traits as part of text.substitute as I had a variable with a bunch of text with the newlines all inside a substitution STRING%n , then all wrapped on text.simple. I'll include the splitlines call, thanks for the suggestion, I was a bit tired to spend more time troubleshooting but wanted to show a sneak peek here 😁
You don't need the splitlines call with text.simple. You need the \n^ for the linebreak.
I just tried in the console
Code:
conosle.text = require("text")
console.text.simple("abd\n^def")
and it splits into two lines as expected. I should have just done the quick test and given complete information.
I'm not sure that \n^ was ever explicitly documented anywhere, so it's not surprising that you didn't come up with it.
 
My tables are indexed by an integer starting with 0, the same way as the ship tables, because this way my espionage module picks a random element from the table (yes, spies will also provide reports on enemy commanders!)
2) My espionage module works retrieving random information. If spies are to produce a report on an enemy ship/commander, I want to display the info of a random ship/commander retrieved from my tables. The best way I thought about meeting this requirement is to generate a random integer between 0 (the 1st key) and the no. of elements in the table-1, that's why these sequential integers are the keys of the table as I ensure I get a valid key that exists. See code on how I do this when spying on an enemy ship (for commanders it will be pretty much the same):

View attachment 639096

Also this code ensures a ship not created yet is not picked up, by checking their uId == 0 in a while loop.

I'm all ears if you have better ideas!
Hi @Pablostuka, there may be a sneaky bug here related to math.random(0, #shiptable-1). The # operator, prefixed to a table name, has very specific behavior: it starts a key counter at 1, and checks the value of that key. If it's nil (unassigned) then it returns key-1 (one less than the key it just checked); if it's non-nil, then it increments the key counter by 1 and repeats the value check. Thus, there are two potential pitfalls here:

First, this does not provide the result you might intend if there are any gaps in your key series. For example,
local myTable = {[1] = "a", [3] = "c", [4] = "d", [5] = "e"}
print(#myTable)

will print 1, not 4, because there is no element [2] in the table. It's not the total count of keys in the table, it's the number of sequential integer keys until the first gap. As an extension of that, the # operator also ignores any keys that are not positive integers -- characters, strings, objects, etc.

Second, the first key that is checked is 1, not 0. So if you build a table that starts at 0, even if it uses all sequential integer keys after that, the result of the # operator is going to be one less than you probably intend:
local myTable = {[0] = "a", [1] = "b", [2] = "c", [3] = "d"}
print(#myTable)

will print 3, not 4.

This second issue is the one that I think might be tripping you up: if the keys in shiptable start at 0, math.random(0, #shiptable-1) will never return the highest key you've defined, even if there are no gaps. You could "fix" this by writing math.random(0, #shiptable), which turns out to give you the result you want, but this makes me cringe a little: you'd be relying on the fact that the # operator is ignoring one of your defined keys. Even though this works, it doesn't feel natural to me -- I can't escape the impression that there are two issues which (coincidentally?) happen to cancel each other out.

Also, math.random() indeed accepts two integer arguments, as you provided, and will return a random number between those integers (inclusive on both ends, meaning math.random(0,2) can return 0, 1, or 2). But if you provide only a single integer argument, the function returns a random number between 1 and that integer, inclusive. So you can force it to start at 0, as you did, but I think it would be fair to say that math.random(), like everything else in Lua, is really designed as a 1-based system instead of 0-based.

I completely understand that if you're used to a 0-based language, adapting to a 1-based approach in Lua can be a mental shift that feels awkward -- speaking from experience, because that's how it was for me! 😅 But Lua is frequently going to make you go out of your way, with extra steps, to get 0-based to work correctly -- and it's easy for a bug to creep in that won't cause an error, but that also isn't producing exactly the result you intend. So I'd personally recommend trying to adopt a 1-based mentality and use 1-based tables as much as possible.

I hope this message doesn't come across as critical, because I think you're doing fabulous work -- just trying to protect you from frustration down the road, as you get into testing and fine-tuning! Based on the regular updates you've provided, there is lots of cool stuff going on here, on many fronts: map usage, graphics, Lua... I'm really looking forward to the point where you're ready to release this, and excited to see how all the features fit together. Good luck with the project!
 
Last edited:
Thanks for that detailed explanation @Knighttime ! I was unaware of all this, from what I read online I thought # would return just the number of elements in my table, simple as that.

My tables are all dense (all their keys are sequential and present no gaps) but yes, the first key is 0. I'm used to start my collections with 0 (17 years working in IT corrupted my brain :D ), so didn't think this would be an issue here. That's why I generate a random number between 0 and the number of elements in the array-1, but yes, now I understand it won't work as expected.

I'll change the keys to start with 1, and then generate my randoms from 1 to # num elements. It will be also easier to read for non-programmers.

Thanks for your comments! I know it's going to take a lot of time before it's released because I simply want to implement too many things! Also I'm showcasing lots of PoC code and graphics but in reality there's loads behind the scenes to be done yet. I haven't even finished placing the scenario start units, or finished with the extra maps. But hey, someday... 🙄
 
Next step: mess with the combat functionality.

First I need to familiarize myself with the LST combat modifiers/attack bonuses/traits/combat groups functionality that @Prof. Garfield has implemented. This will require me some time. I plan to introduce already supported functionality to traditional bonuses (AA artillery vs airplanes, AT guns vs armoured units, etc).

Then implement my own ideas (inspired by another videogame) on top of this, with extra combat modifiers:

  • There will be occassional/random modifier events on battle which will affect at the attacker/defender stats. E.g. encirclement, ambush, counterattack, etc. For instance, "ambush" is defined as "The attacker has managed to ambush the enemy forces inflicting much higher rates of strength and morale damage to the defenders while at the same time greatly reducing the amount of those same types of losses that they sustain themselves". In civ2 terms, it translates into a temporary increase of firepower and hit points for the attacker.
  • If a commander is nearby, their own traits will affect either the chance of these random modifier events happening, or directly affecting the stats of the units in combat. Two examples:
    • if a friendly commander with "urban warfare specialist" trait is nearby (on an adjacent tile), the units taking combat (regardless if attacker or defender) will get their attack/defense stats boosted when fighting on cities and urban terrain.
    • if a friendly commander with "ambusher" trait is nearby, the chance of the "ambush" event happening will get increased by 20%.
I know it's overcomplicating things, but having Lua allowing all this, why don't take advantage? I simply can't resist :)

So far I've set up the basic configuration of these events and leader traits:

1662990903459.png


Leaders/commanders get these traits assigned in their own tables.
1662990999564.png


So I'll see what I can do with all this :)
 
Last edited:
First I need to familiarize myself with the LST combat modifiers/attack bonuses/traits/combat groups functionality that @Prof. Garfield has implemented. This will require me some time. I plan to introduce already supported functionality to traditional bonuses (AA artillery vs airplanes, AT guns vs armoured units, etc).
In my opinion, the attackBonus.lua module is obsolete. It changes the attack value of the unit type, when we can now intervene more directly in the combatSettings.lua file. For a while, attackBonus.lua had the 'feature' that a unit could gain an attack bonus and move to another tile still preserving it, but that is no longer the case due to the change in onActivateUnit which makes it run every time a unit moves. (This doesn't have to be enabled, but other features like onEnterTile rely on it.)

In fact, my next project was going to be a remake of the attack bonus functionality. However, I'm first working on a module that has similar functionality to flags and counters, but associates them with individual units (or cities). With that, I can keep the "has met the leader, now has bonus for rest of turn" option of attackBonus.lua without making a custom data storage. My plan is also to use the traits module to determine what units trigger and receive bonuses.

The combat groups are just a way to get some combat modifiers similar to the pikeman bonus in a format similar to rules.txt.

By the way, if you haven't noticed it, there is a civilopedia.lua module, which lets you document features as you code them, and will generate a describe.txt file for you (though you'll probably have to do a final edit on that file). LuaParameterFiles\samplePedia.lua gives some examples.
 
In my opinion, the attackBonus.lua module is obsolete. It changes the attack value of the unit type, when we can now intervene more directly in the combatSettings.lua file. For a while, attackBonus.lua had the 'feature' that a unit could gain an attack bonus and move to another tile still preserving it, but that is no longer the case due to the change in onActivateUnit which makes it run every time a unit moves. (This doesn't have to be enabled, but other features like onEnterTile rely on it.)
Thanks for confirming, I'll disregard it then.
In fact, my next project was going to be a remake of the attack bonus functionality. However, I'm first working on a module that has similar functionality to flags and counters, but associates them with individual units (or cities). With that, I can keep the "has met the leader, now has bonus for rest of turn" option of attackBonus.lua without making a custom data storage. My plan is also to use the traits module to determine what units trigger and receive bonuses.
Oh this sounds very interesting and somewhat familiar :) here I'm using intensive specific individual unit logic
The combat groups are just a way to get some combat modifiers similar to the pikeman bonus in a format similar to rules.txt.
Yeah that's what I think I'll use for standard unit type bonuses vs other types of units. But then we have the traits module, I dropped a question on your LST thread.
By the way, if you haven't noticed it, there is a civilopedia.lua module, which lets you document features as you code them, and will generate a describe.txt file for you (though you'll probably have to do a final edit on that file). LuaParameterFiles\samplePedia.lua gives some examples.
I'm leaving the civilopedia work for the end but thasnks for the reminder, I'll check it out.
 
Next step: mess with the combat functionality.

First I need to familiarize myself with the LST combat modifiers/attack bonuses/traits/combat groups functionality that @Prof. Garfield has implemented. This will require me some time. I plan to introduce already supported functionality to traditional bonuses (AA artillery vs airplanes, AT guns vs armoured units, etc).
Hi, I decided to use the Traits module instead of the rules_lst.txt functionality, it gives me more flexibility and it's actually much more easy to read for me :D

Sample table of traits implemented in combatSettings.lua (values will be adjusted after playtest):

SQL:
-- Special Combat Combination bonuses
-- This could be implemented with the RULES_LST functionality but for me it's more clear if I define it programmatically in Lua using the LST Traits module
-- I only add special trait combinations that result in extra bonuses being added, otherwise standard unit stats as defined in Rules.txt will apply
-- Table structure:
-- Index = attacker trait
-- tcbtDefendeTable: Table of applicable defender traits against attacker (index)
-- tcbtAttackerBonusTable: Table of attack bonus added to attacker (index) against any traits in tcbtDefendeTable
-- tcbtDefenderBonusTable: Table of defense bonus added to any traits in tcbtDefendeTable against attacker (index)
local traitCombatBonusesTable = {
-- LAND COMBAT
-- Infantry particularly vulnerable attacking MG and armoured units.
["infantry"] = {tcbtDefendeTable={"mg","armoured"},tcbtAttackerBonusTable={0,0},tcbtDefenderBonusTable={1.5,1.75}}, 
-- Armoured units are especially good attacking infantry and MGs, but vulnerable against AT guns and not very effective against other armoured units (unless equipped with AT guns)    
["armoured"] = {tcbtDefendeTable={"infantry","mg","at_gun","armoured"},tcbtAttackerBonusTable={1.5,1.5,0,0},tcbtDefenderBonusTable={0,0,0,2,1.5}},
-- AT guns (both artillery and tank equipped) are particularily effective against armoured units
["at_gun"] = {tcbtDefendeTable={"armoured"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- AIR COMBAT
-- In general, airplanes take no damage when engaging non AA protected ground targets. Give huge attack bonus!
["airplane"] = {tcbtDefendeTable={"ground_air_vulnerable"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- In general, airplanes take no damage when engaging non AA protected naval targets. Give huge attack bonus!
["airplane"] = {tcbtDefendeTable={"naval_air_vulnerable"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- Airplanes are vulnerable against AA equipped naval units
["airplane"] = {tcbtDefendeTable={"naval_aa_equipped"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
-- Airplanes are vulnerable against AA guns
["airplane"] = {tcbtDefendeTable={"aagun"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
-- Fighters get bonus when intercepting bombers
["fighter"] = {tcbtDefendeTable={"bomber"},tcbtAttackerBonusTable={1.5},tcbtDefenderBonusTable={0}},
-- Bombers are very vulnerable if intercepted by enemy fighters
["bomber"] = {tcbtDefendeTable={"fighter"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
-- Assault/dive bomb airplanes get extra bonus against infantry, MGs, AA batteries and armoured vehicles
["assaultplane"] = {tcbtDefendeTable={"infantry","mg","armoured","aagun"},tcbtAttackerBonusTable={1.5,1.5,1.5,1.5},tcbtDefenderBonusTable={0,0,0,0}},
-- Floatplanes and naval bombers (Vildebeest) get bonus against naval units
["naval_bomber"] = {tcbtDefendeTable={"naval"},tcbtAttackerBonusTable={2},tcbtDefenderBonusTable={0}},
-- NAVAL COMBAT
-- Only destroyers are effective against submarines         
["naval_asw"] = {tcbtDefendeTable={"submarine"},tcbtAttackerBonusTable={1.25},tcbtDefenderBonusTable={0}},
-- Naval units without ASW capabilities are useless against submarines
["naval_non_asw"] = {tcbtDefendeTable={"submarine"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={10}},
-- Submarines are very effective against naval units without ASW capabilities
["submarine"] = {tcbtDefendeTable={"naval_non_asw"},tcbtAttackerBonusTable={2},tcbtDefenderBonusTable={0}}
-- Add more combinations if needed
}

The traits for each unit type are defined in setTraits.lua (some examples below)

SQL:
-- Unit Type Traits

traits.assign(object.uCuerpodeCarabineros,"infantry","police","ground_air_vulnerable")
traits.assign(object.uHeavymachinery,"engineer","ground_air_vulnerable")
traits.assign(object.uLegionEspanola,"infantry","african","ground_air_vulnerable")
traits.assign(object.u1aDivisioneCCNNDioloVuole,"infantry","foreign","italian_ctv","ground_air_vulnerable")
traits.assign(object.uEjercitoPopular,"infantry","ground_air_vulnerable")
traits.assign(object.uEjercitoNacional,"infantry","ground_air_vulnerable")
traits.assign(object.uTerciosdeRequetes,"infantry","militia","ground_air_vulnerable")
traits.assign(object.uPanzerschiff,"naval","naval_aa_equipped","naval_non_asw")
traits.assign(object.uPzKpfwIANegrillo,"armoured","ground_air_vulnerable")
traits.assign(object.uAnarchistmilitiaCNTFAI,"infantry","militia","ground_air_vulnerable")
traits.assign(object.uTabordeRegulares,"infantry","african","ground_air_vulnerable")
traits.assign(object.uGuardiaCivil,"infantry","police","ground_air_vulnerable")
traits.assign(object.uCarroVeloceCV33,"armoured","ground_air_vulnerable")
traits.assign(object.uRenaultFT17,"armoured","ground_air_vulnerable")

If you have better ideas I'm all ears!

Pablo
 
If you have better ideas I'm all ears!
Depends on what you whish to achieve with fights between implementing ideas (& their bonus vs their malus towards gameplay and readability/pleasure for players) versus sticking to civ2 system (with limits pushed aside like you did).

The new onInitiateCombat functions is truly powerfull, allowing many features to be built like one-sided-shoots, disengagement, other units involvments, strengh evolution while in fight, etc.

Dunno how the scenario template is using it or is planning to allow to thought.
 
Depends on what you whish to achieve with fights between implementing ideas (& their bonus vs their malus towards gameplay and readability/pleasure for players) versus sticking to civ2 system (with limits pushed aside like you did).

The new onInitiateCombat functions is truly powerfull, allowing many features to be built like one-sided-shoots, disengagement, other units involvments, strengh evolution while in fight, etc.

Dunno how the scenario template is using it or is planning to allow to thought.
Bonjour, I mean on achieving the expected combat bonuses between unit types (typical situation AT vs tank). I've seen implemented in different ways (tables of vulnerabilities, the rules_LST functionality with combat groups...) but never using the traits module :)
 
Bonjour, I mean on achieving the expected combat bonuses between unit types (typical situation AT vs tank). I've seen implemented in different ways (tables of vulnerabilities, the rules_LST functionality with combat groups...) but never using the traits module :)
Then what you did so far seems the right choice (under prof.garfield confirmation) indeed.

A little reminder which may not be needed, please signal units "group", and groups strengths and weaknesses in civilopedia (in describe .txt), or have it done with the LST ;) so players may check this out.
 
Then what you did so far seems the right choice (under prof.garfield confirmation) indeed.

A little reminder which may not be needed, please signal units "group", and groups strengths and weaknesses in civilopedia (in describe .txt), or have it done with the LST ;) so players may check this out.
will do! There's so much to document :)
 
@Pablostuka
Code:
-- AIR COMBAT
-- In general, airplanes take no damage when engaging non AA protected ground targets. Give huge attack bonus!
["airplane"] = {tcbtDefendeTable={"ground_air_vulnerable"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- In general, airplanes take no damage when engaging non AA protected naval targets. Give huge attack bonus!
["airplane"] = {tcbtDefendeTable={"naval_air_vulnerable"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- Airplanes are vulnerable against AA equipped naval units
["airplane"] = {tcbtDefendeTable={"naval_aa_equipped"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
-- Airplanes are vulnerable against AA guns
["airplane"] = {tcbtDefendeTable={"aagun"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
Only one of these is going to be in your table, probably the last one (I don't know how duplicated keys inside a table constructor are resolved). Keys in a Lua table must be unique*.

*You can use metatables to make it look like a key can take on multiple values, like I do for discrete events.
 
@Pablostuka
Code:
-- AIR COMBAT
-- In general, airplanes take no damage when engaging non AA protected ground targets. Give huge attack bonus!
["airplane"] = {tcbtDefendeTable={"ground_air_vulnerable"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- In general, airplanes take no damage when engaging non AA protected naval targets. Give huge attack bonus!
["airplane"] = {tcbtDefendeTable={"naval_air_vulnerable"},tcbtAttackerBonusTable={3},tcbtDefenderBonusTable={0}},
-- Airplanes are vulnerable against AA equipped naval units
["airplane"] = {tcbtDefendeTable={"naval_aa_equipped"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
-- Airplanes are vulnerable against AA guns
["airplane"] = {tcbtDefendeTable={"aagun"},tcbtAttackerBonusTable={0},tcbtDefenderBonusTable={2}},
Only one of these is going to be in your table, probably the last one (I don't know how duplicated keys inside a table constructor are resolved). Keys in a Lua table must be unique*.

*You can use metatables to make it look like a key can take on multiple values, like I do for discrete events.
Argh that's true, silly me, that's why I built the lists inside the tables! Thanks for noticing Prof.

SQL:
["airplane"] = {tcbtDefendeTable={"ground_air_vulnerable","naval_air_vulnerable","naval_aa_equipped","aagun"},tcbtAttackerBonusTable={3,3,0,0},tcbtDefenderBonusTable={0,0,2,2}},
 
Tomorrow I'm flying back to Spain for a break so will stop development for some days. So far I'm implementing a quite complex and nice system to customize the combat based on the below premises:
  • Specific units are more suitable than others against enemy units (as shown above with my usage of the LST Traits module). Nothing new here.
  • There's a chance of specific Combat Events to trigger during combat: counterattack, encirclement, assault, etc which can modify the attacker/defender stats and even cause specific behaviours (troops fleeing or surrendering!). The better trained the units are (by tech), the more chance of these events triggering on their favour. This is innovative in a way that you need to train your troops (research specific techs) to improve your performance in combat and increase the chances to kick off one or more of these events.
  • Commander (Leader) units have specific traits that substantially alter the stats of the units under their command. If combat occurs on an adyacent tile, the fighting troops inherit the effects of these traits. For instance, a "fortress buster" commander will allow troops fighting "under their command" to be stronger when attacking units entrenched in fortifications. An "africanist" will be especially effective when commanding African colonial troops (legión, regulares). A "prince of terror" commander will have a chance to make the enemy withdraw from combat and flee from their positions even before fight starts, other traits will give bonuses when fighting on specific terrain, etc. Many cool combinations that I have implemented, and many more I can include!
Some sample code:

1663342209766.png

or this:
1663342412822.png



I'm loving Lua and its capabilities!

Pablo
 
Top Bottom