Lua Scripting Possibilities

Can someone help me with this?

if city.id == cityID["Condata"] and conqueror.id == romanTribe.id then

justOnce("ThinningOfForests", function ()
civ.ui.text(func.splitlines(thinningForestsText))
state.condataTaken=1
--jp adding this in to see if this can work
for __,location in ipairs(thinningForestsLocations) do
civ.getTile(location[1], location[2], location[3]).terrainType = thinningForestsType
civlua.createUnit(gallicTownfolk, defender, {{48,46,0},{47,47,0}}, {count=1, randomize=false, veteran=false})

end) --This is line 1763 for people helping me

end

I don't know how many "ends" I'm supposed to put or where they are supposed to go but this is not working and I'm getting an error that "D:\Test of Time TOTpp14\Scenario\Caesar\events.lua:1763: unexpected symbol near ')' "

I do have what I believe is also necessary to build this:

local thinningForestsText = "The Romans sack Condata! Some Gallic Townfolk flee into the countryside, but others throw their lot in with Rome, showing us the way through the nearby forests!!"
local thinningForestsTerrainType=3
local thinningForestsLocations={
{49,47,0}
,{53,47,0}
,{53,55,0}
,{49,55,0}
}

Any help would be greatly appreciated!!!
 
You need another 'end' before the ')', the first to close the 'for' loop, the second to close the function.
 
