Forum



Marcus am 04.10.2012 15:28 #10694


Hi,

the current nightly contains support for Lua scripting, but only for maps so far. It is not possible at the moment to store anything in a save game, but that will change. Do not report a bug for that, it's just not implemented yet.

LUA scripts are loaded from pathtomap.lua for a map at pathtomap.swd. The game will look for a file ending in '.lua' instead of '.swd'. Please be aware that case matters for some operating systems. The file will be sent to all clients, overwriting files in the user's own rttr MAPS directory.

There are 'events' in form of Lua functions that are called:

  • onStart()
    called at the start of the game, after the headquarters are placed.

  • onOccupied(player_id, x, y)
    called every time a point on the map gets occupied by a player.

  • onExplored(player_id, x, y)
    called every time a point on the map becomes visible for a player.

  • onGameFrame(gameframe_number)
    Gets called every game frame.

  • onResourceFound(player_id, x, y, type, quantity)
    Given resource (0=iron, 1=gold, 2=coal, 3=granite, 4=water) was found at x,y.

You can remove an event by just overwriting the function with nil: after onResourceFound = nil, a previously defined onResourceFound function won't be called any more.

Functions to be called from Lua:

  • rttr.AddWares(player, type, amount, ...)
    Add the specified goods to the player's first warehouse. 'type' and 'amount' may be repeated, but have to appear in pairs.

  • rttr.AddPeople(player, type, amount, ...)
    Add the specified people to the player's first warehouse. 'type' and 'amount' may be repeated, but have to appear in pairs.

  • rttr.EnableBuilding(player, type, ...)
    Enable the buildings specified after player. 'type' may be repeated to enable multiple buildings.
    With no type given, all buildings are enabled.

  • rttr.DisableBuilding(player, type, ...)
    Disable the buildings specified after player. 'type' may be repeated to disable multiple buildings.
    With no type given, all buildings are disabled.

  • rttr.SetRestrictedArea(player, x1,y1, ...)
    This restricts the area a player may have territory in. If called without any x/y-values, the restrictions are lifted. Otherwise, the coordinates specify one or multiple polygons the player may have territory in. A polygon inside a polygon will create a hole in it. To specify multiple polygons, start with a 0,0-pair followed by the first polygon, another 0,0-pair, the second polygon etc. and a final 0,0-pair at the end. For multiple polygons, each polygons first point has to be repeated at the end of it.

  • rttr.GetGF()
    Returns the current game frame number.

  • rttr.AddStaticObject(x, y, id, file = 0xFFFF, size = 0)
    Adds new static object. Only environment/static objects and empty space can be overwritten.
    id: id in file
    file: 0xFFFF map_?_z.lst, 0-5 mis?bobs.lst
    size: 0 for hut, 1 for house, 2 for castle.
    See http://bazaar.launchpad.net/~flosoft/s25rttr/trunk/view/head:/src/GameWorld.cpp#L264

  • rttr.AddEnvObject(x, y, id, file = 0xFFFF)
    Adds new environment object. Only environment/static objects and empty space can be overwritten.
    id: id in file
    file: 0xFFFF map_?_z.lst, 0-5 mis?bobs.lst
    See http://bazaar.launchpad.net/~flosoft/s25rttr/trunk/view/head:/src/GameWorld.cpp#L264

  • rttr.Chat(player, message...)
    Sends message to player (-1 for all players).

  • rttr.Log(message...)
    Log to console.

  • rttr.PostMessage(player, message...)
    Send post message to player.

  • rttr.PostMessageWithLocation(player, x, y, message...)
    Send post message to player (with location x, y).

  • PostNewBuildings(player, building_type...)
    Inform player that he can now build building_type.

  • rttr.GetPlayerCount()
    Returns number of players.

  • rttr.GetPeopleCount(player, job_type)
    Get number of people a player has with a given job.

  • rttr.GetWareCount(player, ware_type)
    Get number of wares a player has of a given type.

  • rttr.GetBuildingCount(player, building_type)
    Get number of buildings a player has of a given type.

  • rttr.MissionStatement(player, title, message...)
    Send mission statement to player:
    rttr.MissionStatement(0, "Diary", "Forth day\n\n\nThere are ", rttr.GetBuildingCount(0, BLD_WELL), " wells now.")

  • rttr.AIConstructionOrder(player,x,y,buildingtype)
    orders ai to build buildingtype at the given x,y location (ai will usually retry at a nearby location if the location is bad)



A list of valid building IDs (BLD_*), ware IDs (GD_*) and job IDs (JOB_*) may be found here:
http://bazaar.launchpad.net/~flosoft/s25rttr/trunk/view/head:/src/GameConsts.h
However you are encouraged to use the predefined variables (see example below).


