Hipfot's veyDer's User Events

Looking very much forward to it!

Just hope I'll be able to mod the changes in myself as this is my only lua experience so far and it was very much based on other already existing items.

\Skodkim
 
Excellent.

Some of the issues I have seen so far in Veydurs mod. When it adds a New Resource, the resource doesn't seem to show up as useable. So if I get an extra Iron I still can't build a new Swordsman.

Also when it adds bonus yields to tiles they don't usually show up in the city view. Sometimes I think it may actually cancel out the normal effects of the tile.

For example I had 1 :c5production: added to a Mine I was working. Once I went into city view none of the :c5production:'s would show at all. I couldn't figure out if it was just a display issue or an actual missing resource issue. Didn't seem as if the :c5production: for the city added up correctly no matter what I did.

Also if say Wheat is added to a farmed hex, the wheat shows up above the farm unless the game is saved and then reopened. Again just a display thing.

The Color Text is written on the window of the event when it pops up. But the actual Effects of the Event are only shown when hovering over the Button. It is possible to add the effect to the actual text into the main window?
 
Excellent.

Some of the issues I have seen so far in Veydurs mod. When it adds a New Resource, the resource doesn't seem to show up as useable. So if I get an extra Iron I still can't build a new Swordsman.

Also when it adds bonus yields to tiles they don't usually show up in the city view. Sometimes I think it may actually cancel out the normal effects of the tile.

For example I had 1 :c5production: added to a Mine I was working. Once I went into city view none of the :c5production:'s would show at all. I couldn't figure out if it was just a display issue or an actual missing resource issue. Didn't seem as if the :c5production: for the city added up correctly no matter what I did.

Also if say Wheat is added to a farmed hex, the wheat shows up above the farm unless the game is saved and then reopened. Again just a display thing.

The Color Text is written on the window of the event when it pops up. But the actual Effects of the Event are only shown when hovering over the Button. It is possible to add the effect to the actual text into the main window?

Are you using my latest version of the mod (see post 17)? I think this should fix the problems concerning new resources being added.

\Skodkim
 
I'll check it out once I have finished my most recent game. Probably not. I remember reading that you had made some changes but I think I was in the middle of a game and didn't remember to come back and replace.
 
Okay, I did get the events to work correctly for AIs in my Mythology mod, with the AI picking between the outcomes based on up to 3 Flavor values in an admittedly crude way. Unfortunately, my internet's still out at home (I live in Pasadena, and we had big wind storms this last week; I'm posting this from work) so I can't copy the code here, but it's actually surprisingly simple:
1> Make a loop over players bracketing nearly all of UserEvents.lua
2> Inside UserEventsPopup.lua (specifically the BuildPopup function) you just do a simple IF check to see if the player ID passed into the function corresponds to the active player. If yes, set up the UI control table and tie the effect functions to the button callbacks; if no, just pick an outcome semi-randomly and execute the effect function directly without ever initializing the UI.

That's all it took, and I've confirmed that it works just fine in practice. So it shouldn't be hard at all for those of you with Lua skills to duplicate this for your own mods. (Or, you can just wait until my cable's back on.)

