界面上无法识别,提示是

[Unidentified card ID :DAL_010]
[Unidentified card ID :DAL_415]

Unidentified card ID :HERO_02c

首先使用卡牌工具,查询卡牌id对应的卡牌名字

https://github.com/ChuckHearthstone/CardQuery

1.添加卡牌id的枚举

https://github.com/ChuckHearthstone/SilverFish/blob/89ca9e06327f1bd4271c5ebb2ece268530fc194e/DefaultRoutine/Chuck.SilverFish/Enums/CardIdEnum.cs#L1184

2.添加卡牌的Sim

最新的处理方案是,直接引用第三方的类库来做卡牌的数据解析

https://www.nuget.org/packages/Chuck-HearthDb/   HearthBuddy的exe文件,引用这个类库

然后修改卡牌代码的解析 https://github.com/ChuckHearthstone/SilverFish/blob/master/DefaultRoutine/Chuck.SilverFish/ai/CardDB.cs#L137

  private CardDB()
{
CardList.Clear();
cardidToCardList.Clear(); //placeholdercard
Card plchldr = new Card {name = CardName.unknown, cost = };
namelist.Add("unknown");
CardList.Add(plchldr);
unknownCard = CardList[]; string name = "";
var cards = Cards.All;
foreach (var item in cards.Keys)
{
var card = new Card();
allCardIDS.Add(item);
card.cardIDenum = ConvertHelper.cardIdstringToEnum(item);
var dbCard = cards[item];
card.EnglishName = dbCard.GetLocName(Locale.enUS);
card.ChineseName = dbCard.GetLocName(Locale.zhCN);
card.Health = dbCard.Health;
card.Class = (int) dbCard.Class;
card.Attack.Value = dbCard.Attack;
card.race = (int) dbCard.Race;
card.rarity = (int) dbCard.Rarity;
card.cost = dbCard.Cost;
card.type = (CardType) dbCard.Type;
if (card.type == CardType.Token)
{
card.isToken = true;
}
if (card.type == CardType.ENCHANTMENT)
{
continue;
} var trimmedCardName = TrimHelper.TrimEnglishName(dbCard.Name);
if (!string.IsNullOrWhiteSpace(name))
{
namelist.Add(trimmedCardName);
}
card.name = ConvertHelper.cardNamestringToEnum(trimmedCardName); card.poisonous = dbCard.Entity.GetTag(GameTag.POISONOUS) == ;
card.Enrage = dbCard.Entity.GetTag(GameTag.ENRAGED) == ;
card.Aura = dbCard.Entity.GetTag(GameTag.AURA) == ;
card.tank = dbCard.Entity.GetTag(GameTag.TAUNT) == ;
card.battlecry= dbCard.Entity.GetTag(GameTag.BATTLECRY) == ;
card.discover = dbCard.Entity.GetTag(GameTag.DISCOVER) == ;
card.windfury = dbCard.Entity.GetTag(GameTag.WINDFURY) == ;
card.deathrattle = dbCard.Entity.GetTag(GameTag.DEATHRATTLE) == ;
card.Reborn = dbCard.Entity.GetTag(GameTag.REBORN) == ;
card.Inspire = dbCard.Entity.GetTag(GameTag.INSPIRE) == ;
card.Durability = dbCard.Entity.GetTag(GameTag.DURABILITY);
card.Elite = dbCard.Entity.GetTag(GameTag.ELITE) == ;
card.Combo = dbCard.Entity.GetTag(GameTag.COMBO) == ;
card.oneTurnEffect = dbCard.Entity.GetTag(GameTag.TAG_ONE_TURN_EFFECT) == ;
card.overload = dbCard.Entity.GetTag(GameTag.OVERLOAD);
card.lifesteal = dbCard.Entity.GetTag(GameTag.LIFESTEAL) == ;
card.untouchable = dbCard.Entity.GetTag(GameTag.UNTOUCHABLE) == ;
card.Stealth = dbCard.Entity.GetTag(GameTag.STEALTH)==;
card.Secret = dbCard.Entity.GetTag(GameTag.SECRET) == ;
card.Quest = dbCard.Entity.GetTag(GameTag.QUEST) == ;
card.Freeze = dbCard.Entity.GetTag(GameTag.FREEZE) == ;
card.AdjacentBuff = dbCard.Entity.GetTag(GameTag.ADJACENT_BUFF) == ;
card.DivineShield = dbCard.Entity.GetTag(GameTag.DIVINE_SHIELD) == ;
card.Charge = dbCard.Entity.GetTag(GameTag.CHARGE) == ;
card.Rush.Value = dbCard.Entity.GetTag(GameTag.RUSH) == ;
card.Silence = dbCard.Entity.GetTag(GameTag.SILENCE) == ;
card.Morph = dbCard.Entity.GetTag(GameTag.MORPH) == ;
card.Spellpower = dbCard.Entity.GetTag(GameTag.SPELLPOWER) > ;
card.spellpowervalue = dbCard.Entity.GetTag(GameTag.SPELLPOWER);
if (!string.IsNullOrEmpty(dbCard.Text))
{
if (dbCard.Text.ToLower().Contains("choose one"))
{
card.choice = true;
}
} var playRequirements = dbCard.Entity.GetPlayRequirements();
if (playRequirements != null)
{
foreach (var playRequirement in playRequirements)
{
var reqId = Convert.ToInt32(playRequirement.ReqId);
var errorType = (ErrorType) Enum.ToObject(typeof(ErrorType), reqId);
card.playrequires.Add(errorType);
}
} if (card.name != CardName.unknown)
{
CardList.Add(card);
if (!cardidToCardList.ContainsKey(card.cardIDenum))
{
cardidToCardList.Add(card.cardIDenum, card);
}
else
{
Logger.GetLoggerInstanceForType()
.ErrorFormat("[c.cardIDenum:" + card.cardIDenum + "] already exists in cardidToCardList");
}
}
} teacherminion = getCardDataFromID(CardIdEnum.NEW1_026t);
illidanminion = getCardDataFromID(CardIdEnum.EX1_614t);
lepergnome = getCardDataFromID(CardIdEnum.EX1_029);
burlyrockjaw = getCardDataFromID(CardIdEnum.GVG_068); Helpfunctions.Instance.InfoLog("CardList:" + cardidToCardList.Count); }

