170 - API文档汉化 - 对手

Updated 6 days ago

对手

特殊序列器

特殊序列器(Special Sequencers)本质上是“全局能力”——它们监听与卡牌相同的触发器,并能基于这些触发器执行代码(例如当卡牌被打出或死亡时、在每个回合开始时等)。虽然您可以直接继承SpecialBattleSequencer基类,但根据使用场景不同,建议从现有序列器层级结构中选择继承。您可通过dnSpy查看所有可用序列器,但以下三类需特别关注:

  • SpecialBattleSequencer:所有特殊序列器的基类,默认情况下使用该基类
  • BossBattleSequencer:用于头目战场景
  • Part1BossBattleSequencer:专用于第一幕头目战(钓鱼人、矿工、猎人与毛皮商及莱西)

特殊序列器需直接设置在玩家触发战斗的对应地图节点NodeData实例上,通过字符串值关联序列器实现。使用API中的SpecialSequenceManager类进行自定义设置:

public class MyCustomBattleSequencer : Part1BossBattleSequencer
{
    public static readonly string ID = SpecialSequenceManager.Add(Plugin.PluginGuid, "MySequencer", typeof(MyCustomBattleSequencer));
}

对手系统

在本API语境中,对手(Opponents)可理解为头目单位。Opponent.Type枚举类型用于标识对手类型。需注意某些IDE中可能将该参数类型简示为Type,请勿与System.Type类型混淆。

与其他系统类似,对手是需要自行编写的类,负责处理战斗中的特殊事件。根据对手类型不同,需选择以下基类之一:

  • Opponent:所有对手的基类,通常不应直接继承
  • Part1Opponent:用于莱西小屋的常规战斗
  • Part1BossOpponent:用于莱西小屋的头目战
  • PixelOpponent:用于GBC(即第二章)游戏中的战斗
  • PixelBossOpponent:用于GBC游戏中的头目战
  • Part3Opponent:用于机托邦的常规战斗
  • Part3BossOpponent:用于机托邦的头目战

自定义对手需搭配特殊序列器使用(注:特殊序列器与对手系统为何分离属于设计决策,不作解释)。以下代码演示如何创建关联特殊序列器的对手:

public class MyBossOpponent : Part1BossOpponent
{
    public static readonly Opponent.Type ID = OpponentManager.Add(Plugin.PluginGuid, "MyBossOpponent", MyCustomBattleSequencer.ID, typeof(MyBossOpponent)).Id;
}

注意OpponentManager.Add的第三个参数为字符串类型,其值应与前文特殊序列器示例中设置的ID保持一致。

若需为对手类型指定蓝图(blueprint),可参照先前示例设置蓝图,并在AwakeStartIntroSequence重写方法中进行配置(若未在EncounterData对象中设置):

public override IEnumerator IntroSequence(EncounterData encounter)
{
	encounterData.Blueprint = EncounterManager.AllEncountersCopy.Find(enc => enc.name == "TurnPlan_2");
        List<List<CardInfo>> plan = EncounterBuilder.BuildOpponentTurnPlan(encounterData.Blueprint, difficulty, removeLockedCards);
	base.ReplaceAndAppendTurnPlan(plan);
	yield return QueueNewCards();
        yield return base.IntroSequence(encounter);
}

AI系统

多数情况下无需自定义AI。默认情况下,游戏会评估电脑准备打出的卡牌,通过暴力测试所有可能的卡槽位置,并模拟每个位置下完整回合的结果,最终选择最优解。仅有极少数例外情况。

例如在矿工头目战中,电脑会始终在0号槽位打出骡子(Pack Mule),1号槽位打出郊狼(Coyote)。为确保固定出牌顺序,该战斗使用自定义AI覆盖默认逻辑。

如需实现自定义AI,需创建继承自DiskCardGame.AI的类,并重写SelectSlotsForCards虚方法。该方法即自定义AI逻辑的实现位置。

通过API中的AIManager类注册新AI,并保留返回的字符串ID引用:

public class MyCustomAI : AI
{
    public static readonly string ID = AIManager.Add(Plugin.PluginGuid, "MyAI", typeof(MyCustomAI)).Id;

    public override List<CardSlot> SelectSlotsForCards(List<CardInfo> cards, CardSlot[] slots)
    {
        // 在此实现自定义逻辑
    }
}

实际使用自定义AI需在特殊序列器的BuildCustomEncounter方法中设置:

public class MyCustomBattleSequencer : Part1BossBattleSequencer
{
    public override EncounterData BuildCustomEncounter(CardBattleNodeData nodeData)
    {
        EncounterData data = base.BuildCustomEncounter(nodeData);
        data.aiId = MyCustomAI.ID;
        return data;
    }
}

头目面具系统

创建自定义头目对手时,可能需要修改莱西在战斗中佩戴的面具。API提供完整支持方案:

修改现有面具

可替换《邪恶冥刻》中已存在的任何面具(包括原版或其他开发者添加的面具):

MaskManager.Override("guid", "nameOfNewMask", LeshyAnimationController.Mask.Angler, "pathToTexture");

此示例将钓鱼人面具替换为自定义纹理。注意:该方法会同时修改模型以避免UV映射问题。如需保留原模型,需调用.SetModelType(MaskManager.ModelType.Angler)

添加随机选择面具

添加新面具至随机选择池,当莱西佩戴面具时会随机选择:

MaskManager.AddRandom("guid", "nameOfNewMask", LeshyAnimationController.Mask.Prospector, "pathToTexture");

此示例为矿工战斗添加新面具选项,莱西将在默认面具与此新面具间随机选择。可添加数量无限制。

添加自定义面具

基础添加方法:

MaskManager.Add("guid", "nameOfNewMask", "pathToTexture");

添加自定义模型

ResourceLookup resourceLookup = new ResourceLookup();
resourceLookup.FromAssetBundle("pathToAssetBundle", "prefabNameInsideBundle");
MaskManager.ModelType modelType = MaskManager.RegisterPrefab("guid", "nameOfModel", resourceLookup);

var mask = MaskManager.Add("guid", "nameOfMask");
mask.SetModelType(modelType);

佩戴面具指令

强制莱西佩戴指定面具(适用于自定义头目战流程):

LeshyAnimationController.Instance.PutOnMask(LeshyAnimationController.Mask.Woodcarver, false);

添加面具行为

public class Plugin : BaseUnityPlugin
{
    private void Awake()
    {
        MyCustomMask.Setup();        
    }
}
public class MyCustomMask : MaskBehaviour
{
    public static LeshyAnimationController.Mask ID;
    
    public static void Setup()
    {
        var mask = MaskManager.Add("guid", "nameOfNewMask", "pathToTexture");
        mask.SetMaskBehaviour(typeof(MyCustomMask));
        ID = mask.ID;
    }
}