基础

参考1: 非官方文档

参考2: 有关保存数据的讨论

本文只是为了记录自己看到和使用的一些类的大致用途和一些主要方法,具体请使用dnspy等工具进行查看。

dnspy的分析功能很好用。还有程序集搜索功能

  • settlement: settlement是在战役地图中的村庄,城镇,城堡
  • MobileParty: 军队
    • TroopRoster: 军队花名册
    • PrisonRoster: 囚犯花名册
    • TickAi(): 最终决定ai的函数,下面的aibehavior不一定是最终的ai
    • Morale: 士气,通过DefaultPartyMoraleModel决定
    • DefaultBehavior: 当前的ai
    • ShortTermBehavior: 短期ai,用来设置追逐和逃跑行为
    • ItemRoster: 物品清单。
    • InventoryCapacity: 容量,通过InventoryCapacityModel得到
    • Tier: 部队的等阶,最高是6阶,英雄是0阶
    • GetBestInitiativeBehavior(): 里面有一些模拟战的行为
  • Army: 军团,也就是需要凝聚力的那个
    • HourlyTick(): 每小时触发的函数,从中可以看出Army的行为相对固定,也没有ShortTermBehavior
  • Roster: 队伍花名册
    • TotalManCount: 所有人
    • TotalWounded: 所有受伤的人
    • TotalHeroes: 所有英雄
    • 遍历成员信息可以直接使用foreach(TroopRosterElement i in troop_roster),实现了一个接口。
  • Campaign: 战役系统管理类。
    • MainParty: 玩家
    • RealTick(): 每次tick触发的函数
    • CurrentTime: 当前时间(转化为小时的)
  • CampaignTime: 时间类
    • Now: 当前的时间(没有转化为小时)
    • IsDayTime: 是否是白天
  • MapVM: 地图的一些属性,没有仔细看
  • MapScreen: 地图类,里面有鼠标指针和触发指针的一些事件,调用了InformationManager.AddTooltipInformation。但是现在没办法在toolTip中加东西(都是private,真无语)
  • Mission: 在场景里面的战斗。
    • GetAgentslnRange: 获得士兵的观察范围
    • AddMissileAux: 射击时调用的一个函数
    • FindPositionWithBiggestSlopeTowardsDirectionInSquare:用于找合适的战略位置
    • GetMedianPositionOfAgents
    • GetClosetFleePosition
    • SpawnAttachedWeaponOnCorpse: 从尸体上获得武器
    • DecideAgentHitParticles
    • DecideWeaponCollisionReaction
    • UpdateMomentumRemaining: 剩余的动量
    • SpawnTroops(): 生成个体
    • SpawnFormation(): 生成阵营。
  • SandBoxMissions: 打开各种战役的函数(重要)
    • OpenBattleMission(): 开始野战。里面有Battle加载的MissionBehavior
    • 还有许多各种各样的战役,MissionBehavior都是在这里加载的,有一个BattleAiBehaivor(可能有误)里面还加载了一些Agent的Component
  • MissionState:
    • HandleOpenNew: 开始新mission使用的函数
    • AddDefaultMissionBehaviorsTo: 加载mission行为
  • PartyScreenManager: 管理部队界面。
  • OrderController: 部队命令管理。主要是SetOrder决定了按键之后的处理。

Model && Behavior

Model指的是游戏中使用的一些计算模型,例如食物消耗,攻击力计算等模型。这些model会在官方游戏代码中调用。

Behavior是在游戏中触发某些事件时执行的函数。Campaign中可以执行的Behavior可以搜索Campaign类中的AddBehavior函数,这些Behavior的具体实现大多在Campaign.Sandbox.某某Bahavior中

替换官方model和增加Behavior