分析(淘汰)

搜索getcardid,在所有的相关函数处设置断点,看会进入哪里的断点

发现是进入了namespace Triton.Game.Mapping

[Attribute38("EntityBase")]
public class EntityBase : MonoClass

这个类

public string GetCardId()
{
return base.method_13("GetCardId", Array.Empty<object>());
}

namespace Triton.Game

public class HSCard

public string Id
{
get
{
return this.Entity_0.GetCardId();
}
}

namespace HREngine.Bots

public class Silverfish

// HREngine.Bots.Silverfish
// Token: 0x06000048 RID: 72 RVA: 0x0000A16C File Offset: 0x0000836C
private void getDecks()
{
Dictionary<string, int> tmpDeck = new Dictionary<string, int>(this.startDeck);
List<GraveYardItem> graveYard = new List<GraveYardItem>();
Dictionary<CardDB.cardIDEnum, int> og = new Dictionary<CardDB.cardIDEnum, int>();
Dictionary<CardDB.cardIDEnum, int> eg = new Dictionary<CardDB.cardIDEnum, int>();
int owncontroler = TritonHs.OurHero.GetTag(GAME_TAG.CONTROLLER);
int enemycontroler = TritonHs.EnemyHero.GetTag(GAME_TAG.CONTROLLER);
this.turnDeck.Clear();
this.noDuplicates = false;
List<HSCard> allcards = TritonHs.GetAllCards();
int allcardscount = allcards.Count;
for (int i = ; i < allcardscount; i++)
{
HSCard entity = allcards[i];
if (entity.Id != null && !(entity.Id == ""))
{
if (CardDB.Instance.cardIdstringToEnum(entity.Id) == CardDB.cardIDEnum.UNG_116t)
{
this.ownMinionsCost0 = true;
}
if (entity.GetZone() == TAG_ZONE.GRAVEYARD)
{
CardDB.cardIDEnum cide = CardDB.Instance.cardIdstringToEnum(entity.Id);
GraveYardItem gyi = new GraveYardItem(cide, entity.EntityId, entity.GetTag(GAME_TAG.CONTROLLER) == owncontroler);
graveYard.Add(gyi);
if (entity.GetTag(GAME_TAG.CONTROLLER) == owncontroler)
{
if (og.ContainsKey(cide))
{
Dictionary<CardDB.cardIDEnum, int> dictionary;
CardDB.cardIDEnum key;
(dictionary = og)[key = cide] = dictionary[key] + ;
}
else
{
og.Add(cide, );
}
}
else if (entity.GetTag(GAME_TAG.CONTROLLER) == enemycontroler)
{
if (eg.ContainsKey(cide))
{
Dictionary<CardDB.cardIDEnum, int> dictionary;
CardDB.cardIDEnum key;
(dictionary = eg)[key = cide] = dictionary[key] + ;
}
else
{
eg.Add(cide, );
}
}
if (cide == CardDB.cardIDEnum.UNG_067t1)
{
this.ownCrystalCore = ;
}
}
string entityId = entity.Id;
TAG_ZONE entZone = entity.GetZone();
if (i < )
{
if (entityId != "")
{
if (entZone != TAG_ZONE.DECK)
{
if (tmpDeck.ContainsKey(entityId))
{
Dictionary<string, int> dictionary2;
string key2;
(dictionary2 = tmpDeck)[key2 = entityId] = dictionary2[key2] - ;
}
}
}
}
else if (i >= && entity.ControllerId == owncontroler)
{
if (this.extraDeck.ContainsKey(i))
{
if (entityId != "" && entityId != this.extraDeck[i].id)
{
this.extraDeck[i].setId(entityId);
}
if (entZone == TAG_ZONE.DECK != this.extraDeck[i].isindeck)
{
this.extraDeck[i].setisindeck(entZone == TAG_ZONE.DECK);
}
}
else if (entZone == TAG_ZONE.DECK)
{
this.extraDeck.Add(i, new Silverfish.extraCard(entityId, true));
}
}
}
}
Action a = Ai.Instance.bestmove;
foreach (KeyValuePair<int, Silverfish.extraCard> c in this.extraDeck)
{
if (c.Value.isindeck)
{
string entityId = c.Value.id;
if (entityId == "")
{
if (a != null)
{
actionEnum actionType = a.actionType;
if (actionType == actionEnum.playcard)
{
CardDB.cardIDEnum cardIDEnum = a.card.card.cardIDenum;
if (cardIDEnum <= CardDB.cardIDEnum.LOE_019t)
{
if (cardIDEnum == CardDB.cardIDEnum.BRM_007)
{
goto IL_42B;
}
if (cardIDEnum != CardDB.cardIDEnum.LOE_002)
{
if (cardIDEnum == CardDB.cardIDEnum.LOE_019t)
{
entityId = "LOE_019t2";
}
}
else
{
entityId = "LOE_002t";
}
}
else if (cardIDEnum != CardDB.cardIDEnum.LOE_079)
{
if (cardIDEnum == CardDB.cardIDEnum.LOE_104)
{
goto IL_42B;
}
if (cardIDEnum == CardDB.cardIDEnum.LOE_110)
{
entityId = "LOE_110t";
}
}
else
{
entityId = "LOE_019t";
}
goto IL_485;
IL_42B:
if (a.target != null)
{
entityId = a.target.handcard.card.cardIDenum.ToString();
}
}
IL_485:;
}
if (entityId == "")
{
Dictionary<CardDB.cardIDEnum, int> oldCardsOut = Probabilitymaker.Instance.enemyCardsOut;
foreach (KeyValuePair<CardDB.cardIDEnum, int> tmp in eg)
{
if (!oldCardsOut.ContainsKey(tmp.Key) || tmp.Value != oldCardsOut[tmp.Key])
{
CardDB.cardIDEnum cardIDEnum = tmp.Key;
if (cardIDEnum != CardDB.cardIDEnum.AT_035)
{
if (cardIDEnum != CardDB.cardIDEnum.GVG_031)
{
if (cardIDEnum == CardDB.cardIDEnum.LOE_111)
{
entityId = "LOE_111";
}
}
else
{
entityId = "aiextra1";
}
}
else
{
entityId = "AT_035t";
}
}
}
if (entityId == "" && this.lastpf != null)
{
int num = ;
foreach (Minion j in this.enemyMinions)
{
if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_056)
{
num++;
}
}
if (num > )
{
foreach (Minion j in this.lastpf.enemyMinions)
{
if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_056)
{
num--;
}
}
}
if (num > )
{
entityId = "GVG_056t";
}
else
{
num = ;
foreach (Minion j in this.lastpf.ownMinions)
{
if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_035)
{
num++;
}
}
if (num > )
{
foreach (Minion j in this.ownMinions)
{
if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_035)
{
num--;
}
}
}
if (num > )
{
entityId = "GVG_035";
}
}
}
}
if (entityId == "")
{
entityId = "aiextra1";
}
}
c.Value.setId(entityId);
CardDB.cardIDEnum ce = CardDB.Instance.cardIdstringToEnum(entityId);
if (this.turnDeck.ContainsKey(ce))
{
Dictionary<CardDB.cardIDEnum, int> dictionary;
CardDB.cardIDEnum key;
(dictionary = this.turnDeck)[key = ce] = dictionary[key] + ;
}
else
{
this.turnDeck.Add(ce, );
}
}
}
foreach (KeyValuePair<string, int> c2 in tmpDeck)
{
if (c2.Value >= )
{
CardDB.cardIDEnum ce = CardDB.Instance.cardIdstringToEnum(c2.Key);
if (ce != CardDB.cardIDEnum.None)
{
if (this.turnDeck.ContainsKey(ce))
{
Dictionary<CardDB.cardIDEnum, int> dictionary;
CardDB.cardIDEnum key;
(dictionary = this.turnDeck)[key = ce] = dictionary[key] + c2.Value;
}
else
{
this.turnDeck.Add(ce, c2.Value);
}
}
}
}
Probabilitymaker.Instance.setOwnCardsOut(og);
Probabilitymaker.Instance.setEnemyCardsOut(eg);
bool isTurnStart = false;
if (Ai.Instance.nextMoveGuess.mana == -)
{
isTurnStart = true;
Ai.Instance.updateTwoTurnSim();
}
Probabilitymaker.Instance.setGraveYard(graveYard, isTurnStart);
if (this.startDeck.Count != )
{
this.noDuplicates = true;
foreach (int i in this.turnDeck.Values)
{
if (i > )
{
this.noDuplicates = false;
break;
}
}
}
}

