界面上无法识别,提示是
[Unidentified card ID :DAL_010]
[Unidentified card ID :DAL_415]
Unidentified card ID :HERO_02c
首先使用卡牌工具,查询卡牌id对应的卡牌名字
https://github.com/ChuckHearthstone/CardQuery
1.添加卡牌id的枚举
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 = 1000}; namelist.Add("unknown"); CardList.Add(plchldr); unknownCard = CardList[0]; 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) == 1; card.Enrage = dbCard.Entity.GetTag(GameTag.ENRAGED) == 1; card.Aura = dbCard.Entity.GetTag(GameTag.AURA) == 1; card.tank = dbCard.Entity.GetTag(GameTag.TAUNT) == 1; card.battlecry= dbCard.Entity.GetTag(GameTag.BATTLECRY) == 1; card.discover = dbCard.Entity.GetTag(GameTag.DISCOVER) == 1; card.windfury = dbCard.Entity.GetTag(GameTag.WINDFURY) == 1; card.deathrattle = dbCard.Entity.GetTag(GameTag.DEATHRATTLE) == 1; card.Reborn = dbCard.Entity.GetTag(GameTag.REBORN) == 1; card.Inspire = dbCard.Entity.GetTag(GameTag.INSPIRE) == 1; card.Durability = dbCard.Entity.GetTag(GameTag.DURABILITY); card.Elite = dbCard.Entity.GetTag(GameTag.ELITE) == 1; card.Combo = dbCard.Entity.GetTag(GameTag.COMBO) == 1; card.oneTurnEffect = dbCard.Entity.GetTag(GameTag.TAG_ONE_TURN_EFFECT) == 1; card.overload = dbCard.Entity.GetTag(GameTag.OVERLOAD); card.lifesteal = dbCard.Entity.GetTag(GameTag.LIFESTEAL) == 1; card.untouchable = dbCard.Entity.GetTag(GameTag.UNTOUCHABLE) == 1; card.Stealth = dbCard.Entity.GetTag(GameTag.STEALTH)==1; card.Secret = dbCard.Entity.GetTag(GameTag.SECRET) == 1; card.Quest = dbCard.Entity.GetTag(GameTag.QUEST) == 1; card.Freeze = dbCard.Entity.GetTag(GameTag.FREEZE) == 1; card.AdjacentBuff = dbCard.Entity.GetTag(GameTag.ADJACENT_BUFF) == 1; card.DivineShield = dbCard.Entity.GetTag(GameTag.DIVINE_SHIELD) == 1; card.Charge = dbCard.Entity.GetTag(GameTag.CHARGE) == 1; card.Rush.Value = dbCard.Entity.GetTag(GameTag.RUSH) == 1; card.Silence = dbCard.Entity.GetTag(GameTag.SILENCE) == 1; card.Morph = dbCard.Entity.GetTag(GameTag.MORPH) == 1; card.Spellpower = dbCard.Entity.GetTag(GameTag.SPELLPOWER) > 0; 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 = 0; 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] + 1; } else { og.Add(cide, 1); } } 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] + 1; } else { eg.Add(cide, 1); } } if (cide == CardDB.cardIDEnum.UNG_067t1) { this.ownCrystalCore = 5; } } string entityId = entity.Id; TAG_ZONE entZone = entity.GetZone(); if (i < 30) { if (entityId != "") { if (entZone != TAG_ZONE.DECK) { if (tmpDeck.ContainsKey(entityId)) { Dictionary<string, int> dictionary2; string key2; (dictionary2 = tmpDeck)[key2 = entityId] = dictionary2[key2] - 1; } } } } else if (i >= 60 && 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 = 0; foreach (Minion j in this.enemyMinions) { if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_056) { num++; } } if (num > 0) { foreach (Minion j in this.lastpf.enemyMinions) { if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_056) { num--; } } } if (num > 0) { entityId = "GVG_056t"; } else { num = 0; foreach (Minion j in this.lastpf.ownMinions) { if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_035) { num++; } } if (num > 0) { foreach (Minion j in this.ownMinions) { if (j.handcard.card.cardIDenum == CardDB.cardIDEnum.GVG_035) { num--; } } } if (num > 0) { 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] + 1; } else { this.turnDeck.Add(ce, 1); } } } foreach (KeyValuePair<string, int> c2 in tmpDeck) { if (c2.Value >= 1) { 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 == -100) { isTurnStart = true; Ai.Instance.updateTwoTurnSim(); } Probabilitymaker.Instance.setGraveYard(graveYard, isTurnStart); if (this.startDeck.Count != 0) { this.noDuplicates = true; foreach (int i in this.turnDeck.Values) { if (i > 1) { 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 = 0; this.numOptionPlayedThisTurn += this.cardsPlayedThisTurn + this.ownHero.numAttacksThisTurn; foreach (Minion i in this.ownMinions) { if (i.Hp >= 1) { 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, 10); 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 == 0) { 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 > 0 && 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 = -100 }; return false; } for (int j = 0; 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 = -100 }; 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 > 0) { string[] rulesStr = Ai.Instance.bestplay.rulesUsed.Split(new char[] { '@' }); if (rulesStr.Count<string>() > 0 && rulesStr[0] != "") { Helpfunctions.Instance.ErrorLog("ruleWeight " + Ai.Instance.bestplay.ruleWeight * -1); 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(50); } else if (TritonHs.IsInTargetMode()) { if (this.dirtytarget >= 0) { DefaultRoutine.Log.Info("targeting..."); HSCard source = null; if (this.dirtyTargetSource == 9000) { 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 = -1; this.dirtyTargetSource = -1; if (source == null) { await TritonHs.DoTarget(target); } else { await source.DoTarget(target); } await Coroutine.Sleep(555); } } else { DefaultRoutine.Log.Error("target failure..."); TritonHs.CancelTargetingMode(); } } else if (TritonHs.IsInChoiceMode()) { await Coroutine.Sleep(555 + this.makeChoice()); switch (this.dirtychoice) { case 0: TritonHs.ChooseOneClickMiddle(); break; case 1: TritonHs.ChooseOneClickLeft(); break; case 2: TritonHs.ChooseOneClickRight(); break; } this.dirtychoice = -1; await Coroutine.Sleep(555); } else { bool sleepRetry = false; bool templearn = Silverfish.Instance.updateEverything(this.behave, 0, out sleepRetry); if (sleepRetry) { DefaultRoutine.Log.Error("[AI] Readiness error. Attempting recover..."); await Coroutine.Sleep(500); templearn = Silverfish.Instance.updateEverything(this.behave, 1, out sleepRetry); } if (templearn) { this.printlearnmode = true; } if (this.learnmode) { if (this.printlearnmode) { Ai.Instance.simmulateWholeTurnandPrint(); } this.printlearnmode = false; await Coroutine.Sleep(50); } 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 != 0) { doConcede = true; } else if (new Playfield().ownHeroHasDirectLethal()) { Playfield lastChancePl = Ai.Instance.bestplay; bool lastChance = false; if (lastChancePl.owncarddraw > 0) { 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 < 3) { 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 < 9) { 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 >= 1) { 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(500); 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(500); } 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 >= 1) { this.dirtychoice = moveTodo.druidchoice; this.choiceCardId = moveTodo.card.card.cardIDenum.ToString(); } this.dirtyTargetSource = -1; this.dirtytarget = -1; await cardtoplay.Pickup(500); if (moveTodo.card.card.type == CardDB.cardtype.MOB) { await cardtoplay.UseAt(moveTodo.place); } else { await cardtoplay.Use(); } await Coroutine.Sleep(500); } } 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(250); } 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(250); } 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 > 0) ? (" choice: " + moveTodo.druidchoice) : "" })); if (moveTodo.druidchoice > 0) { this.dirtytarget = moveTodo.target.entitiyID; this.dirtychoice = moveTodo.druidchoice; this.choiceCardId = moveTodo.card.card.cardIDenum.ToString(); } this.dirtyTargetSource = 9000; this.dirtytarget = moveTodo.target.entitiyID; await cardtoplay2.Pickup(500); 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(500); } else { Helpfunctions.Instance.ErrorLog("use ablitiy: " + cardtoplay2.Name + " target nothing"); Helpfunctions.Instance.logg("use ablitiy: " + cardtoplay2.Name + " target nothing" + ((moveTodo.druidchoice > 0) ? (" choice: " + moveTodo.druidchoice) : "")); if (moveTodo.druidchoice >= 1) { this.dirtychoice = moveTodo.druidchoice; this.choiceCardId = moveTodo.card.card.cardIDenum.ToString(); } this.dirtyTargetSource = -1; this.dirtytarget = -1; await cardtoplay2.Pickup(500); } } 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[0]); result = CardDB.cardIDEnum.None; } return result; }
根据我的印象,https://github.com/HearthSim/Hearthdb 这个项目就是用来生成卡牌的。
1.生成data
clone项目,编译之后,得到能识别的的CardDefs.xml。然后重命名成_carddb.txt,然后替换掉HearthBuddy里面的
2.自己实现卡牌
另外,这个文件不在HearthBuddy的exe里面,是外部加载进来的。HearthbuddyReleaseRoutinesDefaultRoutineSilverfishaiCardDB.cs
卡牌的实现需要自己写在Silverfishcards文件夹下,并且卡牌在实现的时候,根据需要从Silverfishai的某一个类中继承,实现对应的方法
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,
在HearthbuddyRoutinesDefaultRoutineSilverfishcards,
在目录下新建一个Sim_扩展包缩写+卡牌编号.cs文件,
编写SIM后保存。
==================================
2.编写并增加新卡识别信息,
HearthbuddyRoutinesDefaultRoutineSilverfishdata 下的 _carddb.txt 文件,
其中存储所有卡牌信息。
含:185、184等等。
注意特效卡牌后面添加playRequirement,
每个节点后退两个字符。
(上述是新卡描述)
==================================
3.增加新卡编号及英文名称
在RoutinesDefaultRoutineSilverfishai的CardDB.cs中的3550行后增加新卡
扩展包简称_卡牌编号,
如:
LOOT_149,
LOOT_149e,
扩展包简称均为大写,可以联想GVG,
==================================
在RoutinesDefaultRoutineSilverfishai的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 = 3; 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
}