在继承了Submodule的类中
protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
{
bool flag = game.GameType is Campaign;
if (flag)
{
//将Model和Behavior加入到Campaign中
CampaignGameStarter campaignGameStarter = (CampaignGameStarter)gameStarterObject;
campaignGameStarter.AddModel(new YourModel());//增加model
IList<GameModel> list = starter.Models as IList<GameModel>;
bool flag = list != null;
if (flag)//替换model
{
for(int i=0; i<list.Count; i++)
{
if(list[i] is DefaultxxxModel)
{
list[i] = new replacemodel();
}
}
}
campaignGameStarter.AddBehavior(new YourBehavior());//增加Behavior
}

}

在继承了Behavior的类中
注册Behavior
public override void RegisterEvents()
{
CampaignEvents.HourlyTickEvent.AddNonSerializedListener(this, new Action(this.HourlyEvent));
}

触发过程

一个报错
at TaleWorlds.CampaignSystem.Army.ThinkAboutTravelingToAssignment()
at TaleWorlds.CampaignSystem.Army.HourlyTick(MBCampaignEvent campaignevent, Object[] delegateparams)
at TaleWorlds.CampaignSystem.MBCampaignEvent.RunHandlers(Object[] delegateParams)
at TaleWorlds.CampaignSystem.MBCampaignEvent.CheckUpdate()
at TaleWorlds.CampaignSystem.CampaignEvents.SignalPeriodicEvents()
at TaleWorlds.CampaignSystem.Campaign.Tick(Single dt)
at TaleWorlds.CampaignSystem.Campaign.RealTick(Single realDt)
at TaleWorlds.CampaignSystem.MapState.OnMapModeTick(Single dt)
at TaleWorlds.Core.GameStateManager.OnTick(Single dt)
at TaleWorlds.Core.Game.OnTick(Single dt)
at TaleWorlds.Core.GameManagerBase.OnTick(Single dt)
at TaleWorlds.MountAndBlade.Module.OnApplicationTick_Patch1(Module this, Single dt)

Model

  • DefaultPartyMoraleModel: 士气计算模型。主要是通过GetEffectivePartyMorale()函数得到士气
  • DefaultPartySpeedCalculatingModel: 部队速度计算模型
  • DefaultCombatSimulationModel: 模拟战计算类
  • SandboxAgentStatCalculateModel: mission中士兵状态(速度生命等)模型。

Behavior

  • HourlyTickBehavior(): 每小时触发一次
  • HourlyTickPartyBehavior(): 以部队为单位每小时触发
  • OnPartySizeChangedEvent():
  • OnNewGameCreatedEvent9(): 可以在这里面做一些初始化内容,有一些在Submodule中无法完成。
  • OnBeforeSaveEvent():
  • OnGameLoadedEvent():
  • DefaultNotificationsCampaignBehavior: 负责左下角提示的和上方提示的
  • FollowAgentBehavior: 跟随
  • DefaultLogsCampaignBehavior: addlog最终调用的是displaymessage。

    AiBehavior

这个在Taleworlds.CampaignSystem.Sanbox.CampaignBehaviors.AiBehaviors中

  • ThinkBehavior: 这是总的ai计算模型(但是限制很多,例如GotoPoint在这里设置是没用的,并且之后还要受MobileParty中的TickAi()调节)
    • 其中调用了AiHourTick(),也就是说会调用所有申请了AiHourTickEvent的函数。
    • 它是通过比较AiBehaviorScores来确定到底是进行哪种Ai活动的。也就是说想要更改Ai的话要自己确定这个Score是多少。
  • EngageBehavior、PatrollingBehavior等: 大致结构是通过一些限制条件限制进行这些活动的情况。然后通过计算Score的函数得到最终得数。(得分好像并不大,一般应该在0到1之间)

关于保存

关于上面参考中给出的使用json保存的方式。有一个问题就是无法保存骑砍官方类的内容。所以推荐还是使用Attribute的方式,但是如果是Dictionary的数据无法保存可以试一下在DefineContainerDefinitions()中添加对要保存的类的List定义和Dictionary定义。

并且定义时不仅要定义自己创建的类。里面所有需要保存的成员变量都要确保它已经定义了。例如可能Dictionary都可能没有定义。为了检查是否可以正常保存建议直接打开游戏保存测试一下,如过出现Save error则需要检查定义。