Below you'll find an example for TAL018.SWD (Atomium I, 3 players):
TAL018.lua:
-- start callback, called after everything is set up
function onStart()
    -- add 100 generals and 10 officers for player 0
    rttr.AddPeople(0, JOB_GENERAL, 100, JOB_OFFICER, 10)
end

function onGameFrame(no)
    -- every 100 gf...
    if no % 100 == 0 then
        -- for all players..
        for player = 0, rttr.GetPlayerCount() - 1 do
            rttr.Chat(0xFFFFFFFF, "[Player ", player, " @GF ", rttr.GetGF() , "] helpers: ", rttr.GetPeopleCount(player, JOB_HELPER), ", wells: ", rttr.GetBuildingCount(player, BLD_WELL), ", water: ", rttr.GetWareCount(player, GD_WATER))
        end
    end
end

-- just output a message for now
function onOccupied(player, x, y)
    rttr.Log("Point occupied: ", player, ": ", x, ",", y)
end

-- just output a message for now
function onExplored(player, x, y)
    rttr.Log("Point explored: ", player, ": ", x, ",", y)
end

-- disable barracks (building 1) for player 0
rttr.DisableBuilding(0, BLD_BARRACKS)

-- restrictions for player 0
rttr.SetRestrictedArea(0, 0,42, 255,42, 255,255, 0,255)


Please be aware that the support is experimental at the moment and invalid values from scripts may lead to crashes. Please do not report a bug in this case. This especially applies for area restrictions, as it is necessary that everything a player owns is inside that area. Otherwise the game may crash. Therefore, restricted area should be set at script load time (not in the start() event callback or even later) and include at least the headquarter, but not necessarily the land gained by it. Afterwards the area should only be increased.

That's all for now.

Marcus

Editiert von Marcus am 16.12.2014 20:14

Spike am 04.10.2012 16:07 #10697

Im Ruhestand
Oh my god I can't Tell you how much I really Love this Feature! If you wouldn't live that far away I would give you a cookie -
or well you maybe prefer beer.

What ever,this could lead to the first try's of remaking the old missions!

---



Marcus am 07.10.2012 13:16 #10716


I just wanted to add a few links for learning Lua:

English: http://www.lua.org/pil/index.html
German: http://wiki.multitheftauto.com/wiki/DE/Lua_Tutorial

Feel free to add other sources of Lua knowledge.


PoC am 16.12.2014 18:06 #13210


rttr.AIConstructionOrder(player,x,y,buildingtype)
orders ai to build buildingtype at the given x,y location (ai will usually retry at a nearby location if the location is bad)


Quorzom am 16.12.2014 20:10 #13213


Thanks a lot for documenting lua scripting. With the videos Spike is currently working on it should be easy for most people to understand and use those scripts. Once they're done, they should be added as an additional source for lua scripting too.

Perhaps we'll see some custom scenarios or even a campaign for RttR in the near future.


Spike am 08.04.2015 01:56 #13445

Im Ruhestand
If somebody is searching for some tutorials (RttR-Lua related) - I made some, not perfect but helpfull videos here:
https://www.youtube.com/playlist?list=PLRJTCO4nl4r8AFwfmHlAQSUlpF6CUP0Ca

---



RM1985 am 09.06.2015 12:03 #13539


Hey Spike, I checked out your LUA tutorials and have a few questions now:

1. Post Messages - Will you also include the original post messages from S2 into the game, I always loved these, when Octavius always said some like: We have to build a woodcutter, a stonemason and a sawmill. I mean the original message windows from S2.

2. When enabling a new building, the message says only like: "Forester", but shouldn't it be: "New building: Forester" like in the original game?

3. You can also add people and wares to your HQ by LUA, but you already have people and wares by the presettings you choose, right? And what if you now want to add a ware you already have, like boards, do you have to add just the ones you need more OR the total number you want to have, because maybe the script overwrites the old number of boards?

4. How can I get such a message window at the start of a mission, like it appears in the ones you made?
So far I can only write scripts for chat- or post-messages

---
S2-Freak auf dem Weg zum Titelträger

Editiert von RM1985 am 10.06.2015 00:48

Spike am 10.06.2015 06:29 #13541

Im Ruhestand
1) You mean mission statement? Try rttr.MissionStatement...

2) As maqs stated, it's not finished, most bugs and problems are known

3) It's 'AddWares' and not replace - you may not just take goods of a player, could lead to asyncs easily. Still you may
remove all wares/people at the beginning of a game and reads the wished amount

4)
You may read the info post...
Zitat:
rttr.MissionStatement(player, title, message...)
Send mission statement to player:
rttr.MissionStatement(0, "Diary", "Forth daynnnThere are ", rttr.GetBuildingCount(0, BLD_WELL), " wells now.")


