Events are a Two-Way Street

Pazyryk

Deity
Joined
Jun 13, 2008
Messages
3,584
It's easy to forget that Events can be invoked as well as subscribed to. When you subscribe to them, you are hooking to a call to the graphics engine or some other engine used specifically for the "active player". For example, Events.SerialEventCityCreated doesn't correspond to city founding exactly; its real meaning is "create a city graphic object" for the active player (the AI players aren't interested in graphics). We all do it, but you should think twice (or thrice) before using these as hooks for game rule logic. Even if you think you understand its firing logic, it is as likely as not to change with some future update to the graphics engine.

So what happens if you invoke Events.SerialEventCityCreated instead of subscribing to it? Well ... basically what you are doing is sending instructions to the graphics engine. You can set a city's art style, era and population (apparent, not real) to anything you want. Try it. It works! Do you want your size 1 ancient cities to appear as giant modern metropolises? You can do it. Do you want city art styles to reflect the original owner rather than the current owner (so that a civ has mixed art styles)? You can do it.

Coding this properly into a mod is tricky. You can change a city graphic, but then the event comes along and fires for some reason (city conquest, game load) and undoes your change. You want to link your graphic change so it follows from the event itself. However, invoking an event from a listener for the same event seems like asking for trouble (perhaps its OK depending on the sequence of things; I haven't tried it). Also, many of these events fire many times sequentially (for example, on game load). The code below accounts for all of this. It caches info from a SerialEventCityCreated listener and then (a bit later) invokes SerialEventCityCreated with an altered value. Some care is needed to avoid an infinite loop since your invoking will trigger your listener. The example below forces all cities to have ArtStyleTypes.ARTSTYLE_ASIAN regardless of what is defined in Civilizations. But you can alter it to have full control over city appearance using whatever logic you like.
Code:
local g_cityUpdateInfo = {}
local g_cityUpdateNum = 0

local function ListenerSerialEventCityCreated(vHexPos, iPlayer, iCity, artStyleType, eraType, continent, populationSize, size, fogState)
	print("ListenerSerialEventCityCreated: ", vHexPos, iPlayer, iCity, artStyleType, eraType, continent, populationSize, size, fogState)
	if artStyleType ~= ArtStyleTypes.ARTSTYLE_ASIAN then
		g_cityUpdateNum = g_cityUpdateNum + 1
		g_cityUpdateInfo[g_cityUpdateNum] = g_cityUpdateInfo[g_cityUpdateNum] or {}
		local updateInfo = g_cityUpdateInfo[g_cityUpdateNum]
		updateInfo[1] = {x = vHexPos.x, y = vHexPos.y, z = vHexPos.z}
		updateInfo[2] = iPlayer
		updateInfo[3] = iCity
		updateInfo[4] = ArtStyleTypes.ARTSTYLE_ASIAN
		updateInfo[5] = eraType
		updateInfo[6] = continent
		updateInfo[7] = populationSize
		updateInfo[8] = size
		updateInfo[9] = fogState
		--Warning! Infinite loop if new updateInfo causes an update!
	end
end
Events.SerialEventCityCreated.Add(ListenerSerialEventCityCreated)

local function UpdateCityGraphics()
	if g_cityUpdateNum == 0 then return end
	print("Running UpdateCityGraphics; number cached = ", g_cityUpdateNum)
	while 0 < g_cityUpdateNum do
		local updateInfo = g_cityUpdateInfo[g_cityUpdateNum]
		g_cityUpdateNum = g_cityUpdateNum - 1
		Events.SerialEventCityCreated(updateInfo[1], updateInfo[2], updateInfo[3], updateInfo[4], updateInfo[5], updateInfo[6], updateInfo[7], updateInfo[8], updateInfo[9])
	end
end
Events.SerialEventGameDataDirty.Add(UpdateCityGraphics)
Events.SequenceGameInitComplete.Add(UpdateCityGraphics)
Events.SerialEventCityCaptured.Add(UpdateCityGraphics)	--not sure if this happens before or after city art change
I suspect there is a whole treasure chest graphic of possibilities by invoking Events. As I said above, you are sending instructions to the graphics engine. I'm pretty sure that Events.SerialEventImprovementCreated will work analogously to the above, allowing control of improvement art style, era, state (e.g., pillaged), and even the art asset used. But I have no idea what is possible. Can you run a combat sim (an illusory combat that isn't "real") with Events.RunCombatSim? Can you make a unit member run an animation with Events.UnitMemberCombatStateChanged? Only experimentation will tell.
 
wow, like you said, you've found a true treasure chest here :goodjob:

IIRC from my old test, RunCombatSim can not be called from Lua side (give an error message), and the debug panel has a menu to play animation for units, I suppose it use events in the way you've described it here.
 
Yeah, I did try RunCombatSim and got the error. I wonder if it has to be sequenced carefully to follow other combat related Events (ShowAttackTargets, UnitMemberCombatStateChanged, etc.)? I think the trick here is setting up many listeners to see how these are used normally.

I'm not sure why I wanted illusionary combat anyway. Must be some use for it in a fantasy mod.
 
One thing weird is that all three of these work in the code above:
Code:
Events.SerialEventCityCreated(<args>)
Events.SerialEventCityCreated.Call(<args>)
Events.SerialEventCityCreated.CallImmediate(<args>)
I don't really know what Call and CallImmediate do that is different than invoking the event directly. Perhaps nothing in the example I gave but something in other contexts? Perhaps Call puts it into a queue to run later? If so, then I could simplify my two-step code above and call the event from the event listener (I was too scared to try that).

The full set of Events methods is: Add, Call, CallImmediate, Count, Dispatch, Remove, RemoveAll. Four of these (Add, Count, Remove and RemoveAll) relate to subscribing to the event with your Lua functions. Can someone tell me what Dispatch does?
 
I added a new feature to the game (which uses recolored resource graphics, and a "resource" type art define, like the Oasis does for example), and when I found a city on that feature or remove it using a Worker, it is removed from the tile, but its graphics doesn't disappear (it disappears after reloading the game). Can the use of such events help me remove the feature graphics from the map?
 
There are Events for improvements and resources, but nothing in the wiki with "feature" in the name. There is an Events.LandmarkLibrarySwap() but I have no idea what that is for (used in debug.lua but I don't understand much of anything in there). No args for it so just try it and see what happens. Another shot in the dark is UI.SetDirty(InterfaceDirtyBits.PlotData_DIRTY_BIT, true).
 
Here's another one that works. The code below will put the bottom three rows of the map into permanent "unexplored" state and keep them that way. Basically, it "listens" for a plot being revealed. When that happens, it immediately hides it:

Code:
local function ListenerHexFOWStateChanged(vector2, fowType, bWholeMap)
	if  vector2.y < 3 and fowType ~= 0 then			--listener says off or not vis
		Events.HexFOWStateChanged(vector2, 1, false)	--invoker sets to unexplored (which will be 0 in resulting listener call)
	end
end
Events.HexFOWStateChanged.Add(ListenerHexFOWStateChanged)

It's kind of confusing because the integers are offset for what is reported versus what is needed to cause a change. I figured this out through pure trial and error:

to invoke -> reported (effect)
0 -> 2 ("off" = visible)
1 -> 0 (unexplored)
2 -> 1 (not visible)

So 0 is used to make a plot visible (by you or the game engine), but the listener will report that change with the value 2.
 
Hi Pazyryk.
I don't know if this may be of any interest to you but I was looking for a way to give cultural variation to improvements without using uniques.
(I opened a thread for it: http://forums.civfanatics.com/showth...9#post13411889)

An user pointed me toward this thread and I found it very interesting. Sadly I have almost no LUA skills, so I wanted to ask you 2 things:

1) In the mods you created till now, there's any example of improvement art variation that I can take as example/model?

2) If not, could you consider to create it (if it's not too hard or boring).

Considering all the Arts in the vanilla game and in the mod database, such code would open many possibilities not only to me but also to all art creators.

I hope you'll consider this thing interesting enough and worth the effort. Anyway, thank you for the work done till now and for your time.
 
Answer is no and no. But...

It's possible that SerialEventImprovementCreated will work analogously to SerialEventCityCreated in the OP. Maybe. No one knows until someone tries.

First thing is to look at the event documentation in the wiki, keeping in mind that documentation of (and modder understanding of) Events is awful, the reason being that this is graphics engine stuff and we can't look at the source code. But in any case, here is what we have:

SerialEventImprovementCreated(float hexX, int hexY, ArtStyleType continent1, ArtStyleType continent2, PlayerID player, int createImprovementType, ImprovementType createImprovementRRType, int createImprovementEra, int createImprovementState, unknown ImprovementEra = nil)

There are definitely some things wrong there, and some info may be irrelevant even if it looks promising. For one thing, do improvements have continent variation? I didn't think so but I may be wrong. But they certainly have a playerID, a Type and and an Era, and probably those really are args 5, 6 and 8 as listed.

As a first Lua modding exercise, I'd suggest you make an Events "listener". A listener is simply a Lua function that responds to an event by printing something. The most useful thing to print is the set of arguments received by the function:
Code:
function ListenerSerialEventImprovementCreated(...)
	print("ListenerSerialEventImprovementCreated: ", ...)
end
Events.SerialEventImprovementCreated.Add(ListenerSerialEventImprovementCreated)
There's your first simple Lua mod. (Look elsewhere on forum for how to create a mod or add Lua file to a mod. You also have to have the Fire Tuner running to see the prints.) If it works, you will see it print the quoted part followed by all args that the function receives each time an "improvement graphic object" is created (or recreated). Since you have Fire Tuner running anyway, use it to plop down some improvements. You should see the print statement fire with all the function args. See if you can make sense of the args you see: playerID, createImprovementType, createImprovementEra, createImprovementState (this integer represents half-build, pillaged, fully-built). I believe that createImprovementRRType is for routeType (road/railroad) so will probably be -1 for improvements.

If everything above goes well, use Fire Tuner and SerialEventImprovementCreated to try to change something. You know hexX, hexY and all other args for a visible improvement based on your listener. So now try to change something simple, like era, for an improvement you just plopped. Just invoke the Event like this with your new set of args:
Code:
Events.SerialEventImprovementCreated(<altered args>)
Then report back what happens. No one really knows. I know from experimentation that many of these Events don't work like this. But many do.

Once you get that far, and if it works, I can help you make something like the OP where the listener doesn't just listen but intervenes to change graphic in some way.
 
I know how to add a lua file to my mod (I added many of them to my project).

Those are the value the fire tuner gave (for Chateau): 50, 68, 4, 4, 0, 37, -1(like you said), 1, 4

..but maybe you're overestimating my knowledge of LUA itself:
what I should write in (<altered args>) ?
 
ImprovementTypeID = 37 (should verify this in DB [if you know how to view DB] or Fire Tuner)
Era = 1

In Fire Tuner:
>print(GameInfoTypes.IMPROVEMENT_CHATEAU)
37
--or flip it around:
>print(GameInfo.Improvements[37].Type)
IMPROVEMENT_CHATEAU

Do that just to make sure that 37 is really ImprovementTypeID. Then do the same for Era to make sure 1 is really whatever era you were in.

Then in Fire Tuner:
>Events.SerialEventImprovementCreated(50, 68, 4, 4, 0, 37, -1, 2, 4)

So I'm just trying to change era for that improvement. Unfortunately, I don't think Chateau has era variation so it's probably not going to change appearance even if this works. Try it with some other improvement that has very obvious era variation. Or try changing createImprovementState for Chateau (the last arg which is 4, this is the "half built", "pillaged", "completed' state of the improvement). Or even try changing ImprovementType. If the latter works, then it's just "apparent improvement" (remember that we are only fiddling with UI).
 
I tried with Fort (-19, 66, 3, 3, 0, 44, -1, 1, 4)

Looked in the Database with SQLiteSpy: the ID of IMPROVEMENT_FORT is 14.

I continue to receive syntax errors :)1: unexpected symbol near ">")
I wrote in the live tuner (in the space at the bottom) those exact words:

>print(GameInfoTypes.IMPROVEMENT_FORT)
44

>print(GameInfo.Improvements[44].Type)
IMPROVEMENT_FORT

>print(GameInfoTypes.IMPROVEMENT_FORT)

>print(GameInfo.Improvements[44].Type)

>Events.SerialEventImprovementCreated(-19, 66, 3, 3, 0, 44, -1, 2, 4)
 
I continue to receive syntax errors :)1: unexpected symbol near ">")
I wrote in the live tuner (in the space at the bottom) those exact words:

>print(GameInfoTypes.IMPROVEMENT_FORT)
44

I think you are misunderstanding. Don't type the ">" or the "44" in above line. The ">" is the Fire Tuner prompt, so I'm indicating which lines you type and which are printed back by Fire Tuner. The idea here was to let Fire Tuner tell you the ID for IMPROVEMENT_FORT. (I thought the answer would be 44 but clearly that's wrong.)

This is kind of cliche in program instruction, but try this exactly:
Code:
print("hello world")
Understand now? I'm not giving you step-by-step instructions (it would be faster for me to do it myself then). I'm trying to give you understanding so you can solve this problem. (Then I can profit from your effort. :)) But I'm glad you know how to look at DB - that's a step many modders don't want to take for some reason.


One difficulty I see here is that createImprovementType in the args list doesn't correspond to any ID that we know about:
44 Fort?
37 Chateau?
It's not the improvmentID from Improvments and it's not the graphicID from ArtDefine_Landmarks. It may be some internal cached representation of the improvement graphic. I've seen the same exact thing for SerialEventCreatUnit, where the unitID that the graphics engine uses doesn't correspond to any ID that I can figure out. That adds some challenge to coding, but its not insurmountable. We would just have to figure out with listener what integer represents the improvement that you want to modify.