GUI

  • GauntletLayer: 这个应该是显示的层
    • LoadMovie(“name”, datasource): name是自己写的xml,datasource是…VM.
  • Widget: 各种各样的工具,例如TextWiget其实就是文本,ListPanel是列表容器等等。使用这些widget可以填充面板中的内容。

xxxscreen.xml

这里指的xml就是上面LoadMovie中的xml。可以随便打开一个GUI/Prefabs文件夹看一看结构。一般都是一种Widget,然后再是子节点,子节点中又有各种各样的容器或者工具

<Prefab>
<Window>
<Widget Id="tired_party_information_screen" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" >
<Children>
<Standard.Background/>

<ListPanel Id="MainSectionListPanel" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" MarginTop="164">
<Children>
<Widget Id="CenterPanel" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent">
<Children>
<ScrollablePanel Id="ScrollablePanel" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" HorizontalAlignment="Center" VerticalAlignment="Center" AutoHideScrollBarHandle="true" AutoHideScrollBars="false" ClipRect="MyClipRect" InnerPanel="MyClipRect\TabControl" VerticalScrollbar="..\..\VerticalScrollbar">
<Children>
<Widget Id="MyClipRect" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" HorizontalAlignment="Center" Brush.GlobalAlphaFactor="0.75" ClipContents="true">
<Children>
<TabControl Id="TabControl" WidthSizePolicy="StretchToParent" HeightSizePolicy="CoverChildren">
<Children>
<TiredPartyCategory Id="all_information" DataSource="{AllInformation}"/>
<TiredPartyCategory Id="hidden_information" DataSource="{HiddenInformation}"/>
<TiredPartyCategory Id="important_infomation" DataSource="{ImportantInformation}"/>
</Children>
</TabControl>
</Children>
</Widget>
<Widget WidthSizePolicy="StretchToParent" HeightSizePolicy="Fixed" SuggestedHeight="150" VerticalAlignment="Bottom" Sprite="StdAssets\Popup\scrollable_field_gradient" IsEnabled="false" />
</Children>
</ScrollablePanel>
</Children>
</Widget>


<ScrollbarWidget Id="VerticalScrollbar" WidthSizePolicy="Fixed" HeightSizePolicy="StretchToParent" SuggestedWidth="8" HorizontalAlignment="Right" VerticalAlignment="Center" MarginTop="26" MarginBottom="85" AlignmentAxis="Vertical" Handle="VerticalScrollbarHandle" MaxValue="100" MinValue="0">
<Children>
<Widget WidthSizePolicy="Fixed" HeightSizePolicy="StretchToParent" SuggestedWidth="4" HorizontalAlignment="Center" Sprite="MPLobby\CustomServer\lobby_slider_bed" Brush.AlphaFactor="0.4" />
<ImageWidget Id="VerticalScrollbarHandle" WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="8" SuggestedHeight="10" HorizontalAlignment="Center" Brush="FaceGen.Scrollbar.Handle" />
</Children>
</ScrollbarWidget>
</Children>
</ListPanel>

<Standard.TopPanel Parameter.Title="@Title">
<Children>
<ListPanel WidthSizePolicy="CoverChildren" HeightSizePolicy="Fixed" SuggestedHeight="128" HorizontalAlignment="Center" VerticalAlignment="Bottom" LayoutImp.LayoutMethod="HorizontalLeftToRight">
<Children>
<OptionsTabToggle DataSource="{AllInformation}" Parameter.ButtonBrush="Header.Tab.Left" Parameter.TabName="all_information" />
<OptionsTabToggle DataSource="{HiddenInformation}" PositionYOffset="2" Parameter.ButtonBrush="Header.Tab.Center" Parameter.TabName="hidden_information" />
<OptionsTabToggle DataSource="{ImportantInformation}" PositionYOffset="2" Parameter.ButtonBrush="Header.Tab.Center" Parameter.TabName="important_infomation" />
</Children>
</ListPanel>

</Children>
</Standard.TopPanel>