---



RM1985 am 10.06.2015 10:01 #13542


1. Yea, I think I meant that, but also I asked, if you're going to include the mission Statements from the original missions.

2. Ok, so it will be fixed soon.

3. That was my actual question, if the wares are added or replaced, answered properly!

4. Yea, must've overseen that one, my eyes aren't what the used to be anymore ;). Will try it!

---
S2-Freak auf dem Weg zum Titelträger


CrazyL am 20.07.2016 14:07 #14211


Edit: You can find the post about rewriting the scripts of the orignal Roman Campaign here

Editiert von CrazyL am 24.07.2016 22:57

Marcus am 20.07.2016 15:00 #14212


Hey,

that sounds great.

I'm currently writing a converter that converts the old mission files to lua scripts.

https://dl.dropboxusercontent.com/u/11244516/RTTR/miss200.lua

Doesn't look to nice, I know. If you happen to know some perl, I could share my script. I didn't have the time to do all
the testing yet, but it should hopefully be working.

Marcus


Marcus am 20.07.2016 15:07 #14213

Flamefire am 21.07.2016 14:26 #14219


Very nice. Some charset problems though especially with 'ß' although 'ö' is working. Are they correct UTF-8?
I guess we need some way to include translated strings in lua scripts.

---
Github: https://github.com/Flamefire


Marcus am 21.07.2016 14:31 #14220


Yes, I just need to use iconv or sth. Then hopefully everything should be working. :-) What's stranger is that
https://dl.dropboxusercontent.com/u/11244516/RTTR/miss206.lua seems to be completely off.


CrazyL am 21.07.2016 19:45 #14222


@Marcus
Great work, I got a few good ideas from your translations! Sadly, I don't know python, so I can't help with your script. I uploaded another version of MISS200.lua where I changed the event logic a litttle (it's more flexible now and supports events that need to be called any number of times before triggering) and included the open arc in the final event.


Yeah, some parts of the mission statements in your MISS206.lua belong to "Roman Mission 2", like the first mission statement in trigger0() - veeery strange indeed ;-)

By the way, onLoad() and onSave() didn't work for me (Error: Lua state could not be saved!). I looked into the example file (LuaFunctions.lua) and it seems to me they need to return true (lua-functions return nil by default). I tried it again with a small test script. Saving seems to work, but loading throws an exception and instantly breaks the game (nothing in the logfiles either). Here is the script:

Test.lua:
-- format lua log
function luaLog(str)
    rttr:Log("-- LUA: " .. str)
end

function onSettingsReady()
    luaLog("MISS200.lua loaded...")
end

function onSave(saveGame)
    luaLog("onSave")
    saveGame:PushString("Hello RTTR!")
    return true
end

function onLoad(saveGame)
    luaLog("onLoad, read one string")
    str = saveGame:PopString()
    luaLog("Read " .. str)
    return true
end


Did I do something wrong or do we need a bug report?

Editiert von CrazyL am 21.07.2016 20:00

Flamefire am 21.07.2016 22:27 #14223

CrazyL am 24.07.2016 10:32 #14225


Saving and loading works like a charm, thanks! I rewrote the event logic and onLoad()/onSave() routines for MISS200.lua and MISS201.lua. Now mission progress should be saved and loaded correctly (I tested it on the first mission: All achievements like enabled buildings and event states were saved/loaded)

By the way: Can a human player break teams by attacking an AI? When I set player 0 and 1 both as members of TM_TEAM1, I didn't find any option to break the treaty. Or did I use that setting in a wrong way?

Editiert von CrazyL am 24.07.2016 10:36

Flamefire am 24.07.2016 18:04 #14226


If you are on the same team, you can't attack each other. There is no way to dismiss contracts via LUA ATM, but a player could dismiss the treaty manually via the diplomacy window unless fixed teams are set (default I think)

---
Github: https://github.com/Flamefire


CrazyL am 24.07.2016 23:12 #14228


Thanks for the answer - I tried using the diplomacy window and nothing happened, that's why I asked. But if fixed teams were responsible... I'll try that option. Especially since setting fixed teams to false should work now :)

Another question on teams: Can I set one-sided team options via scripts, e.g.:

AI1 will not attack AI2, AI3
AI2 will not attack AI1, AI3
AI3 will not attack AI1, but will attack AI2 on sight (and thus looses friendship with AI1 on its first attack on AI2)