Now, to make this work better, I also expanded the event table itself. Things like:
> A global player-specific condition (i.e. only execute this event if some arbitrary function X returns true for this player) in addition to the outcome-specific conditions. This was because doing a bunch of loops over cities, units, and plots, and checking each condition, when you KNOW none of them will pass because of some part of the condition you want to use (like no events happening in the first 20 turns of the game, or a certain event never happening again once you leave a certain Era), well, it wastes a bunch of time.
> A global cooldown timer setting on each event (only execute this event if it's been more than N turns since the last event of any kind for this player)
> A local timer (only execute this event if it's been more than N turns since this particular one happened previously to this player, OR give it a negative value and it'll only ever execute this event once per player, period).
These last two use altered versions of the already-existing UserEventsUtils timing checks, so it's not like you couldn't do them already. It was just easier to make those part of the event's header instead of explicitly calling those functions every time. Note that not setting these extra variables for an event would still work the same as before; likewise, if you don't set the Flavors then the AI will just pick randomly, so you don't HAVE to use them.

As mentioned before, I've also added an alignment bias to each event, but that's VERY specific to my mod and skews the probabilities of each event based on your previous choices. (It's non-linear and asymptotic to x0.25/1.75, with most players staying between x0.75 and x1.25, so you'll still have SOME chance of the opposing alignments' events happening.) So when I post the code here, just understand that there'll be a couple bits you'll have to cut out when you put it into your own version.
Or, you could just play my mod.
 
So now that my Internet is back on, here's the very crude bits of code necessary to make the decision process for the AI.

First, let's take a typical event from my mod:
Code:
UserEventAdd{
	id			= "MYTHEVENT_LOW_LM",
	probability	= 0.02,
	alignment = "LM",
	condition = Low_Cond,
	this_event_limit = 20;
	any_event_limit = 5;
	options		= {
		OPTION_L = { order = 1, condition = Once_Cond, effect = Effect_Low_LM_L, flavor1 = "FLAVOR_PRODUCTION", flavor2 = "FLAVOR_SCIENCE" },
		OPTION_C = { order = 2, condition = Once_Cond, effect = Effect_Low_LM_C, flavor1 = "FLAVOR_GROWTH", flavor2 = "FLAVOR_GOLD" },
		OPTION_M = { order = 3, condition = Once_Cond, effect = Effect_Low_LM_M, flavor1 = "FLAVOR_GROWTH", flavor2 = "FLAVOR_PRODUCTION" },
		OPTION_E = { order = 4, condition = Once_Cond, effect = Effect_Low_LM_E, flavor1 = "FLAVOR_GOLD", flavor2 = "FLAVOR_SCIENCE" },
	}
};
Translation: this is the "Low" (common) event in my mod tied to the Lawful-Material alignment (LM). Normally it has a 2% chance of triggering (although my alignment multipliers make this vary between about 1 and 3), but it can't happen within 5 turns of any other event occurring, and it won't repeat itself within 20 turns. "Low_Cond" is a simple condition of not giving any event within the first 20 turns of the game; Once_Cond is a simple condition of saying to only check this event once per turn (by only checking for a capital within the City loop). The four options give the player +5 to one yield and +5 to another in every city, hence the two flavors for every option.

Now, to enable all of this I modified two files. First, inside UserEvents.lua I added an entirely new function:
Code:
function AnyUserEventLastAppeared( player_id )
-- I made this!
	local game_turn = -99;
	if( player_id ~= nil ) then
		local log_event_id="";
		local event_log;

		for log_event_id, _ in pairs( UserEventsLogTable ) do
			if (log_event_id ~= 0) then
				local testval = UserEventLastAppeared( log_event_id, player_id );
				if ( testval > game_turn) then game_turn = testval end;
			end
		end
	end

	return game_turn;
end

This one's used for the global lockout, so that you can say that once an event happens, no event will trigger for X turns. If you don't want to add that functionality, then you don't need that routine at all.

Then, at the top of that same file, I edited UserEventsHandlerProcess like so:
Spoiler :
Code:
function UserEventsHandlerProcess( event_table, scheduled )
	local func_ret	= false;
	local aID	= Game.GetActivePlayer();

	for index,pPlayer in pairs(Players) do
		if (pPlayer:IsAlive() and not (pPlayer:IsMinorCiv() or pPlayer:IsBarbarian() ) ) then
-- Don't want dead civs, minor civs, or barbarians to get events.
			local playerID	= pPlayer:GetID();

			-- cycle through events
			for event_i, event in pairs( event_table ) do

				-- perform an event condition check, only if the event would occur
	-- The old logic would block the event if you gave this condition to all outcomes, but this is faster
				local event_valid = true;
				if type( event.condition ) == "function" then
					event_valid = event.condition( pPlayer );
				end
				if ( event.this_event_limit ~= nil ) then
	-- Sometimes, we want an event to lock itself out for a time.
					local thisLimit = event.this_event_limit;
					local CheckThis = UserEventLastAppeared( event.id, playerID );
					if ( thisLimit > 0 ) then
					-- a positive value means ban if this particular event has happened recently
						if ( (Game:GetGameTurn() - CheckThis) < thisLimit ) then
							event_valid = false;
						end
					elseif ( thisLimit < 0 ) then
					-- a value of -1 means ban if this event has EVER occurred
						if ( CheckThis >= 0 ) then
							event_valid = false;
						end
					end
				end
				if ( event.any_event_limit ~= nil ) then
	-- Sometimes, we want events to ALL lock each other out for a brief period.
					local TimeCheckAny = Game:GetGameTurn() - AnyUserEventLastAppeared( playerID ); -- No two events should be within a few turns of each other, ever.
					if ( TimeCheckAny < event.any_event_limit ) then
						event_valid = false;
					end
				end

				if ( event_valid ) then
					local rand_res = math.random( 20000 ) / ( 20000.0 );
					local test_prob = event.probability;
					test_prob = test_prob * AlignMult(playerID, event.alignment); -- ranges from 0.5 to 1.5, more or less.
					if scheduled or rand_res < test_prob then
						local is_valid_event	= false;
						local valid_elems		= {};

						for option_i, option in pairs( event.options ) do
							local is_valid_option	= true;
							if type( option.condition ) == "function" then
								is_valid_option	= false;
								-- check the cities and surrounding plots
								for city in pPlayer:Cities() do
									local cityID	= city:GetID();

									for i = 0, city:GetNumCityPlots() - 1, 1 do
										local plot	= city:GetCityIndexPlot( i );
										if plot ~= nil and plot:GetOwner() == playerID then
											local params	= { playerID, cityID, nil, { x = plot:GetX(), y = plot:GetY() } };
											local cond_res	= option.condition( pPlayer, city, nil, plot );
											if cond_res then
												is_valid_option	= true;
												table.insert( valid_elems, params );
											end
										end
									end -- for i
								end -- for city

								-- check the units
								for unit in pPlayer:Units() do
									local unitID	= unit:GetID();
									local unit_plot	= unit:GetPlot();
									local unit_params	= { playerID, nil, unitID, { x = unit_plot:GetX(), y = unit_plot:GetY() } };
									local cond_res	= option.condition( pPlayer, nil, unit, unit_plot );
									if cond_res then
										is_valid_option	= true;
										table.insert( valid_elems, unit_params );
									end
								end -- for unit
							end -- if type ( option.condition ) == "function"

							UserEventsTable[ event.id ].options[ option_i ].is_valid	= is_valid_option;
							if is_valid_option then
								is_valid_event = true;
							end
						end -- for option_i, option
			
						if is_valid_event then
							_Shuffle( valid_elems );
							UserEventsTable[ event.id ]._elem = valid_elems[ 1 ];
							UserEventLog( event.id, playerID );
							LuaEvents.UserEventsPopupBuild( event );

							func_ret = true;
							if not scheduled then
								break;
							end
						end
					end -- if scheduled or rand_res < event.probability
				end -- If we met the event condition (if any)
			end -- for event_i, event
		end -- Valid player?
	end -- loop over players
	return func_ret;
end
Events.ActivePlayerTurnStart.Add(UserEventsHandler);

The only thing you can't use in that particular code is the call to AlignMult, which is a custom utility routine in my mod. It's just a multiplier, so just remove it. (Without that, you don't need to declare an Alignment for the event.)

Finally, in UserEventsPopup.lua, the entire function within (BuildUserEventsPopup) just needs to be altered like so:
Spoiler :
Code:
function BuildUserEventPopup( event )
	if ( event ~= nil ) then
		local playerID, cityID, unitID, plot_coords	= unpack( event._elem );

		if playerID == nil or plot_coords == nil then
			return;
		end

		local aID	= Game.GetActivePlayer(); -- TODO: handle AI players, using flavor option value to determine the preferred choice

		local pPlayer	= Players[ playerID ];	
		local pCity		= pPlayer:GetCityByID( cityID );
		local pUnit		= pPlayer:GetUnitByID( unitID );
		local pPlot		= Map.GetPlot( plot_coords.x, plot_coords.y );
		local hex		= ToHexFromGrid( Vector2( plot_coords.x, plot_coords.y ) );
		local i18n_data	= { pPlayer:GetName(), ( pCity and pCity:GetName() ) or "", ( pUnit and Locale.ConvertTextKey( GameInfo.Units[ pUnit:GetUnitType() ].Description ) ) or "" };
		local event_title_i18n	= "TXT_KEY_USER_EVENT_" .. event.id;
		local event_desc_i18n	= "TXT_KEY_USER_EVENT_" .. event.id .. "_DESC";

		if ( playerID == aID ) then
			Events.GameplayFX( hex.x, hex.y, -1 );
			Events.SerialEventHexHighlight( hex, false, Vector4( 0.0, 1.0, 0.0, 1 ) );
			UI.LookAt( pPlot, 0 );

			local controlTable	= {};

			Controls.EventTitle:SetText( Locale.ConvertTextKey( event_title_i18n, unpack( i18n_data ) ) );
			Controls.EventDescription:SetText( Locale.ConvertTextKey(event_desc_i18n, unpack( i18n_data ) ) );
			CivIconHookup( playerID, 64, Controls.CivIcon, Controls.CivIconBG, Controls.CivIconShadow, false, true );

			Controls.EventInfoStack:CalculateSize();
			Controls.EventInfoStack:ReprocessAnchoring();
			Controls.EventOptionStack:DestroyAllChildren();
		end
	
		local event_options	= {};
	
		local ParamWrapper = function( callback )
			callback( pPlayer, pCity, pUnit, pPlot );
		end

		local offs_y	= 0;
		local options	= {};

		for optID, option in pairs( event.options ) do
			option.__id	= optID;
			table.insert( options, option );
		end
		table.sort( options, function( a, b ) return a.order < b.order; end );

		if ( playerID == aID ) then
			for o_i, option in pairs( options ) do
				local optID	= option.__id;
				local opt	= {};

				local option_i18n		= event_title_i18n .."_" .. optID;
				local option_desc_i18n	= option.description or option_i18n .. "_DESC";
				local option_tip_i18n	= option.tooltip or option_i18n .. "_TIP";

				ContextPtr:BuildInstanceForControl( "EventOptionInstance", opt, Controls.EventOptionStack );
				opt.EventOptionDescription:SetOffsetVal( 35, offs_y );
				opt.EventOptionDescription:SetText( Locale.ConvertTextKey( option_desc_i18n, unpack( i18n_data ) ) );
		
				if not option.is_valid then
					opt.EventOptionDescription:SetColor( Vector4( 0.3, 0.3, 0.3, 1 ), 0 );
					opt.EventOptionButton:SetHide( true );
				else
					local but_txt;
					if #options	== 1 then
						but_txt	= "TXT_KEY_USER_EVENT_OPTION_OK";
					else
						but_txt	= "TXT_KEY_USER_EVENT_OPTION_SELECT";
					end
					opt.EventOptionButton:SetText( Locale.ConvertTextKey( but_txt ) );
					opt.EventOptionButton:RegisterCallback( Mouse.eLClick, function() option.effect( pPlayer, pCity, pUnit, pPlot ); Controls.FullScreenOverlay:SetHide( true ); end )
				end

				local tool_tip = Locale.ConvertTextKey( option_tip_i18n, unpack( i18n_data ) );
				opt.EventOptionDescription:SetToolTipString( tool_tip );
				opt.EventOptionButton:SetToolTipString( tool_tip );
				opt.EventOptionButton:SetOffsetVal( 0, offs_y );
				offs_y = offs_y + opt.EventOptionDescription:GetSizeY() + 50;
			end
			Controls.EventOptionStack:CalculateSize();
			Controls.EventOptionStack:SetSizeY( offs_y );
			Controls.EventOptionStack:SetOffsetVal( 0, Controls.EventInfoStack:GetSizeY() + 50 );
			Controls.EventOptionStack:ReprocessAnchoring();
			Controls.MainGrid:SetSizeY( Controls.EventInfoStack:GetSizeY() + offs_y + 20 );
			Controls.BlackGridFrame:SetSizeY( Controls.MainGrid:GetSizeY() );
			Controls.FullScreenOverlay:SetHide( false );
		else
-- It's an AI, don't set up any windows, but just do the selection directly.
			local nValid = 0;
			local optID = {};
			local optWeight = {};
			local totWeight = 0.0;

			local Fnum = DirCheck(playerID); -- 4-element array showing how many gods are available in each direction.
			local Ltype = GameInfo.Leaders[pPlayer:GetLeaderType()].Type;

			for o_i, option in pairs( options ) do
				if ( option.is_valid ) then
					nValid = nValid + 1;
					optID[nValid] = option.__id;
					optWeight[nValid] = Fnum[option.order]*1.0; -- Fnum+1?  So that it CAN pick a direction with zero?

					local flav1 = option.flavor1;
					if ( flav1 ~= nil ) then
						local condition1 = "LeaderType = '" .. Ltype .. "' and FlavorType = '"..flav1.."'";
						for row in GameInfo.Leader_Flavors(condition1) do
							optWeight[nValid] = optWeight[nValid] * (0.5 + row.Flavor/10.0);
						end
					end
					
					local flav2 = option.flavor2;
					if ( flav2 ~= nil ) then
						local condition2 = "LeaderType = '" .. Ltype .. "' and FlavorType = '"..flav2.."'";
						for row in GameInfo.Leader_Flavors(condition2) do
							optWeight[nValid] = optWeight[nValid] * (0.5 + row.Flavor/10.0);
						end
					end

					local flav3 = option.flavor3;
					if ( flav3 ~= nil ) then
						local condition3 = "LeaderType = '" .. Ltype .. "' and FlavorType = '"..flav3.."'";
						for row in GameInfo.Leader_Flavors(condition3) do
							optWeight[nValid] = optWeight[nValid] * (0.5 + row.Flavor/10.0);
						end
					end

					totWeight = totWeight + optWeight[nValid];
				end
			end
			if ( totWeight < 0.01 ) then
			-- No direction had any choices.
				optWeight = {1.0,1.0,1.0,1.0};
				totWeight = nValid*1.0;
			end
			local diceroll = Map.Rand(totWeight*100,"AI event selection") / 100.0;
			for ii = 1,nValid do
				if ( diceroll >= 0.0 and diceroll < optWeight[ii]) then
					for o_i, option in pairs( options ) do
						if ( option.is_valid and option.__id == optID[ii] ) then
-- This is the right one.  Trigger the effect.
							option.effect( pPlayer, pCity, pUnit, pPlot );
							diceroll = -999.0; -- prevents multiple options from triggering.
						end
					end
				else
					diceroll = diceroll - optWeight[ii];
				end
			end
		end
	end
end
LuaEvents.UserEventsPopupBuild.Add(BuildUserEventPopup);

Again, there are a couple bits specific to my mod, specifically the Fnum array and the call to DirCheck. You can just replace the DirCheck part with a 1.0 value and it'll work fine, only weighting by flavors.

Now, I put in code for up to three Flavor values. What happens is that for each Flavor you list, it goes into the Leaders table and sees the base Flavor bias for your particular leader (on a 0-10 scale). A 5 is the baseline, so it adds 5 to whatever it finds and then divides by 10. So that gives a range of 0.5-1.5 for weighting, although most don't vary by that much. It won't adjust these values based on current needs or anything, although I'm working on expanding that.
If you give a Flavor that's not in the leader table (like FLAVOR_ANTI_AIR) it'll stay at 1.0, and if you don't specify one of the flavors it'll skip that one. (The example I gave above used two of the three, for instance.)

If you want an event to REALLY depend on one specific flavor, just put in the same flavor two or three times.

EDIT: Updated the code posted above. I'd accidentally flipped the arguments on the call to UserEventLastAppeared; it's supposed to be event ID, THEN player ID.
 
Spatzimaus

Thanks for the explanation and for sharing your great work with us! As always you have a way of making things other people just don't or can't. :king:

I've read through your instructions and think I get most of it. I updated UserEvents.lua and UserEventsPopup.lua according to your instructions. Only place I didn't follow your instructions is in the line:

Since I don't work with alignments in my mod I simply changed this from.
Spoiler :
test_prob = test_prob


to:
Spoiler :
test_prob = test_prob * AlignMult(playerID, event.alignment); -- ranges from 0.5 to 1.5, more or less.


I'm however not so sure about how to use your instructions to update the events in MyUserEvents.lua.

First of all you only make changes to the UserEventAdd part?

Second I'm not sure how the effects and conditions in the old events mod correspond to the conditions and events you state. The old mod already has conditions so can these just be used instead if Low_Cond / Once_Cond?

Third you just use three flavor categories in your example. Can you use more: FLAVOR_CULTURE, FLAVOR_GREAT_PEOPLE, FLAVOR_HAPPINESS, ...?

I posted one event that I updated according to your instructions. Would you have a look at it?

Spoiler :

--[[ Wandering Wiseman ]]--
function WanderingWisemanCond( player, city, unit, plot )
return city and city:IsCapital() and plot:IsCity();
end

function WanderingWisemanCond1( player, city, unit, plot )
local nera = player:GetCurrentEra();
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 1;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 2;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 3;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 4;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 5;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 6;
else
ncounter = 7;
end
return WanderingWisemanCond( player, city, unit, plot ) and player:GetGold() >= (200 + ncounter*50) ;
end
function WanderingWisemanCond2( player, city, unit, plot )
local nera = player:GetCurrentEra();
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 1;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 2;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 3;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 4;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 5;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 6;
else
ncounter = 7;
end
return WanderingWisemanCond( player, city, unit, plot ) and player:GetGold() >= (100 + ncounter*25) ;
end

function WanderingWisemanNoEffect( player, city, unit, plot )
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT_NONE" ) );
end

function WanderingWisemanEffect1( player, city, unit, plot )
local nera = player:GetCurrentEra();
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 1;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 2;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 3;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 4;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 5;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 6;
else
ncounter = 7;
end
player:ChangeGold( - (200 + ncounter*50) );
_AddNewUnit( player, "UNIT_SCIENTIST", plot:GetX(), plot:GetY() );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT1" ) );
end

function WanderingWisemanEffect2( player, city, unit, plot )
local mera = player:GetCurrentEra();
local mcounter = 0;
if mera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
mcounter = 1;
elseif mera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
mcounter = 2;
elseif mera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
mcounter = 3;
elseif mera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
mcounter = 4;
elseif mera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
mcounter = 5;
elseif mera == GameInfo.Eras[ "ERA_MODERN" ].ID then
mcounter = 6;
else
mcounter = 7;
end
player:ChangeGold ( - (100 + mcounter*25) );
local res = math.random( 0, 99 );
if res < 35 then
local nera = player:GetCurrentEra();
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 40;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 70;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 122;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 214;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 375;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 656;
else
ncounter = 1148;
end
player:GetCurrentCultureBonus(ncounter);
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2A" ) );
elseif res < 40 then
player:SetNumFreeTechs( 1 );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2B" ) );
Events.SerialEventGameMessagePopup( { Type = ButtonPopupTypes.BUTTONPOPUP_CHOOSETECH,
Data1 = player,
Data3 = -1
} );

elseif res < 60 then
local tech = player:GetCurrentResearch();
_PlayerSetTech( player, tech, true );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2C" ) );
Events.SerialEventGameMessagePopup( { Type = ButtonPopupTypes.BUTTONPOPUP_CHOOSETECH,
Data1 = player,
Data3 = tech
} );
elseif res < 95 then
local turns = math.random( 2, 4 );
player:ChangeGoldenAgeTurns( turns );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2D", turns ) );
else
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2E" ) );
end
end
UserEventAdd{
id = "WANDERING_WISEMAN",
probability = 0.002,
condition = Low_Cond,
this_event_limit = 20;
any_event_limit = 5;
options = {
OPTION_L = { order = 1, condition = WanderingWisemanCond1, effect = WanderingWisemanEffect1, flavor1 = "FLAVOR_GROWTH", flavor2 = "FLAVOR_SCIENCE" },
OPTION_C = { order = 2, condition = WanderingWisemanCond2, effect = WanderingWisemanEffect2, flavor1 = "FLAVOR_SCIENCE", flavor2 = "FLAVOR_SCIENCE" },
OPTION_M = { order = 3, condition = WanderingWisemanCond, effect = WanderingWisemanNoEffect, flavor1 = "FLAVOR_GOLD", flavor2 = "FLAVOR_GROWTH" },
}
};
--[[ / Wandering Wiseman ]]--


Once again thanks :goodjob:

\Skodkim
 
One warning: I was trying a test game last night, and for some reason the event-specific lockout wasn't working. I'm debugging it now, so there might be a change once I track that down.

First of all you only make changes to the UserEventAdd part?

Correct. All I did was add three "global" fields (the global condition and the two turn-limit variables) to each event and three flavor fields to each option. The three global fields aren't necessary to make the AI work, and only serve to speed the process up, while the flavors are what drives the AI functionality.

Second I'm not sure how the effects and conditions in the old events mod correspond to the conditions and events you state. The old mod already has conditions so can these just be used instead if Low_Cond / Once_Cond?

Yes, they can, but it's a really bad idea.

You see, what the original code did on every turn, for every event, was this:
> Do the random draw for that event. If it fails, skip the rest.
> Loop over every player. Check the four conditions to see if they key off something player-related.
> Loop over every city. Get its location, and check the four conditions. If each of them is true, set its "valid" option to true.
> Loop over every unit. Get its location, and check the four conditions. Again, if each of them is true, set its "valid" option to true.
> If, after all that is done, any of the four are set "true", then set the event's flag to true and proceed to the popup.

This is just an INCREDIBLY wasteful way to design the system, since most conditions won't include both a city condition AND a unit condition, and if you have a large number of events, on a large map, with a large number of cities and units, then it's executing a couple really large loops on a regular basis, and executing a conditional check on every one. Also, many of those conditions will apply to all four options equally, meaning there's no reason to check the same thing four times.

(I'm going to rewrite the code in the future to where you'll specify in the event's header whether it's a unit-based thing, a city-based thing, or a player-based thing. That way, it won't bother executing the loops that it doesn't need.)

The thing is, most of the time you don't need to bother with those city or unit loops at all, because you know the event will fail because of some player-specific condition, and it'll fail for all four options simultaneously. For instance, in my mod some of the events won't trigger until the player reaches the Classical Era and will only trigger as long as the player hasn't completed a certain Project that ends the mythological content. There's absolutely no point in wasting processor time during the Ancient Era checking every unit and city to see if it'd pass the event, because you know it'll fail on all of them. So, I split the original condition into two pieces: an event condition that only checks for your era and that one Project, and then the conditions for each option as before.

You don't NEED to do it that way if you don't want to, but it'll perform much better this way. The only limitation is that this "global" condition ONLY uses the player as an argument, it can't depend on cities, units, or plots. So you only use it for things that apply to the player as a whole.

Third you just use three flavor categories in your example. Can you use more: FLAVOR_CULTURE, FLAVOR_GREAT_PEOPLE, FLAVOR_HAPPINESS, ...?

Every flavor defined by the game will work. What it does is it goes to the Leader's XML table and looks for his Flavor biases (0-10). If it's a 5 or undeclared (leaders don't have a FLAVOR_ANTIAIR bias, for instance) then it just multiplies by 1.0, but if it's higher or lower than that it'll change the chance of each option depending on the leader's bias.

When I said the part about the three-flavor limit, I meant three on each option. As in, flavor1="X", flavor2="Y", flavor3="Z" for a single option. You just can't have an option link to four flavors. You CAN repeat an option (setting both flavor1 and flavor2 to the same Flavor), which has the effect of making the AI depend even more strongly on whether it likes that particular flavor.

I posted one event that I updated according to your instructions. Would you have a look at it?

A couple things.
0> When posting code, do it in a (CODE) block (except using brackets instead of parentheses). So in this case you'd use (SPOILER)(CODE) stuff (/CODE)(/SPOILER). The CODE environment preserves indentations, which makes it much easier to read.

1> Why bother with the nera condition block, when the way you've set it up ncounter = nera+1 in nearly all cases? You only had one case block that didn't work that way.

2> I named my options OPTION_L, OPTION_C, OPTION_M, and OPTION_E, because each is tied to one of my four alignment directions (L, C, M, and E). You don't have to keep them named that way. The original User Events mod used OPTION_1, OPTION_2, etc. This isn't a code change, you've always been able to name the options whatever you wanted. The only thing that changes as a result are the text key names for the tooltips.

2a> Same goes for "Low_Cond"; you don't have to use my name. Also, you didn't DECLARE Low_Cond. It's not built into the system, that's just the name of the condition I use for all of my "Low" (common) events.

For instance, in my mod Low_Cond is just
Code:
function Low_Cond( player )
	return Myth_Cond(player) and Game:GetGameTurn() > 5;
end
Myth_Cond, in turn, is just a check to see if that player is still in the mythological age. Similarly, that Once_Cond is just my own condition, specifically
Code:
function Once_Cond ( player, city, unit, plot )
	return city and city:IsCapital() and plot:IsCity();
end
whose sole purpose is to ensure that the function can't happen more than once in a turn for each player. Since nearly all of my events have global effects, this was just necessary.
 
There are some problems with the resources that spawn on already improved tiles, they do not get registered. Say for instance you have a farm and 2 units of wheat spawns on that tile. It will not add 2 wheat resources to your empires total. Even worse, if you destroy the farm in order to rebuild it and get the resources, it will deduct the resources from your empires total!?!? :confused:
Same experience with copper.
Maybe related? Have 8 units of iron in the empire from 2 mined iron deposits. Then discovered that an iron deposit, with 6 units of iron, had been hiding under a landmark.
I promptly put a worker to destroying the landmark and build a mine on the tile. I then lost 6 iron resources from my empires total, putting me down to 2 with a 6 unit defecit:mad:
 
Cannes... did you use version 7 posted in #17? That one should fix the problem.

Very much looking forward to the new version once you guys get it straightened out.

@skodkim: You may be well off starting a new thread once you get it to work.
 
@skodkim: You may be well off starting a new thread once you get it to work.

Yeah, guess your right but as I mentioned earlier I'm not really that interested in maintaining a mod as I neither have the time og skills(!!) for it. My interest is in keeping the discussion on random events going, get input and hopefully find people (Like Spatzimaus) who are willing to give a hand.

\Skodkim
 
Spatzimaus

Thanks for your explanation.

I'm sorry for being such a mod-noob but I'm not sure I get how Low_Cond / Once_Cond works entirely...

Generally I think Its a good idea to make some kind of checks saying that an event shouldn't occur within the first xxx rounds, within xxx rounds from the last event and such. That's all they do, right?

But if that's all they do how do I make sure that an event only occurs e.g. on a tile with farms, in the capital, ...?

Pasted the code in the spoiler below using the (SPOILER)(CODE) code (/SPOILER)(/CODE) in brackets. Hope its makes it easier to read. Otherwise I haven't updated the code yet.

Spoiler :
Code:
--[[ Wandering Wiseman ]]--
function WanderingWisemanCond( player, city, unit, plot )
return city and city:IsCapital() and plot:IsCity();
end

function WanderingWisemanCond1( player, city, unit, plot )
local nera = player:GetCurrentEra(); 
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 1;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 2;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 3;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 4;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 5;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 6;
else
ncounter = 7;
end
return WanderingWisemanCond( player, city, unit, plot ) and player:GetGold() >= (200 + ncounter*50) ;
end
function WanderingWisemanCond2( player, city, unit, plot )
local nera = player:GetCurrentEra(); 
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 1;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 2;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 3;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 4;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 5;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 6;
else
ncounter = 7;
end
return WanderingWisemanCond( player, city, unit, plot ) and player:GetGold() >= (100 + ncounter*25) ;
end

function WanderingWisemanNoEffect( player, city, unit, plot )
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT_N ONE" ) );
end

function WanderingWisemanEffect1( player, city, unit, plot )
local nera = player:GetCurrentEra(); 
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 1;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 2;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 3;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 4;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 5;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 6;
else
ncounter = 7;
end
player:ChangeGold( - (200 + ncounter*50) );
_AddNewUnit( player, "UNIT_SCIENTIST", plot:GetX(), plot:GetY() );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT1" ) );
end

function WanderingWisemanEffect2( player, city, unit, plot )
local mera = player:GetCurrentEra(); 
local mcounter = 0;
if mera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
mcounter = 1;
elseif mera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
mcounter = 2;
elseif mera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
mcounter = 3;
elseif mera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
mcounter = 4;
elseif mera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
mcounter = 5;
elseif mera == GameInfo.Eras[ "ERA_MODERN" ].ID then
mcounter = 6;
else
mcounter = 7;
end
player:ChangeGold ( - (100 + mcounter*25) );
local res = math.random( 0, 99 );
if res < 35 then
local nera = player:GetCurrentEra(); 
local ncounter = 0;
if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
ncounter = 40;
elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
ncounter = 70;
elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
ncounter = 122;
elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
ncounter = 214;
elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
ncounter = 375;
elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
ncounter = 656;
else
ncounter = 1148;
end
player:GetCurrentCultureBonus(ncounter);
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2A " ) );
elseif res < 40 then
player:SetNumFreeTechs( 1 );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2B " ) );
Events.SerialEventGameMessagePopup( { Type = ButtonPopupTypes.BUTTONPOPUP_CHOOSETECH,
Data1 = player,
Data3 = -1
} );

elseif res < 60 then
local tech = player:GetCurrentResearch();
_PlayerSetTech( player, tech, true );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2C " ) );
Events.SerialEventGameMessagePopup( { Type = ButtonPopupTypes.BUTTONPOPUP_CHOOSETECH,
Data1 = player,
Data3 = tech
} );
elseif res < 95 then
local turns = math.random( 2, 4 );
player:ChangeGoldenAgeTurns( turns );
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2D ", turns ) );
else
_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2E " ) );
end
end
UserEventAdd{
id = "WANDERING_WISEMAN",
probability = 0.002,
condition = Low_Cond,
this_event_limit = 20;
any_event_limit = 5;
options = {
OPTION_L = { order = 1, condition = WanderingWisemanCond1, effect = WanderingWisemanEffect1, flavor1 = "FLAVOR_GROWTH", flavor2 = "FLAVOR_SCIENCE" },
OPTION_C = { order = 2, condition = WanderingWisemanCond2, effect = WanderingWisemanEffect2, flavor1 = "FLAVOR_SCIENCE", flavor2 = "FLAVOR_SCIENCE" },
OPTION_M = { order = 3, condition = WanderingWisemanCond, effect = WanderingWisemanNoEffect, flavor1 = "FLAVOR_GOLD", flavor2 = "FLAVOR_GROWTH" },
}
};
--[[ / Wandering Wiseman ]]--

\Skodkim
 
I'm sorry for being such a mod-noob but I'm not sure I get how Low_Cond / Once_Cond works entirely...

If you mean "how do the fields those functions fill work?", it goes like this. In my version, each event now has a condition that ONLY passes the Player structure as its argument. (This wasn't in the original version of this mod component, and was purely my own addition.) This allows you to do quick conditional checks to see whether it's even worth bothering with the rest of that event's checks. Maybe the condition depends on the player (whether he has a certain Policy, whether he has a certain Project), or maybe it's global for the game (whether the game turn number is less than 5).
If that condition succeeds, AND the random number draw on the event probability succeeds, then the event does two loops: one over cities (and within that, a loop over plots worked by that city), and a loop over units and the plots THEY occupy. This is why the option-specific conditions have player, city, unit, and plot as the arguments; it's going to loop over every possible combination. If any of the conditions succeed on an option, that option is flagged "valid" and will be included in the popup window as something the user can click on. (Invalid options will be grayed out.)
But, if all options are still invalid after all of that looping, it simply won't create the popup in the first place; this is why the original mod's sample events often used the same condition for all options. This is why I created that global condition option; there's just no point executing a bunch of nested loops if they're going to fail outright because of some global condition.

Now, if you mean "how do those particular bits of code I posted above work?", that's different. I'll get to some of that below, but the short answer is to do whatever you want; those are just examples I use in my own mod, not something you need to be doing yourself.

Generally I think Its a good idea to make some kind of checks saying that an event shouldn't occur within the first xxx rounds, within xxx rounds from the last event and such. That's all they do, right?

Yes, basically.
The this_event_limit value I set just says "once you do this event, don't repeat this same event for another X turns"; setting it to -1 means "don't EVER repeat this event again", which is something I use for rare-but-powerful events.
The any_event_limit value is a global cutoff, as in "don't do this event if there's been ANY events within the past X turns"; it serves to keep one player from getting too many good/bad events right in a row just because he was lucky or unlucky on the random draw. For practical reasons the any_event_limit should usually be set to the same value for all events, or else you can easily get stuck to where those events will never trigger. (If you have an event that has a 5-turn lockout and a 20% chance of occurring, and a second event with a 100-turn lockout, it's horribly unlikely that you'd ever reach the second one, since the counter would reset every time the first one triggered and there would have been 95 opportunities to trigger the first event in that time.)

The "not within the first XXX rounds" part is what I do in Low_Cond. I have five "Low" events (don't do in first 5 turns, don't repeat this particular event within 20 turns), six "Medium" events (don't do in first 20 turns, don't repeat this particular event within 40 turns), and eight "High" events (don't do until you reach the Classical Era, and never repeat this event). This is why I didn't want to put everything onto the option-specific conditions like the original mod had; these conditions are so pervasive that I'd be wasting a huge amount of time executing them repeatedly. And it's why I named it Low_Cond; it's the conditional check for all Low events. I also have a Med_Cond and a High_Cond, for those other categories.

But if that's all they do how do I make sure that an event only occurs e.g. on a tile with farms, in the capital, ...?

This mod loops over cities, and then loops over all tiles worked by that city. So if you wrote it as
Code:
function CapitalFarmCond(player, city, unit, plot)
  local retval = false;

  if ( player ~= nil and city ~= nil and plot ~= nil ) then
    if ( city:IsCapital() and plot:GetImprovementType == GameInfoTypes.IMPROVEMENT_FARM ) then
      retval = true;
    end
  end

  return city and plot and retval;
end

You could shorten that by putting that middle condition on the return, something like
"return city and city:IsCapital() and plot and plot:GetImprovementType == GameInfoTypes.IMPROVEMENT_FARM;", and it'd do pretty much the same thing, but I'm a big believer in sanitizing your inputs wherever possible. After all, you don't KNOW that someone won't try to access this function with one of those accidentally set to nil, and you can't guarantee the results if they pass in some sort of invalid non-null value.

What'll happen is that the mod will see your event, and do a random draw to see if it'll happen. If so, it'll loop over every city-plot combination you have (effectively scanning every single hex in your territory) until it meets one that passes that check. Then, it'll execute the effect. (And note, if multiple hexes meet that requirement, it'll execute the effect for each.) The above code will only work if the checked city-plot combination is a Farm plot within the claimed area for a capital.

Note that this isn't the same as saying a Farm WORKED by the capital. Every hex in your territory was gained through a given city (either from its founding or through a border expansion), and is permanently linked to that one even if some other city is working the tile at the moment. This mod, as written, is not designed to get around that. You can drop the city check, use the plot structure to find out which city is working that tile, and check to see if THAT city is your capital; it's messy, but it'd work.

Pasted the code in the spoiler below using the (SPOILER)(CODE) code (/SPOILER)(/CODE) in brackets. Hope its makes it easier to read.

It won't help because the thing you were quoting had no indentation. Try using the "quote" reply to my earlier post; you can see the tab indentation in the text I've wrapped those tags around. Board software doesn't handle tabs well when you're typing them in, but it does just fine with tabs in quoted code. The (CODE) tag won't add indentation where there wasn't any; it's just designed to NOT trim out indentation, which the standard post environment loses.
 
Hi Spatzimaus

I'm very very sorry to ask but I spend some time massing around with updating the mod according to your directions and to be frank I lost all overview of it.

Really embarrassed to ask but do you think you could have a look at it and update the files :blush:? I attached the entire mod but if you could just take a look at the two sample events from the mod seen below (this time with indents!) and update them I could possibly do the rest - just need to see the greater picture here.

Spoiler :
Code:
--[[ Wandering Wiseman ]]--
function WanderingWisemanCond( player, city, unit, plot )
 	return city and city:IsCapital() and plot:IsCity();
end

function WanderingWisemanCond1( player, city, unit, plot )
	local nera	= player:GetCurrentEra(); 
	local ncounter = 0;
	if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
		ncounter = 1;
	elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
		ncounter = 2;
	elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
		ncounter = 3;
	elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
		ncounter = 4;
	elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
		ncounter = 5;
	elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
		ncounter = 6;
	else
		ncounter = 7;
	end
	return WanderingWisemanCond( player, city, unit, plot ) and player:GetGold() >= (200 + ncounter*50) ;
end
function WanderingWisemanCond2( player, city, unit, plot )
	local nera	= player:GetCurrentEra(); 
	local ncounter = 0;
	if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
		ncounter = 1;
	elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
		ncounter = 2;
	elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
		ncounter = 3;
	elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
		ncounter = 4;
	elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
		ncounter = 5;
	elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
		ncounter = 6;
	else
		ncounter = 7;
	end
	return WanderingWisemanCond( player, city, unit, plot ) and player:GetGold() >= (100 + ncounter*25) ;
end

function WanderingWisemanNoEffect( player, city, unit, plot )
	_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT_NONE" ) );
end

function WanderingWisemanEffect1( player, city, unit, plot )
	local nera	= player:GetCurrentEra(); 
	local ncounter = 0;
	if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
		ncounter = 1;
	elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
		ncounter = 2;
	elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
		ncounter = 3;
	elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
		ncounter = 4;
	elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
		ncounter = 5;
	elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
		ncounter = 6;
	else
		ncounter = 7;
	end
	player:ChangeGold( - (200 + ncounter*50) );
	_AddNewUnit( player, "UNIT_SCIENTIST", plot:GetX(), plot:GetY() );
	_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT1" ) );
end

function WanderingWisemanEffect2( player, city, unit, plot )
	local mera	= player:GetCurrentEra(); 
	local mcounter = 0;
	if mera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
		mcounter = 1;
	elseif mera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
		mcounter = 2;
	elseif mera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
		mcounter = 3;
	elseif mera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
		mcounter = 4;
	elseif mera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
		mcounter = 5;
	elseif mera == GameInfo.Eras[ "ERA_MODERN" ].ID then
		mcounter = 6;
	else
		mcounter = 7;
	end
	player:ChangeGold ( - (100 + mcounter*25) );
	local res	= math.random( 0, 99 );
	if res < 35 then
		local nera	= player:GetCurrentEra(); 
		local ncounter = 0;
		if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
			ncounter = 40;
		elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
			ncounter = 70;
		elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
			ncounter = 122;
		elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
			ncounter = 214;
		elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
			ncounter = 375;
		elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
			ncounter = 656;
		else
			ncounter = 1148;
		end
		player:GetCurrentCultureBonus(ncounter);
		_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2A" ) );
	elseif res < 40 then
		player:SetNumFreeTechs( 1 );
		_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2B" ) );
		Events.SerialEventGameMessagePopup( {	Type	= ButtonPopupTypes.BUTTONPOPUP_CHOOSETECH,
												Data1	= player,
												Data3	= -1
											} );

	elseif res < 60 then
		local tech = player:GetCurrentResearch();
		_PlayerSetTech( player, tech, true );
		_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2C" ) );
		Events.SerialEventGameMessagePopup( {	Type	= ButtonPopupTypes.BUTTONPOPUP_CHOOSETECH,
												Data1	= player,
												Data3	= tech
											} );
	elseif res < 95 then
		local turns	= math.random( 2, 4 );
		player:ChangeGoldenAgeTurns( turns );
		_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2D", turns ) );
	else
		_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_WANDERING_WISEMAN_EFFECT2E" ) );
	end
end
UserEventAdd{
	id			= "WANDERING_WISEMAN",
	probability	= 0.002,
	condition = Low_Cond,
	this_event_limit = 20;
	any_event_limit = 5;
	options		= {
		OPTION_L = { order = 1, condition = WanderingWisemanCond1, effect = WanderingWisemanEffect1, flavor1 = "FLAVOR_GROWTH", flavor2 = "FLAVOR_SCIENCE" },
		OPTION_C = { order = 2, condition = WanderingWisemanCond2, effect = WanderingWisemanEffect2, flavor1 = "FLAVOR_SCIENCE", flavor2 = "FLAVOR_SCIENCE" },
		OPTION_M = { order = 3, condition = WanderingWisemanCond, effect = WanderingWisemanNoEffect, flavor1 = "FLAVOR_GOLD", flavor2 = "FLAVOR_GROWTH" },
	}

};
--[[ / Wandering Wiseman ]]--

--[[ Singer]]--
-- condition function
function SingerCond( player, city, unit, plot )
	-- valid only if player has writing and on plots with improvements
		return city and _PlayerHasTech( player, "TECH_WRITING") and (plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_MINE" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_FARM" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_IRRIGATION" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_TRADING_POST" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_FP_TRADING_POST" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_QUARRY" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_PASTURE" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_CAMP" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_PLANTATION" ].ID or plot:GetImprovementType() == GameInfo.Improvements[ "IMPROVEMENT_LUMBERMILL" ].ID);
	end
 --effect function
function SingerEffect( player, city, unit, plot )
	local nera	= player:GetCurrentEra(); 
	local ncounter = 0;
	if nera == GameInfo.Eras[ "ERA_ANCIENT" ].ID then
		ncounter = 1;
	elseif nera == GameInfo.Eras[ "ERA_CLASSICAL" ].ID then
		ncounter = 2;
	elseif nera == GameInfo.Eras[ "ERA_MEDIEVAL" ].ID then
		ncounter = 3;
	elseif nera == GameInfo.Eras[ "ERA_RENAISSANCE" ].ID then
		ncounter = 4;
	elseif nera == GameInfo.Eras[ "ERA_INDUSTRIAL" ].ID then
		ncounter = 5;
	elseif nera == GameInfo.Eras[ "ERA_MODERN" ].ID then
		ncounter = 6;
	else
		ncounter = 7;
	end	
-- add +1 culture per era to the plot
	plot:ChangeCulture(ncounter);	
	_UserEventMessage( Locale.ConvertTextKey( "TXT_KEY_USER_EVENT_SINGER_EFFECT", city:GetName() ) );
end
-- enable the event
UserEventAdd{
    id			= "SINGER",
	probability	= 0.002,
	options		= {
		OPTION_L = {	order	= 1, condition	= SingerCond, effect	= SingerEffect },
		}
 };
-- / Singer ]
[/SPOILER]

\Skodkim
 
Im sorry but for some reason I'm not allowed to upload the mod again as a zip file. I keep getting the message that I already uploaded the file to the thread once. Tried changing name and everything, but the only thing that seems to solve the issue is packing the mod as a *.tar file.

\Skodkim
 
I keep getting the message that I already uploaded the file to the thread once.

The board software works that way. If you want to upload a file with the same name as a previous upload, you have to either remove the original from your earlier post, or change the name. In this case, it doesn't matter; the mod you uploaded had no event logic in it at all. It's just a bunch of XML tweaks that have nothing to do with this topic. So, I'll comment on what you posted above instead.

As I said before, you don't have to name the options OPTION_L, OPTION_C, and so on. Those are MY names for things, because of the alignment system my mod uses. Every event I add has four choices, but obviously you don't have to do the same. More importantly, you can't have a line like
Code:
condition = Low_Cond,
when you don't HAVE anything named "Low_Cond". It's just like the conditions on the individual options, it has to point to a function you've declared in your file.

Because of this, I can't just rewrite your code for you, because I don't know what you're trying to DO with the features I've added. If there isn't some sort of global condition you want to put on the event, then just don't declare that line. All of the things I wrote into my logic are backwards-compatible; passing in the old-style event declarations will still work just fine, so you only need to declare those extra lines if you want to take advantage of the features they add. The same goes for Flavors; you can have zero, you can have three, you can have any number in between. (And if you want more than three, it's easy enough to patch in.) Don't feel obligated to use two just because I did.
 
The board software works that way. If you want to upload a file with the same name as a previous upload, you have to either remove the original from your earlier post, or change the name. In this case, it doesn't matter; the mod you uploaded had no event logic in it at all. It's just a bunch of XML tweaks that have nothing to do with this topic. So, I'll comment on what you posted above instead.

As I said before, you don't have to name the options OPTION_L, OPTION_C, and so on. Those are MY names for things, because of the alignment system my mod uses. Every event I add has four choices, but obviously you don't have to do the same. More importantly, you can't have a line like
Code:
condition = Low_Cond,
when you don't HAVE anything named "Low_Cond". It's just like the conditions on the individual options, it has to point to a function you've declared in your file.

Because of this, I can't just rewrite your code for you, because I don't know what you're trying to DO with the features I've added. If there isn't some sort of global condition you want to put on the event, then just don't declare that line. All of the things I wrote into my logic are backwards-compatible; passing in the old-style event declarations will still work just fine, so you only need to declare those extra lines if you want to take advantage of the features they add. The same goes for Flavors; you can have zero, you can have three, you can have any number in between. (And if you want more than three, it's easy enough to patch in.) Don't feel obligated to use two just because I did.

Sorry about the upload - uploaded the wrong mod due to all my frustrations. The uploaded version was just xml tweak and such.

My entire point with my request for you was to make the events happen for AI's. Of course it would be nice with more advanced tweaks like code optimizations, assuring that events doesn't occur within x turns of each other, within the first x rounds of the game but really, if I could just have the AI part I'd be happy.

If I understand you correct I could just keep as simple as to mod in your changes (from post 26) in UserEvents.lua and UserEventsPopup.lua?

Optionally I could mod in the flavor part in MyuserEvents.lua to make the AI decide on events based on leader personalities , e.g.

Spoiler :
OPTION1 = { order = 1, condition = WanderingWisemanCond1, effect = WanderingWisemanEffect1, flavor1 = "FLAVOR_GROWTH", flavor2 = "FLAVOR_SCIENCE" },
OPTION2 = { order = 2, condition = WanderingWisemanCond2, effect = WanderingWisemanEffect2, flavor1 = "FLAVOR_SCIENCE", flavor2 = "FLAVOR_SCIENCE" },
OPTION3 = { order = 3, condition = WanderingWisemanCond, effect = WanderingWisemanNoEffect, flavor1 = "FLAVOR_GOLD", flavor2 = "FLAVOR_GROWTH" },

(sorry about identing - only have access to the code from previous posts right now)

That's it - events should happen for AI players too?

\Skodkim
 
If I understand you correct I could just keep as simple as to mod in your changes (from post 26) in UserEvents.lua and UserEventsPopup.lua?

Correct. If you include the changes I made to those two files, all of your existing Event declarations should work exactly the same as before, and the AI would simply pick an outcome at random. You don't have to use those extra bits like the any_event thing if you don't want to.

Optionally I could mod in the flavor part in MyuserEvents.lua to make the AI decide on events based on leader personalities , e.g.

Right. The AI will weight each option's chances by (X+5)/10, where X is the leader's bias in that particular Flavor. Give multiple flavors (or the same one multiple times), and it'll do this multiplication again for each one.

Note that this is the bias in the DATABASE. The game randomizes each bias slightly at the start of the game, up or down by 0-2, and that shift isn't included. Likewise, if you pick "Randomize Personalities", then it'll still weight the outcomes by what you would have had if you hadn't randomized.

If you're having a hard time getting those changes into the Lua, just go download my Mythology mod and copy directly from my versions of those files. I KNOW those versions work, after all.
 
Correct. If you include the changes I made to those two files, all of your existing Event declarations should work exactly the same as before, and the AI would simply pick an outcome at random. You don't have to use those extra bits like the any_event thing if you don't want to.



Right. The AI will weight each option's chances by (X+5)/10, where X is the leader's bias in that particular Flavor. Give multiple flavors (or the same one multiple times), and it'll do this multiplication again for each one.

Note that this is the bias in the DATABASE. The game randomizes each bias slightly at the start of the game, up or down by 0-2, and that shift isn't included. Likewise, if you pick "Randomize Personalities", then it'll still weight the outcomes by what you would have had if you hadn't randomized.

If you're having a hard time getting those changes into the Lua, just go download my Mythology mod and copy directly from my versions of those files. I KNOW those versions work, after all.

Thanks for the repply. I'll try to mod in your changes then - dont think its too hard. Right now I'm in the middle of tracking down why I can't get event with techs as prerequirement to work, i.e. "_PlayerHasTech( player, "TECH_###")". It seems they never trigger even though I have the tech - funny as it used to work...

More Info: Actually I just tried the old version attached to a previous post and it doesn't work there either - that's really weird. Maybe that trigger seized to work with the new update? I'll do some more poking around.

\Skodkim
 
Right now I'm in the middle of tracking down why I can't get event with techs as prerequirement to work, i.e. "_PlayerHasTech( player, "TECH_###")".

That function never seemed to work right for me. I always end up going through the Teams and TeamTechs functions instead, since they seem much more stable.

Also, you should note that most of those sorts of Lua checks need ID numbers, and NOT the "TECH_XXX" name. It's easy enough to convert through the GameInfoTypes functions (for techs you know the name of) and/or GameInfo.Technologies (for trying to match an arbitrary string), but it's something you have to account for. A similar problem comes up with functions like InitUnit; while it wants the unit's type, it only works with an integer ID and not a text string.
 
Back
Top Bottom