<Standard.DialogCloseButtons Parameter.CancelButtonAction="ExecuteCancel" Parameter.CancelButtonText="@CloseText" Parameter.DoneButtonAction="ExecuteDone" Parameter.DoneButtonText="@SaveText" />
</Children>
</Widget>
</Window>
</Prefab>

首先和其他语言中的GUI相同,都是window->panel->widget结构。可以看ScrollablePanel下的children表示scrollablepanel容器有这些子结构,而<>中的是这个Scrollablepanel的性质。InnerPanel是内部的面板,VerticalScrollbar是滚动条。我们可以单独对滚动条的格式进行设置。

ScrollablePanel下有一个TiredPartyCategory,这是自己写的另外一个xml,里面是每一个标签页的具体结构。

DataSource指的是你加载的VM中的变量,我这里的AllInformation实际上是另外一个VM。然后在这个VM中又有许多变量,可以通过{变量名}的方式进行引用。

@SaveText在最下方,是最外层的VM(LoadMovie中作为参数)中的一个属性,想使用这些变量前面都要加上一个@

我们进场可以看到[DataSourceProperty]的Attribute。格式为

[DataSourceProperty]
public string Name
{
get
{
return this._name;
}
set
{
bool flag = value != this._name;
if (flag)
{
this._name = value;
base.OnPropertyChanged("Name");
}
}
}

这些变量也是使用@变量名的方式进行使用的,它的区别是可以动态进行变更,每次数据改变时都会调用OnPropertyChanged来对数据进行刷新。注意这里指的变量名是OnPropertyChanged中的名字。

如果参数是MBBindingList<…VM>类型的,可以使用ListPanel,将所有条目都列举出来,格式为

<ListPanel DataSource="{Texts}" WidthSizePolicy="StretchToParent" HeightSizePolicy="CoverChildren"
>
<ItemTamplate>
<Widget .. >
<Children>
your information
</Children>
</Widget>
</ItemTamplate>
</ListPanel>

XML

如果没有特殊说明可以直接在MB文件夹里搜索得到

  • action_sets.xml: 里面有各种各样的动作,使用ActionIndexCache.Create(“name”)可以创建动作,使用agent.SetActionChannel可以设置动作。有死亡,坐,躺,蹲,站等一系列动作。注意有些动作如死亡是不能重新设置的,这时候只有使用agent.SetActionSet()对动作重置。
  • HotKeyCategory.xml: 这个在document/Documents/Mount and Blade II Bannerlord/Configs文件夹中。里面有一系列的热键,通过这些热键可以查找这些热键对应功能位置。
  • Prefabs: 里面有一系列的预制件(也就是已经做好了的物体,只需要调整位置放到地图中就好了),将预制件放到地图中的函数是Mission.Current.CreateMissionObjectFromPrefab(environment_things[0], matrix_frame);第一个参数是预制件的名字,第二个参数时摆放物体的位置及朝向。预制件的名字可以在各个Prefabs文件夹中的xml文件中找到(也可以在Sceneobj中的preferance.txt文件夹中找到该场景所使用的预制件名字)

Harmony

官方文档

教程1

教程2

内部类

如果想要修改内部类中的方法,首先要获得这个方法。例如:

MethodInfo method = typeof(MissionAgentSpawnLogic).Assembly.
GetType("TaleWorlds.MountAndBlade.MissionAgentSpawnLogic+MissionSide").
GetMethod("SpawnTroops", BindingFlags.Instance | BindingFlags.Public);

注意:内部类用+号链接,而不是.

之后可以使用手动patch

MethodInfo prefix = typeof(spawn_logic_patch).
GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public);
harmony.Patch(method, new HarmonyMethod(prefix), null, null, null);

因为内部类类型不可访问,所以只能使用Object __instance表示这个实例,访问内部类中参数的方法是.

Type t = typeof(MissionAgentSpawnLogic).Assembly.
GetType("TaleWorlds.MountAndBlade.MissionAgentSpawnLogic+MissionSide");

((IMissionTroopSupplier)AccessTools.Field(t,"_troopSupplier").GetValue(__instance))