在updateEverything函数里面的this.getDecks();的下一行设置断点,发现这个执行完,就看到卡牌错误提示

// HREngine.Bots.Silverfish
// Token: 0x06000044 RID: 68 RVA: 0x00008154 File Offset: 0x00006354
public bool updateEverything(Behavior botbase, int numcal, out bool sleepRetry)
{
this.needSleep = false;
this.updateBehaveString(botbase);
this.ownPlayerController = TritonHs.OurHero.ControllerId;
Hrtprozis.Instance.clearAllRecalc();
Handmanager.Instance.clearAllRecalc();
this.getHerostuff();
this.getMinions();
this.getHandcards();
this.getDecks();
Hrtprozis.Instance.updateTurnDeck(this.turnDeck, this.noDuplicates);
Hrtprozis.Instance.setOwnPlayer(this.ownPlayerController);
Handmanager.Instance.setOwnPlayer(this.ownPlayerController);
this.numOptionPlayedThisTurn = ;
this.numOptionPlayedThisTurn += this.cardsPlayedThisTurn + this.ownHero.numAttacksThisTurn;
foreach (Minion i in this.ownMinions)
{
if (i.Hp >= )
{
this.numOptionPlayedThisTurn += i.numAttacksThisTurn;
}
}
List<HSCard> list = TritonHs.GetCards(CardZone.Graveyard, true);
foreach (GraveYardItem gi in Probabilitymaker.Instance.turngraveyard)
{
if (gi.own)
{
foreach (HSCard entiti in list)
{
if (gi.entity == entiti.EntityId)
{
this.numOptionPlayedThisTurn += entiti.NumAttackThisTurn;
break;
}
}
}
}
Hrtprozis.Instance.updatePlayer(this.ownMaxMana, this.currentMana, this.cardsPlayedThisTurn, this.numMinionsPlayedThisTurn, this.numOptionPlayedThisTurn, this.ueberladung, this.lockedMana, TritonHs.OurHero.EntityId, TritonHs.EnemyHero.EntityId);
Hrtprozis.Instance.updateSecretStuff(this.ownSecretList, this.enemySecretList.Count);
Hrtprozis.Instance.updateHero(this.ownWeapon, this.heroname, this.heroAbility, this.ownAbilityisReady, this.ownHeroPowerCost, this.ownHero, );
Hrtprozis.Instance.updateHero(this.enemyWeapon, this.enemyHeroname, this.enemyAbility, false, this.enemyHeroPowerCost, this.enemyHero, this.enemyMaxMana);
Questmanager.Instance.updatePlayedMobs(this.gTurnStep);
Hrtprozis.Instance.updateMinions(this.ownMinions, this.enemyMinions);
Hrtprozis.Instance.updateLurkersDB(this.LurkersDB);
Handmanager.Instance.setHandcards(this.handCards, this.anzcards, this.enemyAnzCards);
Hrtprozis.Instance.updateFatigueStats(this.ownDecksize, this.ownHeroFatigue, this.enemyDecksize, this.enemyHeroFatigue);
Hrtprozis.Instance.updateJadeGolemsInfo(GameState.Get().GetFriendlySidePlayer().GetTag(GAME_TAG.JADE_GOLEM), GameState.Get().GetOpposingSidePlayer().GetTag(GAME_TAG.JADE_GOLEM));
Hrtprozis.Instance.updateTurnInfo(this.gTurn, this.gTurnStep);
this.updateCThunInfobyCThun();
Hrtprozis.Instance.updateCThunInfo(this.anzOgOwnCThunAngrBonus, this.anzOgOwnCThunHpBonus, this.anzOgOwnCThunTaunt);
Hrtprozis.Instance.updateCrystalCore(this.ownCrystalCore);
Hrtprozis.Instance.updateOwnMinionsInDeckCost0(this.ownMinionsCost0);
Probabilitymaker.Instance.setEnemySecretGuesses(this.enemySecretList);
sleepRetry = this.needSleep;
bool result;
if (sleepRetry && numcal == )
{
result = false;
}
else
{
if (!Hrtprozis.Instance.setGameRule)
{
RulesEngine.Instance.setCardIdRulesGame(this.ownHero.cardClass, this.enemyHero.cardClass);
Hrtprozis.Instance.setGameRule = true;
}
Playfield p = new Playfield();
p.enemyCardsOut = new Dictionary<CardDB.cardIDEnum, int>(Probabilitymaker.Instance.enemyCardsOut);
if (this.lastpf != null)
{
if (this.lastpf.isEqualf(p))
{
return false;
}
if (p.gTurnStep > && Ai.Instance.nextMoveGuess.ownMinions.Count == p.ownMinions.Count)
{
if (Ai.Instance.nextMoveGuess.ownHero.Ready != p.ownHero.Ready && !p.ownHero.Ready)
{
sleepRetry = true;
Helpfunctions.Instance.ErrorLog("[AI] Hero ready = " + p.ownHero.Ready + ". Attempting recover....");
Ai.Instance.nextMoveGuess = new Playfield
{
mana = -
};
return false;
}
for (int j = ; j < p.ownMinions.Count; j++)
{
if (Ai.Instance.nextMoveGuess.ownMinions[j].Ready != p.ownMinions[j].Ready && !p.ownMinions[j].Ready)
{
sleepRetry = true;
Helpfunctions.Instance.ErrorLog(string.Concat(new object[]
{
"[AI] Minion ready = ",
p.ownMinions[j].Ready,
" (",
p.ownMinions[j].entitiyID,
" ",
p.ownMinions[j].handcard.card.cardIDenum,
" ",
p.ownMinions[j].name,
"). Attempting recover...."
}));
Ai.Instance.nextMoveGuess = new Playfield
{
mana = -
};
return false;
}
}
}
Probabilitymaker.Instance.updateSecretList(p, this.lastpf);
this.lastpf = p;
}
else
{
this.lastpf = p;
}
p = new Playfield();
Helpfunctions.Instance.ErrorLog("calculating stuff... " + DateTime.Now.ToString("HH:mm:ss.ffff"));
using (TritonHs.Memory.ReleaseFrame(true))
{
this.printstuff();
Ai.Instance.dosomethingclever(botbase);
}
Helpfunctions.Instance.ErrorLog("calculating ended! " + DateTime.Now.ToString("HH:mm:ss.ffff"));
if (this.sttngs.printRules > )
{
string[] rulesStr = Ai.Instance.bestplay.rulesUsed.Split(new char[]
{
'@'
});
if (rulesStr.Count<string>() > && rulesStr[] != "")
{
Helpfunctions.Instance.ErrorLog("ruleWeight " + Ai.Instance.bestplay.ruleWeight * -);
foreach (string rs in rulesStr)
{
if (!(rs == ""))
{
Helpfunctions.Instance.ErrorLog("rule: " + rs);
}
}
}
}
result = true;
}
return result;
}

