新版服务端指定阵营生成失败(如11变成2)

生成敌人指定阵营为11但是还是被重置为2(使用了最新的sdk和服务端),并且添加下面代码还是失败

1 Like

Are you using the SpawnEnemy method to do that? Can you share the snippet responsible for spawning enemies in your mod?

There was an issue like this in 0.2.2, and in 0.2.3 we added the following patch:

[HarmonyPatch]
[HarmonyPatchCategory(PatchCategory.Connected)]
public class TamerTeamResetPatch
{
    [HarmonyTargetMethodHint("b1.BUS_TeamIDManageComp", "SetDefaultTeamIDInternal")]
    private static MethodBase TargetMethod()
    {
        return AccessTools.Method("b1.BUS_TeamIDManageComp:SetDefaultTeamIDInternal");
    }

    public static bool Prefix(BGUCharacterCS ___OwnerAsCharacterCS)
    {
        if (!DI.Instance.AreaState.InRoom)
            return true;

        var tamer = DI.Instance.PawnState.GetEntityByTamerMonster(___OwnerAsCharacterCS);

        if (tamer != null)
        {
            var team = tamer.Value.GetTeam().TeamId;
            if (tamer.Value.Pawn == ___OwnerAsCharacterCS && team != 0)
            {
                Logging.LogDebug("Prevented team ID reset for {Guid} with team ID {TeamId}", tamer.Value.GetTamer().Guid, team);
                return false;
            }
        }

        return true;
    }
}

1.patchTamerTeam:using System;
using System.Reflection;
using b1;
using HarmonyLib;
using PreludeLib.Attributes;
using WukongMp.Api;
using WukongMp.Api.Configuration;
using WukongMp.Sdk.Api;
using WukongMp.Sdk.Entities;

namespace TangledWarfare.Core;

///
/// 阻止游戏引擎在怪物 Reset 时把 TeamId 覆盖回默认值。
/// SpawnEnemy 传入的 TeamId 会被 SetDefaultTeamIDInternal 重置为 2,
/// 导致 OnMonsterSpawned 回调中的 TeamId 与我们请求的不一致。
///
/// 旧实现只「跳过」重置、从不主动写入期望值,所以即使 Patch 生效,怪物最终仍是 2(默认)。
/// 新实现改为 Postfix:让原始方法照常执行(重置为 2),随后用 BattleManager 记录的
/// 期望 TeamId(玩家 TeamId + 10)把值写回,保证我们的写入是「最后一次」,从而真正生效。
///
/// 注意:本 Patch 仅用于「队伍上色/归属」的正确性;战斗核心逻辑已经基于
/// GUID 映射、不再依赖 TeamId(见 BattleManager),因此即使本 Patch 失效,玩法依然正确。
///
[HarmonyPatch]
[HarmonyPatchCategory(PatchCategory.Connected)]
public class PatchTamerTeamReset
{
[HarmonyTargetMethodHint(“b1.BUS_TeamIDManageComp”, “SetDefaultTeamIDInternal”)]
private static MethodBase TargetMethod()
{
return AccessTools.Method(“b1.BUS_TeamIDManageComp:SetDefaultTeamIDInternal”);
}

// 引擎内部字段名存在拼写变体(如 OwenerAsCharacterCS)。
// BUS_TeamIDManageComp 类型本身不可访问(保护级别限制),
// 故全部通过字符串反射解析,并在类加载时探测可用的 Owner 字段名,
// 避免 Harmony 因字段名不匹配而静默失效。
private static readonly Func<object, BGUCharacterCS> GetOwner = CreateOwnerGetter();

private static Func<object, BGUCharacterCS> CreateOwnerGetter()
{
    var compType = AccessTools.TypeByName("b1.BUS_TeamIDManageComp");
    if (compType == null)
    {
        Logging.LogError("TamerResetPatch: 找不到类型 b1.BUS_TeamIDManageComp,TeamId 保护失效!");
        return _ => null;
    }

    var candidates = new[]
    {
        "OwnerAsCharacterCS", "OwenerAsCharacterCS", "_ownerAsCharacterCS",
        "OwnerCharacterCS", "ownerAsCharacterCS", "_OwnerAsCharacterCS"
    };
    foreach (var name in candidates)
    {
        var field = AccessTools.Field(compType, name);
        if (field != null)
        {
            Logging.LogInformation("TamerResetPatch: 绑定 Owner 字段成功 -> {0}", name);
            return comp => (BGUCharacterCS)field.GetValue(comp);
        }
    }

    Logging.LogError(
        "TamerResetPatch: 未能在 BUS_TeamIDManageComp 中找到 Owner 字段,TeamId 保护将失效!请检查 SDK/游戏版本是否变更。");
    return _ => null;
}

// 原始方法照常执行(会把 TeamId 重置为 2),我们在其后写回期望值,确保我们的写入是最后一次
public static void Postfix(object __instance)
{
    if (!WukongApi.Sync.InArea)
        return;

    var owner = GetOwner(__instance);
    if (owner == null)
        return;

    foreach (var tamer in WukongApi.Sync.AllTamers)
    {
        if (tamer.Pawn != null && tamer.Pawn == owner && tamer.Guid != null)
        {
            if (BattleManager.DesiredTeamByGuid.TryGetValue(tamer.Guid, out var teamId))
                BattleManager.SetTeamIdField(__instance, teamId);
            return;
        }
    }
}

};