Well, that got the text part to work. Thank you! Any idea why the Gallic Townfolk unit won't show up and the terrain won't change? (I found I had mistyped thinningForestsTerrainType but even after changing, as seen below, it still doesn't work).

if city.id == cityID["Condata"] and conqueror.id == romanTribe.id then

justOnce("ThinningOfForests", function ()
civ.ui.text(func.splitlines(thinningForestsText))
state.condataTaken=1
--jp adding this in to see if this can work
for __,location in ipairs(thinningForestsLocations) do
civ.getTile(location[1], location[2], location[3]).terrainType = thinningForestsTerrainType
civlua.createUnit(gallicTownfolk, defender, {{48,46,0},{47,47,0}}, {count=1, randomize=false, veteran=false})

end end)

end

if I try putting another "end" below the thinningForests, (which is where I found one in a walls of Alesia event Grishnach had written which I tried to copy) I get another error again...

civ.getTile(location[1], location[2], location[3]).terrainType = thinningForestsTerrainType
end
 
Hmm, I wish I could help, but I'm not seeing any issues with that; I don't think you need another "end" though. Your code actually ran fine for me and did set the terrain type. For the call to civlua.createUnit, I added these two lines just above that in order to test:
Code:
local gallicTownfolk = civ.getUnitType(4)
local defender = civ.getTribe(2)
... and then that worked as well: 4 Japanese archers appeared at 48,46. I'm not sure how you defined those two variables in your code though.
 
In Caesar @Grishnach was able to tie in the recruitment of units to the presence of improvements. I'm curious if anyone thinks it is possible to RESTRICT the building of IMPROVEMENTS if another improvement exists?

I'm building a scenario of the Combined Bomber Offensive in Europe. I want part of the fun to be establishing new bases (Allied build up). I want to restrict planes to airbase cities (different graphic than regular cities as they lack city walls). I don't want 'city' cities to be able to build planes. Does anyone think it would be possible for the game to check and see if a certain improvement is present, and if it is, then another improvements cannot be built?
 
Definitely possible, and pretty simple. The code that checks whether an improvement can be built works almost identically to the way it checks whether a unit can be built, and you can base the check on either the presence or absence of other improvements -- or on a thousand other conditions!

I believe the following code will not allow Airports (improvement 32) to be built in cities that already contain a City Walls (improvement 8). You could substitute different improvement IDs, of course:
Code:
civ.scen.onCanBuild(function (defaultBuildFunction, city, item)
   if civ.isImprovement(item) and item.id == 32 and civ.hasImprovement(city, civ.getImprovement(8)) then
       return false
   else
       return defaultBuildFunction(city, item)
   end
end)
 
Last edited:
This is just some really cool stuff. I have to admit, when I was first wading in to the whole ToTPP thread I was asking for this thing or that, and didn't really understand why @TheNamelessOne invested so much time in lua, but it really is the most expansive difference he made. This quick code you wrote, Knighttime, is the key from moving from 7 civs to 2 in a scenario. Crazy implications. Thank you!

Definitely looking forward to what you and @tootall_2012 come up with for Napoleon!
 
I'm trying to figure out if it is possible to make it cost money every time a unit moves. I plan on using money to represent "fuel" in my combined bomber offensive scenario. I'd first thought about using a key press to do this (every time a key is pressed on the number pad moving the unit, money is deducted) but then I realized that some people play with a mouse, and I"m not 100% certain that lua will recognize the number paid 1-9 and 0 vs. the 1-0 at the top of the keyboard.

Does anyone have another idea of how else I can accomplish this? Maybe the onActivateUnit combined with a check to see what type of terrain it is on? (I'd have a terrain for "Airbase" where I wouldn't want there to be a cost).

Perhaps another idea?

onActivateUnit
civ.scen.onActivateUnit(function (unit, source) -> void)
Registers a function to be called every time a unit is activated. The callback takes the unit activated as a parameter, and the source of unit activation. Source is `true` if activated by keyboard or mouse click, `false` if activated by the game itself.
 
How about this:

At the start of each turn (maybe an on-production call) use a loop and the moveSpent command to set each "fuel bound" unit to 1/3 movement point. The player can use a key press command to "fuel up" all the units on a square, again looping over units and setting the moveSpent command. (Or maybe just the active unit.) Units with 0 movement left have been used and are not fueled up. Another key can "unfuel" a unit, returning the fuel to the treasury. Perhaps you can have a key to fuel and unfuel everything you own on the board.

This means that each unit uses its maximum allotment of fuel on a mission, but that might not be a serious problem for your game. Perhaps you could even have an option to increment the fuel. That would be a lot of micromanagement, so perhaps you would only allow fuel increments when the fuel situation gets desperate.
 
I'm really hoping to find a solution where you simply move one space and lose X amount of dollars if possible to keep it super simple for the player.

What about a huge table (or better yet small equation) that would have the game search for any location on the maps that weren't a certain type of terrain, and if the unit moved onto that space, money would be deducted?

While we are on the topic - I believe it is possible to have onUnitKilled cross reference what location the unit is sitting, correct? I'm 95% sure that has been hashed out prior but I'd better ask that now too because I could probably (sadly) live without the fuel, but I'm not going to be able to go on without my industry sequence (which would be a shame considering I just finished the rules and am now keen to get to building the events!!!):

To Destroy Industry:
If a particular unit (industry) is killed at a particular location (near a certain city-let's say Berlin) then:
A particular city improvement (Industry I) is removed from a particular city (Berlin)
The terrain changes on 1 to 3 maps to rubble near Berlin

To Rebuild Industry
If a particular city improvement (Industry I) is built in a particular city (Berlin), then:
A unit is built at a specific location on the map (near Berlin)
The terrain changes on 1 to 3 maps to industry
 
As I see it, the issue with the fuel cost idea is that the "onActivateUnit" trigger only fires when you (or the AI) first select a unit and make it active. But there isn't a trigger that fires each time it moves one tile, or one that fires at the end of its turn when its movement points are used up, or that would detect when a bomber returns to base and ends its turn with movement points to spare.

Numeric keypad keys 1-9 do have different key codes than the number keys across the top of the keyboard, so the Lua "onKeyPress" trigger can distinguish between them. But as you said, it's easy to move units with a mouse, so detecting keys isn't a very reliable way to trap unit movement.

It is possible to have Lua check all tiles and take action based on their terrain type, but there isn't an event which would fire when a unit moved into such a tile. As a result, you could obtain a "snapshot" of unit locations at a given point in time, but this would be once per turn (or once per something) and still wouldn't reliably detect movement points spent. (If a unit moved from tile A to tile B and then back to tile A again, it used two movement points but ended up in the same place where it started.)

This is a tough one... everything that comes to mind is some version of what @Prof. Garfield suggested, and would be pretty heavy on the micromanagement. It would also only work for the human player -- is that sufficient, or did you intend the concept of fuel cost to apply to AI-controlled civs as well? There might not be any way at all to do that.

On a more positive note, though, your industry sequence sounds feasible to me. It might take quite a bit of code to accomplish that, and it could get pretty complex depending on the flexibility you're envisioning! But I don't see any major gaps in your plan -- it all seems possible with Lua.
 
Well some background - Over the Reich is a multiplayer only scenario so we are 100% focused on things the human player can control. I don't want to make this a micromanagement issue for anyone though as that was a huge drawback of earlier versions and I'm trying to use lua to streamline.

I'm trying to tie fuel into money somehow because with my industry idea above I can have the Allies attack a fuel refinery unit and have a stock exchange removed, which means that there is less money/fuel to go around for the other player. From what you're telling me, my original idea wouldn't have worked. Even though you say the keypad would work, there's the issue of potential cheating, but more importantly, people will probably want to use the "go to" command and they can't do that without losing fuel.

I guess if I want to keep it I'm going to have to go "abstract" and just tie the fuel loss to attack runs only. Because these units are all "k units" (units that fire ammo) I know from Caesar's Gallic Wars that I can have a monetary reduction each time the k key is pressed. This should work well enough. People can move their aircraft around but won't be able to make an attack run (a combat sortie) unless they have fuel. So I'll just go with that and be happy that I can do something! I can even code that myself by copying, pasting, and modifying what has been done before. I do think the concept of attacking the fuel industry is important enough that I'm willing to deal with this abstract way of representing it.

The other issue with @Prof. Garfield 's method (and I DO appreciate the thought/help) is that I'm afraid if you had a city with 20 units in it that you could end up pressing the key and going down to $0 funds which may be problematic the next turn. Doesn't the game want you to have enough to cover the bills for the next turn before you hit end turn or else items are sold? I feel like I've had this happen to me but you would know better, Professor.

While I have you both though - would you mind letting me know the feasibility of a few other things I've been taking for granted while building this whole thing? :lol: I know MOST of what I want to do can be done because it's nothing new, but here are a few things I've been taking for granted and if they really won't work I need to figure something else out.

1. At the start of each turn I want the game to check for a unit type and see how many of them a certain civ has, and depending on how many, I want that civ to be given a certain amount of money (so if a civ has 8 docks they might get $8000. If they have 4, they'd get $4000).

2. I'm also planning on doing this in reverse, meaning if Germany has 15 sub pens the allies get no extra money, but if Germany has 14, the Allies get an extra $1000, 13 and they get $2000, and so on.

3. When an aircraft moves onto a certain terrain type, it is deleted. Because I'm dealing with aircraft, this is OK for a check at the start of a turn (the terrain type will exclusively be under certain cities so as soon as the aircraft moves there its turn is over anyway) but I'd rather it happen instantly so I can have a text box warning the player it happened and they don't land their whole air force there.

4. Is there a way to change terrain under any new city that is created? Because a potential workaround/cheat for the player would obviously be to build cities elsewhere than airfield terrain. I see that there is an onCityFounded
civ.scen.onCityFounded(function (city) -> void)
so I'm guessing/hoping that this would work. The only cities that can be built are airfields, so this would happen for every city that is founded.

Those are the only ones I'm really not sure will work. I do have a clarifying question about the industry one you helped me with above though - this isn't relying on an integer for the precise unit (beyond [0] = settlers - I mean the actual unit that has an id that might be [1,275] if it was the 1275th unit created). Just curious because I envision the industry mechanism going over and over again.
 
1. No problem, this should be pretty straightforward.

2. Same

3. Since you only care where an aircraft ends its turn, this would be pretty straightforward to check at the beginning of the following turn. "I'd rather it happen instantly so I can have a text box warning the player it happened and they don't land their whole air force there." -- To do that, though, you'd need to use a different trigger, besides civ.scen.onTurn(). But there isn't a trigger that fires when a unit moves, or stops moving. I think you could take the same code, and copy it to civ.scen.onActivateUnit() as well. This means it would also fire at the beginning of the next unit's turn, and then if the game found any aircraft to delete, the player would be notified immediately, before they did the same thing with the next unit. My only concern would be performance; the Lua code needs to process quickly enough that you don't notice any lag, since it would be firing so many times per turn. It would probably be faster to loop over cities with civ.iterateCities() rather than units with civ.iterateUnits(), since you said all instances of this terrain type will be beneath a city and there are probably fewer cities than units in the game. Looping over all map tiles with civlua.iterateTiles() would probably be slower yet, I'd avoid that.

4. Yes, should be possible. The civ.scen.onCityFounded() trigger would be the natural way to go, as you said, and you can set tile.terrainType for any tile -- you're not prevented from changing a tile that contains a city. The only potential issue is one I pointed out here as #2 -- TNO hasn't released a patch since then to correct the timing of this trigger. I think this means that the player could choose to found a city, then hit cancel, but the terrain would change to airfield anyway. If that's a major "cheating" concern, then you might need to move the event into civ.scen.onTurn() and change the terrain beneath the city at the beginning of the following turn, instead of immediately upon the city being founded.

For the industry idea, correct: you'll want to base your actions on a unit's type, probably also referencing location or home city -- not any list of particular unit IDs.

Good luck, and have fun with Lua!
 
I've encountered my own issue with Lua events, and would appreciate any thoughts or suggestions.

I'm writing Lua events for @tootall_2012 for use in his upcoming Napoléon scenario, and one of the key goals is to have events manage all conditions for peace or war, and set those states between tribes accordingly, based on actual historical causes (or, in some cases, speculative ones, but deliberately chosen based on historical context). I'm successfully able to update tribe.attitude and tribe.treaties using Lua, to "force" tribes to declare war, or sign a peace treaty or alliance, so that aspect is going well.

The problem is that this relies on tribes not making their own decisions about when to take those diplomacy actions, and I'm having a lot of issues with the civ.scen.onNegotation() trigger. My goal is very simple: to prevent all in-game negotation between human or AI tribes, since Lua is managing all changes that should occur. The documentation for that function is a little scarce, but I thought this would do the trick:
Code:
civ.scen.onNegotiation(function (talker, listener)
   return false
end)
This does seem to prevent the human player from talking to an AI tribe (it essentially disables the F3 key) and it also seems to prevent AI tribes from contacting me. Unfortunately, though, they still seem to be able to sign peace treaties and declare war with each other! As you can imagine, this is a major problem in the scenario, if (for example) allies who are supposed to be united against the human player suddenly turn on each other and divert their resources to a war on another front.

I'm only able to come up with 3 possibilities:

1. I'm misunderstanding the scope of this function, and it was only ever intended to apply to interactions involving the human player. That doesn't seem right, though, since I've verified that it is called when both the talker and listener are AI tribes.

2. There is some type of bug in TOTPP that is utilizing the return value I'm providing if either the talker or listener is human, but fails to apply this if both tribes are controlled by the AI.

3. The return value of "false" prevents AI tribes from meeting with each other to exchange techs, maps, etc., but Civ2 permits peace and war statuses to change even without "negotation" between two AI tribes. In other words this is intentional Civ2 game behavior despite TOTPP.

I added a lot of debugging statements to my script to watch what was happening, and as far as I can tell, the issue is not that the relationship between two allies is gradually worsening until they finally declare war. It looks to me like they have a perfect relationship but suddenly, with no advance warning, one of them breaks the alliance and declares war. (This happens in reverse for warring civs that suddenly sign a peace treaty, despite being "enraged" with other.)

@JPetroski I see that you and @Grishnach used the same code in Caesar's Gallic Wars to block negotiations. Did you ever experience anything like this in your testing? or has anyone else who's played that scenario seen this AI behavior?

Are there any other Lua scenarios in progress out there, where this could be tested?

@TheNamelessOne, if you're available, do you have any idea what's going on here?

Thanks in advance to anyone who can assist!
 
Last edited:
Honestly I have no idea what the AI was doing in Gallic Wars in terms of speaking with each other as that wasn't a huge factor - you were just supposed to go out and conquer them all. I've PM'd the two of you some code that may or may not be useful. I wish I could be more helpful but I barely understand this stuff myself :(
 
I tried to plug in your code right here but it doesn't seem to be triggering. I get an <eof> problem when I try to insert it. Not sure where it should go or if it should fit with the way the other parts are written or not. Any ideas?



Definitely possible, and pretty simple. The code that checks whether an improvement can be built works almost identically to the way it checks whether a unit can be built, and you can base the check on either the presence or absence of other improvements -- or on a thousand other conditions!

I believe the following code will not allow Airports (improvement 32) to be built in cities that already contain a City Walls (improvement 8). You could substitute different improvement IDs, of course:
Code:
civ.scen.onCanBuild(function (defaultBuildFunction, city, item)
   if civ.isImprovement(item) and item.id == 32 and civ.hasImprovement(city, civ.getImprovement(8)) then
       return false
   else
       return defaultBuildFunction(city, item)
   end
end)

Here is the whole production sequence code that I'm using

Code:
--g limitations in production
local buildRestrictionsUnits={
   ["Me109G6"] = {unit=civ.getUnitType(12), conditionMet=function (city, state) return civ.hasImprovement(city, civ.getImprovement(13)) end}
   ,["MossiePR"] = {unit=civ.getUnitType(125), conditionMet=function (city, state) return civ.hasImprovement(city, civ.getImprovement(17)) end}
}

local buildRestrictionsImprovements={
   ["TestImp"] = {improvement=civ.getImprovement(26), conditionMet=function (city, state) if state.thisCanBeBuilt==true then return true else return false end end},
 
}

local buildRestrictionsWonders={
   ["TestWonder"] = {wonder=civ.getWonder(18), conditionMet=function (city, state) if state.thisCanBeBuilt==true then return true else return false end end}
}

--
 
---DELETE 480-485 - 415 if this doesn't work
civ.scen.onCanBuild(function (defaultBuildFunction, city, item)

   local separateCondition=nil
 
   if civ.isUnitType(item) then
       for _,restrictedUnit in pairs(buildRestrictionsUnits) do
           --[[justOnce("DebugPrint", function ()
               civ.ui.text(func.splitlines(tostring(restrictedUnit.unit.id)))
           end)--]]
           if item.id==restrictedUnit.unit.id then
               separateCondition=restrictedUnit.conditionMet(city,state)
           end
       end
   end
 

 
   if civ.isImprovement(item) then
       for _,restrictedImprovement in pairs(buildRestrictionsImprovements) do
           if item.id==restrictedImprovement.improvement.id then
               separateCondition=restrictedImprovement.conditionMet(city,state)
           end
       end
   end
 


 
   if civ.isWonder(item) then
       for _,restrictedWonder in pairs(buildRestrictionsWonders) do
           if item.id==restrictedWonder.wonder.id then
               separateCondition=restrictedWonder.conditionMet(city,state)
           end
       end
   end
 
   if separateCondition==nil then
       return defaultBuildFunction(city,item)
   else
       return separateCondition
   end

end)
 
Hmm, well, OK. You have a rather different approach already in place, and you probably don't want to plug my code exactly as it's written into that. Even if you could get it to work, it's just a clash of styles, and you have a framework in place that covers much of what my code already did.

Given what you already have, and assuming that's working correctly for you, I think you could simply insert one line. On the blank line immediately after this:
Code:
["TestImp"] = {improvement=civ.getImprovement(26), conditionMet=function (city, state) if state.thisCanBeBuilt==true then return true else return false end end},
try adding this:
Code:
["Aiport"] = {improvement=civ.getImprovement(32), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(8)) then return false else return nil end end}

I haven't tried running this myself! But I think that ought to give you the result I described: the game will not allow Airports (improvement 32) to be built in cities that already contain a City Walls (improvement 8). Note the "else return nil" in what I provided. That value is being provided back to your "separateCondition" variable -- so if the city does not have City Walls, near the end of your code, you'll run defaultBuildFunction and let the game's normal logic determine whether or not an airport can be built.
 
That works! Thank you!

I tried replacing (32) with (32, 17, 33) and find that this does not work, however - only the first one is taken. So I guess I need to do this individually, but I can live with that.
 
This lua is not an easy thing to handle.

I really want to be build a table/code similar to the build restrictions a few above @Grishnach made that will look at the particular unit killed, where on the map it is, and will then remove an improvement from a certain city, while changing some terrain. But that might as well be space age technology to me. I tried writing the below code simply to see if I could a single "onUnitKilled" to work and no dice - nothing fires at all ... I'm not sure what I'm missing?

What I've been doing to get this scenario ready is painstakingly going through every single city and picking the coordinates where I want each of these factories to go which is a huge process but one I'm willing to do but trying to get the above is very challenging when I can't get the below to work!

Code:
--[[I am attempting to create a situation where if a certain unit [45]
 on a particular tile (364,68,1) is killed, then an improvement [15]
is taken from that city (Berlin) and a particular tile (364,68,0) is changed to terrain (14). 
I am trying to build a table to reference because I am going to need to to do this 9-10 times per city which is
going to be a pain any other way.




]]


--LIST OF UNITS
local unitAliases = {}
--Targets
unitAliases.Industry1  = civ.getUnitType(45)

--LIST OF CITIES
local cityAliases={}

cityAliases.London = civ.getCity(0)
cityAliases.Berlin = civ.getCity(1)

--LIST OF TRIBES
local tribeAliases={}

tribeAliases.Allies = civ.getTribe(1)
tribeAliases.Germans = civ.getTribe(2)
tribeAliases.EventsTribe = civ.getTribe(3)
------

civ.scen.onUnitKilled(function (killed, killedBy)
    local deadUnit = killed.type.id
    local attacker = civ.getCurrentTribe()
   
    if deadUnit == Industry1 and killed.owner.id == Germans then
    civ.ui.text(func.splitlines("This is a test to see if this works."))
    civ.removeImprovement(Berlin, 15)
end  
   

end)


I also need to figure out a way to get the production of improvement 15 in a particular city (Berlin) to spawn a new Industry1 unit at (364,68,1) and to change the terrain at (364,68,0) to terrain 8 and I'm not totally sure the best way to do this. civ.hasImprovement seems like the closest trigger but wouldn't that cause the industry to keep being built each turn? That won't work... And I also need it to keep triggering over and over again so justOnce wouldn't be an option here.
civ.hasImprovement(city, improvement)

The good news I guess is that since this is a multiplayer scenario, if I can get this industry mechanism working I really don't have a heck of a lot of other events left that I need to code (tediously add hundres of lines to tables, yes, but that i can do).

Industry Mechanism
Counting How Many Sub Pens are around and giving money if there are less
Weather/Changing Terrain
Units dying if they activate/start their turn on a particular terrain

I'd love to be able to do these all with tables that are easy to copy/paste, and keep plugging through but I'm not sure how realistic that is. The hard part for me is getting the mechanism to work.
 
civ.removeImprovement(Berlin, 15)

Haven't looked much at Lua since our last discussion, so maybe this is wrong. However, I think you should try

civ.removeImprovement(cityAliases.Berlin, 15)

or, something like

local Berlin = cityAliases.Berlin

EDIT: put local Berlin = cityAliases.Berlin on an appropriate line earlier in the code.
 
Back
Top Bottom