namespace HREngine.Bots

public class DefaultRoutine : IRoutine, IRunnable, IAuthored, IBase, IConfigurable

在update everything函数调用的地方设置断点,发现执行pdate everything函数,就有错误提示。

所以卡牌的错误,是在update everything里面出来的。

// HREngine.Bots.DefaultRoutine
// Token: 0x06000017 RID: 23 RVA: 0x00005A54 File Offset: 0x00003C54
public async Task OurTurnLogic()
{
if (this.behave.BehaviorName() != DefaultRoutineSettings.Instance.DefaultBehavior)
{
this.behave = this.sf.getBehaviorByName(DefaultRoutineSettings.Instance.DefaultBehavior);
Silverfish.Instance.lastpf = null;
}
if (this.learnmode && (TritonHs.IsInTargetMode() || TritonHs.IsInChoiceMode()))
{
await Coroutine.Sleep();
}
else if (TritonHs.IsInTargetMode())
{
if (this.dirtytarget >= )
{
DefaultRoutine.Log.Info("targeting...");
HSCard source = null;
if (this.dirtyTargetSource == )
{
source = TritonHs.OurHeroPowerCard;
}
else
{
source = this.getEntityWithNumber(this.dirtyTargetSource);
}
HSCard target = this.getEntityWithNumber(this.dirtytarget);
if (target == null)
{
DefaultRoutine.Log.Error("target is null...");
TritonHs.CancelTargetingMode();
}
else
{
this.dirtytarget = -;
this.dirtyTargetSource = -;
if (source == null)
{
await TritonHs.DoTarget(target);
}
else
{
await source.DoTarget(target);
}
await Coroutine.Sleep();
}
}
else
{
DefaultRoutine.Log.Error("target failure...");
TritonHs.CancelTargetingMode();
}
}
else if (TritonHs.IsInChoiceMode())
{
await Coroutine.Sleep( + this.makeChoice());
switch (this.dirtychoice)
{
case :
TritonHs.ChooseOneClickMiddle();
break;
case :
TritonHs.ChooseOneClickLeft();
break;
case :
TritonHs.ChooseOneClickRight();
break;
}
this.dirtychoice = -;
await Coroutine.Sleep();
}
else
{
bool sleepRetry = false;
bool templearn = Silverfish.Instance.updateEverything(this.behave, , out sleepRetry);
if (sleepRetry)
{
DefaultRoutine.Log.Error("[AI] Readiness error. Attempting recover...");
await Coroutine.Sleep();
templearn = Silverfish.Instance.updateEverything(this.behave, , out sleepRetry);
}
if (templearn)
{
this.printlearnmode = true;
}
if (this.learnmode)
{
if (this.printlearnmode)
{
Ai.Instance.simmulateWholeTurnandPrint();
}
this.printlearnmode = false;
await Coroutine.Sleep();
}
else
{
Action moveTodo = Ai.Instance.bestmove;
if (moveTodo == null || moveTodo.actionType == actionEnum.endturn || Ai.Instance.bestmoveValue < -9999f)
{
bool doEndTurn = false;
bool doConcede = false;
if (Ai.Instance.bestmoveValue > -10000f)
{
doEndTurn = true;
}
else if (Settings.Instance.concedeMode != )
{
doConcede = true;
}
else if (new Playfield().ownHeroHasDirectLethal())
{
Playfield lastChancePl = Ai.Instance.bestplay;
bool lastChance = false;
if (lastChancePl.owncarddraw > )
{
foreach (Handmanager.Handcard hc in lastChancePl.owncards)
{
if (hc.card.name == CardDB.cardName.unknown)
{
lastChance = true;
}
}
if (!lastChance)
{
doConcede = true;
}
}
else
{
doConcede = true;
}
if (doConcede)
{
foreach (Minion i in lastChancePl.ownMinions)
{
if (i.playedThisTurn)
{
CardDB.cardName name = i.handcard.card.name;
if (name <= CardDB.cardName.nzoththecorruptor)
{
if (name != CardDB.cardName.barongeddon)
{
if (name != CardDB.cardName.cthun)
{
if (name == CardDB.cardName.nzoththecorruptor)
{
lastChance = true;
}
}
else
{
lastChance = true;
}
}
else if (lastChancePl.enemyHero.Hp < )
{
lastChance = true;
}
}
else if (name != CardDB.cardName.ragnarosthefirelord)
{
if (name != CardDB.cardName.sirfinleymrrgglton)
{
if (name == CardDB.cardName.yoggsaronhopesend)
{
lastChance = true;
}
}
else
{
lastChance = true;
}
}
else if (lastChancePl.enemyHero.Hp < )
{
lastChance = true;
}
}
}
}
if (lastChance)
{
doConcede = false;
}
}
else if (moveTodo == null || moveTodo.actionType == actionEnum.endturn)
{
doEndTurn = true;
}
if (doEndTurn)
{
Helpfunctions.Instance.ErrorLog("end turn");
await TritonHs.EndTurn();
return;
}
if (doConcede)
{
Helpfunctions.Instance.ErrorLog("Lethal detected. Concede...");
Helpfunctions.Instance.logg("Concede... Lethal detected###############################################");
TritonHs.Concede(true);
return;
}
}
Helpfunctions.Instance.ErrorLog("play action");
if (moveTodo == null)
{
Helpfunctions.Instance.ErrorLog("moveTodo == null. EndTurn");
await TritonHs.EndTurn();
}
else
{
moveTodo.print(false);
if (moveTodo.actionType == actionEnum.playcard)
{
Questmanager.Instance.updatePlayedCardFromHand(moveTodo.card);
HSCard cardtoplay = this.getCardWithNumber(moveTodo.card.entity);
if (cardtoplay == null)
{
Helpfunctions.Instance.ErrorLog("[Tick] cardtoplay == null");
}
else if (moveTodo.target != null)
{
HSCard target2 = this.getEntityWithNumber(moveTodo.target.entitiyID);
if (target2 != null)
{
Helpfunctions.Instance.ErrorLog(string.Concat(new object[]
{
"play: ",
cardtoplay.Name,
" (",
cardtoplay.EntityId,
") target: ",
target2.Name,
" (",
target2.EntityId,
")"
}));
Helpfunctions.Instance.logg(string.Concat(new object[]
{
"play: ",
cardtoplay.Name,
" (",
cardtoplay.EntityId,
") target: ",
target2.Name,
" (",
target2.EntityId,
") choice: ",
moveTodo.druidchoice
}));
if (moveTodo.druidchoice >= )
{
this.dirtytarget = moveTodo.target.entitiyID;
this.dirtychoice = moveTodo.druidchoice;
this.choiceCardId = moveTodo.card.card.cardIDenum.ToString();
}
this.dirtyTargetSource = moveTodo.card.entity;
this.dirtytarget = moveTodo.target.entitiyID;
await cardtoplay.Pickup();
if (moveTodo.card.card.type == CardDB.cardtype.MOB)
{
await cardtoplay.UseAt(moveTodo.place);
}
else if (moveTodo.card.card.type == CardDB.cardtype.WEAPON)
{
await cardtoplay.UseOn(target2.Card);
}
else if (moveTodo.card.card.type == CardDB.cardtype.SPELL)
{
await cardtoplay.UseOn(target2.Card);
}
else
{
await cardtoplay.UseOn(target2.Card);
}
}
else
{
Helpfunctions.Instance.ErrorLog("[AI] Target is missing. Attempting recover...");
Helpfunctions.Instance.logg("[AI] Target " + moveTodo.target.entitiyID + "is missing. Attempting recover...");
}
await Coroutine.Sleep();
}
else
{
Helpfunctions.Instance.ErrorLog(string.Concat(new object[]
{
"play: ",
cardtoplay.Name,
" (",
cardtoplay.EntityId,
") target nothing"
}));
Helpfunctions.Instance.logg(string.Concat(new object[]
{
"play: ",
cardtoplay.Name,
" (",
cardtoplay.EntityId,
") choice: ",
moveTodo.druidchoice
}));
if (moveTodo.druidchoice >= )
{
this.dirtychoice = moveTodo.druidchoice;
this.choiceCardId = moveTodo.card.card.cardIDenum.ToString();
}
this.dirtyTargetSource = -;
this.dirtytarget = -;
await cardtoplay.Pickup();
if (moveTodo.card.card.type == CardDB.cardtype.MOB)
{
await cardtoplay.UseAt(moveTodo.place);
}
else
{
await cardtoplay.Use();
}
await Coroutine.Sleep();
}
}
else if (moveTodo.actionType == actionEnum.attackWithMinion)
{
HSCard attacker = this.getEntityWithNumber(moveTodo.own.entitiyID);
HSCard target3 = this.getEntityWithNumber(moveTodo.target.entitiyID);
if (attacker != null)
{
if (target3 != null)
{
Helpfunctions.Instance.ErrorLog("minion attack: " + attacker.Name + " target: " + target3.Name);
Helpfunctions.Instance.logg("minion attack: " + attacker.Name + " target: " + target3.Name);
await attacker.DoAttack(target3);
}
else
{
Helpfunctions.Instance.ErrorLog("[AI] Target is missing. Attempting recover...");
Helpfunctions.Instance.logg("[AI] Target " + moveTodo.target.entitiyID + "is missing. Attempting recover...");
}
}
else
{
Helpfunctions.Instance.ErrorLog("[AI] Attacker is missing. Attempting recover...");
Helpfunctions.Instance.logg("[AI] Attacker " + moveTodo.own.entitiyID + " is missing. Attempting recover...");
}
await Coroutine.Sleep();
}
else if (moveTodo.actionType == actionEnum.attackWithHero)
{
HSCard attacker2 = this.getEntityWithNumber(moveTodo.own.entitiyID);
HSCard target4 = this.getEntityWithNumber(moveTodo.target.entitiyID);
if (attacker2 != null)
{
if (target4 != null)
{
this.dirtytarget = moveTodo.target.entitiyID;
Helpfunctions.Instance.ErrorLog("heroattack: " + attacker2.Name + " target: " + target4.Name);
Helpfunctions.Instance.logg("heroattack: " + attacker2.Name + " target: " + target4.Name);
this.dirtyTargetSource = moveTodo.own.entitiyID;
this.dirtytarget = moveTodo.target.entitiyID;
await attacker2.DoAttack(target4);
}
else
{
Helpfunctions.Instance.ErrorLog("[AI] Target is missing. Attempting recover...");
Helpfunctions.Instance.logg("[AI] Target " + moveTodo.target.entitiyID + "is missing (H). Attempting recover...");
}
}
else
{
Helpfunctions.Instance.ErrorLog("[AI] Attacker is missing. Attempting recover...");
Helpfunctions.Instance.logg("[AI] Attacker " + moveTodo.own.entitiyID + " is missing (H). Attempting recover...");
}
await Coroutine.Sleep();
}
else if (moveTodo.actionType == actionEnum.useHeroPower)
{
HSCard cardtoplay2 = TritonHs.OurHeroPowerCard;
if (moveTodo.target != null)
{
HSCard target5 = this.getEntityWithNumber(moveTodo.target.entitiyID);
if (target5 != null)
{
Helpfunctions.Instance.ErrorLog("use ablitiy: " + cardtoplay2.Name + " target " + target5.Name);
Helpfunctions.Instance.logg(string.Concat(new string[]
{
"use ablitiy: ",
cardtoplay2.Name,
" target ",
target5.Name,
(moveTodo.druidchoice > ) ? (" choice: " + moveTodo.druidchoice) : ""
}));
if (moveTodo.druidchoice > )
{
this.dirtytarget = moveTodo.target.entitiyID;
this.dirtychoice = moveTodo.druidchoice;
this.choiceCardId = moveTodo.card.card.cardIDenum.ToString();
}
this.dirtyTargetSource = ;
this.dirtytarget = moveTodo.target.entitiyID;
await cardtoplay2.Pickup();
await cardtoplay2.UseOn(target5.Card);
}
else
{
Helpfunctions.Instance.ErrorLog("[AI] Target is missing. Attempting recover...");
Helpfunctions.Instance.logg("[AI] Target " + moveTodo.target.entitiyID + "is missing. Attempting recover...");
}
await Coroutine.Sleep();
}
else
{
Helpfunctions.Instance.ErrorLog("use ablitiy: " + cardtoplay2.Name + " target nothing");
Helpfunctions.Instance.logg("use ablitiy: " + cardtoplay2.Name + " target nothing" + ((moveTodo.druidchoice > ) ? (" choice: " + moveTodo.druidchoice) : ""));
if (moveTodo.druidchoice >= )
{
this.dirtychoice = moveTodo.druidchoice;
this.choiceCardId = moveTodo.card.card.cardIDenum.ToString();
}
this.dirtyTargetSource = -;
this.dirtytarget = -;
await cardtoplay2.Pickup();
}
}
else
{
await TritonHs.EndTurn();
}
}
}
}
}