So try again. Remember that the args list has to come from the listener (except for the one you change) so that hexX, hexY and everything else is correct.
 
In LUA, and in programming in general, I'm a newbie, so I need precise instructions. Lack of time didn't allow me to learn properly (as I would like to do).
Now I know what ">" means. Before I didn't.
Please, don't give for granted things you could with more expert modders; if you want me to do something, be specific.

About the Database: I have no problems with everything concerning Database, SQL, XML. I already wrote a lot of modification in my mod project, about almost every aspect of the game with both SQL (mostly) and XML (at the beginning, now almost never).

Anyway, here's what I did:

>print(GameInfo.Improvements[14].Type)
IMPROVEMENT_FORT

>print(GameInfoTypes.IMPROVEMENT_FORT)
Runtime error: _cmdr = {print(GameInfoTypes.IMPROVEMENT_FORT)} :1: attempt to index global "GameInfoTypes" (a nil value)
stack traceback
_cmdr = {print(GameInfoTypes.IMPROVEMENT_FORT)} :1: in main chunk

>Events.SerialEventImprovementCreated(106, 40, 4, 4, 0, 37, -1, 2, 4)

Runtime error: _cmdr = {Events.SerialEventImprovementCreated(106, 40, 4, 4, 0, 37, -1, 2, 4)} :1: attempt to index global "Events" (a nil value)
stack traceback
_cmdr = {Events.SerialEventImprovementCreated(106, 40, 4, 4, 0, 37, -1, 3, 4)} :1: in main chunk

