Change pop-increase for houses (statemodel_schema)

对房屋人口的修改可以通过“游戏模式”模组实现。这个解决方案基于Dave在问题Anyone know how to change how much pop a house/town center gives you?下的回答,并做出了一些改进。

Modifications to the house population cap can be achieved through the Game Mode mod. This solution is an enhanced version based on Dave’s answer to the question Anyone know how to change how much pop a house/town center gives you?.

1. 创建"游戏模式" (Creating the Game Mode)

在Context Editor中创建一个“游戏模式”而非“调整包”。

Create a Game Mode in the Context Editor instead of a Tuning Pack.

选择Game Mode

选择一个模板(这有三个选项,blank templateroyal rumble template以及template with examples,AoE官网有一些介绍)。这里我们选择blank template,因为它仅仅提供了最基础的代码,方便我们进行修改。填完Game Mode Name和Display Name后,点击Next。

Select a template (there are three options: blank template, royal rumble template, and template with examples. The AoE official website provides some guides). Choose the blank template because it only provides the most basic code, making it convenient to modify. Fill in the Game Mode Name and Display Name. Click Next.

补完后续出现的Mod Description并为模组选择好位置后,点击Finish。

Complete the Mod Description and select a location. Click Finish.

2. 编写SCAR代码(Editing the SCAR Script)

可以看到两个文件已经打开:HousePopCapModification.scar和HousePopCapModification.rdo。前者是需要编辑的SCAR代码,后者则用于定义更高级的游戏选项。接下来我们将编辑HousePopCapModification.scar以将一个房屋提供的人口从10改为100。

You can see that two files are now open: HousePopCapModification.scar and HousePopCapModification.rdo. The former is the SCAR script that needs to be edited, while the latter is used to define advanced game options. Next, I will show how to change the house population cap from 10 to 100 by editing HousePopCapModification.scar.

两个文件,左边的scar文件是重点

2.1 代码框架(Script Framework)

SCAR代码采用Lua语言编写而成。HousePopCapModification.scar文件通过注释分为了三个部分:Imported Scripts、Data和Scripting framework。在Data部分,定义了一个名为_mod的table;在Scripting framework部分定义了六个函数:Mod_OnGameSetup()Mod_PreInit()Mod_OnInit()Mod_Start()Mod_OnPlayerDefeated()Mod_OnGameOver(),它们分别对应六个游戏阶段。

SCAR scripts are written in Lua. The HousePopCapModification.scar file is structured into three sections: Imported Scripts, Data, and Scripting Framework. The Data section defines a table named _mod, while the Scripting Framework section defines six functions: Mod_OnGameSetup(), Mod_PreInit(), Mod_OnInit(), Mod_Start(), Mod_OnPlayerDefeated(), and Mod_OnGameOver(), each corresponding to a specific period of the game.

2.2 实现思路(Implementation Approach)

维护两个计数器table,分别记录当前各位玩家已经建成的城镇中心和房屋数量。监听“建筑建造完成”和“建筑被摧毁”这两个事件,如果该建筑的类型为“城镇中心”,则更新城镇中心计数器;如果该建筑的类型为“房屋”,则更新房屋计数器并主动更新人口容量。

Maintain two counter tables to track the number of Town Centers and Houses built by each player respectively. Listen for the GE_ConstructionComplete and GE_EntityKilled events. If the building type is “Town Center”, update the Town Center counter table; if it is “House”, update the House counter table and proactively adjust the population cap.

需要注意的是,游戏基本规则和scar代码都将被运行,所以我们必须将规则带来的人口容量的改变考虑在内。比如,游戏开始时的人口容量为10,建造一个房屋后变为20,但我们希望每个房屋带来的人口容量增长为100,所以通过SCAR代码进行的人口容量修正应为+90。

It is important to note that both the basic game rules and the script will be executed simultaneously, so we must account for population cap changes introduced by the rules. For example, the initial population cap at the start of a game is 10. After building one House, it becomes 20. However, since we aim for each House to provide a population increase of 100, the script should apply an adjustment of +90.

处理房屋摧毁带来的人口容量变化则稍微复杂一些。这需要用到先前创建的计数器,计算剩余城镇中心和房屋所能提供的人口容量并进行更新。主要是因为,“建造中”对于建筑而言是一种特殊的状态。

It’s slightly more complex to handle population cap changes due to House destruction. This requires using the previously maintained counter tables to calculate the population cap provided by the Town Centers and remaining Houses and updating the population cap. Because “under construction” is very strange for buildings.

  1. SCAR提供了一个可以用来获取特定玩家所控制的特定实体数量的函数Player_GetEntityCountByUnitType(),但它会对仍在建造的建筑计数(比如,对于三个建好的房屋和一个正在建造的房屋,该函数的返回值是4而不是3),这也是我维护两个计数器table的原因。
  • SCAR provides a function, Player_GetEntityCountByUnitType(), which can retrieve the number of a specific type of entities controlled by a specific player. However, it includes buildings that are still under construction (e.g., for three completed Houses and one under construction, the function returns 4 instead of 3). This is why we maintain two separate counter tables.
  1. 当建造中的建筑被取消建造时,也会触发“建筑被摧毁”这个事件。因此,有必要对“取消建造”和真正的“建筑被摧毁”做出区分。这个区分是通过上下文实现的。
  • When a building under construction is cancelled, it also triggers the GE_EntityKilled event. Therefore, it is necessary to distinguish between “cancelled construction” and actual “building destruction”. This distinction can be achieved through contextual checks.

2.3 代码(The Code)

-----------------------------------------------------------------------
-- Imported Scripts
-----------------------------------------------------------------------