(alliance logic: My enemy's friends shall be my enemies)

The roman campaign does NOT use one sided teams to create alliances that change suddenly in-game independently from the player, but I know the FANpaign does.


By the way, is there some function like
Code:
onContact(p1, p2)
to catch the first contact between two players? Right now, I use onExplored() on some landmarks where I guess players will meet, but that is very inaccurate. In a worst case scenario, the onContact message triggers before the players meet!

Somehow I was pretty sure it existed, but I couldn't find it neither in the wiki nor in spike's videos, so I must be mistaken...

Editiert von CrazyL am 24.07.2016 23:36

FloSoft am 25.07.2016 13:35 #14229

Großmeister
@Marcus: why not integrate the knowledge of your script into s25client, to "dynamically" use those rtx files?

---
mfg
Flo



Marcus am 25.07.2016 13:46 #14230


@FloSoft: I guess this would be a lot of work. I think a separate converter to lua makes more sense, as the lua scripting
engine is already implemented. At the moment I am still trying to fully understand how exactly those RTX files work,
therefore I'm using a script that's way easier to modify than some C++ implementation. Sadly it takes a lot of time, as I
have to try the scripts in both the original and rttr and compare if everything runs exactly the same.


Spike am 25.07.2016 13:48 #14231

Im Ruhestand
Zitat:
to catch the first contact between two players? Right now, I use onExplored() on some landmarks where I guess players will
meet, but that is very inaccurate. In a worst case scenario, the onContact message triggers before the players meet!
I don't
think so. I worked with saving positions the player who will be met has conquered and then checking if the player who meets the other
player explores one of those points.

Not the best solution I guess but it's working.

Sefaring has something like that - When an exploration expedition first explores an enemy, you'll get a message about that. Can't we
just use something similar for LUA-Scripting?

---



Flamefire am 25.07.2016 18:38 #14235


@FloSoft: Would also not do that as we already have Lua scripting. Adding another scripting engine just makes things much more complex.

@Spike: We could add a world:GetOwner(pt) method so you can check the owner of an explored point and save the firstExplored variable yourself, if you need that. Otherwise it would slow down the game even when this is not used.

---
Github: https://github.com/Flamefire


Spike am 25.07.2016 19:58 #14236

Im Ruhestand
Couldn't we add a o to the on exploration function?
onExplored(player_id, x, y, o)

---



CrazyL am 26.07.2016 02:32 #14238


Zitat von Spike:
I don't think so. I worked with saving positions the player who will be met has conquered and then checking if the player who meets the other
player explores one of those points.


Even if the AI always expanded in exactly the same way every time one plays the mission, I don't think that would be true. The player is human, and different players use different strategies. Some expand faster, some very slow.

Suppose I found the place where I met the AI during my test runs and used that for onExplored(). Because I'm always the same guy I'll use similar expansion and meet the AI in roughly the same place every time. The next person playing that mission might be more experienced than me and expands a lot faster. So that person will reach the very same place a lot earlier than the AI and get the contact message without any contact...

Perhaps even stranger: A very slow player might only see that place after conquering the first barracks. Only then would the onExplored() function be triggered -- and would be meaningless by now, because the player and the AI have long since met!

Editiert von CrazyL am 26.07.2016 11:35

Spike am 26.07.2016 12:13 #14239

Im Ruhestand
Then you have to do it for both. Save in an array any point any for any player (onOccupation) and then check if one player explored a
point owned by an other player?

---



CrazyL am 26.07.2016 19:46 #14240


I don't think you have to save the ownership state for all points yourself. Everytime onExplored() is triggered you can check

Code:

function onExplored(p, x, y, owner)
    if( ((p == p1) and (owner == p2))
     or ((p == p2) and (owner == p1))
    ) then ...
end


That way, you will always notice if p1 explores p2 territory or vice versa. As I save flags for all activated events anyway (eState array), an onContact()-event will only be triggered once.

I just updated the second and third mission, it works just as expected ;) Big thanks to Marcus!

Editiert von CrazyL am 27.07.2016 00:01

Spike am 26.07.2016 20:27 #14241

Im Ruhestand
I know, as I suggested to make work that way. My solution worked before this version was implemented ;)

---



CrazyL am 28.07.2016 13:41 #14242


Oops, I should have checked the timestamp of your post, sry ;)


Spike am 13.01.2020 18:13 #15376

Im Ruhestand
Does anyone still have marcus script? We may convert the fanpaigns as well

---



Marcus am 13.01.2020 21:25 #15377


Spike: Yes, of course. ;)


Spike am 13.01.2020 21:26 #15378

Im Ruhestand
May I have it? Or may you convert the fanpaign scripts? :)

---



Marcus am 13.01.2020 21:39 #15379


Have a look at your mails (or IRC).




Feel free to post in English!

Antwort schreiben

Username:
Security code:
Text:

   
  Convert smilies like :), ;) etc. into small graphics?
  Convert WWW-addresses into clickable links?
  Soll Boardcode in ihrer Nachricht aktiviert werden?