[TOTPP] Lua Scenario Template

I cut and pasted your code in and got a text box (didn't check that all tribes had money spent).

Do you happen to have multiple lines like this?
Code:
function unitKilledEvents.unitKilledInCombat(loser,winner) do
That is, do you define multiple unitKilledEvents.unitKilledInCombat functions? There should only be 1, and all your unit killed events should be in it. (You also don't need the do/end for the function, but it doesn't hurt anything.) If this isn't the problem, please post your events file, and I'll have a look at it.
 
You have 2 unitKilledEvents.unitKilledInCombat functions:
Code:
function unitKilledEvents.unitKilledInCombat(loser,winner) do
loser.owner.money = math.max(loser.owner.money -campaignCost,0)
winner.owner.money = math.max(winner.owner.money-campaignCost,0)
    if loser.type == object.uLabourers then
    local newLabourers = civ.createUnit(object.uLabourers,winner.owner,winner.location)
    end
    if loser.type == object.uEngineers then
    local newEngineers = civ.createUnit(object.uEngineers,winner.owner,winner.location)
    end
end
Code:
function unitKilledEvents.unitKilledInCombat(loser,winner) do
loser.owner.money = math.max(loser.owner.money -campaignCost,0)
winner.owner.money = math.max(winner.owner.money-campaignCost,0)
    if loser.type == object.uMfgGoods then
    object.tRussians.money = object.tRussians.money - param.MfgGoodsKillPenalty
    object.tJapanese.money = object.tJapanese.money - param.MfgGoodsKillPenalty
    object.tGermans.money = object.tGermans.money - param.MfgGoodsKillPenalty
    object.tIndependent.money = object.tIndependent.money - param.MfgGoodsKillPenalty
    object.tAmericans.money = object.tAmericans.money - param.MfgGoodsKillPenalty
    object.tBritish.money = object.tBritish.money - param.MfgGoodsKillPenalty
    object.tFrench.money = object.tFrench.money - param.MfgGoodsKillPenalty
        end
end   
end

You should only have one, which would look something like this:

Code:
function unitKilledEvents.unitKilledInCombat(loser,winner) 
    loser.owner.money = math.max(loser.owner.money -campaignCost,0)
    winner.owner.money = math.max(winner.owner.money-campaignCost,0)
    if loser.type == object.uLabourers then
        local newLabourers = civ.createUnit(object.uLabourers,winner.owner,winner.location)
    end
    if loser.type == object.uEngineers then
        local newEngineers = civ.createUnit(object.uEngineers,winner.owner,winner.location)
    end
    if loser.type == object.uMfgGoods then
        civ.ui.text("Mfg Goods unit killed")
        object.tRussians.money = object.tRussians.money - param.MfgGoodsKillPenalty
        object.tJapanese.money = object.tJapanese.money - param.MfgGoodsKillPenalty
        object.tGermans.money = object.tGermans.money - param.MfgGoodsKillPenalty
        object.tIndependent.money = object.tIndependent.money - param.MfgGoodsKillPenalty
        object.tAmericans.money = object.tAmericans.money - param.MfgGoodsKillPenalty
        object.tBritish.money = object.tBritish.money - param.MfgGoodsKillPenalty
        object.tFrench.money = object.tFrench.money - param.MfgGoodsKillPenalty
    end
end

I added an Mfg Goods killed textbox. I also removed the 'do' (and corresponding 'end') at the beginning of the function, since it is not necessary.
 
Got this to work, messing with a few tweaks...
Code:
local campaignCost = 10
local MfgGoodsPlunder = -150

-- This will only run when a unit is killed in combat (i.e. not when an event
-- 'kills' a unit)
-- note that if the aggressor loses, aggressor.location will not work
--

function unitKilledEvents.unitKilledInCombat(loser,winner)
    loser.owner.money = math.max(loser.owner.money - campaignCost,0)
    winner.owner.money = math.max(winner.owner.money - campaignCost,0)
    if loser.type == object.uLabourers then
        local newLabourers = civ.createUnit(object.uLabourers,winner.owner,winner.location)
    end
    if loser.type == object.uEngineers then
        local newEngineers = civ.createUnit(object.uEngineers,winner.owner,winner.location)
    end
    if loser.type == object.uMfgGoods then
        civ.ui.text("Goods train destroyed! Attackers plunder 150 gold!")
        object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
        object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
        object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
        object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
        object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
        object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
        object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
    end
end

Progress for me...Onwards and sideways!

@Prof. Garfield , just one more request on this...
What is the best way to make the text appear only to the human player?
 
@Prof. Garfield , just one more request on this...
What is the best way to make the text appear only to the human player?

Probably to use civ.getPlayerTribe()

getPlayerTribe
civ.getPlayerTribe() -> tribe

Returns the player's tribe.

You can use this to check if the intended recipient of the message is the (current) human player. So, maybe you only want to show a combat message if the winner or loser is the human player

Code:
if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
    civ.ui.text("Your unit participated in combat.")
end
 
Probably to use civ.getPlayerTribe()

getPlayerTribe
civ.getPlayerTribe() -> tribe

Returns the player's tribe.

You can use this to check if the intended recipient of the message is the (current) human player. So, maybe you only want to show a combat message if the winner or loser is the human player

Code:
if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
    civ.ui.text("Your unit participated in combat.")
end

Thanks, sir...
Would the getplayertribe lines be added at the bottom, as a new supplementary command?

Code:
 if loser.type == object.uMfgGoods then
        object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
        object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
        object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
        object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
        object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
        object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
        object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
end
if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
        civ.ui.text("Goods train destroyed! Your men plunder 150 gold.")
end

Does this look OK?
 
Does this look OK?

No, you should put it inside the body of the if statement pertaining to a destroyed Mfg Goods unit.

That is
Code:
if loser.type == object.uMfgGoods then
        if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
                civ.ui.text("Goods train destroyed! Your men plunder 150 gold.")
        end
        loser.money = math.max(loser.money-MfgGoodsPlunder,0)
       winner.money = winner.money+MfgGoodsPlunder
       --object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
        --object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
        --object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
        --object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
       -- object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
        --object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
        --object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
end
 
Thanks, ProfGarfield - During testing, Lua is running an error after the unit attacks a Mfg train.

...LuaTriggerEvents\UniversalTriggerEvents\onUnitKilled.lua:26: attempt to perform arithmetic on a nil value (field 'money')
stack traceback:
...LuaTriggerEvents\UniversalTriggerEvents\onUnitKilled.lua:26: in function 'UniversalTriggerEvents\onUnitKilled.unitKilledInCombat'
...mperialism III Update\LuaTriggerEvents\triggerEvents.lua:113: in function 'triggerEvents.unitKilledInCombat'
N:\CIV2 TOT\1_Imperialism III Update\events.lua:201: in upvalue 'doOnUnitDefeatedInCombat'
N:\CIV2 TOT\1_Imperialism III Update\events.lua:269: in function <N:\CIV2 TOT\1_Imperialism III Update\events.lua:252>
 
My bad, the plunder code I provided was wrong:

loser.owner.money = math.max(loser.money-MfgGoodsPlunder,0)
winner.owner.money = winner.money+MfgGoodsPlunder

Units don't have a 'money' field (so, they return nil when calling .money), but the tribe objects do have a money field.
 
Still getting the same error...I'm doing something wrong here, but cannot see it...

Code:
if loser.type == object.uMfgGoods then
        if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
        civ.ui.text("Goods train destroyed! Your men plunder 150 gold.")
        end
    loser.owner.money = math.max(loser.money-MfgGoodsPlunder,0)
    winner.owner.money = winner.money+MfgGoodsPlunder
        --object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
        --object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
        --object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
        --object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
        -- object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
        --object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
        --object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
    end
 
Still getting the same error...I'm doing something wrong here, but cannot see it...

same lines, I just forgot that I needed to add owner on both sides of the assignment...
loser.owner.money = math.max(loser.owner.money-MfgGoodsPlunder,0)
winner.owner.money = winner.owner.money+MfgGoodsPlunder

I think it will work now. Such are the perils of providing untested code...
 
Is it possible to change certain terrain tiles if an event triggers?

Yes. The "old" method is to set
Code:
tile.terrainType= number between 0 and 15
This sometimes gets rid of the river.

TOTPP v0.16 has new functionality

baseTerrain (get/set) (since 0.16)
tile.baseTerrain -> baseterrain

Returns the baseterrain object associated with the tile.

terrain (get/set) (since 0.16)
tile.terrain -> terrain

Returns the terrain object associated with the tile.

river (get/set)
tile.river -> boolean

Returns `true` if the tile has a river, `false` otherwise.

getBaseTerrain (since 0.16)
civ.getBaseTerrain(map, terrainType) -> baseterrain

Returns the base terrain object for the given map and terrain type.

getTerrain (since 0.16)
civ.getTerrain(map, terrainType, resource) -> terrain

Returns the terrain object for the given map, terrain type and resource.

I haven't played around with these enough to know exactly how to use them, and whether you should change a tile's terrain or baseTerrain (or, if both are OK). I think the getTerrain and getBaseTerrain take integers as acceptable arguments. My advice would be to open a console and play around a bit with these commands.
 
This sometimes gets rid of the river.
I can see why that feels like a possibility, but in Medieval Millennium (where I change terrain types a lot) I've never experienced this -- rivers were always preserved.

TOTPP v0.16 has new functionality
...
I haven't played around with these enough to know exactly how to use them, and whether you should change a tile's terrain or baseTerrain (or, if both are OK). I think the getTerrain and getBaseTerrain take integers as acceptable arguments. My advice would be to open a console and play around a bit with these commands.
I've tested these out a little bit from the console, as you recommended. Here's my understanding about what's possible and how the various pieces fit together:

The big issue related to these is whether or not custom terrain resources have been enabled for the given map. This requires enabling the "Custom resources" patch in the TOTPP launcher, and then also turning it on (per map) using either [Ctrl]-[F8] or by setting map.customResources = true using Lua. Once it is turned on, resource "specials" can be added to or removed from any tile, ignoring the pattern that is ordinarily determined by the map resource seed.

The base terrain of a tile is what we could change previously by setting tile.terrainType. The value you assign to tile.baseTerrain is a baseterrain object (not just an integer) which you can find by using civ.getBaseTerrain(). This object type has no information about special resources that may or may not exist on that tile, but contains all the details about irrigation, mining, etc. as specified in Rules.txt. Changing the base terrain of a tile with a special resource will result in a tile that has the corresponding special resource of the new terrain type.

On the other hand, the terrain of a tile contains information about whether or not the tile has a special resource, and what it's production values are. Even if you don't have custom resources enabled, this could be very useful to check production values. If you do have custom resources enabled, then you can assign a terrain object to tile.terrain which specifies not only the underlying terrain type (by inheritance, you could say), but also whether or not it contains a special resource, and which one. You find the appropriate terrain object using civ.getTerrain().

If you don't have custom resources enabled and try to update tile.terrain, this will work as long as the terrain object you pick has the matching resource value (none, first resource, or second resource) as the tile does currently. If not, you'll get an error message. So for the sake of simplicity, my personal recommendation would be to change tile.baseTerrain when possible, and only resort to changing tile.terrain when you deliberately want to change resources (and have enabled that capability).
 
I can see why that feels like a possibility, but in Medieval Millennium (where I change terrain types a lot) I've never experienced this -- rivers were always preserved.

I thought there was some sort of problem with this for OTR, but since the terrain change events were written before I got involved, I never looked that closely. @JPetroski would know for sure.

Thanks for your explanation of the terrain events.
 
Many thanks for the explanation, @Prof. Garfield and @Knighttime :thumbsup:

ok, as an absolute newbie I try to understand what I have to do :D.
First I think I have to write in my object.lua file the terrain types I'm currently using. I would use the civ.getTerrain() command. As an example I would use this command for the desert terrain tile:
Code:
object.rDesert = civ.getTerrain(0)
(I used the prefix 'r' for terrain because 't' is already forgiven.)

But after that I unfortunately don't know what to do. One small example of a script code is often enough for me to understand. But please only if someone of you have the time for it. If it is too much time consuming for you than it's not neccessary.
What I would like to realize is a change of one or two terrain tiles if a certain city or technology is researched.
 
First I think I have to write in my object.lua file the terrain types I'm currently using. I would use the civ.getTerrain() command. As an example I would use this command for the desert terrain tile:
Code:
object.rDesert = civ.getTerrain(0)
(I used the prefix 'r' for terrain because 't' is already forgiven.)
Just so it's clear, adding object definitions is never required. But using named objects like this is a good idea and generally a best practice, because it makes code more readable.

My proposal would be to use the prefix 'b' for baseterrain objects and the prefix 'r' for terrain objects.

If you want to find the object for "Desert", though, that feels to me like baseterrain 0, not terrain 0. So instead of what you wrote, I would add this line to object.lua:
object.bDesert = civ.getBaseTerrain(0, 0)

Note that civ.getBaseTerrain() takes two parameters, not one: the first is the map number, and the second is the terrain type or key. (And civ.getTerrain() takes three parameters: the first is the map number, the second is the terrain type or key, and the third is the resource number: 0 (no resource), 1 (first resource), or 2 (second resource).)

But after that I unfortunately don't know what to do. One small example of a script code is often enough for me to understand. But please only if someone of you have the time for it. If it is too much time consuming for you than it's not necessary.
What I would like to realize is a change of one or two terrain tiles if a certain city or technology is researched.
Setting aside the condition for the moment, here's how to handle the effect.

First, let's find a tile you want to change, and assign it to a variable:
local tileToChange = civ.getTile(10, 15, 0)
The parameters in the call to civ.getTile() are the x, y, and z (map) coordinates of the tile.

Then, change the base terrain of that tile:
tileToChange.baseTerrain = object.bDesert

That's all there is to it!

In order to set up the condition, there would probably be more references to entries in the object table. But if the right objects were defined, something like this ought to work:
Code:
if object.cAthens.owner == object.tRomans then
    local tileToChange = civ.getTile(10, 15, 0)
    tileToChange.baseTerrain = object.bDesert
end
But realize this will change the terrain at that tile on every turn (well, every time this piece of code is run) while the city of Athens is owned by the Romans -- over and over again. If the tile is already Desert, you wouldn't notice a change of course, but the event is still running and setting it. So this may not be a reasonable condition for this effect, or you might want to use civlua.justOnce(), etc.

Hope this is helpful!
 
Top Bottom