最后发现错误提示,是从

namespace HREngine.Bots

public class CardDB里面报错的

把cardid的字符串转换为cardIDEnum的时候出错

public CardDB.cardIDEnum cardIdstringToEnum(string s)
{
CardDB.cardIDEnum CardEnum;
CardDB.cardIDEnum result;
if (Enum.TryParse<CardDB.cardIDEnum>(s, false, out CardEnum))
{
result = CardEnum;
}
else
{
Logger.GetLoggerInstanceForType().ErrorFormat("[Unidentified card ID :" + s + "]", new object[]);
result = CardDB.cardIDEnum.None;
}
return result;
}

根据我的印象,https://github.com/HearthSim/Hearthdb 这个项目就是用来生成卡牌的。

1.生成data

clone项目,编译之后,得到能识别的的CardDefs.xml。然后重命名成_carddb.txt,然后替换掉HearthBuddy里面的

2.自己实现卡牌

另外,这个文件不在HearthBuddy的exe里面,是外部加载进来的。HearthbuddyRelease\Routines\DefaultRoutine\Silverfish\ai\CardDB.cs

卡牌的实现需要自己写在Silverfish\cards文件夹下,并且卡牌在实现的时候,根据需要从Silverfish\ai的某一个类中继承,实现对应的方法

github上面尝试找了一下实现https://github.com/search?l=C%23&o=desc&q=getBattlecryEffect&s=indexed&type=Code

