using CommonAI.Zone; using CommonAI.Zone.Instance; using CommonAI.ZoneServer; using CommonLang; using CommonLang.ByteOrder; using CommonLang.Concurrent; using CommonLang.IO; using CommonLang.IO.Attribute; using CommonLang.Property; using CommonLang.Protocol; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using XmdsCommon.Message; using XmdsCommon.Plugin; using XmdsCommonServer.Message; using XmdsCommonServer.Plugin; using XmdsServerNode.Node.Interface; using CommonAIServer.Node; using CommonAI.ZoneClient; using XmdsCommonServer.Plugin.Units; using XmdsCommonServer.Plugin.Scene; using CommonAI.Zone.ZoneEditor; using System.Dynamic; using XmdsServerNode.CheatingDeath; using Pomelo; using System.Web.Helpers; using static CommonAI.XmdsConstConfig; using CommonAI.Zone.Helper; using CommonAI.Data; namespace XmdsServerNode.Node { public class ServerZoneNode : CommonAIServer.Node.ZoneNode { private GameOverEvent mGameOver = null; public bool PauseOnNoPlayer { get; set; } public string UUID { get { return Zone.UUID; } } public bool IsGameOver { get { return mGameOver != null; } } public ICEZoneSession Callback; private readonly string BindGameServerId; public ServerZoneNode(string gameServerId) : base(ZoneNodeManager.Templates, ZoneNodeManager.NodeConfig) { this.BindGameServerId = gameServerId; PauseOnNoPlayer = false; } public string GetBindGameSrvId() { return this.BindGameServerId; } /// <summary> /// 房间初始化 /// </summary> public void Start(string instanceID, int scene_id, string monsterHard, byte allowAutoGuard, bool calPKValue, int average_level, double monsterAddPropPercentWithFloor, double monsterAddPropRatioWithLv, int killInterval, int killMax, int killMaxCoolTime, bool usespaceDiv, CommonAI.Data.SceneType scenetype, GSCreateAreaData areaData, CommonAI.XmdsConstConfig.AreaType areaType, bool isteam = false, bool canRiding = true) { try { var sd = base.DataRoot.LoadScene_Server(scene_id, true, false); sd.DefaultUnitLevel = average_level; sd.MonsterAddPropPercentWithFloor = monsterAddPropPercentWithFloor; sd.MonsterAddPropRatioWithLv = monsterAddPropRatioWithLv; sd.canRiding = canRiding; sd.killInterval = killInterval; sd.killMax = killMax; sd.killMaxCoolTime = killMaxCoolTime; sd.isTeam = isteam; sd.sceneType = scenetype; XmdsSceneProperties zsp = sd.Properties as XmdsSceneProperties; XmdsServerSceneData sceneData = new XmdsServerSceneData(); sceneData.AllowAutoGuard = allowAutoGuard; sceneData.CalPKValue = calPKValue; sceneData.SceneHard = monsterHard; sceneData.UsespaceDiv = usespaceDiv; sceneData.CurAreaType = areaType; zsp.ServerSceneData = sceneData; base.Start(sd, areaData, this.BindGameServerId); base.Zone.UUID = instanceID; } catch (System.Exception e) { throw new Exception(string.Format("scene {0} start error : {1}", scene_id, e.ToString()), e); } } public void QueueTaskAsync(System.Action<EditorScene> action) { base.QueueTask(() => { action(base.Zone); }); } public void QueueTaskAsync(System.Action action) { base.QueueTask(() => { action(); }); } public void SendToGameServer(string name, Object param) { if (Callback == null) { this.Callback = IceManager.instance().getCallback(this.BindGameServerId); log.Warn("SendToGameServer callback - 1 - null : " + this.BindGameServerId + ", name:" + name + ", data:" + param); } string json = null; if (Callback != null && Callback.callback != null) { try { json = Json.Encode(param); Callback.callback.eventNotify(name, json); } catch (Exception err) { if(json == null) { log.Error("SendToGameServer序列化就异常了,放弃记录:" + name + ", err:" + err.Message, err); } else { Callback.notifyFailData.Enqueue(new ICENotifyData(name, json)); log.Error("SendToGameServer: " + name + ", err:" + err.Message, err); EventNotifyFail(); } } } else { log.Warn("SendToGameServer callback - 2- null : " + this.BindGameServerId + ", name:" + name + ", data:" + param); } } private void EventNotifyFail() { try { if (Callback.callback.ice_getCachedConnection() == null) { Callback.failTimes++; if (Callback.fastSession != null/* && Callback.failTimes >= 3*/) { Callback.fastSession.doClose(); Callback.failTimes = 0; log.Warn("EventNotifyFail 重置连接: " + Callback.fastSession.GetDescribe()); } else { log.Error("EventNotifyFail fastSession null:" + Callback.callback.ToString()); } } } catch (System.Exception e) { log.Error("EventNotifyFail catch:" + e); } } //------------------------------------------------------------------------------------------------------------ #region _主线程内回调_线程安全_ public override bool ZoneUpdate(int intervalMS) { if (PauseOnNoPlayer && base.PlayerCount == 0) { intervalMS = 0; } return base.ZoneUpdate(intervalMS); } //过滤游戏场景推送出的消息// protected override bool OnMessageHandlerFromInstanceZone(Event e) { //战斗服To游戏服. if (e is MessagePlayerEventB2R) { e.sender = null; var msg = e as MessagePlayerEventB2R; msg.eventName = e.GetType().Name; SendToGameServer("playerEvent", e); return true; } else if (e is MessageZoneEventB2R) { e.sender = null; var msg = e as MessageZoneEventB2R; msg.eventName = e.GetType().Name; SendToGameServer("zoneEvent", e); return true; } else if(e is MessageMapEventB2R) { var msg = e as MessageMapEventB2R; msg.eventName = e.GetType().Name; SendToGameServer("mapNotify", e); return true; } else if (e is GameOverEvent) { if (mGameOver == null) { mGameOver = e as GameOverEvent; var eventParam = new { eventName = "gameOver", instanceId = this.UUID, winForce = mGameOver.WinForce }; SendToGameServer("areaEvent", eventParam); } } return false; } protected override void OnEndUpdate() { base.ForEachPlayers((c) => { var zone_client = c.Client as ZoneNodePlayer; zone_client.Flush(); }); } protected override void OnPlayerEntered(PlayerClient client) { base.OnPlayerEntered(client); CheatingDeath.CheatingDeathManager.OnPlayerEnter(client.Actor); //进去默认自动战斗的情况下 if(this.SceneData.Properties.GetAutoFightFlag() == 3 && !client.Actor.IsGuard) { client.Actor.doEnterAutoFight(); } client.Actor.Virtual.doEvent(JSGCustomOpType.UpdateAutoBattleFlag); } protected override void OnPlayerLeft(PlayerClient client) { base.OnPlayerLeft(client); this.KillPlayerAOIObjects(client); } //干掉所有和玩家同一位面单位// protected void KillPlayerAOIObjects(PlayerClient client) { var player = client.Actor; if (player != null && player.AoiStatus != null) { var src_aoi = player.AoiStatus as XmdsPlayerAOI; if (src_aoi.Owner == player) { src_aoi.Cleanup(); } } } #endregion //------------------------------------------------------------------------------------------------------------ #region _游戏服控制玩家_ /// <summary> /// 单位进入场景 /// </summary> /// <param name="player"></param> /// <param name="enter"></param> public void OnPlayerEnter(IPlayer player, PlayerEnterRoomS2R enter, Action<PlayerClient> callback, Action<Exception> callerror) { if (enter == null) { // 建立绑定关系 // var zone_player = new ZoneNodePlayer(this, player); base.PlayerEnter(zone_player, null, 0, 0, 0, null,0, callback, callerror); } else { // 有出生点则放入出生点 // CommonLang.Vector.Vector2 enterPos = enter.Pos; if (enterPos.X == 0 && enterPos.Y == 0) { enterPos = null; } if (!string.IsNullOrEmpty(enter.FlagName)) { InstanceFlag p = Zone.getFlag(enter.FlagName); if(p != null) { if (p is ZoneRegion) { // Region随机取一个坐标 var pos = (p as ZoneRegion).getRandomPos(Zone.RandomN); enterPos.SetX(pos.X); enterPos.SetY(pos.Y); } else { enterPos = p.Pos; } } } UnitInfo temp = ZoneNodeManager.Templates.Templates.getUnit(enter.UnitData.UnitTemplateID); if (temp != null) { temp = temp.Clone() as UnitInfo; temp.UType = UnitInfo.UnitType.TYPE_PLAYER; XmdsUnitProperties tprop = (temp.Properties as XmdsUnitProperties); //临时代码. //默认无限等待. temp.RebirthTimeMS = int.MaxValue; // 插入玩家能力属性 // tprop.ServerData = ((enter.UnitData.UnitPropData) as XmdsUnitProperties).ServerData; // 建立绑定关系 // var zone_player = new ZoneNodePlayer(this, player); base.PlayerEnter(zone_player, temp, enter.UnitData.Force, enter.UnitData.alliesForce, tprop.ServerData.BaseInfo.UnitLv, enterPos,enter.direction, callback, callerror); } else { throw new Exception("Unit template not exist : " + enter); } } } /// <summary> /// 单位离开场景 /// </summary> /// <param name="player"></param> public void OnPlayerLeave(InstancePlayer player, Action<PlayerClient> callback, Action<Exception> callerror, bool keep_object = false) { if (player != null) { base.PlayerLeave(player, callback, callerror, keep_object); } } /// <summary> /// 玩家网络状况改变 /// </summary> /// <param name="player"></param> /// <param name="state"></param> public void OnPlayerNetStateChanged(IPlayer player, string state) { QueueTask(() => { var zc = player.BindingObject; if (zc != null) { zc.NetStateChanged(state); } }); } /// <summary> /// 单位收到来自客户端的消息 /// </summary> /// <param name="client"></param> /// <param name="message"></param> public void OnPlayerReceivedMessage(IPlayer player, byte[] msg) { QueueTask(() => { //if (doDecodePing(player, msg)) //{ // return; //} //else { var zone_player = player.BindingObject; if (zone_player != null) { zone_player.Recv(msg); } } }); } public void ReceiveMsgR2B(IPlayer client, object status) { QueueTask(() => { var zc = client.BindingObject; if (zc != null && zc.BindingActor != null) { XmdsVirtual zv = (XmdsVirtual)zc.BindingActor.Virtual; zv.ReceiveMsgR2B(status); } }); } /// <summary> /// 玩家复活,分原地复活和复活点复活 /// </summary> /// <param name="client"></param> /// <param name="status"></param> public void OnHelpPlayerRebirth(IPlayer player, IPlayer revivePlayer, int time) { QueueTask(() => { var p = player.BindingObject; var r = revivePlayer.BindingObject; if (p != null && r != null && p.BindingActor != null && r.BindingActor != null) { PlayerSaveEventR2B r2b = new PlayerSaveEventR2B(); XmdsVirtual Decedent = (XmdsVirtual)(r.BindingActor).Virtual; XmdsVirtual Saver = (XmdsVirtual)(p.BindingActor).Virtual; r2b.Decedent = Decedent.mUnit; r2b.Saver = Saver.mUnit; r2b.SaveTime = time; Saver.ReceiveMsgR2B(r2b); } }); } public void OnPlayerReadyR2B(IPlayer client) { QueueTask(() => { InstancePlayer out_player; PlayerClient out_client; if (GetPlayerAndClient(client.PlayerUUID, out out_player, out out_client)) { if (out_player != null && out_client != null) { (out_player as XmdsInstancePlayer).PlayerReady(); } } }); } public void OnPlayerReviveTeamInfoEventR2B(IPlayer client, TeamInfoEventR2B team) { QueueTask(() => { InstancePlayer out_player; PlayerClient out_client; if (GetPlayerAndClient(client.PlayerUUID, out out_player, out out_client)) { if (out_player != null && out_client != null) { (out_client as BindingPlayerClient).OnPlayerReviveTeamInfoEventR2B(team); var zv = (XmdsVirtual)out_player.Virtual; zv.ReceiveMsgR2B(team); } } }); } public void OnPlayerAddHPByItem(List<IPlayer> notifyPlayers, IPlayer client, int addHP) { QueueTask(() => { var nodePlayer = client.BindingObject as ZoneNodePlayer; if (nodePlayer != null) { XmdsVirtual zv = (nodePlayer.BindingActor.Virtual) as XmdsVirtual; int finalHp = zv.AddHPByItem(addHP); ZoneNodePlayer temp = null; foreach (IPlayer p in notifyPlayers) { temp = p.BindingObject as ZoneNodePlayer; XmdsVirtual notifyPlayer = null; if (temp != null) { notifyPlayer = (temp.BindingActor.Virtual) as XmdsVirtual; notifyPlayer.SendHPChangeMessage(zv.mUnit.ID, finalHp); } } } }); } public float GetDropRange(int items) { if(items >= 10) { return 5.0f; } if (items >= 7) { return 4.0f; } else if(items > 4) { return 3.6f; } return 2.8f; } public float GetDropOffect(float dropRange) { int index = (this.Zone.RandomN.Next() % 2 == 0) ? 1 : -1; return dropRange * this.Zone.RandomN.Next(0, 100) / 100f * index; } public void AddDropItem(float x, float y, dynamic items) { QueueTask(() => { //阿基米德螺线<-装B用,实际不需要这样 //20170621wuyonghui修改为随机掉落位置算法 float dropRange = GetDropRange(((DynamicJsonArray)items).Length);// 掉落半径范围为5 bool addSuc = false; foreach (dynamic obj in items) { XmdsCommon.Message.DropItem di = new XmdsCommon.Message.DropItem(); di.FileName = (string)obj.showId; di.FreezeTime = (int)obj.freezeTime; di.Name = (string)obj.name; di.ObjID = (string)obj.id; di.ProtectTime = (int)obj.protectTime; di.Qty = (int)obj.groupCount; di.Quality = (int)obj.qColor; di.TemplateID = (int)obj.itemTypeId; di.TTL = (int)obj.lifeTime; di.PlayerUUID = new List<string>(); di.Mode = (byte)obj.distributeType; di.OriginX = x; di.OriginY = y; bool showLifeTimes = (bool)obj.showLifeTime; di.code = (String)obj.code; di.bindPlayerId = obj.bindPlayerId; di.createTime = (int)(CommonLang.CUtils.S_LOCAL_TIMESTAMPMS/1000); if (obj.PlayerUUID != null) { foreach (dynamic playeruuid in obj.PlayerUUID) { di.PlayerUUID.Add(playeruuid); } } di.IconName = (string)obj.IconName; ItemTemplate templateTemp = Zone.Templates.getItem(di.TemplateID); if(templateTemp == null) { log.Warn("AddDropItem没有对应单位信息:" + di.TemplateID + ", " + di.code); return; } ItemTemplate template = templateTemp.Clone() as ItemTemplate; //每个物品类型可能有多个显示模型,所以按游戏服传过来的策划配置模型名称 template.FileName = di.FileName; //修改模板的部分数据,由于模板是共享的,所以必须每次都把以下数据完整覆盖 template.LifeTimeMS = di.TTL; template.GotCoolDownTimeMS = di.FreezeTime; template.DropForAll = true; template.GotOnUse = true; template.showLifeTime = showLifeTimes; //template.Pickable = false;//这里注释掉,直接用模板里的值,不要改变 XmdsItemProperties props = template.Properties as XmdsItemProperties; props.ItemType = XmdsItemProperties.XmdsItemType.Equip; float x1 = this.GetDropOffect(dropRange);//获得X偏移量 float y1 = this.GetDropOffect(dropRange);//获得Y偏移量 if(Zone.TryTouchMap(null,x+x1,y+y1)) { x1 = 0; y1 = 0; } //System.Console.WriteLine("掉落物:" + di.Name + ", 偏移:" + x1 + ", " + y1 + "----" + (x + x1) + ", " + (y + y1)); AddItemEvent aie; XmdsDropableInstanceItem drop_item = Zone.AddItem(template, obj.name, x + x1, y + y1, 0, 0, obj.name, out aie, null,1); if(drop_item == null || aie == null) { log.Warn("添加bs掉落物失败:" + template.ID + ", " + template.Name + ", " + this.SceneID); return; } aie.Sync.ExtData = di; if(drop_item != null) { addSuc = true; } } if (addSuc) { base.RecordDropItem(); } }); } public void FinishPickItem(IPlayer player, string itemIcon, int quality, int num) { QueueTask(() => { var p = player.BindingObject; if (p != null && p.BindingActor != null) { PlayerGotItemB2C b2c = new PlayerGotItemB2C(); XmdsVirtual zv = (XmdsVirtual)(p.BindingActor).Virtual; b2c.IconName = itemIcon; b2c.Qty = num; b2c.Quality = quality; zv.mUnit.queueEvent(b2c); } }); } #endregion //------------------------------------------------------------------------------------------------------------ #region _PingPong_ //private static readonly int MSG_TYPE_ID_PING = PropertyUtil.GetAttribute<MessageTypeAttribute>(typeof(Ping)).MessageTypeID; //private static readonly int MSG_TYPE_ID_PONG = PropertyUtil.GetAttribute<MessageTypeAttribute>(typeof(Pong)).MessageTypeID; //private ArraySegment<byte> pong_data = new ArraySegment<byte>(new byte[8], 0, 8); //public bool doDecodePing(IPlayer player, byte[] data) //{ // int pos = 0; // int msg_id = LittleEdian.GetS32(data, ref pos); // if (msg_id == MSG_TYPE_ID_PING) // { // uint time = LittleEdian.GetU32(data, ref pos); // pos = 0; // LittleEdian.PutS32(pong_data.Array, ref pos, MSG_TYPE_ID_PONG); // LittleEdian.PutU32(pong_data.Array, ref pos, time); // player.SendToClient(pong_data); // return true; // } // return false; //} #endregion //------------------------------------------------------------------------------------------------------------ protected override PlayerClient CreatePlayerClient(CommonAIServer.Node.Interface.IPlayer client, InstancePlayer actor) { var syncIn = base.Config.CLIENT_SYNC_OBJECT_IN_RANGE; var syncOut = base.Config.CLIENT_SYNC_OBJECT_OUT_RANGE; var zpp = base.SceneData.Properties as XmdsSceneProperties; if (zpp.AOIMinRange > 0 && zpp.AOIMaxRange > 0) { syncIn = zpp.AOIMinRange; syncOut = zpp.AOIMaxRange; } return new BindingPlayerClient(client, actor, this, syncIn, syncOut); } //------------------------------------------------------------------------------------------------------------ public class ZoneNodePlayer : CommonAIServer.Node.Interface.IPlayer { private static AtomicInteger s_alloc_object_count = new AtomicInteger(0); /// <summary> /// 分配实例数量 /// </summary> public static int AllocCount { get { return s_alloc_object_count.Value; } } public readonly ZoneNode node; public readonly IPlayer player; private ZoneNodeCodec mCodec = new ZoneNodeCodec(ZoneNodeManager.MessageFactory); private AtomicReference<PlayerClient> binding_player = new AtomicReference<PlayerClient>(null); private Action<object> handler; private PackEvent mSendingQueue = new PackEvent(); private AtomicInteger mSendingSequenceNo = new AtomicInteger(0); private string display_name = ""; //private Queue<object> mPreQueue = new Queue<object>(); internal ZoneNodePlayer(ZoneNode node, IPlayer player) { s_alloc_object_count++; this.node = node; this.player = player; this.player.BindingObject = this; this.Connected = true; } ~ZoneNodePlayer() { s_alloc_object_count--; } public IPlayer SessionPlayer {get { return player; }} public PlayerClient BindingPlayer { get { return binding_player.Value; } set { binding_player.Value = value; if (value != null) { try { display_name = (value.Actor.Properties as XmdsUnitProperties).ServerData.BaseInfo.name; } catch (Exception err) { display_name = err.Message; } } } } public InstancePlayer BindingActor { get { var player = binding_player.Value; if (player != null) { return player.Actor; } return null; } } public bool Connected { get; private set; } internal void NetStateChanged(string state) { BaseZoneNode.log.InfoFormat("Player:{0} NetStateChanged:{1}", player.PlayerUUID, state); switch (state) { case "disconnected": this.Connected = false; break; case "connected": this.Connected = true; break; default: return; } } internal void Recv(byte[] data) { IMessage message; if (mCodec.doDecode(data, out message)) { //if(handler == null) //{ // mPreQueue.Enqueue(message); //} //else //{ // handler.Invoke(message); //} if (handler != null) { handler.Invoke(message); } } else { CheatingDeathManager.SendPlayerException(BindingActor, "decode error"); } } public void Send(IMessage msg) { if (msg is PlayerLeaveScene) { this.Flush(); this.ForceSend(msg); } else if (this.Connected) { lock (mSendingQueue) { mSendingQueue.events.Add(msg); } } } internal void Flush() { lock (mSendingQueue) { if (mSendingQueue.events.Count > 0) { try { if (this.Connected) { mSendingQueue.sequenceNo = mSendingSequenceNo.GetAndIncrement(); ForceSend(mSendingQueue); } } finally { mSendingQueue.events.Clear(); } } } } internal void ForceSend(IMessage msg) { //BaseZoneNode.log.Debug("B2C msg>>>" + msg); ArraySegment<byte> data; if (mCodec.doEncode(msg, out data)) { this.player.SendToClient(data); } } #region CommonAIServer.Node.Interface.IPlayer. void CommonAIServer.Node.Interface.IPlayer.OnConnected(PlayerClient bindingg) { } void CommonAIServer.Node.Interface.IPlayer.OnDisconnect(PlayerClient bindingg) { this.player.BindingObject = null; this.player.Dispose(); } string CommonAIServer.Node.Interface.IPlayer.DisplayName { get { return display_name; } } string CommonAIServer.Node.Interface.IPlayer.PlayerUUID { get { return player.PlayerUUID; } } object CommonAIServer.Node.Interface.IPlayer.GetAttribute(string key) { return player.GetAttribute(key); } bool CommonAIServer.Node.Interface.IPlayer.IsAttribute(string key) { return player.IsAttribute(key); } void CommonAIServer.Node.Interface.IPlayer.SetAttribute(string key, object value) { player.SetAttribute(key, value); } void CommonAIServer.Node.Interface.IPlayer.Listen(Action<object> handler) { this.handler = handler; //while(mPreQueue.Count > 0) //{ // this.handler.Invoke(mPreQueue.Dequeue()); //} } #endregion } public class BindingPlayerClient : PlayerClient { private readonly ZoneNodePlayer Xmds_client; private readonly HashSet<string> team_info = new HashSet<string>(); private List<string> team_removing = new List<string>(1); internal BindingPlayerClient(CommonAIServer.Node.Interface.IPlayer client, InstancePlayer actor, CommonAIServer.Node.ZoneNode node, float syncIn, float syncOut) : base(client, actor, node, syncIn, syncOut) { this.Xmds_client = client as ZoneNodePlayer; } protected override void OnStart() { base.OnStart(); foreach (var obj in base.Zone.AllUnits) { if (IsStaticUnit(obj)) { base.ForceAddObjectInView(obj); } } } private bool IsStaticUnit(InstanceZoneObject obj) { if (obj is InstanceUnit) { XmdsUnitProperties prop = (obj as InstanceUnit).Info.Properties as XmdsUnitProperties; return prop.IsStaticUnit; } return false; } protected override bool IsLookInRange(InstanceZoneObject obj) { if (obj is InstancePlayer) { //组队列表中的单位永远不出视野// var player = obj as InstancePlayer; if (team_info.Contains(player.PlayerUUID)) { return true; } //if((player.CurrentActionSubstate & (byte)UnitActionSubStatus.Stealth) > 0 // && (this.Actor.CurrentActionSubstate & (byte)UnitActionSubStatus.Stealth) <= 0) //{ // return false; //} } else if (IsStaticUnit(obj)) { return true; } return base.IsLookInRange(obj); } protected override bool IsLookOutRange(InstanceZoneObject obj) { if (obj is InstancePlayer) { //组队列表中的单位永远不出视野// var player = obj as InstancePlayer; if (team_info.Contains(player.PlayerUUID)) { return false; } // if ((player.CurrentActionSubstate & (byte)UnitActionSubStatus.Stealth) > 0 // && (this.Actor.CurrentActionSubstate & (byte)UnitActionSubStatus.Stealth) <= 0 // && !player.Virtual.IsAllies(this.Actor.Virtual)) //{ // return true; //} } else if (IsStaticUnit(obj)) { return false; } return base.IsLookOutRange(obj); } public override bool RemoveInRange(InstanceZoneObject o) { if (IsLookOutRange(o)) { m_RemovingList.Add(o.ID); OnLeaveView(o); return true; } return false; } internal void OnPlayerReviveTeamInfoEventR2B(TeamInfoEventR2B team) { if (team.UUIDList != null) { //检测加入队伍// foreach (var uuid in team.UUIDList) { if (!string.IsNullOrEmpty(uuid) && !team_info.Contains(uuid)) { InstancePlayer tu = base.Zone.getPlayerByUUID(uuid); if (tu != null) { team_info.Add(uuid); base.ForceAddObjectInView(tu); } } } //检测离开队伍// team_removing.Clear(); foreach (var uuid in team_info) { if (!team.UUIDList.Contains(uuid)) { team_removing.Add(uuid); } } if (team_removing.Count > 0) { foreach (var uuid in team_removing) { team_info.Remove(uuid); } } } } } //------------------------------------------------------------------------------------------------------------ } }