2.spawnEnemy:///


/// 为所有玩家生成出战怪物,并追踪每个实体的 GUID
///

public void SpawnAllDeployedMonsters()
{
if (!WukongApi.Sync.LocalMainCharacter.HasValue) return;

var center = WukongApi.Sync.LocalMainCharacter.Value.Location;
var spawnRadius = \_gameState.Config.SpawnRadius;

\_spawnedGuids.Clear();
\_tamerToMonster.Clear();
\_pendingSpawns.Clear();
\_expectedSpawnCount = 0;

Logging.LogInformation("\[Spawn\] Round={0}  players={1}",
    \_gameState.CurrentRound, \_gameState.ActivePlayerCount);

foreach (var player in \_gameState.ActivePlayers)
{
    if (player.DeployedMonsters.Count == 0) continue;

    Logging.LogInformation("\[Spawn\] Player\[{0}\] deploying {1} monster(s)",
        player.Nickname, player.DeployedMonsters.Count);

    var teamId = player.TeamId;

    for (int mi = 0; mi < player.DeployedMonsters.Count; mi++)
    {
        var monster = player.DeployedMonsters\[mi\];
        var tamerKind = TamerKinds.GetTamerKind(monster.TamerKindName);

        var angle = (float)(\_random.NextDouble() \* Math.PI \* 2);
        var dist = spawnRadius \* 0.5f \* (1f + mi \* 0.3f);
        var spawnPos = new Vector3(
            center.X + (float)(Math.Cos(angle) \* dist),
            center.Y,
            center.Z + (float)(Math.Sin(angle) \* dist)
        );

        try
        {
            \_pendingSpawns.Add((player, monster));
            \_expectedSpawnCount++;
            WukongApi.Sync.SpawnEnemy(tamerKind, spawnPos, 1, teamId);
            Logging.LogInformation("\[Spawn\]   Requested: {0} TeamId={1}", monster.DisplayName, teamId);
        }
        catch (Exception ex)
        {
            Logging.LogError("\[Spawn\] FAILED: {0} - {1}", monster.DisplayName, ex.Message);
        }
    }
}
Logging.LogInformation("\[Spawn\] Done: expectedSpawns={0} (waiting for OnMonsterSpawned events)", \_expectedSpawnCount);

}
3.OnMonsterSpawned:///
/// SDK 异步回调:怪物实体生成后触发,在此完成 GUID->OwnedMonster 映射
/// 由 GameServer 调用(GameLoopSystem 订阅 OnMonsterSpawned 后路由)
///
public void OnMonsterSpawned(ReadyTamer tamer)
{
Logging.LogInformation(“[Spawn] OnMonsterSpawned FIRED: GUID={0} TeamId={1} IsDead={2} pendingCount={3}”,
tamer.Guid, tamer.TeamId, tamer.IsDead, _pendingSpawns.Count);

    // SDK 返回的 TeamId 不可靠(总是2),直接 FIFO 出队
    if (_pendingSpawns.Count == 0) return;

    var pending = _pendingSpawns[0];
    _pendingSpawns.RemoveAt(0);
    _spawnedGuids.Add(tamer.Guid!);
    pending.Monster.OwnerPlayerRawId = pending.Owner.PlayerId.RawValue;
    _tamerToMonster[tamer.Guid] = (pending.Owner, pending.Monster);

    // 覆盖引擎对怪物 TeamId 的强制重置:记录期望 TeamId 并立即写回一次
    var desiredTeam = pending.Owner.TeamId;
    DesiredTeamByGuid[tamer.Guid] = desiredTeam;
    ApplyDesiredTeam(tamer);
    Logging.LogInformation("[Team] GUID={0} 期望 TeamId={1} (玩家Team={2})",
        tamer.Guid, desiredTeam, pending.Owner.TeamId);

    Logging.LogInformation("[Spawn]   → MATCHED: {0}  GUID={1}  Owner={2}  remaining={3}",
        pending.Monster.DisplayName, tamer.Guid, pending.Owner.Nickname, _pendingSpawns.Count);
}