<Entity CardID="DAL_010" ID="51375" version="2">
<MasterPower>773590e3-24fa-4564-994a-94049b32d3f3</MasterPower>
<Tag enumID="185" name="CARDNAME" type="LocString">
<deDE>Togwaggels Plan</deDE>
<enUS>Togwaggle's Scheme</enUS>
<esES>Plan de Togafloja</esES>
<esMX>Complot de Togwaggle</esMX>
<frFR>Manœuvre de Cire-Pilleur</frFR>
<itIT>Piano di Cobaldo</itIT>
<jaJP>トグワグルの計略</jaJP>
<koKR>토그왜글의 계략</koKR>
<plPL>Intryga Trzęsibrzucha</plPL>
<ptBR>Estratagema de Fubalumba</ptBR>
<ruRU>Козни Вихлепыха</ruRU>
<thTH>แผนการของท็อกแว็กเกิล</thTH>
<zhCN>托瓦格尔的阴谋</zhCN>
<zhTW>托戈瓦哥的陰謀</zhTW>
</Tag>

<Entity CardID="DAL_415" ID="52111" version="2">
<MasterPower>2ef4d4c2-dbdf-4ae4-9f14-079984458f86</MasterPower>
<Tag enumID="185" name="CARDNAME" type="LocString">
<deDE>Ü.B.E.L.-Täter</deDE>
<enUS>EVIL Miscreant</enUS>
<esES>Malhechor del MAL</esES>
<esMX>Bellaco del MAL</esMX>
<frFR>Voyou du M.A.L.</frFR>
<itIT>Furfante del M.A.L.E.</itIT>
<jaJP>悪党同盟の悪漢</jaJP>
<koKR>잔.악.무.도. 악당</koKR>
<plPL>Szubrawiec Ligi Z.Ł.A.</plPL>
<ptBR>Mandrião da MAL</ptBR>
<ruRU>Негодяй ЗЛА</ruRU>
<thTH>จอมโฉด EVIL</thTH>
<zhCN>怪盗恶霸</zhCN>
<zhTW>邪惡陣線無賴</zhTW>
</Tag>