I tried to modify almost all the values (except the first two of course). Always this answer.

Since I'm not able to modify them, I've no clue on what they represent. Any advice?
 
Make sure you start up a game (base or mod, doesn't matter). Then, in Fire Tuner, pick any Lua state other than "Main State", which is what it is in. This will put you in a Lua state where GameInfoTypes and Events are available. Then try again.
 
Changed lua state (it worked)

>print(GameInfoTypes.IMPROVEMENT_FORT)
ActionInfoPanel: 14

About the arguments:
Starting arguments: Events.SerialEventImprovementCreated(87, 29, 3, 3, 0, 37, -1, 1, 4)

int createImprovementState: it is in effect the state of construction
1: pillaged
2: half built
3: built, apparently (I don't know the difference with 4)
4: built

int createImprovementEra: The Era, from 1 (Ancient).
The Fort, as expected, changed its Art at the right Era.

int createImprovementType: any number change the art. I tried several numbers, here's the results:
Spoiler :

01: stars ?
02: NW El Dorado
03:
04: fallout
05: pavement of stones?
06: fountain of youth
07:
08: geyser (smoke out of terrain, nothing else)
09: hadrian's wall
10: hadrian's wall
11: hadrian's wall (I remember them, they are from a scenario: 3 different Art defines put in adjacent hexes to form the "hadrian's wall")
12: snowy hole on tundra-like terrain
13: black shadows?
14: -
15: white forms... on tundra-like terrain
16: ???
17: Oasis
18: rock and aluminum?
19: -
20: Solomon Mine
21: Mountain NW texture (just texture on the ground, no height)
22: -
23: Mountain NW texture (just texture on the ground, no height)
24: Mountain NW texture (just texture on the ground, no height)
25: Fire with smoke (seems part of the barbarian camp, ..without camp)
26: Academy
27: Archelogical Site
28: Barbarian Camp (with fire)
29: Brazilwood Camp
30: -
31: Chateau
32: Citadel
33: Customs House
34: -
35: Feritoia (with coastal castle)
36: Feitoria (without coastal castle)
37: Fort
38: Goody Hut
39: Holy Site
40: Kasbah
41: Lumbermill
42: Manufactory
43: Mine
44: Moai
45: part of Landmark (african style?)
46: Landmark (american style)
47: Landmark (Eastern style)
48: Landmark (euro style)
49: Motte and baley
50: Motte and baley
51: Motte and baley
52: Motte and baley
53: Polder
54: Pontoon Bridge
55: -
56: terrace farm
57: tipis
58: trading post
59: trading post (ver 2)
60: trading post (ver 3)
61: trading post (ver 4)
62: Fort
63: -
64: blue mineral
65: different blue mineral
66: archeological dig
67: Bananas?
68: Bisonts
69: Lemons?
70: Bisonts Camp

There are many others, probably all the arts in the game.





ArtStyleType continent1: does nothing visible

ArtStyleType continent2: does nothing visible

About the last two, I noticed that in a previous test they were 4, 4 (in this test they were 3, 3), I will do some other test (maybe the position on the map?)

ImprovementType createImprovementRRType: does nothing visible
 
ImprovementType createImprovementRRType seems to be not a road:
It gives a number when the improvement is on a resource, for example:

114: when on Wheat
93: when on Ivory
75: when on Copper
-1: when not on a resource

bane_: I don't know if we were thinking the same thing, but I wish new route types too!

About the difference between the improvements numbers I gave in the latest post and the ones I gave earlier: the first test I did was with a modded game (my project), who adds new improvements. This is probably the reason of the different numbers (Fort: 44 -37 Chateau: 37 - 31). However since the second test I'm using a blank mod (except for the LUA listener).
 
Let me take this out of spoiler, because you've done something that every advanced modder was sure was impossible. Just to be clear, you can turn these graphics on and off?

int createImprovementType: any number change the art. I tried several numbers, here's the results:
01: stars ?
02: NW El Dorado
03:
04: fallout
05: pavement of stones?
06: fountain of youth
07:
08: geyser (smoke out of terrain, nothing else)
09: hadrian's wall
10: hadrian's wall
11: hadrian's wall (I remember them, they are from a scenario: 3 different Art defines put in adjacent hexes to form the "hadrian's wall")
12: snowy hole on tundra-like terrain
13: black shadows?
14: -
15: white forms... on tundra-like terrain
16: ???
17: Oasis
18: rock and aluminum?
19: -
20: Solomon Mine
21: Mountain NW texture (just texture on the ground, no height)
22: -
23: Mountain NW texture (just texture on the ground, no height)
24: Mountain NW texture (just texture on the ground, no height)
25: Fire with smoke (seems part of the barbarian camp, ..without camp)
26: Academy
27: Archelogical Site
28: Barbarian Camp (with fire)
29: Brazilwood Camp
30: -
31: Chateau
32: Citadel
33: Customs House
34: -
35: Feritoia (with coastal castle)
36: Feitoria (without coastal castle)
37: Fort
38: Goody Hut
39: Holy Site
40: Kasbah
41: Lumbermill
42: Manufactory
43: Mine
44: Moai
45: part of Landmark (african style?)
46: Landmark (american style)
47: Landmark (Eastern style)
48: Landmark (euro style)
49: Motte and baley
50: Motte and baley
51: Motte and baley
52: Motte and baley
53: Polder
54: Pontoon Bridge
55: -
56: terrace farm
57: tipis
58: trading post
59: trading post (ver 2)
60: trading post (ver 3)
61: trading post (ver 4)
62: Fort
63: -
64: blue mineral
65: different blue mineral
66: archeological dig
67: Bananas?
68: Bisonts
69: Lemons?
70: Bisonts Camp

There are many others, probably all the arts in the game.

The integer values don't correspond to anything I can think of. But that still leaves two possibilities:
  1. ID values are totally hard-coded to specific hard-coded graphic in the graphics engine
  2. ID values and graphics are derived from external data in some way that we don't know
Well, either way it could be figured out by brute force.

I don't have much time to do anything this week. But I'll come back and try to write some code for you. What was it you wanted to do again?
 
Let me take this out of spoiler, because you've done something that every advanced modder was sure was impossible.

Not bad for a newbie! :smoke:

Just to be clear, you can turn these graphics on and off?

Yes. Let's say I've placed a Fort. Now I change the number from 37 to 31(with Events.SerialEventImprovementCreated(<altered args>)): Now I have a Fort with the art of a Chateau. It's still a Fort but with different art
(the mouse still indicates it as a fort, it don't have Chateau yield and when tested in combat, it gives the defensive bonus)
If I change the number again to 37: I've back the vanilla Fort art.

Exceptions: Polder and Bisonts. For some reason when I changed number the previous art didn't disappeared (so I had, for example, Fort over Polder, Lemons among Bisonts).

I don't have much time to do anything this week. But I'll come back and try to write some code for you.

Thank you very much!

What was it you wanted to do again?

Cultural variation for improvements (as it is possible for Units). Basically, I want the option to give graphic diversity, let's say for example, to the Fort, so that a Greek Fort looks different from a Chinese or Aztec Fort. I want to apply this diversity to many improvements (like the chateau, changing its name and making it avalaible to all civs, or Feitoria in the same way).
I don't want to give unique improvements to civs: it's complicated to handle, considering their number and the number of various bonuses from policies/techs/buildings to apply to each one of them, besides I had crash problems with them (I consider this a secondary reason but if you want to know more read the thread http://forums.civfanatics.com/showth...9#post13411889).


I'm still trying to figure out the logic behind ArtStyleType continent1 and ArtStyleType continent2. They don't seem to be exactly related to continents, distance from the player, terrain or features. I'll do more testing.
 
Top Bottom