-- Import Utility Scripts
import("cardinal.scar")							-- Contains sfx references, UI templates, and Civ/Age helper functions
import("ScarUtil.scar")							-- Contains game helper functions

-- Import Gameplay Systems
import("gameplay/score.scar")					-- Tracks player score
import("gameplay/diplomacy.scar")				-- Manages Tribute

-- Import Win Conditions
import("winconditions/annihilation.scar")		-- Support for eliminating a player when they can no longer fight or produce units
import("winconditions/elimination.scar")		-- Support for player quitting or dropping (through pause menu or disconnection)
import("winconditions/surrender.scar")			-- Support for player surrender (through pause menu)

-- Import UI Support
import("gameplay/chi/current_dynasty_ui.scar")	-- Displays Chinese Dynasty UI
import("gameplay/event_cues.scar")
import("gameplay/currentageui.scar")

-----------------------------------------------------------------------
-- Data
-----------------------------------------------------------------------

-- Global data table that can be referenced in script functions (e.g. _mod.module = "Mod")
_mod = {
	module = "Mod",
	housePopulationCap = 100,
	tcCount = {},
	houseCount = {},
}

-- Register the win condition (Some functions can be prepended with "Mod_" to be called automatically as part of the scripting framework)
Core_RegisterModule(_mod.module)

-----------------------------------------------------------------------
-- Scripting framework 
-----------------------------------------------------------------------

-- Called during load as part of the game setup sequence
function Mod_OnGameSetup()
	
	
	
end

-- Called before initialization, preceding other module OnInit functions
function Mod_PreInit()
	
	-- Enable Tribute UI
	Core_CallDelegateFunctions("TributeEnabled", true)
	
end

-- Called on match initialization before handing control to the player
function Mod_OnInit()
	
	function Mod_OnConstructionComplete(context)
		if Entity_IsEBPOfType(context.pbg, "town_center") then
			_mod.tcCount[World_GetPlayerIndex(context.player)] = _mod.tcCount[World_GetPlayerIndex(context.player)] + 1
		elseif Entity_IsEBPOfType(context.pbg, "house") then
			_mod.houseCount[World_GetPlayerIndex(context.player)] = _mod.houseCount[World_GetPlayerIndex(context.player)] + 1
			local current_population_cap = Player_GetCurrentPopulationCap(context.player, CT_Personnel)
			Player_SetMaxPopulation(context.player, CT_Personnel, current_population_cap - 10 + _mod.housePopulationCap)
	    end
	end
	
	function Mod_OnBuildingDestroyed(context)
		if context.killer ~= nil and context.victimOwner ~= nil and not context.victimOwner.isEliminated then
			if Entity_IsEBPOfType(Entity_GetBlueprint(context.victim), "town_center") then
				_mod.tcCount[World_GetPlayerIndex(context.victimOwner)] = _mod.tcCount[World_GetPlayerIndex(context.victimOwner)] - 1
			elseif Entity_IsEBPOfType(Entity_GetBlueprint(context.victim), "house") then
				_mod.houseCount[World_GetPlayerIndex(context.victimOwner)] = _mod.houseCount[World_GetPlayerIndex(context.victimOwner)] - 1
				local tc_count = _mod.tcCount[World_GetPlayerIndex(context.victimOwner)]
				local house_count = _mod.houseCount[World_GetPlayerIndex(context.victimOwner)]
				local new_population_cap = 10 * tc_count + _mod.housePopulationCap * house_count
				local max_population_cap = Player_GetMaxPopulationCap(context.victimOwner, CT_Personnel)
				if new_population_cap > max_population_cap then
					new_population_cap = max_population_cap
				end
				local current_population_cap = Player_GetCurrentPopulationCap(context.victimOwner, CT_Personnel)
				Player_SetMaxPopulation(context.victimOwner, CT_Personnel, new_population_cap)
			end
		end
	end
	
	Rule_AddGlobalEvent(Mod_OnConstructionComplete, GE_ConstructionComplete)
	Rule_AddGlobalEvent(Mod_OnBuildingDestroyed, GE_EntityKilled)
	
	local num_players = World_GetPlayerCount()
	for i = 1, num_players do
		_mod.tcCount[i] = Player_GetEntityCountByUnitType(World_GetPlayerAt(i), "town_center")
		_mod.houseCount[i] = Player_GetEntityCountByUnitType(World_GetPlayerAt(i), "house")
	end
	
end

-- Called after initialization is done when game is fading up from black
function Mod_Start()
	
	
	
end

-- Called when Core_SetPlayerDefeated() is invoked. Signals that a player has been eliminated from play due to defeat.
function Mod_OnPlayerDefeated(player, reason)
	
	
	
end

-- When a victory condition is met, a module must call Core_OnGameOver() in order to invoke this delegate and notify all modules that the match is about to end. Generally used for clean up (removal of rules, objectives, and UI elements specific to the module).
function Mod_OnGameOver()
	
	
	
end

3. 不足(Limitations)

与“调整包”不同,“游戏模式”模组需要编写胜利条件逻辑。由于开始创建模组时选择的模板为blank template,所以并未实现任何胜利条件。这意味着,在这个游戏模式中,虽然房屋人口从10变成了100,但你无法取得胜利,除非你退出否则游戏将会一直进行下去。不过你可以继续修改代码,自行添加胜利条件,让它变成一个完整的游戏模式。

Unlike Tuning Packs, a Game Mode mod requires the implementation of victory condition logic. Since the blank template was selected when starting the mod creation process, no victory conditions have been implemented. This means that in this game mode, although the house population cap has been increased from 10 to 100, you cannot achieve victory—the game will continue indefinitely unless you exit. However, you can further modify the code to add victory conditions, turning it into a fully functional game mode.

希望这些对你有用。

Hope this helps.