对了,貌似贴吧已经有部分教程了。https://tieba.baidu.com/p/5663869359

偶数萨电鳗狼王添加方法

==================================
1.编写新卡SIM,
在\Hearthbuddy\Routines\DefaultRoutine\Silverfish\cards\,
在目录下新建一个Sim_扩展包缩写+卡牌编号.cs文件,
编写SIM后保存。
==================================
2.编写并增加新卡识别信息,
\Hearthbuddy\Routines\DefaultRoutine\Silverfish\data\ 下的 _carddb.txt 文件,
其中存储所有卡牌信息。
含:185、184等等。
注意特效卡牌后面添加playRequirement,
每个节点后退两个字符。
(上述是新卡描述)
==================================
3.增加新卡编号及英文名称
在\Routines\DefaultRoutine\Silverfish\ai的CardDB.cs中的3550行后增加新卡
扩展包简称_卡牌编号,
如:
LOOT_149,
LOOT_149e,
扩展包简称均为大写,可以联想GVG,
==================================
在\Routines\DefaultRoutine\Silverfish\ai的CardDB.cs中的6303行后,增加新增卡牌英文名称。
注意卡牌英文名称没有空格且奇数偶数卡牌需添加两个名字,编写两个卡牌信息。
2费电鳗murksparkeel
6费偶数狼王geengreymane
战斗号角calltoarms
5费吵吵gigglingInventor
5费菌菇fungalmancer
3费荆棘Hench-ClanThug
刺杀花vilespineslayer
9费奇数bakuthemooneater

实际操作

发现阴燃电鳗无法识别

https://github.com/chucklu/HearthBuddyAnalyze/tree/master/CardQuery

使用工具,查询

阴燃电鳗的战吼效果和火元素类似,查找火元素的编号CS2_042,

然后在solution explorer搜索这个编号,找到

注释居然是法语

    class Sim_CS2_042 : SimTemplate //fireelemental
{ // kampfschrei:/ verursacht 3 schaden.
public override void getBattlecryEffect(Playfield p, Minion own, Minion target, int choice)
{
int dmg = ;
p.minionGetDamageOrHeal(target, dmg); } }

public enum cardIDEnum 里面需要新加一个卡牌枚举

可能还需要加一个类似Pen_CS2_042.cs的

在整个解决方案搜火元素的名字fireelemental

public enum cardName需要添加一个枚举,这里的cardName就是在下面记录伤害和记录血量的地方使用

public Dictionary<CardDB.cardName, int> DamageTargetDatabase = new Dictionary<CardDB.cardName, int>();  记录伤害的

Dictionary<CardDB.cardName, int> GangUpDatabase = new Dictionary<CardDB.cardName, int>(); 这个应该是卡牌的评分

GangUpDatabase.Add(CardDB.cardName.emperorthaurissan, 5);

{
"Id": "BRM_028",
"DbfId": 2262,
"Name": "索瑞森大帝",
"Text": "在你的回合结束时,你所有手牌的法力值消耗减少(1)点。",
"FlavorText": "把一个邪恶的炎魔之王召唤到这个世界上,然后看着这个家伙奴役了他所有的子民并非是他最为后悔的事情。",
"Class": "NEUTRAL",
"Rarity": "LEGENDARY",
"Type": "MINION",
"Race": "INVALID",
"Set": "BRM",
"Faction": "INVALID",
"Cost": 6,
"Attack": 5,
"Health": 5,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "Wayne Reynolds",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

GangUpDatabase.Add(CardDB.cardName.etherealpeddler, 3);

{
"Id": "KAR_070",
"DbfId": 39700,
"Name": "虚灵商人",
"Text": "<b>战吼:</b>如果你的手牌中有其他职业的卡牌,则其法力值消耗减少(2)点。",
"FlavorText": "虚灵以热衷收集和买卖各种魔法物品和神器而著称,比那些贪婪的地精靠谱多了。",
"Class": "ROGUE",
"Rarity": "RARE",
"Type": "MINION",
"Race": "INVALID",
"Set": "KARA",
"Faction": "INVALID",
"Cost": 5,
"Attack": 5,
"Health": 6,
"Durability": 0,
"Armor": 0,
"Mechanics": [
"Battlecry"
],
"ArtistName": "Alex Horley Orlandelli",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

GangUpDatabase.Add(CardDB.cardName.felcannon, 0);

{
"Id": "GVG_020",
"DbfId": 1997,
"Name": "邪能火炮",
"Text": "在你的回合结束时,对一个非机械随从造成2点伤害。",
"FlavorText": "包装盒上写着:全新改良,比旧款更邪门!",
"Class": "WARLOCK",
"Rarity": "RARE",
"Type": "MINION",
"Race": "MECHANICAL",
"Set": "GVG",
"Faction": "INVALID",
"Cost": 4,
"Attack": 3,
"Health": 5,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "Matt Gaser",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

GangUpDatabase.Add(CardDB.cardName.fireelemental, 5);

{
"Id": "CS2_042",
"DbfId": 189,
"Name": "火元素",
"Text": "<b>战吼:</b>造成3点伤害。",
"FlavorText": "他从来不洗澡。嗯...",
"Class": "SHAMAN",
"Rarity": "FREE",
"Type": "MINION",
"Race": "ELEMENTAL",
"Set": "CORE",
"Faction": "NEUTRAL",
"Cost": 6,
"Attack": 6,
"Health": 5,
"Durability": 0,
"Armor": 0,
"Mechanics": [
"Battlecry"
],
"ArtistName": "Ralph Horsley",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": false
}

GangUpDatabase.Add(CardDB.cardName.fireguarddestroyer, 4);

{
"Id": "BRM_012",
"DbfId": 2290,
"Name": "火焰驱逐者",
"Text": "<b>战吼:</b>获得1-4点攻击力。<b>过载:</b>(1)",
"FlavorText": "很多火元素竞相面试“火焰驱逐者”的职位,但拉格纳罗斯认为只有极少数火元素能够胜任。",
"Class": "SHAMAN",
"Rarity": "COMMON",
"Type": "MINION",
"Race": "ELEMENTAL",
"Set": "BRM",
"Faction": "INVALID",
"Cost": 4,
"Attack": 3,
"Health": 6,
"Durability": 0,
"Armor": 0,
"Mechanics": [
"Battlecry"
],
"ArtistName": "Paul Mafayon",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

GangUpDatabase.Add(CardDB.cardName.flametonguetotem, 4);

{
"Id": "EX1_565",
"DbfId": 1008,
"Name": "火舌图腾",
"Text": "相邻的随从获得+2攻击力。",
"FlavorText": "图腾制造师喜欢用最稀有的木材来打造图腾。甚至有传言说,这些图腾是由埃隆巴克保护者身上的树皮做的。",
"Class": "SHAMAN",
"Rarity": "FREE",
"Type": "MINION",
"Race": "TOTEM",
"Set": "CORE",
"Faction": "NEUTRAL",
"Cost": 3,
"Attack": 0,
"Health": 3,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "Jonathan Ryder",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": false
}

GangUpDatabase.Add(CardDB.cardName.flamewaker, 4);

{
"Id": "BRM_002",
"DbfId": 2275,
"Name": "火妖",
"Text": "在你施放一个法术后,造成2点伤害,随机分配到所有敌人身上。",
"FlavorText": "他们深居于炎热的洞窟之中,听候管理者的命令:消灭一切胆敢打扰炎魔之王的敌人。",
"Class": "MAGE",
"Rarity": "RARE",
"Type": "MINION",
"Race": "INVALID",
"Set": "BRM",
"Faction": "INVALID",
"Cost": 3,
"Attack": 2,
"Health": 4,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "Alex Horley Orlandelli",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

GangUpDatabase.Add(CardDB.cardName.flesheatingghoul, 0);

{
"Id": "tt_004",
"DbfId": 397,
"Name": "腐肉食尸鬼",
"Text": "每当一个随从死亡,便获得+1攻击力。",
"FlavorText": "诟病食尸鬼吃“腐肉”其实对它们并不公平,它们只是没有别的可吃了而已。",
"Class": "NEUTRAL",
"Rarity": "COMMON",
"Type": "MINION",
"Race": "INVALID",
"Set": "EXPERT1",
"Faction": "NEUTRAL",
"Cost": 3,
"Attack": 2,
"Health": 3,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "Alex Horley Orlandelli",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": false
}

GangUpDatabase.Add(CardDB.cardName.floatingwatcher, 0);  漂浮观察者,术士卡,544

{
"Id": "GVG_100",
"DbfId": 2068,
"Name": "漂浮观察者",
"Text": "每当你的英雄在你的回合受到伤害,便获得+2/+2。",
"FlavorText": "和他对话时,你很难与之进行眼神交流。",
"Class": "WARLOCK",
"Rarity": "COMMON",
"Type": "MINION",
"Race": "DEMON",
"Set": "GVG",
"Faction": "INVALID",
"Cost": 5,
"Attack": 4,
"Health": 4,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "Todd Lockwood",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

GangUpDatabase.Add(CardDB.cardName.foereaper4000, 1);   死神4000型,中立卡

{
"Id": "GVG_113",
"DbfId": 2081,
"Name": "死神4000型",
"Text": "同时对其攻击目标相邻的随从造成伤害。",
"FlavorText": "对于田里的庄稼来说,所有的收割机都是死神。",
"Class": "NEUTRAL",
"Rarity": "LEGENDARY",
"Type": "MINION",
"Race": "MECHANICAL",
"Set": "GVG",
"Faction": "INVALID",
"Cost": 8,
"Attack": 6,
"Health": 9,
"Durability": 0,
"Armor": 0,
"Mechanics": [],
"ArtistName": "James Ryman",
"EntourageCardIds": [],
"DefaultLanguage": "zhCN",
"Collectible": true,
"IsWild": true
}

最新文章

  1. Android添加快捷方式
  2. iOS开发实用技巧—在手机浏览器头部弹出app应用下载提示
  3. [ZZ] RGBM and RGBE encoding for HDR
  4. Javascript中最常用的经典技巧
  5. proxool在web环境中的使用
  6. mybatis0210 mybatis和ehcache缓存框架整合
  7. 【WPF】布局控件总结
  8. Android引导界面
  9. Android-------- AlertDialog中EditText无法弹出输入法的解决
  10. Mirror–使用证书配置镜像模板
  11. CSS3学习系列之盒样式(二)
  12. [译]async/await中阻塞死锁
  13. numpy中的stack操作:hstack()、vstack()、stack()、dstack()、vsplit()、concatenate()
  14. 洛谷 P1451 求细胞数量
  15. 【Spring Security】六、自定义认证处理的过滤器
  16. 给Libgdx的ShapeRenderer开启抗锯齿
  17. IE8不支持数组的indexOf方法
  18. CodeForces - 988C(STL大法好)
  19. Java NIO之拥抱Path和Files
  20. Linux下的pure-ftp的安装详解

热门文章

  1. Centos7查不出ip地址
  2. Visual Studio 添加 自定义 路径宏
  3. AWK程序设计语言
  4. Hive权限管理(十)
  5. cubase 的FX轨道使用方法
  6. python+Appium自动化:运行第一个appium脚本
  7. 关于多线程使用sqlite3的问题
  8. nginx第七天
  9. 高并发下的Nginx优化
  10. ESP8266—“ICACHE_FLASH_ATTR”宏