From 5bac8a05fa0f7f2ac70b9754770d7d2b8ae8b6cc Mon Sep 17 00:00:00 2001 From: Jseph Date: Sun, 28 Dec 2025 23:04:55 -0800 Subject: [PATCH 1/9] Check in ininitial work for purchasing reserve cards --- Jseph/purchase_reserve_cards.lua | 138 +++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Jseph/purchase_reserve_cards.lua diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua new file mode 100644 index 0000000..495e879 --- /dev/null +++ b/Jseph/purchase_reserve_cards.lua @@ -0,0 +1,138 @@ +require 'herorealms' +require 'decks' +require 'stdlib' +require 'stdcards' +require 'hardai' +require 'mediumai' +require 'easyai' + +-- Buff that counts how many times the player's deck was shuffled (due to draw or otherwise) +function draw_shuffle_counter_def() + local layout = createLayout({ + name = "Draw Shuffle Counter", + art = "art/t_cunning_of_the_wolf", + text = format("Shuffles: {0}", { getCounter("draw_shuffles") }) + }) + local currentReserve = loc(currentPid, reservePloc) + local reserveSlot = createSlot({ + id = "reserve_card_slot", + expiresArray = { neverExpiry }, + }) + local moveBackToReserve = createCardEffectAbility({ + id = "move_back_to_reserve", + effect = moveTarget(currentReserve).apply(selectTargets().where( + isCardWithSlot("reserve_card_slot").And( + isCardWithSlot("purchased_reserve_card_slot").invert() + ))), + cost = noCost, + trigger = locationChangedCardTrigger, + check = const(1).eq(1), + activations = multipleActivations, + tags = { } + }) + + return createBuffDef({ + id = "draw_shuffle_counter", + name = "Draw Shuffle Counter", + layout = layout, + abilities = { + createAbility({ + id = "draw_shuffle_counter_inc", + trigger = deckShuffledTrigger, + activations = multipleActivations, + effect = incrementCounterEffect("draw_shuffles", const(1)).seq(showTextExpressionEffect( + format("Your deck has been shuffled {0} times.", { getCounter("draw_shuffles") }) + )) + }), + createAbility({ + id = "add_slot_to_reserve_cards", + trigger = autoTrigger, + activations = singleActivation, + effect = addSlotToTarget(reserveSlot).apply(selectLoc(currentReserve)) + }), + }, + cardEffectAbilities = { + moveBackToReserve, + } + }) +end + +-- Skill: pay 1 gold to purchase the first card in your reserve +function purchase_first_reserve_skill_def() + -- TODO implement this basic proof of concept skill. +end + +function setupGame(g) + registerCards(g, { + --purchase_first_reserve_skill_def() + }) + standardSetup(g, { + description = "Custom no heroes game", + playerOrder = { plid1, plid2 }, + ai = ai.CreateKillSwitchAi(createAggressiveAI(), createHardAi2()), + timeoutAi = createTimeoutAi(), + opponents = { { plid1, plid2 } }, + players = { + { + id = plid1, + startDraw = 1, + name = "Player 1", + avatar="assassin", + health = 50, + cards = { + deck = { -- orc deck still in progress + { qty=1, card=dagger_carddef() }, + { qty=1, card=gold_carddef() }, + }, + reserve = { + { qty = 1, card = wizard_treasure_map_carddef() }, + { qty = 1, card = ranger_parrot_carddef() }, + }, + skills = { + --purchase_first_reserve_skill_def() + }, + buffs = { + drawCardsCountAtTurnEndDef(5), + discardCardsAtTurnStartDef(), + fatigueCount(40, 1, "FatigueP1"), + draw_shuffle_counter_def(), + } + } + }, + { + id = plid2, + startDraw = 5, + name = "Player 2", + avatar="assassin", + health = 50, + cards = { + deck = { + { qty=2, card=dagger_carddef() }, + { qty=8, card=gold_carddef() }, + }, + buffs = { + drawCardsCountAtTurnEndDef(5), + discardCardsAtTurnStartDef(), + fatigueCount(40, 1, "FatigueP2") + } + } + } + } + }) +end + +function endGame(g) +end + + function setupMeta(meta) + meta.name = "purchase_reserve_cards" + meta.minLevel = 0 + meta.maxLevel = 0 + meta.introbackground = "" + meta.introheader = "" + meta.introdescription = "" + meta.path = "C:/Users/jseph/hero-realms-lua-scripts/hero-realms-lua-scripts/Jseph/purchase_reserve_cards.lua" + meta.features = { +} + + end \ No newline at end of file From 4f0475721867d418197564adfad84654741e55a5 Mon Sep 17 00:00:00 2001 From: Jseph Date: Mon, 29 Dec 2025 21:24:13 -0800 Subject: [PATCH 2/9] Add skill to purchase reserve cards. Also update lua documentation and add in trickster mod. --- Jseph/purchase_reserve_cards.lua | 63 +- Jseph/trickster.lua | 529 ++++++ Lua_documentation.md | 2857 +++++++++++++++++++++++------- 3 files changed, 2844 insertions(+), 605 deletions(-) create mode 100644 Jseph/trickster.lua diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index 495e879..c776513 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -57,14 +57,60 @@ function draw_shuffle_counter_def() }) end --- Skill: pay 1 gold to purchase the first card in your reserve +-- Helper function to create purchase abilities for reserve cards +local function create_purchase_ability(baseCost, index, slot) + local target = selectLoc(loc(currentPid, reservePloc)).take(index).reverse().take(1) + local rawCost = const(baseCost).add(getTurnsPlayed(currentPid).negate()) + local dynamicCost = ifInt(rawCost.gte(const(0)), rawCost, const(0)) + return createAbility({ + id = "purchase_reserve_activate_" .. index, + trigger = uiTrigger, + promptType = showPrompt, + layout = createLayout({ + name = "Purchase Reserve", + art = "art/T_Taxation", + text = format("{0}, : Move {1} to your hand.\n(-1 per turn)", { dynamicCost, getCardNameStringExpression(target) }), + }), + effect = addSlotToTarget(slot).apply(target) + .seq(moveTarget(currentHandLoc).apply(target)), + cost = combineCosts({ expendCost, goldCost(dynamicCost) }), + check = selectLoc(loc(currentPid, reservePloc)).count().gte(index) + }) +end + +-- Skill: pay dynamic gold to purchase one of the first 4 cards in your reserve function purchase_first_reserve_skill_def() - -- TODO implement this basic proof of concept skill. + local slot = createSlot({ id = "purchased_reserve_card_slot", expiresArray = { neverExpiry } }) + + -- Initial layout for the skill itself + local firstCard = selectLoc(loc(currentPid, reservePloc)).take(1) + local rawCost = const(9).add(getTurnsPlayed(currentPid).negate()) + local dynamicCost = ifInt(rawCost.gte(const(0)), rawCost, const(0)) + local mainLayout = createLayout({ + name = "Purchase Reserve", + art = "art/T_Taxation", + text = "Acquire reserve card.\nCost: 7 gold + 2 gold per index - 1 gold per turn.", + }) + + return createSkillDef({ + id = "purchase_first_reserve_skill", + name = "Purchase Reserve", + cardTypeLabel = "Skill", + types = { skillType }, + abilities = { + create_purchase_ability(9, 1, slot), + create_purchase_ability(11, 2, slot), + create_purchase_ability(13, 3, slot), + create_purchase_ability(15, 4, slot), + }, + layout = mainLayout, + layoutPath = "avatars/profit" + }) end function setupGame(g) registerCards(g, { - --purchase_first_reserve_skill_def() + purchase_first_reserve_skill_def() }) standardSetup(g, { description = "Custom no heroes game", @@ -75,21 +121,22 @@ function setupGame(g) players = { { id = plid1, - startDraw = 1, + startDraw = 5, name = "Player 1", avatar="assassin", health = 50, cards = { - deck = { -- orc deck still in progress - { qty=1, card=dagger_carddef() }, - { qty=1, card=gold_carddef() }, + deck = { + { qty=10, card=fire_gem_carddef() }, }, reserve = { { qty = 1, card = wizard_treasure_map_carddef() }, { qty = 1, card = ranger_parrot_carddef() }, + { qty = 1, card = fire_gem_carddef() }, + { qty = 1, card = gold_carddef() }, }, skills = { - --purchase_first_reserve_skill_def() + purchase_first_reserve_skill_def() }, buffs = { drawCardsCountAtTurnEndDef(5), diff --git a/Jseph/trickster.lua b/Jseph/trickster.lua new file mode 100644 index 0000000..64ac0f1 --- /dev/null +++ b/Jseph/trickster.lua @@ -0,0 +1,529 @@ +require 'herorealms' +require 'decks' +require 'stdlib' +require 'stdcards' +require 'hardai' +require 'mediumai' +require 'easyai' + +--[[ Notes +Good art for reshuffle art: art/epicart/erratic_research +Good art for pet monkey: art/epicart/deathbringer, art/classes/druid/nimble_fox, art/epicart/kong +Good art for stack the deck: art/t_sleight_of_hand +]] + +double_or_nothing_layout = [[ + + + + + + + + +]] + +hidden_power_layout = [[ + + + + + + + + + + + + + + + + + + + + + + + + + +]] + +replace_ability_layout = [[ + + + + + + + + + + +]] + +local function show_choices_effect() + return pushChoiceEffect({ + choices = { + { + effect = gainGoldEffect(s.Minus(s.Const(1), s.getCounter("reshuffle_card_count"))) + .seq(animateShuffleDeckEffect(currentPid)) + .seq(shuffleEffect(loc(currentPid, deckPloc))) + .seq(drawCardsEffect(selectTargets().count())), + layout = createLayout({ + name = "Continue", + art = "art/t_prism_rainerpetter", + text = format("{0}: Shuffle your deck then draw {1} card(s)", + {s.Minus(s.getCounter("reshuffle_card_count"), s.Const(1)), + getCounter("reshuffle_card_count")}) + }), + }, + } + }) +end + +local function reshuffle_effect() + return pushTargetedEffect({ + desc = + "Pay X to put X+1 cards back in your deck, shuffle, and then draw X+1 cards.", + min=1, + max=ifInt( + selectLoc(loc(currentPid, handPloc)).count() + .gte(getPlayerGold(currentPid).add(const(1))), + getPlayerGold(currentPid).add(const(1)), + selectLoc(loc(currentPid, handPloc)).count()), + validTargets = selectLoc(loc(currentPid, handPloc)), + targetEffect = moveTarget(loc(currentPid, deckPloc)) + .seq(resetCounterEffect("reshuffle_card_count")) + .seq(incrementCounterEffect("reshuffle_card_count", selectTargets().count())) + .seq(show_choices_effect()) + }) +end + +local function reshuffle_hand_skill() + local cardLayout = createLayout({ + name = "Mulligan", + art = "art/t_prism_rainerpetter", + frame = "frames/alchemist_frames/alchemist_item_cardframe", + text = ": Pay X to put X+1 cards back in your deck, shuffle, and then draw X+1 cards." + }) + --[[local effect_chain = ignoreTarget(showTextEffect("Listing functions!")) + for name, value in pairs(_G) do + if type(value) == "function" and string.find(name, "animate") ~= nil then + effect_chain = effect_chain.seq(ignoreTarget(waitForClickEffect(name,""))).seq(animateShuffleDeckEffect(currentPid)) + end + end]] + card = createSkillDef({ + id = "reshuffle_skill_jseph", + name = "Reshuffle", + abilities = { + createAbility({ + id = "reshuffle_ability_id_jseph", + trigger = uiTrigger, + promptType = showPrompt, + layout = cardLayout, + effect = reshuffle_effect(), + cost = expendCost, + check = selectLoc(loc(currentPid, handPloc)).count() + .gte(const(1)), + activations = multipleActivations, + }) + }, + layout = cardLayout, + layoutPath = "art/t_prism_rainerpetter", + }) + return card +end + +local function sacrifice_from_discard_effect() + return pushTargetedEffect({ + desc = "Sacrifice from discard.", + min=0, + max=1, + validTargets = sacrificeSelector(selectLoc(currentDiscardLoc)), + targetEffect = sacrificeTarget() + }) +end + +local function switch_impl(val, cases) + local len = #cases + local effect = nullEffect() + for i = len, 1, -1 do + local c = cases[i][1] + local e = cases[i][2] + effect = ifElseEffect(val.where(c).count().gte(1),e,effect) + end + return effect +end + +local function hidden_power_card_def() + local cardLayout = createLayout({ + name = "Hidden Power", + art = "art/epicart/reusable_knowledge", + frame = "frames/alchemist_frames/alchemist_item_cardframe", + xmlText = hidden_power_layout, + }) + local kTopCardText = "Top card of the market deck." + local add_faction = function(faction) + return addSlotToTarget(createFactionsSlot({faction},{leavesPlayExpiry, endOfTurnExpiry})) + .apply(selectSource()) + end + local kBonusEffects = { + {isCardFaction(wildFaction), gainCombatEffect(3).seq(add_faction(wildFaction))}, + {isCardFaction(guildFaction), gainGoldEffect(2).seq(add_faction(guildFaction))}, + {isCardFaction(necrosFaction), sacrifice_from_discard_effect().seq(add_faction(necrosFaction))}, + {isCardFaction(imperialFaction), gainHealthEffect(5).seq(add_faction(imperialFaction))}, + } + return createActionDef({ + id = "hidden_power_jseph", + name = "Hidden Power", + tags = {}, + types = {}, + factions = {}, + acquireCost = 0, + abilities = { + createAbility({ + id = "hidden_power_ability_jseph", + effect = noUndoEffect() + .seq(moveTarget(revealLoc).apply(selectLoc(tradeDeckLoc).take(1))) + .seq(waitForClickEffect(kTopCardText, kTopCardText)) + .seq(switch_impl(selectLoc(revealLoc), kBonusEffects)) + .seq(moveTarget(tradeDeckLoc).apply(selectLoc(revealLoc))), + cost = noCost, + trigger = onPlayTrigger, + tags = {gainCombatTag}, + aiPriority = toIntExpression(5), + }) + }, + layout=cardLayout, + }) +end + +local function double_or_nothing_effect() + local slot = createSlot({ + id = "double_or_nothing_discount", + expiresArray = { endOfTurnExpiry } }) + return pushChoiceEffect({ + choices = { + { + effect = gainGoldEffect(1), + layout = createLayout({ + name = "Gold", + art = "art/treasures/t_trick_dice", + text = format("{{{0} gold}}",{1}) + }), + }, + { + effect = randomTarget(const(2), addSlotToTarget(slot) + .seq(noUndoEffect())) + .apply(selectLoc(centerRowLoc)) + .seq(createCardEffect(getCostDiscountBuff("Double or Nothing", 2, selectLoc(centerRowLoc).where(isCardWithSlotString("double_or_nothing_discount"))), currentBuffsLoc)), + + layout = createLayout({ + name = "Discount", + art = "art/treasures/t_trick_dice", + text = "Discount two random cards in the market by until you acquire your next card." + }), + } + } + }) +end + +local function double_or_nothing_card_def() + local cardLayout = createLayout({ + name = "Double or Nothing", + art = "art/treasures/t_trick_dice", + frame = "frames/alchemist_frames/alchemist_item_cardframe", + xmlText = double_or_nothing_layout + }) + return createActionDef({ + id = "double_or_nothing_jseph", + name = "Double or Nothing", + tags = {}, + types = {}, + factions = {}, + acquireCost = 0, + abilities = { + createAbility({ + id = "double_or_nothing_ability_jseph", + effect = double_or_nothing_effect(), + cost = noCost, + trigger = onPlayTrigger, + tags = {gainCombatTag}, + aiPriority = toIntExpression(5), + }) + }, + layout=cardLayout, + }) +end + +local function pet_monkey_effect() + local kMoveText = "Moving to top of market deck." + return pushTargetedEffect({ + desc = "Select a card to swap with the top card of the market deck.", + min=1, + max=1, + validTargets = selectLoc(centerRowLoc), + targetEffect = moveTarget(revealLoc) + .seq(waitForClickEffect(kMoveText, kMoveText)) + .seq(moveTarget(tradeDeckLoc).apply(selectLoc(revealLoc))) + .seq(noUndoEffect()) + }) +end + +local function pet_monkey_card_def() + local cardLayout = createLayout({ + name = "Pet Monkey", + art = "art/epicart/kong", + frame = "frames/alchemist_frames/alchemist_item_cardframe", + text = ": Swap any card on the market row with the top card of the market deck.", + }) + return createChampionDef({ + id = "pet_monkey_jseph", + name = "Pet Monkey", + types = {minionType, championType}, + acquireCost = 0, + health = 2, + isGuard = false, + abilities = { + createAbility({ + id = "pet_monkey_ability_jseph", + trigger = uiTrigger, + cost = expendCost, + activations = multipleActivations, + effect = pet_monkey_effect()}) + }, + layout = cardLayout, + }) +end + +function card_in_current_discard() + return selectTargets().exclude(selectLoc(currentDiscardLoc)).count().eq(0) +end + +function sacrifice_target_and_replace(location) + local equalValue = getCardPrintedCost().eq(selectTargets().sum(getCardPrintedCost())) + return sacrificeTarget().apply(selectTargets()) + .seq(moveTarget(location).apply( + selectLoc(tradeDeckLoc).take(1).where(equalValue).union(selectLoc(tradeDeckLoc).where(equalValue)).take(1))) + .seq(noUndoEffect()) +end + +function stack_the_deck_effect_2(location) + return pushTargetedEffect({ + desc = "Select the second card to sacrifice and replace.", + min=0, + max=1, + validTargets = sacrificeSelector(selectLoc(location).where(getCardPrintedCost().gte(1))), + targetEffect = ignoreTarget(sacrifice_target_and_replace(location)) + }) +end + +function stack_the_deck_effect() + return pushTargetedEffect({ + desc = "Select the first card to sacrifice and replace.", + min=0, + max=1, + validTargets = sacrificeSelector( + selectLoc(loc(oppPid, discardPloc)).union(selectLoc(currentDiscardLoc))).where(getCardPrintedCost().gte(1)), + targetEffect = ignoreTarget( + ifElseEffect( + card_in_current_discard(), + sacrifice_target_and_replace(currentDiscardLoc) + .seq(stack_the_deck_effect_2(loc(oppPid, discardPloc))), + sacrifice_target_and_replace(loc(oppPid, discardPloc)) + .seq(stack_the_deck_effect_2(currentDiscardLoc)))) + }) +end + +function stack_the_deck_ability_def() + local cardLayout = createLayout({ + name = "Stack The Deck", + art = "art/t_angry_skeleton", + frame = "frames/necromancer_frames/necromancer_item_cardframe", + xmlText=replace_ability_layout + }) + + return createSkillDef({ + id = "replace_ability_jseph", + name = "Stack The Deck", + types = { skillType }, + layout = cardLayout, + layoutPath = "art/t_angry_skeleton", + abilities = { + createAbility({ + id = "replace_ability_ability_jseph", + trigger = uiTrigger, + activations = singleActivation, + layout = cardLayout, + promptType = showPrompt, + effect = stack_the_deck_effect(), + cost = sacrificeSelfCost, + }), + }, + }) +end + +local function chooseStart() + return cardChoiceSelectorEffect({ + id = "SoG_Choice", + name = "First choice", + trigger = startOfTurnTrigger, + + upperTitle = "Welcome! How would you like to proceed?", + lowerTitle = "", +-- 1.1 choice + effectFirst = moveTarget(sacrificePloc).apply(selectLoc(loc(currentPid, handPloc)).union(selectLoc(loc(currentPid, deckPloc)).union(selectLoc(loc(currentPid, skillsPloc)).union(selectLoc(loc(currentPid, reservePloc)) + .union(selectLoc(loc(currentPid, buffsPloc)).where(isCardType(ogreType).Or(isCardType(orcType)).Or(isCardType(elfType).Or(isCardType(dwarfType).Or(isCardType(smallfolkType)).Or(isCardType(halfDemonType)).Or(isCardType(magicArmorType)).Or(isCardName("smallfolk_hide_buff")))))))))) + .seq(sacrificeTarget().apply(selectLoc(loc(currentPid, skillsPloc)))) + .seq(moveTarget(sacrificePloc).apply(selectLoc(loc(currentPid, handPloc)))) + .seq(setPlayerNameEffect("Trickster", currentPid)) + .seq(setPlayerAvatarEffect("smugglers", currentPid)) + .seq(gainMaxHealthEffect(currentPid, const(56).add(getPlayerMaxHealth(currentPid).negate()))) + .seq(gainHealthEffect(56)) + .seq(createCardEffect(reshuffle_hand_skill(), currentSkillsLoc)) + .seq(createCardEffect(stack_the_deck_ability_def(), currentSkillsLoc)) + .seq(createCardEffect(hidden_power_card_def(), loc(currentPid, asidePloc))) + .seq(createCardEffect(double_or_nothing_card_def(), loc(currentPid, asidePloc))) + .seq(createCardEffect(pet_monkey_card_def(), loc(currentPid, asidePloc))) + .seq(createCardEffect(gold_carddef(), loc(currentPid, asidePloc))) + .seq(createCardEffect(gold_carddef(), loc(currentPid, asidePloc))) + .seq(createCardEffect(gold_carddef(), loc(currentPid, asidePloc))) + .seq(createCardEffect(ruby_carddef(), loc(currentPid, asidePloc))) + .seq(createCardEffect(dagger_carddef(), loc(currentPid, asidePloc))) + .seq(createCardEffect(gold_carddef(), loc(currentPid, asidePloc))) + .seq(createCardEffect(gold_carddef(), loc(currentPid, asidePloc))) + .seq(moveTarget(deckPloc).apply(selectLoc(loc(currentPid, asidePloc)))) + .seq(shuffleEffect(currentDeckLoc)) + .seq(sacrificeTarget().apply(selectSource())) + .seq(moveTarget(deckPloc).apply(selectLoc(loc(currentPid, asidePloc)))) + .seq(shuffleEffect(currentDeckLoc)) + .seq(ifElseEffect( + getTurnsPlayed(oppPid).eq(0), + drawCardsEffect(5), + drawCardsEffect(3) + )), + layoutFirst = createLayout({ + name = "Play as Trickster", + art = "art/T_Storm_Siregar", + xmlText=[[ + + + + + + ]] + }), + -- We should reshuffle, that would be a bit more fair here but who cares for testing. + effectSecond = sacrificeTarget().apply(selectSource()), + layoutSecond = createLayout({ + name = "Selected class", + art = "art/T_All_Heroes", + xmlText=[[ + + + + + + ]] + }), + turn = 1, + }) +end + +function setupGame(g) + registerCards(g, { + reshuffle_hand_skill(), + hidden_power_card_def(), + double_or_nothing_card_def(), + pet_monkey_card_def(), + stack_the_deck_ability_def(), + }) + + standardSetup(g, { + description = "Level 3 Trickster Class.", + --This defines the play order, only 2 players is supported at this time + playerOrder = { plid1, plid2 }, + --AI is optional but I always include it to have the option to test with an AI player + -- + ai = ai.CreateKillSwitchAi(createAggressiveAI(), createHardAi2()), + timeoutAi = createTimeoutAi(), + opponents = { { plid1, plid2 } }, + --This player object contains/creates all the information about each player. + --In this example the data is imported from the player/character (ie the class, health, and starting cards) + players = { + { + id = plid1, + init = { + --This is the part where the character data is imported + fromEnv = plid1 + }, + --Uncomment the line below to make this player an AI. One note about AI, it doesnt work in online games so make sure to comment it back out before uploading + --isAi = true, + --This sets how many cards a player has on thier first turn (player 1 normally has only 3) + -- startDraw = 3, + name = "Trickster", + avatar = "smugglers", + cards = { + --Since the starting deck cards/skills are predetermined, only buffs need to be here + buffs = { + --This sets how many cards a player draws at the end of the turn, normally 5 + drawCardsCountAtTurnEndDef(5), + --This handles the case where a player needs to discard a card at the start of their turn (discard effects/skills) + discardCardsAtTurnStartDef(), + --This is used to track the turn counter so the "Enrage" syste, triggers to try and end the game after the set number of turns. + fatigueCount(40, 1, "FatigueP1"), + chooseStart(), + }, + } + }, + { + id = plid2, + --Uncomment the line below to make this player an AI. One note about AI, it doesnt work in online games so make sure to comment it back out before uploading + --isAi = true, + --This sets how many cards a player has on thier first turn (player 1 normally has only 3) + -- startDraw = 5, + init = { + --This is the part where the character data is imported + fromEnv = plid2 + }, + cards = { + --Since the starting deck cards/skills are predetermined, only buffs need to be here + buffs = { + --This sets how many cards a player draws at the end of the turn, normally 5 + drawCardsCountAtTurnEndDef(5), + --This handles the case where a player needs to discard a card at the start of their turn (discard effects/skills) + discardCardsAtTurnStartDef(), + --This is used to track the turn counter so the "Enrage" syste, triggers to try and end the game after the set number of turns. + fatigueCount(40, 1, "FatigueP2"), + chooseStart(), + }, + } + }, + } + }) +end + + +function endGame(g) -- more info on this later +end + + + + function setupMeta(meta) + meta.name = "trickster" + meta.minLevel = 3 + meta.maxLevel = 3 + meta.introbackground = "" + meta.introheader = "" + meta.introdescription = "" + meta.path = "C:/Program Files (x86)/Steam/steamapps/common/Hero Realms/Custom Scripts/trickster.lua" + meta.features = { +} + + end \ No newline at end of file diff --git a/Lua_documentation.md b/Lua_documentation.md index a5f7101..bcc5eb1 100644 --- a/Lua_documentation.md +++ b/Lua_documentation.md @@ -1,6 +1,47 @@ -Lua Effect Documentation v1.1 - -Changes in 1.1 +# Lua Effect Documentation v1.6 +WARNING: There's a special random algorithm inside the engine. It is impossible to use lua or any other random inside the script without breaking a game while running your script. + + +## Changes in 1.6 +1. Coop section added +2. backgrounds list added +# Changes in 1.5 +1. Bug fixed where some layouts were not working with string expressions +2. getCardNameStringExpression(selector) added, returning name of the first card in selector, so you can use it to display card name whereever you want as string expression +3. getFactionCount(selector) added, counting number of distinct factions in selector +4. Reserve should now be working whenever you have any cards in reserve scroller +5. skillSacrificePloc exposed. Contains cards sacrificed from skills scroller (abilities etc) +6. moveToBottomDeckTarget(isHidden, order, playerExpression) added, allowing bottomdecking to any player’s deck +7. createCardFromIdEffect(id, locExpression) added allowing creating a card using card string id. Like “influence” +8. createAbilitySlot(t) now accepts t.displayText to be displayed when buffed card is zoomed in +9. winEndReason, concedeEndReason extra end game reasons exposed over API +10. appendEffect(effect) added, allowing to send an effect to the end of current effects queue (needed for endturneffect) +11. .OrBool(boolExpression) added to allow chaining simple bool check along with card bool expressions. selector.where(isChampion().And(isCardStunned().OrBool(yourBookCheckHere))) +12. player.cards.reserve added. you may put reserve cards there in script init data structure, no extra scripting needed +13. reservePloc exposed via API +14. randomStringExpression(expressions) added, allowing to pick one random from the list provided +15. setPlayerNameExpressionEffect(name, player) added, so you can set player name using string expression +16. noHeal player slot added, when on player - they can’t be healed +17. healOverride player slot added. when on player - they get healed for 5 on their turn start instead of discarding a card +18. healInsteadOfEffect(effect, healAmount) effect added. It heals for healAmount if healOverride is on current player +19. controllerPid added - current card controller. Different from owner - player the card originated from +20. isCardWithSlot(slotKey) card bool expression added +21. isCardWithSlotString(slotKey, stringValue) - checks if card has a slot with a key and certain string value +22. isCardAffordableExt(extraGold) - same as isCardAffordable, but you may also specify amount if virtual gold added to you actual gold (usable for cases like +23. hasCardTag(tag) bool card expression added to check if card has given tag +# Changes in 1.4 +1. divide(intDividendExpression, intDivisorExpression) added +2. selector.avg(intCardExpression) added +3. showTextExpressionEffect added +4. Dynamic layouts - see “Dynamic string generation” section +# Changes in 1.3 +1. getHeroLevel intExpression added +2. getCardPrintedCost intExpression added +3. intExpression players slots added +# Changes in 1.2 +1. More ability triggers +2. Custom ability triggers +# Changes in 1.1 1. IMPORTANT - set of mandatory player buffs changed. Check updated example scripts and update your scripts correspondingly 2. transformTarget now properly changes card face on the board 3. nuUndoEffect renamed to noUndoEffect @@ -16,26 +57,112 @@ Changes in 1.1 13. coop scenarios development support (see coop_pirates_example.lua) 14. new card formatting option in “xmltext” field. See examples below. You may still use “text” for simple things, but for complex formatting - only xmltext 15. new arts available -Goal -To standardize naming of all lua effects to make it clear what kind of effect it is. -At a high level +# Requirements + +**Minimal requirements:** + +- Steam - no other platform supports development mode +- basic knowledge of script programming +- any text editor (notepad works just fine) + +**Optimal requirements:** + +- Steam - no other platform supports development mode +- LUA scripting language experience +- text editor with syntax highlight support (notepad++, sublime, vsCode) +# How to start +## Development +1. Have a text editor installed. Any editor works for this, but better grab one with LUA syntax highlight support. +2. Set up a folder for your scripts on hard drive. For best results, try not to include any special symbols in either directory name/path and file name. Do not rename or move the folder after creation, as path to the script on your hard drive is used for versioning. If your script changes path of file name, it will be recognised as a new script. +3. Put a sample script into that folder +4. in the app, open settings and navigate to game tab. There - set development mode to ON. NOTE: it’s visible only on desktop. +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688058452469_image.png) + + + +4. Now you have “Develop” menu item in main menu +5. It opens Script Editor screen +6. There, click “Open” button, navigate to the folder you created on step 2 and select a script to use. +7. Only one script is loaded into editor and can be uploaded at a time +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688059017626_image.png) + +8. You may either click edit icon and script will open in your system default editor. Or you may load script into editor of your choice and it will be reloaded when you get back into script editor screen. +9. If script contains errors, error icon will appear, showing full error text when clicked. +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688059744029_image.png) + +10. Play button lets you test your script in Pass’n’Play mode. +11. When you think your script is ready for the world - tap upload icon and script upload screen will open +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688059791833_image.png) + +12. Fill all the values, making sure they are informative enough. +13. Script name will appear on online lobby tiles, and longer description will appear on script selection screen and in in-game menu. +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688060019008_image.png) + + + +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688060074122_image.png) + + + + + + + + + + + + + + + + -- all lua functions will start with a lowercase -- card definitions should only call other lua functions, which will delegate to c# as required -Other goals: -- as few top level functions as possible -- try to use methods to free the user from having to remember how to deal with similar related types, e.g. intexpression, intcardexpression etc -Overview + + + + + +14. Choose an image for your scriptget +15. Click underlined EULA link and either accept or decline it. +16. Upload button appears only if you have accepted the terms. +## Using your scripts online +1. After upload, you may challenge anyone with your script. +2. To do that, go to online challenge screen, tap game type selector button and choose “Custom” option +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688060197533_image.png) + +3. Fill you opponent name, other options and click “Challenge” button +4. On “Select Script” screen, click “My scripts” tile and there you find scripts you uploaded. +## Verification and Favorities + +When you successfully uploaded your script, there’s a chance it still contains bugs, so initially script has “Not verified” status. +To get verified, you need to play a full game to the win on one of the sides. +Only after game finishes gracefully, script is marked as Verified and may now be added Favorities by other players. + +To add script to favorities, tap on ended custom script game and select “Add to Favorities” option. + +![](https://paper-attachments.dropboxusercontent.com/s_8F32093F9D1BC25CDB861F9978E50FF5798A412C3D7094A6FD010F642CC00894_1688060845796_image.png) + + +Note that you can’t fav your own script. + +## Versioning + +After some time, you might want to update your script to balance it or fix some bugs that were not found during initial playtesting. +To do that, just upload your fixed script version from the same hard drive location as original one. +New version needs another round of verification. + +# Script Engine Reference The rule engine is built using a few concepts that are combined together to form the full effect system that is used to implement all the cards and effects in the game -Selectors are used to select cards, e.g. cards in the market row, cards in hand, champions with 3 or more defense etc. +Selectors are used to select cards, e.g. cards in the market row, cards in hand, champions with 3 or more defence etc. Effects are used to change the game state, e.g. sacrifice, add combat etc. @@ -64,7 +191,7 @@ Arkus’s Ally Ability: - Effect: Gain 6 health -Players +## Players Predefined constants to select players: @@ -73,22 +200,32 @@ nextPid - next player to take a turn oppPid - opponent of current player nextAllyPid - next ally player nextOppPid - next opponent +ownerPid - card owner +controllerPid - current card controller + +## End game reason + winEndReason - win + concedeEndReason - concede -Locations + +## Locations To specify a location: loc(player, ploc), where ploc is (ends with Ploc or player loc): -inPlayPloc -discardPloc -handPloc -castPloc -deckPloc -buffsPloc -skillsPloc +inPlayPloc - champions location +discardPloc - discard pile +handPloc +castPloc - played non-champion cards (items and actions) +deckPloc - deck pile +buffsPloc - special location, where buff cards should be created +skillsPloc - skills, abilities and armors around avatar revealPloc - common reveal location for any cards that get revealed to both players myRevealPloc - used when you need to reveal cards only to player +sacrificePloc - where cards go after sacrifice +skillSacrificePloc - where ability and any other cards from skills scroller go when sacrificed +reservePloc - reserve cards chest also, predefined locs without a need to specify player (ends with -Loc): @@ -106,7 +243,7 @@ currentBuffsLoc currentSkillsLoc currentRevealLoc -Examples: +**Examples**: -- these two are the same @@ -114,38 +251,64 @@ Examples: loc(currentPid, inPlayPloc) -Factions +## Factions wildFaction guildFaction necrosFaction imperialFaction -BoolCardExpressions (TODO: check if all expressions are present) -They all start with isCard- +Setting card faction: + + local card = createActionDef({ + id = "dragon_fire", + name = "Dragon Fire", + tags = { galleryCardTag, callToArmsSetTag }, + types = { dragonType }, + factions = { imperialFaction }, + acquireCost = 7, + + +## BoolCardExpressions (TODO: check if all expressions are present) + +They all start with isCard*() and used to check cards state. Mostly used when filtering selectors isCardExpended() isCardStunned() isCardStunnable() isCardChampion() isCardAction() -isCardFaction(Faction.Wild) -isCardType(“Knife”) -> checks type and subtype +isCardFaction(*imperialFaction*) +isCardType(*knifeType*) -> checks type and subtype isCardName(“thief_throwing_knife”) → checks card id isCardAtLoc(ploc) → true if target is in the passed CardLocEnum isGuard() +isCardHasController(playerExpression) +isCardFactionless() +isCardAffordable() - checks if you have enough gold to buy it considering any effective cost changes +isCardSkill() +isCardAbility() +isCardAquirable() - checks if you are able to purchase it if you had enough gold +isCardSelf() - checks if card is the origin of an effect executing +isCardDamaged() +isCardExpended() +isCardWithSlot(slotKey) +isCardWithSlotString(slotKey, stringValue) - checks if card has a slot with a key and certain string value +isCardAffordableExt(extraGold) - same as isCardAffordable, but you may also specify amount if virtual gold added to you actual gold. +hasCardTag(tag) bool card expression added to check if card has given tag operators -.And(isCardXX()), .Or(isCardXX()), .invert() +.And(isCardXX()) - bool and + .Or(isCardXX()) - bool or + .invert() - bool not + .OrBool(boolExpression) - allows to or with a simple boolExpression, not card one Note that these are uppercase, as and/or are keywords in lua -constBoolExpression(value) returns a BoolExpression that always returns the value it was initially passed. - -Usage +**Usage** Bool card expressions are mostly used to filter out data from selectors (see below) -Examples: +**Examples**: -- selects cards from opponent's in play, which are not expended and can be stunned @@ -153,7 +316,8 @@ Examples: selectLoc(loc(oppPid, inPlayPloc)).where(isCardExpended().invert().And(isCardStunnable())) -Selectors TODO: check if there are more predefined selectors +## Selectors +## TODO: check if there are more predefined selectors Selectors are used to start a chain of selecting cards. All selectors start with the selectX: @@ -161,8 +325,11 @@ selectLoc(loc): select cards in a location selectSource(): select source card selectTargets(): selects target from targeted effect or cardeffect ability trigger. selectOppStunnable(): select all opponent’s champions that can be stunned +saveTarget(key) - saves targets under key name +selectSavedTargets(key) - selects saved targets +clearTargets(key) - clears targets for the key -Filtering +**Filtering** selector.where(BoolCardExpression) to filter by a property on each card @@ -173,7 +340,7 @@ Example: selectLoc(loc(oppPid, inPlayPloc)).where(isCardExpended().invert().And(isCardStunnable())) -Chaining +**Chaining** selector1.union(selector2) to return the union of both selectors. @@ -183,7 +350,7 @@ selector1.union(selector2) to return the union of both selectors. selector.exclude(selectSource()) to exclude the source card -Ordering +**Ordering** selector.order(intcardexpression) to order by the provided value .orderDesc(intcardexpression) @@ -193,7 +360,7 @@ selector.order(intcardexpression) to order by the provided value selector.take(n) to take the first X cards .take(intExpression) -Conversions +**Conversions** To Int: .count() @@ -202,14 +369,17 @@ or You can use .take(1).sum(getCardCost()) to get the cost of a single card, e.g. for comparison -Predefined selectors +.avg(intCardExpression) - returns average value of int card values for the selector + +**Predefined selectors** We predefine commonly used selectors in lua selectCurrentChampions() selectNextChampions() +sacrificeSelector(selector) - special selector, to be used to get cards that can be sacrificed. E.g. if you have a noSacrifice slot on a card this selector will exclude it from the list. -Examples +**Examples** to select all stunned champions @@ -224,7 +394,7 @@ action cards in trade row selectLoc(centerRowLoc).where(isCardAction()) -IntExpression TODO: check if we have all int expressions specified here +## IntExpression TODO: check if we have all int expressions specified here Most int expressions start with get- @@ -232,20 +402,28 @@ From selector: selector.count() getCounter(counter) getPlayerHealth(pid) -getPlayerCombat(pid) +getHeroLevel(pid) +getCurrentPlayerCombat() +getOppPlayerCombat() getPlayerGold(pid) getPlayerDamageReceivedThisTurn(pid) getTurnsPlayed(pid) getSacrificedCardsCounter() +getRankedFactionCount(rank) - returns number of faction cards for current player for the given rank. Where rank 1 faction has most cards. +getChampionsPlayedThisTurn() +getFactionCount(selector) - counting number of distinct factions in selector + constant int: const(int) Arithmetic -.add(intExpression), .multiply(intExpression), negate() +.add(intExpression), .multiply(intExpression), .negate() use negate with add to subtract -Conversions: +divide(intDividendExpression, intDivisorExpression) + +Comparison: .gte(intExpression or int), .lte(intExpression or int), .eq(intExpression or int) Used when you need to check for something - returns BoolExpression @@ -258,47 +436,123 @@ Operations: minInt(intExp, intExp) - returns min of two values -StringExpression +## StringExpression There are several places where we can show dynamic strings. To create a dynamic string, just use the format function. -format(“{0}”, { int, intexpressoin etc}) +**String format** +format(“{0}”, { int, intexpression etc}) + + format("{0} {1}.", { randomNameTitle, randomName}) +**Random string expression** +randomStringExpression(expressions) - returns random string expression from provided array -IntCardExpression + local randomNameTitle = randomStringExpression({ + "Mr.", + "Mrs.", + "Ms.", + "Dr.", + "Prof." + }) + +**getCardName** +getCardNameStringExpression(selector) - returns name of the first card from selector + +**getCurrentGender** + + getCurrentGender() - returns either male of female string + +**ifElseString** +ifElseString(boolExpression, yesStringExpression, noStringExpression) + + ifElseString(condition, + toStringExpression("an"), + toStringExpression("a")) + +**S****witch string** +switchString(intExpression, { string items array} +Returns corresponding value based on incoming int + + switchString( + intExprValue, + { + stringItem(8, "Wild"), + stringItem(4, "Imperial"), + stringItem(2, "Necros"), + stringItem(1, "Guild"), + }) +## Dynamic string generation + +Sometimes you might need to calculate text on a card in runtime. + +Any text on a card, a choice or other text fields can now be dynamic. + +Examples: + +Targeted effect: + + local val = minInt(toIntExpression(v), currentHand().count()) + return pushTargetedEffect({ + desc = format("Discard {0} card(s)", { val }), + +Layout: + + layout = layoutCard({ + title = "Heal Myself", + art = "art/cleric_lesser_resurrect", + text = format("{{{0} health}}", { currentV }), + flavor = format("Health: {0}", { getPlayerHealth(currentPid) }) + }) + +Choices: + + layout = createLayout({ + name = "Dispersion", + art = "art/classes/alchemist/dispersion", + frame = "frames/alchemist_frames/alchemist_skill_cardframe", + text = format("Gain {0} ", { getRankedFactionCountInPlay(1).add(getRankedFactionCountInPlay(2)) }) + }), + +Certain effect: + + simpleMessageExpressionEffect(format("{0} Summoning Power", {getCounter("summon_meter")})) +## IntCardExpression These start with getCard- getCardCost() +getCardPrintedCost() getCardHealth() -BoolExpression +## BoolExpression hasClass(playerExpression, heroClass) hasPlayerSlot(playerExpression, slot) constBoolExpression(value) - constant value +isWinner(playerExpression) - checks if player is a winner, useful for after game ends effects -Negate +**Negate** .invert() to negate, e.g. isExpended().invert() returns false if the card is expended (not is a reserved word in lua) -Combining +**Combining** .And(boolExp), .Or(boolExp) -Extended examples TODO: add more examples for selectors +## Extended examples TODO: add more examples for selectors Select all cards in trade row where their cost is greater than the players’ gold -selectTradeRow().where(getCardCost().gt(getPlayerGold(currentPid))) +selectLoc(centerRowLoc).where(getCardCost().gt(getPlayerGold(currentPid))) Note that getCardCost() is a IntCardExpr, the gt() accepts an IntExpr, which should get upgraded to an IntCardExpr, and the entire expression becomes a boolcardexpr -Effects -Simple Effect TODO: check if all available effects are present here +# Effects +## Simple Effect TODO: check if all available effects are present here All simple effects end with the word -Effect. These require no input from user. @@ -319,14 +573,16 @@ Predefined simple effects: Game state effects -- gainCombatEffect(int(expression)) -- gainGoldEffect(int(expression)) -- gainHealthEffect(int(expression)) -- drawCardsEffect(int(expression)) -- drawToLocationEffect(intExpression, locExpression) - draws count cards to location -- hitOpponentEffect(int(expression)) - deals damage to opponent -- hitSelfEffect(int(expression)) - deals damage to self -- oppDiscardEffect(int(expression)) - makes opponent discard cards when their turn starts +- addDisplayDiscardEffect(oppPid, const(1)) - adds a number to discard display. Not actually requires anyone to discard. Used for cards like Distracted Exchange and other similar thief skills +- gainCombatEffect(int/expression) +- gainGoldEffect(int/expression) +- gainHealthEffect(int/expression) +- gainToughnessEffect(int/expression) +- drawCardsEffect(int/expression) +- drawToLocationEffect(intExpression, locExpression, order) - draws count cards to location. Order possible values -1, 0. Use one or another depending on deck you’re trying to bottom card to. Bottoms for deck and for discard differ, because cards facing is opposite. +- hitOpponentEffect(int/expression) - deals damage to opponent +- hitSelfEffect(int/expression) - deals damage to self +- oppDiscardEffect(int/expression) - makes opponent discard cards when their turn starts - createCardEffect(cardId, location) - creates a card at location - endGameEffect(winnerId, endReason) - ends game with a winner and given reason - setMarketLengthEffect(int) - sets length of market row to the value @@ -335,20 +591,21 @@ Game state effects - shuffleEffect(locExr) - shuffles all cards contained in the passed location - shuffleTradeDeckEffect() - shuffles trade deck -Conditionals +**Conditionals** conditions are bool expressions - ifEffect(condition, simpleEffect) - if condition is true, executes the effect - ifElseEffect(condition, yesSimpleEffect, noSimpleEffect) - if condition is true executes yes effect, otherwise no effect -Specials +**Specials** - noUndoEffect() - prevents user from undoing their actions from this point - nullEffect() - does nothing - can be used to do nothing in certain cases +- browseCards({ locExpr = …, header = “Header text”, oppHeader = “Header text for opponent”}) - allows to browse cards from a location until dismissed -Fatigue counter +**Fatigue counter** fatigueCount(startingTurn, delta, name) - fatigue counter @@ -409,17 +666,22 @@ Control flow Animations / text -- animateShuffleEffect(player) - plays deck shuffle animation +- animateShuffleDeckEffect(player) - plays deck shuffle animation - showOpponentTextEffect(text) - shows text to the opponent - hideOpponentTextEffect(text) - hides previously shown opponent text - showCardEffect(layout) - you may build a layout to be show to player to several seconds or until clicked. createLayout("avatars/troll", "Injured Troll I", "{3 combat}", "The troll gets angry") - showTextEffect(text) - shows text on the screen - waitForClickEffect(playerText, oppText)- shows text to corresponding player and waits for click +- waitForClickExpressionEffect(playerStringExpression, oppTextExpression)- same as previous but with string expression support shows text to corresponding player and waits for click. - customAnimationEffect({id, player}) - plays a custom animation with “id” for “player” - cardAbilityAnimationEffect({ id, layout }) - plays card animation with “id” style and “layout” card face +- setPlayerAvatarEffect(avatar, playerExpression) - changes avatar for a hero +- setPlayerNameEffect(name, playerExpression) - changes name of a hero +- showTextExpressionEffect(format, { expressions }) - shows flying text + showTextExpressionEffect(format("10/3 result: {0}", {divide(10, 3)})) -Card Effects TODO: check if all effects are listed +## Card Effects TODO: check if all effects are listed Card effects require a list of cards to be supplied, normally via a selector. They end with -Target @@ -435,7 +697,8 @@ Game state card effects - expendTarget() - expends targets - grantHealthToTarget(intExpression) - grants +X defense to target champions - moveTarget(locExr) - moves targets to location -- moveToTopDeckTarget(isHidden) - moves targets to top of current player’s deck. Set isHidden to true if you don’t want card’s name reflected in log +- moveToBottomDeckTarget(isHidden, order) - order possible values -1, 0. Use one or another depending on deck you’re trying to bottom card to. Bottoms for deck and for discard differ, because cards facing is opposite. +1. moveToBottomDeckTarget(isHidden, order, playerExpression) added, allowing bottomdecking to any player’s deck - nullTarget() - does nothing - playTarget() - plays targets - prepareTarget() - prepares targets @@ -447,7 +710,7 @@ Random card effects - probabilityTarget({ chance, onSuccessTarget, onFailureTarget}) - executes onSuccess target effect with chance probability, otherwise executes onFalureTarget effect. -- randomTarget(cardEffect, intExpression) - passes X random targets to underlying card effect +- randomTarget(intExpression, cardEffect) - passes X random targets to underlying card effect Animation card effects @@ -472,7 +735,7 @@ We can also convert a simple effect into a card effect by ignoring the target: ignoreTarget(simpleeffect) -Targeted Effects +## Targeted Effects Targeted effects can be pushed into the effect queue with pushTargetedEffect(). They will prompt the player to select a target, and then execute the provided card effect on the targets. @@ -495,9 +758,9 @@ Example to prompt the user to sacrifice a card in hand: }) -Special target effects +## Special target effects -promptSplit effect +**promptSplit effect** allows to sort cards from a location. Used for track and channel abilities. @@ -508,25 +771,31 @@ Example: selector = selectLoc(currentRevealLoc), take = const(4), -- number of cards to take for split sort = const(2), -- number of cards to be sorted for ef2 + minTake = const(1), -- number of mandatory cards moved to ef2 ef1 =moveToTopDeckTarget(true), -- effect to be applied to cards left ef2 = discardTarget(), -- effect to be applied to sorted cards header = "Careful Track", -- prompt header description = "Look at the top four cards of your deck. You may put up to two of them into your discard pile, then put the rest back in any order.", + rightPileDesc = "Text explaining right pile rules, e.g. ordering", pile1Name = "Top of Deck", pile2Name = "Discard Pile", eff1Tags = { buytopdeckTag }, eff2Tags = { cheapestTag } })) -Choice Effects +## Choice Effects The user will make a choice, then a SimpleEffect will be executed for the chosen item. + pushChoiceEffect({ choices={ { - id="choicelayoutid", - effect=gainCombatEffect(5) + layout = ... + effect=gainCombatEffect(5), + title = "Extra text displayed over a choice item", + condition = constBoolExpression(true), -- if condition is not fulfilled - choice would be grayed out + tags = { tag1, tag2, tag3 } }, { effect = healPlayerEffect(currentPid, v), @@ -553,16 +822,27 @@ The user will make a choice, then a SimpleEffect will be executed for the chosen } }) +Optionally, you may use pushChoiceEffectWithTitle + + + pushChoiceEffectWithTitle({ + choices = ..., + upperTitle = "Extra text displayed above choice items", + lowerTitle = "Extra text displayed below choice items" + }) + If you are using the field "text" in createLayout ({ }) and want to display the gold/combat/health icon in the description use {X gold} ; {X combat} ; {X health} where X - count of points + +![](https://paper-attachments.dropboxusercontent.com/s_31F66394ABC2465F353C26C1171909EFCCC7A751E53E3C241560ABB665EB7125_1647011256237_ima22ge.png) + + Otherwise, use “xmltext” field. Example: -![](https://paper-attachments.dropbox.com/s_31F66394ABC2465F353C26C1171909EFCCC7A751E53E3C241560ABB665EB7125_1647011256237_ima22ge.png) - layout = createLayout({ title = "Hunter's Cloak", art = "icons/ranger_hunters_cloak", @@ -573,24 +853,9 @@ Example: Note that choice effects can be dynamically generated using thecreateLayout() function, which returns a dynamic layout for the art. All the fields of createLayout accept StringExpression. -Target Player effects (not implemented yet) - -In coop, we might need to target players. - - - pushTargetPlayerEffect({ - mode = targetPlayers, - effect = playerGainHealthEffect(targetPid, 5) - }) - -targetPid will be defined within the context of a target player effect. +## Storytelling effects -mode can only be targetPlayers for now. - - -Storytelling effects - -Speech bubble effect +**Speech bubble effect** Shows a speech bubble for defined player, which may wait to be dismissed or not. showSpeechBubbleEffect({ @@ -599,84 +864,136 @@ Shows a speech bubble for defined player, which may wait to be dismissed or not. waitForClick=constBoolExpression(false) }) -Story tell effect effect with portrait +**Story tell effect effect with portrait** Shows an avatar sayng a string. storyTellEffectWithPortrait("lys__the_unseen", "Delicious…") + leftStoryTellEffectWithPortrait("lys__the_unseen", "Delicious…") -- shows portrait on left side -Story lines effect +**Story lines effect** Shows strings one after another. storyLinesEffect({"You’ve defeated the Necros but lost many allies in the battle.", "You leave the temple, but the surrounding catacombs are like a maze.", "Without the captive Priest for a guide, you quickly become lost." }) -Creating abilities + +**Dramatic pause effect** +Does nothing for seconds. Useful when you need a dramatic pause within character dialog + + dramaticPauseEffect(seconds) +# Creating abilities Abilities link effects with their trigger times. -createAbility({id, trigger, effect, cost, activations, check}) -id: unique id of the ability (within a card) + createAbility({ + id = "uniqueAbilityId", + trigger = startOfTurnTrigger, + effect = nullEffect(), -- a simple effect to execute + cost = noCost, -- noCost by default + activations = singleActivation, -- single by defaule + check = constBoolExpression(true), -- true by default + promptType = showPrompt -- noprompt by default, + allyFactions { guildFaction, wildFaction } -- needs a guild or wild ally in play to activate + }) -trigger: one of +**Ability Triggers** startOfTurnTrigger - endOfTurnTrigger + oppStartOfTurnTrigger endOfOppTurnTrigger oppStartOfTurnTrigger autoTrigger - automatic trigger, like death cultist's expend ability has auto trigger onExpendTrigger - onSacrificeTrigger - triggers this trigger on sacrificed cards - onSacrificeGlobal - triggers all cards with this trigger - onStunTrigger - triggers this trigger on sacrificed cards - onStunGlobal - triggers all cards with this trigger + onSacrificeTrigger - pulls this trigger for sacrificed card only + onSacrificeGlobalTrigger - pulls this trigger for all cards when any card is sacrificed + onStunTrigger - pulls this trigger for stunned card only + onStunGlobalTrigger - pulls this trigger for all cards when any card is stunned uiTrigger - user will need to manually trigger it - onAcquireTrigger - triggers this trigger on sacrificed cards - onAcquireGlobal - triggers all cards with this trigger + onAcquireTrigger - triggers this trigger on acquired card + onAcquireGlobalTrigger - triggers all cards with this trigger onPlayTrigger - triggers every time you play any card, not just the card with the onPlay ability startOfGameTrigger endOfGameTrigger onZeroHealthTrigger - triggers when player's health becomes 0 or below + deckPreShuffleTrigger - triggers before player deck shuffled deckShuffledTrigger - triggers when player deck shuffled onLeavePlayTrigger - triggers when champion leaves InPlay area + onDiscardTrigger - triggers from card gets into discard pile + gainedHealthTrigger - current player gains health (only gains) + oppGainedHealthTrigger - opponent gained health + onMarketChangedTrigger - market changed + onDiscardTrigger - when a card discarded + onCreateTrigger - a card created + oppCardDrawnTrigger - opp drew a card -effect: an effect to run when triggered +**Custom ability triggers** +You may now create custom ability triggers -cost: requirements to use the effect, set if required; to give an ability multiple costs, such as expending and also paying gold, use combineCosts(t), passing it a table that contains each of the desired costs, e.g. combineCosts({ expendCost, goldCost(2) }) + customAbilityTrigger = "customAbilityTrigger" + +You may specify it as a trigger for any ablity. +Then you may fire it at any moment: + + fireAbilityTriggerEffect(customAbilityTrigger) -- fire trigger for all cards in play + + fireAbilityTriggerForTarget(customAbilityTrigger) -- fire trigger for targets - use with either .apply(selector) or as part or targeted effect + +Usage on an ability + + trigger = abilityTrigger(customAbilityTrigger) + +**Cost** + +Requirements to use the effect, set if required; to give an ability multiple costs, such as expending and also paying gold, use combineCosts(t), passing it a table that contains each of the desired costs, e.g. combineCosts({ expendCost, goldCost(2) }) - expendCost - sacrificeSelfCost - goldCost(value) - sacrificeSelfCost -- costAnd({ expendCost, goldCost(value) }) +- combineCosts({ expendCost, goldCost(value) }) -activations: set if required +**Activations** +Set if required - singleActivation: normally used with auto abilities to indicate that the ability can only be activated once per turn. Default. - multipleActivations: used with expend abilities to indicate that it can be activated multiple times per turn. -promptType: set if you want to show confirmation card before executing ability. +**PromptType** +Set if you want to show confirmation card before executing ability. - noPrompt - default - showPrompt - enables prompt -priority: trigger priority - when multiple same trigger abilities fire, they are executed in priority order from highest to lowest. 0 by default. +**PlayAllType** +Determines what happens when player clicks play all with this ability on a card in hand -layout: layout to be displayed when activated on prompt + noPlayPlayType -- card won’t be played as part of play all + blockPlayType -- blocks using play all (elven gift, dark reward) + playFirstPlayType -- has priority when using play all + normalPlayType -- card will be played normally -check: bool expression to decide if the ability can be triggered +**Priority** +Trigger priority - when multiple same trigger abilities fire, they are executed in priority order from highest to lowest. 0 by default. -allyFactions: used for Ally Abilities, which can only activate if a card of the specified faction(s) is also in play; required for createUiAllyAbility() and createAutoAllyAbility() +**Layout** +Layout to be displayed when activated via prompt +**Check** +Bool expression to decide if the ability can be triggereautd -Creating CardEffectAbilities +**AllyFactions** +Used for Ally Abilities, which can only activate if a card of the specified faction(s) is also in play; required for createUiAllyAbility() and createAutoAllyAbility() -Standard Abilities can have the onAcquire trigger, which makes them activate whenever any card is acquired (regardless of destination), which can make it difficult to have the ability affect the acquired card, as the onAcquire ability does not have a reference to it. If you want to create an ability that will always be able to affect the specific card that was acquired, you need to give the Card Def a CardEffectAbility. The createDef() function accepts a cardEffectAbilities table in its table parameter, though the abilities table is still required, even if a card has a CardEffectAbility but no Ability: - card = createDef({ +## Creating CardEffectAbilities + +Standard Abilities can have the onAcquire trigger, which makes them activate whenever *any* card is acquired (regardless of destination), which can make it difficult to have the ability affect the acquired card, as the onAcquire ability does not have a reference to it. If you want to create an ability that will always be able to affect the specific card that was acquired, you need to give the Card Def a CardEffectAbility. The createDef() function accepts a cardEffectAbilities table in its table parameter, though the abilities table is still required, even if a card has a CardEffectAbility but no Ability: + + card = createActionDef({ ... abilities = { }, cardEffectAbilities = { @@ -696,20 +1013,20 @@ There are several possible trigger timings for a CardEffectAbility: - postSelfAcquiredCardTrigger - like postAcquired, but only triggers for the card itself - playedCardTrigger - triggers after a card is played from hand. The trigger will happen after after played triggers, but before auto triggers happen. This way, if the played card trigger expends a champion, auto triggers will not fire. -CardEffectAbilities must use CardEffects, which require specific card targets. The CardEffectAbility will pass the acquired card as the target for the CardEffect assigned to it if the trigger is onAcquire or postAcquire; it will pass the card whose location changed if the trigger is onLocationChanged. You can also use selectTargets() to obtain the targets +CardEffectAbilities must use CardEffects, which require specific card targets. The CardEffectAbility will pass the acquired cmoveard as the target for the CardEffect assigned to it if the trigger is onAcquire or postAcquire; it will pass the card whose location changed if the trigger is onLocationChanged. You can also use selectTargets() to obtain the targets Additionally, for a card that contains a CardEffectAbility with the onLocationChanged trigger, when that card is first created, that ability will trigger, targeting every possible card. You can pass a Simple Effect to the CardEffectAbility, it will be auto converted to a CardEffect. -Creating Card Defs +## Creating Card Defs Finally, we put the abilities into a Card Def to allow us to actually create a card. `createDef({id, name, cardTypeLabel, playLocation, abilities, types, ….})` -id: a unique id for the card +id: a unique id for the card. Better add something, say, your username to the id to avoid breaking your script if we introduce a card with same name. “frostbite_randomnoob” name: Title of card @@ -727,7 +1044,7 @@ healthType: for champions, its either defenseHealthType (default) or healthHealt buffDetails: for global buffs, this creates the display when its clicked for details: - createBuffDetails({ + buffDetails = createBuffDetails({ art = "wizard_spell_components", name = "Soak", text = "+1 cost" @@ -745,7 +1062,7 @@ layout: if we want to dynamically generate the card layout, example code: See layout text chapter below for more formatting info. Alternatively, we can load a card texture as layout, e.g. -`loadLayoutTexture(``"``Textures/death_touch``"``)` +`loadLayoutTexture("Textures/death_touch")` cardTypeLabel: type of card, used only for display. Normally auto filled in. @@ -755,27 +1072,37 @@ Helper methods for creating card defs. These generally just fill in the required createActionDef() - function confused_apparition_carddef() - return createActionDef({ - id="confused_apparition", - name="Confused Apparition", - types={noStealType}, - acquireCost=0, + function dragon_fire_carddef() + local card = createActionDef({ + id = "dragon_fire", + name = "Dragon Fire", + tags = { galleryCardTag, callToArmsSetTag }, + types = { dragonType }, + factions = { imperialFaction }, + acquireCost = 7, abilities = { createAbility({ - id="confused_apparition_auto", - trigger= autoTrigger, - effect = ifEffect(selectLoc(currentInPlayLoc).where(isCardName("weak_skeleton")).count().lte(0), healPlayerEffect(oppPid, 1)) + id = "dragon_fire", + effect = gainCombatEffect(7).seq(drawCardsWithAnimation(1)), + cost = noCost, + trigger = onPlayTrigger, + tags = { gainCombatTag, draw1Tag, aiPlayAllTag }, + aiPriority = toIntExpression(300) + }), + createAbility({ + id = "dragon_fire_sacrifice", + layout = loadLayoutData("layouts/firstkickstarter/dragon_fire_sacrifice"), + effect = getFireballDamageEffect(4, "art/sets/promos1art/dragon_fire", "art/sets/promos1art/dragon_fire", false); + cost = sacrificeSelfCost, + trigger = uiTrigger, + promptType = showPrompt, + tags = { damageTag }, + aiPriority = toIntExpression(-1) }) }, - layout = createLayout({ - name = "Confused Apparition", - art = "art/T_Confused_Apparition", - frame = "frames/Coop_Campaign_CardFrame", - text = "Opponent gains 1 unless you have a Weak Skeleton in play." - }) - }) - end + layout = loadLayoutData("layouts/firstkickstarter/dragon_fire") + } + ) createChampionDef() @@ -806,6 +1133,29 @@ createChampionDef() }) end +createItemDef() + + function thief_trick_dice_carddef() + return createItemDef({ + id = "thief_trick_dice", + name = "Trick Dice", + types = { thiefType, weaponType, diceType }, + acquireCost = 0, + abilities = { + createAbility({ + id = "thief_trick_dice", + effect = drawCardsEffect(2).seq(forceDiscard(1)), + cost = noCost, + trigger = onPlayTrigger, + playAllType = blockPlayType, + tags = { drawTag, discardTag, aiPlayAllTag }, + aiPriority = toIntExpression(200) + }) + }, + layout = ... + }) + end + createBuffDef() createSkillDef() @@ -879,7 +1229,7 @@ createMagicArmorDef() createHeroAbilityDef() -Layout Text formatting +## Layout Text formatting To set up layout text, use xmltext field of layout table. Double square brackets allow multiline code for easier reading. @@ -897,7 +1247,7 @@ To set up layout text, use xmltext field of layout table. Double square brackets ![](https://paper-attachments.dropbox.com/s_3393425E83F8175532A1ABBAD0B8649F1F9A50A4DBF53772E500C4EB4C156AC7_1661177102391_spriteatlas.png) -Sprites: +**Sprites:** {shield} {combat} {combat_1} 1-9 {expend_1} @@ -914,7 +1264,7 @@ Sprites: {wild} {requiresHealth_40} 10 - 50 step 5 -Examples: +**Examples:** @@ -1142,7 +1492,7 @@ Examples: ]], ![](https://paper-attachments.dropbox.com/s_3393425E83F8175532A1ABBAD0B8649F1F9A50A4DBF53772E500C4EB4C156AC7_1664378926283_image.png) -Slots TODO: add slots info +## Slots TODO: add slots info There are two types of slots in the engine: @@ -1152,7 +1502,7 @@ There are two types of slots in the engine: Slots are customizable objects that can be attached to cards and players for various purposes. All slots have a lifespan, determined by expiration field. -Slot Expiry +**Slot Expiry** startOfOwnerTurnExpiry endOfOwnerTurnExpiry @@ -1160,30 +1510,50 @@ Slot Expiry leavesPlayExpiry linkedExpiry neverExpiry + leavesMarketExpiry -Card slots +## Card slots Card slots are used to change card properties in various ways or to set flags for further use. -Adding slot to a card +**Adding slot to a card** - addSlotToTarget(slot) + local slot = createSlot({ + id = noSacrificeSlot, + expiresArray = { neverExpiry } + // optional fields + expireEffect = nullEffect() // effect to execute on expire + }) + + addSlotToTarget(slot).apply(selector) As a card effect, it should either be used as part of targeted effect, or followed by .apply(selector) -Checking slots +**Checking slots** To check slots, tail selector with .where(isCardWithSlot(“stringSlotKey”)) Available slot types: -Factions Slot +*slotNoSacrifice - prevents card from being sacrificed (use sacrificeSelector(selector) to make use of it)* +*slotNoPlay - prevents card from being played* +*slotNoBuy - prevents card from being acquired* +*slotNoStun - card can’t be stunned* +*slotHealthChange - modifies champion’s def* +*slotCostChange - modifies card’s acquire cost* +*slotExpend - card is expended when it has this slot* +*slotFactions - extra factions for a card* +*slotAbility - adds an ability to a card* +*slotNoDamage - card can’t be damaged* +*slotNoAttack - card can’t be directly attacked* + +**Factions Slot** When this slot is attached to a card, it’s considered to have factions specified and this effect falls of when expiration event comes. createFactionsSlot({ necrosFaction, guildFaction }, { leavesPlayExpiry, endOfTurnExpiry }) -Ability Slot +**Ability Slot** When this slot attached to a card, it also has the ability in it. @@ -1197,9 +1567,13 @@ When this slot attached to a card, it also has the ability in it. tags = { } }) - createAbilitySlot(ab, { endOfTurnExpiry }) + createAbilitySlot({ + ability = ab, + expiry = { endOfTurnExpiry }}, + displayText = "Text to be displayed when zoomed on card with slot attached" + }) -Cost change Slot +**Cost change Slot** Modifies cost of a card it attached to. @@ -1207,13 +1581,13 @@ Modifies cost of a card it attached to. if discount is negative, card will cost more. -NoBuy Slot +**NoBuy Slot** When this slot is attached to a card in the market row, a card can’t be acquired even if you have enough gold. createNoBuySlot(expires) -Health change Slot +**Health change Slot** Modifies defense of a card it attached to. @@ -1221,36 +1595,76 @@ Modifies defense of a card it attached to. if discount is negative, card will cost more. -No stun slot +**No stun slot** createNoStunSlot(expiresArray) card with this slot can’t be stunned -No damage slot +**No damage slot** createNoDamageSlot(expiresArray) card with this slot can’t be damaged -Custom slots +**Guard switch slot** + + createGuardSwitchSlot(isGuard, expiresArray) + +card with this slot has guard set by isGuard parameter + +**Custom slots** Custom slots can be used to set some flag on a card - createSlot({ id = "abilityActivated", expiry = endOfTurnExpiry }) + createSlot({ id = "abilityActivated", expiry = { endOfTurnExpiry } }) -Player slots (coming soon) +## Player slots Player slots can be attached to players and then can be checked and used to make decisions. Player slot consists of a key and optional string value. For example, slots are used to mark a player as one who has a champion stunned this turn, so phoenix helm could activate. +**Creating player slot** + + createPlayerSlot({ key = "usedSkillThisTurn", expiry = { endOfTurnExpiry } }) + +**Adding player slot** + + addSlotToPlayerEffect(playerExpression, createPlayerSlot({ key = "usedSkillThisTurn", expiry = { endOfTurnExpiry } })) + +**Removing player slot** + + removeSlotFromPlayerEffect(playerExpression, "usedSkillThisTurn") + +**IntExpression Player slot** +Using this slot, you may store calculable values for a player and then use them for other things. + + createPlayerIntExpressionSlot(key, intExpression, expiresArray) + +You can get total sum of stored values using following function, which is an IntExpression. + + sumIntSlots(playerExpression, key) + +**Internal player slots** +*undamageablePlayerSlot - player can’t be damaged.* +untargetable*PlayerSlot - player can’t be damaged.* +noHealKey - player can’t be healed -Examples: TBD +## Custom indicator -AI +Indicator on player avatar is tied to custom player value. It would appear once value is more than 0 and disappear once value returns to 0. + + gainCustomValueEffect(intExpression) + getCustomValue(pid) - returns intExpression + gainCustomValueEffect(getCustomValue(pid).negate()) - sets value to 0 + +**Warning**: Monk uses this indicator for Tao Lu display. If your script uses custom value and Monk hero is playing it - it will change values unexpectedly. So make sure your general script code is not using it while allowing any hero to play it. + +## Examples: TBD +## AI There are 3 types of AI available in the app: @@ -1260,446 +1674,1224 @@ There are 3 types of AI available in the app: - createHardAi() -Image resources +# Coop +## Intro screen setup +**** +To customize intro screen, add this function to the end of the script and fill it with data required. +Find available backgrounds at corresponding arts section. + + function setupCoopIntro(encounter) + encounter.name = "Script name" + encounter.description = "Complex long description with update date" + encounter.minlevel = 2 + encounter.maxlevel = 24 + encounter.avatar = "avatars/chest" + encounter.heroname = "CustomHeroName" + encounter.backgroundPath = "scenariointrobackgrounds/journeys/confiscate" + encounter.features = { + encounter.feature("avatars/ambushers", "feature text1"), + encounter.feature("avatars/bard_01", "feature text2"), + encounter.feature("avatars/broyelin", "feature text3") + } + end +# Image resources Those can be art, frames, icons and avatars. Virtually all arts except frames are interchangeable. You may use avatars for choices and layouts, but not vice versa. Path should be set as a path, see exact values below. -Art +## Art Art folder is used to generate layouts. It determines the art on the card. Possible values: - art/dark_sign.png - art/gold_female_black.png - art/gold_female_dark.png - art/gold_female_pale.png - art/gold_female_white.png - art/gold_female_white_grayscale.png - art/gold_male_black.png - art/gold_male_dark.png - art/gold_male_pale.png - art/gold_male_white.png - art/Gorg__Orc_Shaman.png - art/T_All_Heroes.png - art/T_Angry_Skeleton.png - art/T_Arkus_Imperial_Dragon.png - art/T_Banshee.png - art/T_Barreling_Fireball.png - art/T_Basilisk.png - art/T_Battle_Cry.png - art/T_Battle_Resurrect.png - art/T_Black_Arrow.png - art/T_Black_Knight.png - art/T_Blazing_Fire.png - art/T_Blazing_Heat.png - art/T_Bless.png - art/T_Bless_Of_Heart.png - art/T_Bless_Of_Iron.png - art/T_Bless_Of_Soul.png - art/T_Bless_Of_Steel.png - art/T_Bless_The_Flock.png - art/T_Blistering_Blaze.png - art/T_Blow_Away.png - art/T_Borg_Ogre_Mercenary.png - art/T_Bounty_Collection.png - art/T_Bribe.png - art/T_Broadsides.png - art/T_Broelyn_Loreweaver.png - art/T_Broelyn_Loreweaver_Old.png - art/T_Calm_Channel.png - art/T_Capsize.png - art/T_Captain_Goldtooth.png - art/T_Careful_Track.png - art/T_Cat_Familiar.png - art/T_Channel.png - art/T_Chaotic_Gust.png - art/T_Charing_Guardian.png - art/T_Cleric_Brightstar_Shield.png - art/T_Cleric_Divine_Resurrect.png - art/T_Cleric_Everburning_Candle.png - art/T_Cleric_Female.png - art/T_Cleric_Hammer_Of_Light.png - art/T_Cleric_Holy_Resurrect.png - art/T_Cleric_Lesser_Resurrect.png - art/T_Cleric_MaleAlternate.png - art/T_Cleric_Mass_Resurrect.png - art/T_Cleric_Minor_Resurrect.png - art/T_Cleric_Phoenix_Helm.png - art/T_Cleric_Redeemed_Ruinos.png - art/T_Cleric_Rightous_Resurrect.png - art/T_Cleric_Shining_Breastplate.png - art/T_Cleric_Talisman_Of_Renewal.png - art/T_Cleric_Veteran_Follower.png - art/T_Close_Ranks.png - art/T_Command.png - art/T_Confused_Apparition.png - art/T_Crashing_Torrent.png - art/T_Crashing_Wave.png - art/T_Cristov_The_Just.png - art/T_Cron_The_Berserker.png - art/T_Crushing_Strength.png - art/T_Cult_Priest.png - art/T_Cunning_Of_The_Wolf.png - art/T_Dagger.png - art/T_Darian_War_Mage.png - art/T_Dark_Energy.png - art/T_Dark_Reward.png - art/T_Death_Cultist.png - art/T_Death_Threat.png - art/T_Death_Touch.png - art/T_Deception.png - art/T_Deep_Channel.png - art/T_Demon.png - art/T_Devastating_Blow.png - art/T_Devil.png - art/T_Devotion.png - art/T_Dire_Wolf.png - art/T_Distracted_Exchange.png - art/T_Divine_Resurrect.png - art/T_Domination.png - art/T_Dragon_Fire.png - art/T_Drench.png - art/T_Edge_Of_The_Moat.png - art/T_Elf.png - art/T_Elixir_Of_Concentration.png - art/T_Elixir_Of_Endurance.png - art/T_Elixir_Of_Fortune.png - art/T_Elixir_Of_Strength.png - art/T_Elixir_Of_Wisdom.png - art/T_Elven_Curse.png - art/T_Elven_Gift.png - art/T_Evangelize.png - art/T_Expansion.png - art/T_Explosive_Fireball.png - art/T_Fairy.png - art/T_Feisty_Orcling.png - art/T_Fierce_Gale.png - art/T_Fighter_Crushing_Blow.png - art/T_Fighter_Double_Bladed_Axe.png - art/T_Fighter_FemaleAlternate.png - art/T_Fighter_Hand_Scythe.png - art/T_Fighter_Helm_Of_Fury.png - art/T_Fighter_Helm_Of_Fury_2.png - art/T_Fighter_Jagged_Spear.png - art/T_Fighter_Male.png - art/T_Fighter_Rallying_Flag.png - art/T_Fighter_Seasoned_Shield_Bearer.png - art/T_Fighter_Sharpening_Stone.png - art/T_Fighter_Spiked_Pauldrons.png - art/T_Fighter_Sweeping_Blow.png - art/T_Fighter_Whirling_Blow.png - art/T_Fireball.png - art/T_Fire_Blast.png - art/T_Fire_Bomb.png - art/T_Fire_Gem.png - art/T_Fire_Staff.png - art/T_Fissure.png - art/T_Flame_Burst.png - art/T_Flawless_Track.png - art/T_Flesh_Ripper.png - art/T_Flood.png - art/T_Follower_A.png - art/T_Follower_B.png - art/T_Giant_Knight.png - art/T_Glittering_Spray.png - art/T_Glittering_Torrent.png - art/T_Glittering_Wave.png - art/T_Goblin.png - art/T_Gold.png - art/T_GrakStormGiant.png - art/T_Granite_Smasher.png - art/T_GroupTackle.png - art/T_Growing_Flame.png - art/T_HalfDemon_AntonisPapantoniou.png - art/T_HalfDemon_HellFire_ShenFei.png - art/T_Headshot.png - art/T_Headshot_1.png - art/T_Heavy_Gust.png - art/T_Heist.png - art/T_HitJob.png - art/T_Holy_Resurrect.png - art/T_Horn_Of_Calling.png - art/T_Hunting_Bow.png - art/T_Ignite.png - art/T_Influence.png - art/T_Intimidation.png - art/T_Knock_Back.png - art/T_Knock_Down.png - art/T_Kraka_High_Priest.png - art/T_Krythos_Master_Vampire.png - art/T_Large_Twister.png - art/T_Lesser_Resurrect.png - art/T_Lesser_Vampire.png - art/T_Life_Drain.png - art/T_Life_Force.png - art/T_Lift.png - art/T_Light_Crossbow.png - art/T_Longshot.png - art/T_Longsword.png - art/T_Lys_The_Unseen.png - art/T_Man_At_Arms.png - art/T_Man_At_Arms_Old.png - art/T_Maroon.png - art/T_Mass_Resurrect.png - art/T_Masterful_Heist.png - art/T_Master_Weyan.png - art/T_Maurader.png - art/T_Midnight_Knight.png - art/T_Mighty_Blow.png - art/T_Minor_Resurrect.png - art/T_Misdirection.png - art/T_Myros_Guild_Mage.png - art/T_Nature_S_Bounty.png - art/T_Orc.png - art/T_Orc_Grunt.png - art/T_Orc_Guardian.png - art/T_Orc_Riot.png - art/T_Paladin_Sword.png - art/T_Parov_The_Enforcer.png - art/T_Pick_Pocket.png - art/T_Pilfer.png - art/T_Pillar_Of_Fire.png - art/T_Piracy.png - art/T_Powerful_Blow.png - art/T_Practiced_Heist.png - art/T_Prayer_Beads.png - art/T_Precision_Blow.png - art/T_Prism_RainerPetter.png - art/T_Profit.png - art/T_Pure_Channel.png - art/T_Quickshot.png - art/T_Raiding_Party.png - art/T_Rake_Master_Assassin.png - art/T_Rally_The_Troops.png - art/T_Rampage.png - art/T_Ranger_Fast_Track.png - art/T_Ranger_Female_Alternate.png - art/T_Ranger_Flashfire_Arrow.png - art/T_Ranger_Honed_Black_Arrow.png - art/T_Ranger_Hunters_Cloak.png - art/T_Ranger_Instinctive_Track.png - art/T_Ranger_Male.png - art/T_Ranger_Pathfinder_Compass.png - art/T_Ranger_Snake_Pet.png - art/T_Ranger_Sureshot_Bracer.png - art/T_Ranger_Twin_Shot.png - art/T_Ranger_Unending_Quiver.png - art/T_Rasmus_The_Smuggler.png - art/T_Rayla_Endweaver.png - art/T_Recruit.png - art/T_Relentless_Track.png - art/T_Resurrect.png - art/T_Righteous_Resurrect.png - art/T_Rolling_Fireball.png - art/T_Ruby.png - art/T_Scorching_Fireball.png - art/T_Searing_Fireball.png - art/T_Searing_Guardian.png - art/T_Seek_Revenge.png - art/T_Serene_Channel.png - art/T_Set_Sail.png - art/T_Shadow_Spell_09.png - art/T_Shadow_Spell_09_Blue.png - art/T_Shadow_Spell_09_Green.png - art/T_Shambling_Dirt.png - art/T_Shield_Bearer.png - art/T_Shining_Spray.png - art/T_Shortsword.png - art/T_Shoulder_Bash.png - art/T_Shoulder_Crush.png - art/T_Shoulder_Smash.png - art/T_Skeleton.png - art/T_Skeleton_Blue.png - art/T_Skeleton_Green.png - art/T_Skillful_Heist.png - art/T_Sleight_Of_Hand.png - art/T_Small_Twister.png - art/T_Smashing_Blow.png - art/T_Smash_And_Grab.png - art/T_Smooth_Heist.png - art/T_Snapshot.png - art/T_Soothing_Torrent.png - art/T_Soothing_Wave.png - art/T_Soul_Channel.png - art/T_Spark.png - art/T_Spell_Components.png - art/T_Spider.png - art/T_Spiked_Mace.png - art/T_Splashing_Wave.png - art/T_Spreading_Blaze.png - art/T_Spreading_Flames.png - art/T_Spreading_Sparks.png - art/T_Steady_Shot.png - art/T_Stone_Golem.png - art/T_Stone_Guardian.png - art/T_Storm_Siregar.png - art/T_Street_Thug.png - art/T_Strength_In_Numbers.png - art/T_Strength_Of_The_Wolf.png - art/T_Sweltering_Heat.png - art/T_Swipe.png - art/T_Taxation.png - art/T_Theft.png - art/T_TheRot.png - art/T_The_Rot.png - art/T_Thief_Blackjack.png - art/T_Thief_Enchanted_Garrote.png - art/T_Thief_Female.png - art/T_Thief_Jewelers_Loupe.png - art/T_Thief_Keen_Throwing_Knife.png - art/T_Thief_Knife_Belt.png - art/T_Thief_Male_Alternate.png - art/T_Thief_Masterful_Heist.png - art/T_Thief_Sacrificial_Dagger.png - art/T_Thief_Shadow_Mask.png - art/T_Thief_Shadow_Mask_2.png - art/T_Thief_Silent_Boots.png - art/T_Throwing_Axe.png - art/T_Throwing_Knife.png - art/T_TimelyHeist.png - art/T_Tithe_Priest.png - art/T_Torgen_Rocksplitter.png - art/T_Track.png - art/T_Triple_Shot.png - art/T_Turn_To_Ash.png - art/T_TwinShot.png - art/T_Tyrannor_The_Devourer.png - art/T_Unify_Apsara.png - art/T_Varrick_The_Necromancer.png - art/T_Venom.png - art/T_Violent_Gale.png - art/T_Walking_Dirt.png - art/T_Weak_Skeleton.png - art/T_Well_Placed_Shot.png - art/T_Whirling_Blow.png - art/T_Wind_Storm.png - art/T_Wind_Tunnel.png - art/T_Wizard_Alchemist_S_Stone.png - art/T_Wizard_Arcane_Wand.png - art/T_Wizard_Blazing_Staff.png - art/T_Wizard_Female_Alternate.png - art/T_Wizard_Magic_Mirror.png - art/T_Wizard_Magic_Mirror_2.png - art/T_Wizard_Male.png - art/T_Wizard_Runic_Robes.png - art/T_Wizard_Runic_Robes_2.png - art/T_Wizard_Serpentine_Staff.png - art/T_Wizard_Silverskull_Amulet.png - art/T_Wizard_Spellcaster_Gloves.png - art/T_Wolf_Form.png - art/T_Wolf_Shaman.png - art/T_Word_Of_Power.png - art/T_Wurm.png - art/T_Wyvern.png - art/Promos1Art/Afterlife.png - art/Promos1Art/Bjorn_the_Centurion.png - art/Promos1Art/Bloodfang.png - art/Promos1Art/Crime_Spree.png - art/Promos1Art/Devotion.png - art/Promos1Art/Dragon_Fire.png - art/Promos1Art/Droga__Guild_Enforcer.png - art/Promos1Art/Galok__the_Vile.png - art/Promos1Art/Gorg__Orc_Shaman.png - art/Promos1Art/Kasha__the_Awakener.png - art/Promos1Art/Legionnaire.png - art/Promos1Art/Mobia__Elf_Lord.png - art/Promos1Art/Raiding_Party.png - art/Promos1Art/Ren__Bounty_Hunter.png - art/Promos1Art/Robbery.png - art/Promos1Art/The_Summoning.png - art/Promos1Art/Valius__Fire_Dragon.png - art/Promos1Art/Zombie.png - art/treasures/T_Bottle_Of_Rum.png - art/treasures/T_Bracers_Of_Brawn.png - art/treasures/T_Brillant_Ruby.png - art/treasures/T_Cleric_Elixir_Blue_Purple.png - art/treasures/T_Cleric_Elixir_Golden.png - art/treasures/T_Cleric_Elixir_Green.png - art/treasures/T_Fat_Cat_Familiar.png - art/treasures/T_Fighter_Elixir_Blue.png - art/treasures/T_Fighter_Elixir_Green.png - art/treasures/T_Fighter_Elixir_Red.png - art/treasures/T_Flaming_Longsword.PNG - art/treasures/T_Green_Potions_Large.png - art/treasures/T_Green_Potions_Medium.png - art/treasures/T_Hook_Weapon.png - art/treasures/T_Horn_Of_Command.png - art/treasures/T_Horn_Of_Need.png - art/treasures/T_Imperial_Sailor.png - art/treasures/T_Lightning_Longsword.png - art/treasures/T_Magic_Scroll_Souveraine.png - art/treasures/T_Parrot.png - art/treasures/T_Pirate_Cutlass.png - art/treasures/T_Ranger_Elixir_Orange.png - art/treasures/T_Ranger_Elixir_Red_Brownish.png - art/treasures/T_Ranger_Elixir_Yellow.png - art/treasures/T_Sharpened_Ruby.png - art/treasures/T_Ship_Bell.png - art/treasures/T_Ship_In_A_Bottle.png - art/treasures/T_Spiked_Mace_Of_Healing.png - art/treasures/T_Spiked_Mace_Of_Inspiration.png - art/treasures/T_Spyglass.png - art/treasures/T_Thief_Elixir_Black.png - art/treasures/T_Thief_Elixir_Red.png - art/treasures/T_Thief_Elixir_White.png - art/treasures/T_Treasure_Map.png - art/treasures/T_Trick_Dice.png - art/treasures/T_Wise_Cat_Familiar.png - art/treasures/T_Wizard_Elixir_Blue.png - art/treasures/T_Wizard_Elixir_Orange.png - art/treasures/T_Wizard_Elixir_Silver.png - - -Frames + art/gorg__orc_shaman + art/t_all_heroes + art/t_all_heroes_2 + art/t_angry_skeleton + art/t_arkus_imperial_dragon + art/t_banshee + art/t_barreling_fireball + art/t_basilisk + art/t_battle_cry + art/t_battle_resurrect + art/t_black_arrow + art/t_black_knight + art/t_blazing_fire + art/t_blazing_heat + art/t_bless + art/t_bless_of_heart + art/t_bless_of_iron + art/t_bless_of_soul + art/t_bless_of_steel + art/t_bless_the_flock + art/t_blistering_blaze + art/t_blow_away + art/t_borg_ogre_mercenary + art/t_bounty_collection + art/t_bribe + art/t_broadsides + art/t_broelyn_loreweaver + art/t_broelyn_loreweaver_old + art/t_calm_channel + art/t_capsize + art/t_captain_goldtooth + art/t_careful_track + art/t_cat_familiar + art/t_cat_familiar_fixed + art/t_channel + art/t_chaotic_gust + art/t_charing_guardian + art/t_cleric_brightstar_shield + art/t_cleric_divine_resurrect + art/t_cleric_everburning_candle + art/t_cleric_female + art/t_cleric_hammer_of_light + art/t_cleric_holy_resurrect + art/t_cleric_lesser_resurrect + art/t_cleric_malealternate + art/t_cleric_mass_resurrect + art/t_cleric_minor_resurrect + art/t_cleric_phoenix_helm + art/t_cleric_redeemed_ruinos + art/t_cleric_rightous_resurrect + art/t_cleric_shining_breastplate + art/t_cleric_talisman_of_renewal + art/t_cleric_veteran_follower + art/t_close_ranks + art/t_command + art/t_confused_apparition + art/t_crashing_torrent + art/t_crashing_wave + art/t_cristov_the_just + art/t_cron_the_berserker + art/t_crushing_strength + art/t_cult_priest + art/t_cunning_of_the_wolf + art/t_dagger + art/t_darian_war_mage + art/t_dark_energy + art/t_dark_reward + art/t_death_cultist + art/t_death_threat + art/t_death_touch + art/t_deception + art/t_deep_channel + art/t_demon + art/t_devastating_blow + art/t_devil + art/t_devotion + art/t_dire_wolf + art/t_distracted_exchange + art/t_divine_resurrect + art/t_domination + art/t_dragon_fire + art/t_drench + art/t_edge_of_the_moat + art/t_elf + art/t_elixir_of_concentration + art/t_elixir_of_endurance + art/t_elixir_of_fortune + art/t_elixir_of_strength + art/t_elixir_of_wisdom + art/t_elven_curse + art/t_elven_gift + art/t_evangelize + art/t_expansion + art/t_explosive_fireball + art/t_fairy + art/t_feisty_orcling + art/t_fierce_gale + art/t_fighter_crushing_blow + art/t_fighter_double_bladed_axe + art/t_fighter_femalealternate + art/t_fighter_hand_scythe + art/t_fighter_helm_of_fury + art/t_fighter_helm_of_fury_2 + art/t_fighter_jagged_spear + art/t_fighter_male + art/t_fighter_rallying_flag + art/t_fighter_seasoned_shield_bearer + art/t_fighter_sharpening_stone + art/t_fighter_spiked_pauldrons + art/t_fighter_sweeping_blow + art/t_fighter_sweeping_blow_fixed + art/t_fighter_whirling_blow + art/t_fighter_whirling_blow_fixed + art/t_fire_blast + art/t_fire_bomb + art/t_fire_gem + art/t_fire_staff + art/t_fireball + art/t_fissure + art/t_flame_burst + art/t_flawless_track + art/t_flesh_ripper + art/t_flood + art/t_follower_a + art/t_follower_b + art/t_giant_knight + art/t_glittering_spray + art/t_glittering_torrent + art/t_glittering_wave + art/t_goblin + art/t_gold + art/t_grakstormgiant + art/t_granite_smasher + art/t_grouptackle + art/t_growing_flame + art/t_halfdemon_antonispapantoniou + art/t_halfdemon_hellfire_shenfei + art/t_headshot + art/t_headshot_1 + art/t_heavy_gust + art/t_heist + art/t_hitjob + art/t_holy_resurrect + art/t_horn_of_calling + art/t_hunting_bow + art/t_ignite + art/t_influence + art/t_intimidation + art/t_knock_back + art/t_knock_down + art/t_kraka_high_priest + art/t_krythos_master_vampire + art/t_large_twister + art/t_lesser_resurrect + art/t_lesser_vampire + art/t_life_drain + art/t_life_force + art/t_lift + art/t_light_crossbow + art/t_longshot + art/t_longsword + art/t_lys_the_unseen + art/t_man_at_arms + art/t_man_at_arms_old + art/t_maroon + art/t_mass_resurrect + art/t_master_weyan + art/t_masterful_heist + art/t_maurader + art/t_midnight_knight + art/t_mighty_blow + art/t_minor_resurrect + art/t_misdirection + art/t_mud_pile + art/t_myros_guild_mage + art/t_nature_s_bounty + art/t_orc + art/t_orc_grunt + art/t_orc_guardian + art/t_orc_riot + art/t_paladin_sword + art/t_parov_the_enforcer + art/t_pick_pocket + art/t_pilfer + art/t_pillar_of_fire + art/t_piracy + art/t_powerful_blow + art/t_practiced_heist + art/t_prayer_beads + art/t_precision_blow + art/t_prism_rainerpetter + art/t_profit + art/t_pure_channel + art/t_quickshot + art/t_raiding_party + art/t_rake_master_assassin + art/t_rally_the_troops + art/t_rampage + art/t_ranger_fast_track + art/t_ranger_female_alternate + art/t_ranger_flashfire_arrow + art/t_ranger_honed_black_arrow + art/t_ranger_hunters_cloak + art/t_ranger_instinctive_track + art/t_ranger_instinctive_track_fixed + art/t_ranger_male + art/t_ranger_pathfinder_compass + art/t_ranger_snake_pet + art/t_ranger_sureshot_bracer + art/t_ranger_twin_shot + art/t_ranger_unending_quiver + art/t_rasmus_the_smuggler + art/t_rayla_endweaver + art/t_recruit + art/t_relentless_track + art/t_resurrect + art/t_righteous_resurrect + art/t_rock_guardian + art/t_rolling_fireball + art/t_ruby + art/t_scorching_fireball + art/t_searing_fireball + art/t_searing_guardian + art/t_seek_revenge + art/t_serene_channel + art/t_set_sail + art/t_shadow_spell_09 + art/t_shadow_spell_09_blue + art/t_shadow_spell_09_green + art/t_shambling_dirt + art/t_shield_bearer + art/t_shining_spray + art/t_shortsword + art/t_shoulder_bash + art/t_shoulder_crush + art/t_shoulder_smash + art/t_skeleton + art/t_skeleton_blue + art/t_skeleton_green + art/t_skillful_heist + art/t_sleight_of_hand + art/t_small_twister + art/t_smash_and_grab + art/t_smashing_blow + art/t_smooth_heist + art/t_snapshot + art/t_soothing_torrent + art/t_soothing_wave + art/t_soul_channel + art/t_spark + art/t_spell_components + art/t_spider + art/t_spiked_mace + art/t_splashing_wave + art/t_spreading_blaze + art/t_spreading_flames + art/t_spreading_sparks + art/t_steady_shot + art/t_stone_golem + art/t_stone_guardian + art/t_storm_siregar + art/t_street_thug + art/t_strength_in_numbers + art/t_strength_of_the_wolf + art/t_sweltering_heat + art/t_swipe + art/t_taxation + art/t_therot + art/t_the_rot + art/t_theft + art/t_thief_blackjack + art/t_thief_enchanted_garrote + art/t_thief_female + art/t_thief_jewelers_loupe + art/t_thief_keen_throwing_knife + art/t_thief_knife_belt + art/t_thief_male_alternate + art/t_thief_masterful_heist + art/t_thief_sacrificial_dagger + art/t_thief_shadow_mask + art/t_thief_shadow_mask_2 + art/t_thief_silent_boots + art/t_throwing_axe + art/t_throwing_knife + art/t_timelyheist + art/t_tithe_priest + art/t_torgen_rocksplitter + art/t_track + art/t_triple_shot + art/t_turn_to_ash + art/t_twinshot + art/t_tyrannor_the_devourer + art/t_unify_apsara + art/t_varrick_the_necromancer + art/t_venom + art/t_violent_gale + art/t_walking_dirt + art/t_weak_skeleton + art/t_well_placed_shot + art/t_whirling_blow + art/t_wind_storm + art/t_wind_tunnel + art/t_wizard_alchemist_s_stone + art/t_wizard_arcane_wand + art/t_wizard_blazing_staff + art/t_wizard_female_alternate + art/t_wizard_magic_mirror + art/t_wizard_magic_mirror_2 + art/t_wizard_male + art/t_wizard_runic_robes + art/t_wizard_runic_robes_2 + art/t_wizard_serpentine_staff + art/t_wizard_silverskull_amulet + art/t_wizard_spellcaster_gloves + art/t_wolf_form + art/t_wolf_shaman + art/t_word_of_power + art/t_wurm + art/t_wyvern + art/catacombs + art/cleric_lesser_resurrect + art/cleric_shining_breastplate + art/dark_sign + art/drak__storm_giant + art/fighter_sweeping_blow_old + art/fighter_whirling_blow_old + art/gold_female_black + art/gold_female_dark + art/gold_female_pale + art/gold_female_white + art/gold_female_white_grayscale + art/gold_male_black + art/gold_male_dark + art/gold_male_pale + art/gold_male_white + art/monsters_in_the_dark + art/ranger_hunters_cloak + art/the_call + art/wizard_fireball + art/ancestry/battle_rage + art/ancestry/bully + art/ancestry/burgle + art/ancestry/crush_you! + art/ancestry/demon_blood + art/ancestry/demonic_strength + art/ancestry/dwarf + art/ancestry/elf + art/ancestry/elven_grace + art/ancestry/elven_wisdom + art/ancestry/friendly_banter + art/ancestry/half-demon + art/ancestry/hammer + art/ancestry/hammer_strike + art/ancestry/hellfire + art/ancestry/hide + art/ancestry/ogre + art/ancestry/orc + art/ancestry/orc_male + art/ancestry/pick + art/ancestry/ragged_blade + art/ancestry/shiny_rock + art/ancestry/smallfolk + art/ancestry/sunstone_brooch + art/ancestry/war_club + art/epicart/absolve + art/epicart/abyss_summoner + art/epicart/aerial_assassin + art/epicart/alchemist_assassin + art/epicart/ambush_party + art/epicart/amnesia + art/epicart/ancient_chant + art/epicart/angel_of_death + art/epicart/angel_of_death_alt + art/epicart/angel_of_light + art/epicart/angel_of_mercy + art/epicart/angel_of_the_gate + art/epicart/angelic_protector + art/epicart/angeline_s_favor + art/epicart/angeline_s_will + art/epicart/anguish_demon + art/epicart/ankylosaurus + art/epicart/apocalypse + art/epicart/arcane_research + art/epicart/arm + art/epicart/army_of_the_apocalypse + art/epicart/ascendant_pyromancer + art/epicart/avenger_of_covenant + art/epicart/avenging_angel + art/epicart/banishment + art/epicart/battle_cry + art/epicart/bellowing_minotaur + art/epicart/bitten + art/epicart/blind_faith + art/epicart/blue_dragon + art/epicart/bodyguard + art/epicart/bounty_hunter + art/epicart/brachiosaurus + art/epicart/brak__fist_of_lashnok + art/epicart/brand__rebel_fighter + art/epicart/brave_squire + art/epicart/breath_of_life + art/epicart/bruger__the_pathfinder + art/epicart/burrowing_wurm + art/epicart/canopy_ranger + art/epicart/carrion_demon + art/epicart/cast_out + art/epicart/cave_troll + art/epicart/ceasefire + art/epicart/chamberlain_kark + art/epicart/champion_of_the_wicked + art/epicart/charged_dragon + art/epicart/chomp + art/epicart/chomp_ + art/epicart/chronicler + art/epicart/citadel_raven + art/epicart/citadel_scholar + art/epicart/consume + art/epicart/corpse_taker + art/epicart/corpsemonger + art/epicart/courageous_soul + art/epicart/crystal_golem + art/epicart/cyan_dragon + art/epicart/dark_assassin + art/epicart/dark_eyes + art/epicart/dark_knight + art/epicart/dark_leader + art/epicart/dark_offering + art/epicart/dark_one_s_apprentice + art/epicart/dark_one_s_fury + art/epicart/dark_prince + art/epicart/deadly_raid + art/epicart/deathbringer + art/epicart/demon_breach + art/epicart/demon_token + art/epicart/demonic_rising + art/epicart/den_mother + art/epicart/devour + art/epicart/dirge_of_scara + art/epicart/disappearing_act + art/epicart/divine_judgment + art/epicart/djinn_of_the_sands + art/epicart/dragonling + art/epicart/drain_essence + art/epicart/draka__dragon_tyrant + art/epicart/draka_s_enforcer + art/epicart/draka_s_fire + art/epicart/drifting_terror + art/epicart/drinker_of_blood + art/epicart/eager_necromancer + art/epicart/elara__the_lycomancer + art/epicart/elder_greatwurm + art/epicart/endbringer_ritualist + art/epicart/enrage + art/epicart/ensnaring_rune + art/epicart/entangling_vines + art/epicart/erase + art/epicart/erratic_research + art/epicart/erwin__architect_of_war + art/epicart/ethereal_dragon + art/epicart/evict + art/epicart/fairy_entrancer + art/epicart/fairy_trickster + art/epicart/faithful_pegasus + art/epicart/feeding_frenzy + art/epicart/feint + art/epicart/fiery_demise + art/epicart/final_task + art/epicart/fire_shaman + art/epicart/fire_spirit + art/epicart/fireball + art/epicart/fires_of_rebellion + art/epicart/flame_spike + art/epicart/flame_strike + art/epicart/flames_of_furios + art/epicart/flanking_maneuver + art/epicart/flash_fire + art/epicart/flood_of_fire + art/epicart/forbidden_research + art/epicart/force_field + art/epicart/force_lance + art/epicart/forced_exile + art/epicart/forcemage_apprentice + art/epicart/forest_dweller + art/epicart/forest_giant + art/epicart/forked_jolt + art/epicart/forked_lightning + art/epicart/frantic_digging + art/epicart/from_beyond + art/epicart/frost_giant + art/epicart/fumble + art/epicart/garbage_golem + art/epicart/gareth_s_automaton + art/epicart/gareth_s_juggernaut + art/epicart/gareth_s_processor + art/epicart/gareth_s_will + art/epicart/gift_of_furios + art/epicart/gladius_the_defender + art/epicart/go_wild + art/epicart/gold_dragon + art/epicart/grave_demon + art/epicart/great_horned_lizard + art/epicart/greater_lightning_wurm + art/epicart/guilt_demon + art/epicart/halt_ + art/epicart/hand_of_angeline + art/epicart/hands_from_below + art/epicart/harbinger_of_anguish + art/epicart/hasty_retreat + art/epicart/heinous_feast + art/epicart/helena_s_chosen + art/epicart/helion__the_dominator + art/epicart/helion_s_fury + art/epicart/herald_of_angeline + art/epicart/herald_of_lashnok + art/epicart/herald_of_scara + art/epicart/high_king + art/epicart/hill_giant + art/epicart/howl + art/epicart/human_token + art/epicart/hunting_pack + art/epicart/hunting_pterosaur + art/epicart/hunting_raptors + art/epicart/hunting_wyvern + art/epicart/hurricane + art/epicart/ice_drake + art/epicart/imperial_cavalry + art/epicart/imperial_commander + art/epicart/infernal_gatekeeper + art/epicart/infest + art/epicart/inheritance_of_the_meek + art/epicart/inner_demon + art/epicart/inner_peace + art/epicart/insurgency + art/epicart/invoke_pact + art/epicart/javelin_thrower + art/epicart/juggernaut + art/epicart/jungle_queen + art/epicart/justice_prevails + art/epicart/kalani__woodreader + art/epicart/keeper_of_secrets + art/epicart/keira__wolf_caller + art/epicart/knight_of_elara + art/epicart/knight_of_glory + art/epicart/knight_of_shadows + art/epicart/knight_of_the_dawn + art/epicart/knight_of_the_dragon + art/epicart/kong + art/epicart/krieg__dark_one_s_chosen + art/epicart/lash + art/epicart/lashnok_s_will + art/epicart/lay_claim + art/epicart/lesser_angel + art/epicart/lesser_arms + art/epicart/lesser_chaos + art/epicart/lesser_demonize + art/epicart/lesser_devil + art/epicart/lesser_digging + art/epicart/lesser_dinosaur + art/epicart/lesser_dragon + art/epicart/lesser_elf + art/epicart/lesser_flame + art/epicart/lesser_giant + art/epicart/lesser_golem + art/epicart/lesser_night_stalker + art/epicart/lesser_planning + art/epicart/lesser_squire + art/epicart/lesson_learned + art/epicart/lightning_elemental + art/epicart/lightning_mage + art/epicart/lightning_storm + art/epicart/lightning_strike + art/epicart/little_devil + art/epicart/lord_of_the_arena + art/epicart/lurking_giant + art/epicart/lycomancy + art/epicart/lying_in_wait + art/epicart/markus__watch_captain + art/epicart/marshal + art/epicart/martial_law + art/epicart/master_forcemage + art/epicart/master_zo + art/epicart/medusa + art/epicart/memory_spirit + art/epicart/mighty_blow + art/epicart/mirage_wielder + art/epicart/mist_guide_herald + art/epicart/mobilize + art/epicart/murderous_necromancer + art/epicart/muse + art/epicart/mystic_researcher + art/epicart/mythic_monster + art/epicart/necromancer_apprentice + art/epicart/necromancer_lord + art/epicart/necrovirus + art/epicart/new_dawn + art/epicart/no_escape + art/epicart/noble_martyr + art/epicart/noble_unicorn + art/epicart/novice_wizard + art/epicart/ogre_mercenary + art/epicart/owl_familiar + art/epicart/pack_alpha + art/epicart/palace_guard + art/epicart/paros__rebel_leader + art/epicart/pelios__storm_lord + art/epicart/plague + art/epicart/plague_zombies + art/epicart/plentiful_dead + art/epicart/polar_shock + art/epicart/preacher + art/epicart/priest_of_kalnor + art/epicart/priestess_of_angeline + art/epicart/psionic_assault + art/epicart/psyche_snare + art/epicart/pyromancer + art/epicart/pyrosaur + art/epicart/quell + art/epicart/rabble_rouser + art/epicart/rage + art/epicart/raging_t_rex + art/epicart/rain_of_fire + art/epicart/rally_the_people + art/epicart/rampaging_cyclops + art/epicart/rampaging_wurm + art/epicart/raxxa__demon_tyrant + art/epicart/raxxa_s_curse + art/epicart/raxxa_s_displeasure + art/epicart/raxxa_s_enforcer + art/epicart/reality_shift + art/epicart/reap_or_sow + art/epicart/reaper + art/epicart/red_mist + art/epicart/rescue_griffin + art/epicart/reset + art/epicart/resurrection + art/epicart/reusable_knowledge + art/epicart/revolt + art/epicart/rift_summoner + art/epicart/rise_of_the_many + art/epicart/rising_storm + art/epicart/ritual_of_scara + art/epicart/royal_escort + art/epicart/royal_intervention + art/epicart/run_riot + art/epicart/runek__dark_duelist + art/epicart/rybas__canopy_sniper + art/epicart/sand_wurm + art/epicart/saren__night_stalker + art/epicart/savage_uprising + art/epicart/scara_s_gift + art/epicart/scara_s_will + art/epicart/scarred_berserker + art/epicart/scarred_cultist + art/epicart/scarred_priestess + art/epicart/scarros__hound_of_draka + art/epicart/scrap_golem + art/epicart/scrap_golem_token + art/epicart/scrap_token + art/epicart/sea_hydra + art/epicart/sea_titan + art/epicart/second_wind + art/epicart/secret_legion + art/epicart/sequester + art/epicart/shadow_imp + art/epicart/shield_of_tarken + art/epicart/shock_trooper + art/epicart/silver_dragon + art/epicart/silver_wing_griffin + art/epicart/silver_wing_guardian + art/epicart/silver_wing_lancer + art/epicart/silver_wing_paladin + art/epicart/silver_wing_savior + art/epicart/siren_s_song + art/epicart/sky_serpent + art/epicart/smash_and_burn + art/epicart/soul_hunter + art/epicart/soul_storm + art/epicart/sparkmage + art/epicart/spawning_demon + art/epicart/spike_trap + art/epicart/spore_beast + art/epicart/stalking_werewolf + art/epicart/stampeding_einiosaurus + art/epicart/stand_alone + art/epicart/standard_bearer + art/epicart/stealthy_predator + art/epicart/steed_of_zaltessa + art/epicart/steel_golem + art/epicart/steel_titan + art/epicart/storm_dragon + art/epicart/strafing_dragon + art/epicart/street_swindler + art/epicart/subjugate + art/epicart/succubus + art/epicart/sun_strike + art/epicart/surprise_attack + art/epicart/teleport + art/epicart/temporal_enforcer + art/epicart/temporal_shift + art/epicart/temporize + art/epicart/terrorize + art/epicart/the_gudgeon + art/epicart/the_people_s_champion + art/epicart/the_risen + art/epicart/thought_plucker + art/epicart/thrasher_demon + art/epicart/thundarus + art/epicart/time_bender + art/epicart/time_thief + art/epicart/time_walker + art/epicart/transfigure + art/epicart/transform + art/epicart/transmogrify + art/epicart/triceratops + art/epicart/trihorror + art/epicart/turn + art/epicart/unquenchable_thirst + art/epicart/urgent_messengers + art/epicart/valentia_justice_bringer + art/epicart/valiant_knight + art/epicart/vampire_lord + art/epicart/vanishing + art/epicart/velden__frost_titan + art/epicart/vilify + art/epicart/village_protector + art/epicart/vital_mission + art/epicart/wake_the_dead + art/epicart/war_lion_of_valentia + art/epicart/war_machine + art/epicart/war_priest + art/epicart/warrior_angel + art/epicart/warrior_golem + art/epicart/watchful_gargoyle + art/epicart/wave_of_transformation + art/epicart/whirlwind + art/epicart/white_dragon + art/epicart/white_knight + art/epicart/wild_roc + art/epicart/winds_of_change + art/epicart/winged_death + art/epicart/winter_fairy + art/epicart/wither + art/epicart/wolf_companion + art/epicart/wolf_s_bite + art/epicart/wolf_s_call + art/epicart/wolf_token + art/epicart/woodland_bushwacker + art/epicart/word_of_summoning + art/epicart/wurm_hatchling + art/epicart/wyvern + art/epicart/zaltessa_s_fire + art/epicart/zannos__corpse_lord + art/epicart/zealous_necromancer + art/epicart/zombie_apocalypse + art/epicart/zombie_token + art/treasures/t_bottle_of_rum + art/treasures/t_bracers_of_brawn + art/treasures/t_cleric_elixir_blue_purple + art/treasures/t_cleric_elixir_golden + art/treasures/t_cleric_elixir_green + art/treasures/t_fighter_elixir_blue + art/treasures/t_fighter_elixir_green + art/treasures/t_fighter_elixir_red + art/treasures/t_green_potions_large + art/treasures/t_green_potions_medium + art/treasures/t_hook_weapon + art/treasures/t_imperial_sailor + art/treasures/t_magic_scroll_souveraine + art/treasures/t_parrot + art/treasures/t_pirate_cutlass + art/treasures/t_ranger_elixir_orange + art/treasures/t_ranger_elixir_red_brownish + art/treasures/t_ranger_elixir_yellow + art/treasures/t_ship_bell + art/treasures/t_ship_in_a_bottle + art/treasures/t_spyglass + art/treasures/t_thief_elixir_black + art/treasures/t_thief_elixir_red + art/treasures/t_thief_elixir_white + art/treasures/t_treasure_map + art/treasures/t_trick_dice + art/treasures/t_wizard_elixir_blue + art/treasures/t_wizard_elixir_orange + art/treasures/t_wizard_elixir_silver + art/treasures/alchemist_diamond + art/treasures/alchemist_kaleidoscope + art/treasures/alchemist_prismatic_cloak + art/treasures/alchemist_rainbow_potion + art/treasures/alchemist_recipe_book + art/treasures/alchemist_silver_scales + art/treasures/alchemist_vial_of_acid + art/treasures/alchemist_waistcoat_of_infinite_pockets + art/treasures/barbarian_caltrops + art/treasures/barbarian_cloak_of_rage + art/treasures/barbarian_double_bladed_hand_axe + art/treasures/barbarian_net_of_thorns + art/treasures/barbarian_pillage + art/treasures/barbarian_roaring_cowl + art/treasures/barbarian_seething_spear + art/treasures/barbarian_whip + art/treasures/bard_captivating_herald + art/treasures/bard_dancing_shoes + art/treasures/bard_enchanted_flute + art/treasures/bard_minstrel_s_gloves + art/treasures/bard_muse_s_paper + art/treasures/bard_music_box + art/treasures/bard_tuning_fork + art/treasures/bard_whistling_rapier + art/treasures/cleric_benediction_beads + art/treasures/cleric_blessed_bracers + art/treasures/cleric_enduring_follower + art/treasures/cleric_holy_water + art/treasures/cleric_mantle_of_rebirth + art/treasures/cleric_morningstar + art/treasures/cleric_motes_of_light + art/treasures/cleric_sacred_censer + art/treasures/cleric_spiked_mace_of_healing + art/treasures/cleric_spiked_mace_of_might + art/treasures/druid_flying_squirrel + art/treasures/druid_great_stag + art/treasures/druid_grizzled_pauldrons + art/treasures/druid_hardy_hedgehog + art/treasures/druid_lore_telling + art/treasures/druid_reclaim_the_forest + art/treasures/druid_sunbird + art/treasures/druid_verdant_boots + art/treasures/druid_wisdom_of_the_woods + art/treasures/fighter_adamantine_breastplate + art/treasures/fighter_chain + art/treasures/fighter_dazzling_ruby + art/treasures/fighter_flaming_longsword + art/treasures/fighter_iron_shield + art/treasures/fighter_javelin + art/treasures/fighter_lightning_longsword + art/treasures/fighter_runic_throwing_axe + art/treasures/fighter_spiked_sabatons + art/treasures/fighter_taunting_talisman + art/treasures/monk_ancestral_circlet + art/treasures/monk_arm_bands_of_defense + art/treasures/monk_dragon_staff + art/treasures/monk_radiant_blossom + art/treasures/monk_staff_of_the_phoenix + art/treasures/monk_tranquil_wind + art/treasures/monk_void_s_eye + art/treasures/monk_wraps_of_illusionary_speed + art/treasures/necromancer_bag_of_bones + art/treasures/necromancer_bone_gauntlets + art/treasures/necromancer_company_of_corpses + art/treasures/necromancer_dread_cauldron + art/treasures/necromancer_greaves_of_grieving + art/treasures/necromancer_preserved_heart + art/treasures/necromancer_soul_cage + art/treasures/necromancer_withered_wand + art/treasures/ranger_death_arrow + art/treasures/ranger_gloves_of_accuracy + art/treasures/ranger_gold_tipped_arrow + art/treasures/ranger_hawk_pet + art/treasures/ranger_horn_of_command + art/treasures/ranger_horn_of_need + art/treasures/ranger_stalking_bow + art/treasures/ranger_tracker_s_boots + art/treasures/ranger_trick_arrow + art/treasures/ranger_veiled_trap + art/treasures/thief_blinding_powder + art/treasures/thief_brillant_ruby + art/treasures/thief_hideaway_cloak + art/treasures/thief_kunai + art/treasures/thief_lockpick + art/treasures/thief_lucky_knife + art/treasures/thief_parrying_dagger + art/treasures/thief_ruby_encrusted_knife + art/treasures/thief_sharpened_ruby + art/treasures/thief_stickyfinger_gloves + art/treasures/wizard_adept_s_components + art/treasures/wizard_clock_of_ages + art/treasures/wizard_combust + art/treasures/wizard_content_familiar + art/treasures/wizard_enchanter_s_hat + art/treasures/wizard_incandescent_sash + art/treasures/wizard_magic_scroll + art/treasures/wizard_prestidigitation_charm + art/treasures/wizard_wand_of_wanting + art/treasures/wizard_wizened_familiar + art/campaign/journeys/imperial_persistence + art/campaign/warinthewild/kasha__the_awakener_cropped + art/campaign/warinthewild/slaughterclaw + art/campaign/warinthewild/slaughterclaw_fixed + art/campaign/warinthewild/slaughterclaw_zoomed + art/campaign/warinthewild/the_summoning + art/classes/alchemist/crucible + art/classes/alchemist/diamond + art/classes/alchemist/dispersion + art/classes/alchemist/dissolve + art/classes/alchemist/fireworks + art/classes/alchemist/reflection + art/classes/alchemist/refraction + art/classes/alchemist/transmogrification + art/classes/alchemist/transmutation + art/classes/alchemist/alchemy_powders + art/classes/alchemist/auric_gloves + art/classes/alchemist/bottled_tempest + art/classes/alchemist/brittle_gas + art/classes/alchemist/fools_gold + art/classes/alchemist/frothing_potion + art/classes/alchemist/instant_transmutation + art/classes/alchemist/major_transmogrification + art/classes/alchemist/minor_transmutation + art/classes/alchemist/perfect_refraction + art/classes/alchemist/philosophers_stone + art/classes/alchemist/polished_philosophers_stone + art/classes/alchemist/prismatic_dispersion + art/classes/alchemist/rainbow_potion + art/classes/alchemist/rapid_transmogrification + art/classes/alchemist/rapid_transmutation + art/classes/alchemist/recalibration_crystal + art/classes/alchemist/recipe_book + art/classes/alchemist/sloshing_potion + art/classes/alchemist/spectrum_spectacles + art/classes/alchemist/swirling_philosophers_stone + art/classes/alchemist/vial_of_acid_gas + art/classes/alchemist/waistcoat_of_infinite_pockets + art/classes/alchemist/wide_diffusion + art/classes/barbarian/amulet_of_stifled_pain + art/classes/barbarian/battle_cry + art/classes/barbarian/battle_roar_incorrect + art/classes/barbarian/battle_roar_l3 + art/classes/barbarian/battle_roar_l5 + art/classes/barbarian/bellow + art/classes/barbarian/bone_axe + art/classes/barbarian/crushed_coin + art/classes/barbarian/disorienting_headbutt + art/classes/barbarian/double_bladed_hand_axe + art/classes/barbarian/earthshaker + art/classes/barbarian/eternal_rage_berserk + art/classes/barbarian/eternal_rage_calm + art/classes/barbarian/explosive_rage_berserk + art/classes/barbarian/explosive_rage_calm + art/classes/barbarian/fiery_rage_berserk + art/classes/barbarian/fiery_rage_calm + art/classes/barbarian/flail + art/classes/barbarian/flaring_rage_berserk + art/classes/barbarian/flaring_rage_calm + art/classes/barbarian/growl + art/classes/barbarian/hand_axe + art/classes/barbarian/headbutt + art/classes/barbarian/inner_rage_berserk + art/classes/barbarian/inner_rage_calm + art/classes/barbarian/plunder + art/classes/barbarian/razor_bracers + art/classes/barbarian/ring_of_rage + art/classes/barbarian/roar + art/classes/barbarian/seething_spear + art/classes/barbarian/serrated_hand_axe + art/classes/barbarian/shattering_headbutt + art/classes/barbarian/smoldering_rage_berserk + art/classes/barbarian/smoldering_rage_calm + art/classes/barbarian/stomping_boots + art/classes/barbarian/terrifying_howl + art/classes/barbarian/terrifying_roar + art/classes/barbarian/war_cry + art/classes/barbarian/wolf_companion + art/classes/bard/bard_battle_march + art/classes/bard/bard_bold_saga + art/classes/bard/bard_brave_story + art/classes/bard/bard_captivating_herald + art/classes/bard/bard_character_female + art/classes/bard/bard_character_male + art/classes/bard/bard_coat_of_encores + art/classes/bard/bard_collecting_cap + art/classes/bard/bard_dancing_blade + art/classes/bard/bard_dancing_shoes + art/classes/bard/bard_enchanted_flute + art/classes/bard/bard_epic_poem + art/classes/bard/bard_flute + art/classes/bard/bard_goblet_of_whimsy + art/classes/bard/bard_golden_harp + art/classes/bard/bard_grand_legend + art/classes/bard/bard_guild_tale + art/classes/bard/bard_harp + art/classes/bard/bard_herald + art/classes/bard/bard_heroic_fable + art/classes/bard/bard_imperial_anthem + art/classes/bard/bard_inspiring_tune + art/classes/bard/bard_intrepid_tale + art/classes/bard/bard_lullaby_harp + art/classes/bard/bard_lute + art/classes/bard/bard_minstrel_s_gloves + art/classes/bard/bard_moving_melody + art/classes/bard/bard_muse_s_paper + art/classes/bard/bard_music_box + art/classes/bard/bard_musical_darts + art/classes/bard/bard_mythic_chronicle + art/classes/bard/bard_necros_dirge + art/classes/bard/bard_rally_hymn + art/classes/bard/bard_rousing_ode + art/classes/bard/bard_silencing_scepter + art/classes/bard/bard_song_of_the_wild + art/classes/bard/bard_songbook + art/classes/bard/bard_stirring_song + art/classes/bard/bard_summoning_drum + art/classes/bard/bard_tuning_fork + art/classes/bard/bard_valiant_verse + art/classes/bard/bard_whistling_rapier + art/classes/druid/animal_strength + art/classes/druid/bear_form + art/classes/druid/bear_strength + art/classes/druid/circlet_of_flowers + art/classes/druid/entangling_roots + art/classes/druid/feral_fox + art/classes/druid/flourishing_staff + art/classes/druid/forest_fury + art/classes/druid/forest_rage + art/classes/druid/forest_vengeance + art/classes/druid/fox + art/classes/druid/gnarled_staff + art/classes/druid/grass_weave_sash + art/classes/druid/grizzly_form + art/classes/druid/hardy_hedgehog + art/classes/druid/heartwood_splinter + art/classes/druid/hedgehog + art/classes/druid/honeycomb + art/classes/druid/lore_telling + art/classes/druid/nimble_fox + art/classes/druid/owl + art/classes/druid/panther_eye_ring + art/classes/druid/polar_bear_form + art/classes/druid/pure_bear_form + art/classes/druid/rabbit + art/classes/druid/reclaim_the_forest + art/classes/druid/soul_of_the_forest + art/classes/druid/spirit_bear_form + art/classes/druid/spirit_grizzly_form + art/classes/druid/spirit_of_the_forest + art/classes/druid/squirrel + art/classes/druid/sunbird + art/classes/druid/ursine_rod + art/classes/druid/way_of_the_forest + art/classes/monk/monk_amulet_of_resolve + art/classes/monk/monk_ancestral_circlet + art/classes/monk/monk_arm_bands_of_defense + art/classes/monk/monk_calm + art/classes/monk/monk_cobra_fang + art/classes/monk/monk_dim_mak + art/classes/monk/monk_dragon_staff + art/classes/monk/monk_favored_technique + art/classes/monk/monk_female + art/classes/monk/monk_flowing_technique + art/classes/monk/monk_fluid_technique + art/classes/monk/monk_focus + art/classes/monk/monk_focused_strike + art/classes/monk/monk_horn_of_ascendance + art/classes/monk/monk_jing + art/classes/monk/monk_magnificent_blossom + art/classes/monk/monk_male + art/classes/monk/monk_masterful_technique + art/classes/monk/monk_practiced_technique + art/classes/monk/monk_precise_technique + art/classes/monk/monk_qi + art/classes/monk/monk_qigong + art/classes/monk/monk_radiant_blossom + art/classes/monk/monk_resplendent_blossom + art/classes/monk/monk_ring_of_1000_palms + art/classes/monk/monk_serene_wind + art/classes/monk/monk_slippers_of_the_crane + art/classes/monk/monk_spring_blossom + art/classes/monk/monk_staff_of_meditation + art/classes/monk/monk_staff_of_the_phoenix + art/classes/monk/monk_stillness_of_water + art/classes/monk/monk_striking_cobra + art/classes/monk/monk_tonfas_of_balance + art/classes/monk/monk_tranquil_wind + art/classes/monk/monk_void_s_eye + art/classes/monk/monk_wraps_of_illusinary_speed + art/classes/monk/monk_wraps_of_strength + art/classes/monk/monk_yin_yang + art/classes/necromancer/anguish_blade + art/classes/necromancer/animate_dead + art/classes/necromancer/bag_of_bones + art/classes/necromancer/bloodrose + art/classes/necromancer/bone_gauntlets + art/classes/necromancer/bone_raising + art/classes/necromancer/bone_waltz + art/classes/necromancer/bone_waltz_v2 + art/classes/necromancer/collection_of_corpses + art/classes/necromancer/company_of_corpses + art/classes/necromancer/corpse_horde + art/classes/necromancer/corpse_raising + art/classes/necromancer/dread_cauldron + art/classes/necromancer/empty_graves + art/classes/necromancer/final_return + art/classes/necromancer/fresh_harvest + art/classes/necromancer/grave_robbery + art/classes/necromancer/greaves_of_grieving + art/classes/necromancer/necromancer_female + art/classes/necromancer/necromancer_male + art/classes/necromancer/onyx_skull + art/classes/necromancer/plague_belt + art/classes/necromancer/preserved_heart + art/classes/necromancer/puzzle_box + art/classes/necromancer/raise_dead + art/classes/necromancer/raise_skeleton + art/classes/necromancer/reanimate + art/classes/necromancer/reawaken + art/classes/necromancer/rod_of_reanimation + art/classes/necromancer/rod_of_spite + art/classes/necromancer/rod_of_unlife + art/classes/necromancer/rotting_crown + art/classes/necromancer/severing_scythe + art/classes/necromancer/skeleton_servant + art/classes/necromancer/skeleton_warrior + art/classes/necromancer/skull_swarm + art/classes/necromancer/soul_cage + art/classes/necromancer/soul_prism + art/classes/necromancer/stitcher_s_kit + art/classes/necromancer/strong_bones + art/classes/necromancer/upgraded_collection_of_corpses + art/classes/necromancer/voidstone + art/classes/necromancer/withered_wand + art/sets/promos1art/afterlife + art/sets/promos1art/bjorn__the_centurion + art/sets/promos1art/bloodfang + art/sets/promos1art/crime_spree + art/sets/promos1art/devotion + art/sets/promos1art/dragon_fire + art/sets/promos1art/droga__guild_enforcer + art/sets/promos1art/galok__the_vile + art/sets/promos1art/gorg__orc_shaman + art/sets/promos1art/kasha__the_awakener + art/sets/promos1art/legionnaire + art/sets/promos1art/mobia__elf_lord + art/sets/promos1art/raiding_party + art/sets/promos1art/ren__bounty_hunter + art/sets/promos1art/robbery + art/sets/promos1art/the_summoning + art/sets/promos1art/valius_fire_dragon_under_the_rockslide + art/sets/promos1art/valius__fire_dragon + art/sets/promos1art/zombie + + +## Frames Frames are used to set a frame for a card layout Possible values: - frames/Cleric_armor_frame.png - frames/Cleric_CardFrame.png - frames/Fighter_armor_frame.png - frames/Generic_CardFrame.png - frames/Generic_Top_CardFrame.png - frames/Guild_Action_CardFrame.png - frames/Guild_Champion_CardFrame.png - frames/HR_CardFrame_Action_Guild.png - frames/HR_CardFrame_Action_Imperial.png - frames/HR_CardFrame_Action_Necros.png - frames/HR_CardFrame_Action_Wild.png - frames/HR_CardFrame_Champion_Guild.png - frames/HR_CardFrame_Champion_Imperial.png - frames/HR_CardFrame_Champion_Necros.png - frames/HR_CardFrame_Champion_Wild.png - frames/HR_CardFrame_Item_Generic.png - frames/Imperial_Action_CardFrame.png - frames/Imperial_Champion_CardFrame.png - frames/Necros_Action_CardFrame.png - frames/Necros_Champion_CardFrame.png - frames/Ranger_armor_frame.png - frames/Ranger_CardFrame.png - frames/Thief_armor_frame.png - frames/Thief_CardFrame.png - frames/Treasure_CardFrame.png - frames/Warrior_CardFrame.png - frames/warrior_top.png - frames/Wild_Action_CardFrame.png - frames/Wild_Champion_CardFrame.png - frames/Wizard_armor_frame.png - frames/Wizard_CardFrame.png - frames/Cleric_Frames/Cleric_Treasure_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Guild_Action_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Guild_Champion_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Imperial_Action_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Imperial_Champion_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Necros_Action_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Necros_Champion_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Wild_Action_CardFrame.png - frames/FactionFrames_IconOnTheLeft/Wild_Champion_CardFrame.png + frames/Cleric_armor_frame + frames/Cleric_CardFrame + frames/Fighter_armor_frame + frames/Generic_CardFrame + frames/Generic_Top_CardFrame + frames/Guild_Action_CardFrame + frames/Guild_Champion_CardFrame + frames/HR_CardFrame_Action_Guild + frames/HR_CardFrame_Action_Imperial + frames/HR_CardFrame_Action_Necros + frames/HR_CardFrame_Action_Wild + frames/HR_CardFrame_Champion_Guild + frames/HR_CardFrame_Champion_Imperial + frames/HR_CardFrame_Champion_Necros + frames/HR_CardFrame_Champion_Wild + frames/HR_CardFrame_Item_Generic + frames/Imperial_Action_CardFrame + frames/Imperial_Champion_CardFrame + frames/Necros_Action_CardFrame + frames/Necros_Champion_CardFrame + frames/Ranger_armor_frame + frames/Ranger_CardFrame + frames/Thief_armor_frame + frames/Thief_CardFrame + frames/Treasure_CardFrame + frames/Warrior_CardFrame + frames/warrior_top + frames/Wild_Action_CardFrame + frames/Wild_Champion_CardFrame + frames/Wizard_armor_frame + frames/Wizard_CardFrame + frames/Cleric_Frames/Cleric_Treasure_CardFrame + frames/FactionFrames_IconOnTheLeft/Guild_Action_CardFrame + frames/FactionFrames_IconOnTheLeft/Guild_Champion_CardFrame + frames/FactionFrames_IconOnTheLeft/Imperial_Action_CardFrame + frames/FactionFrames_IconOnTheLeft/Imperial_Champion_CardFrame + frames/FactionFrames_IconOnTheLeft/Necros_Action_CardFrame + frames/FactionFrames_IconOnTheLeft/Necros_Champion_CardFrame + frames/FactionFrames_IconOnTheLeft/Wild_Action_CardFrame + frames/FactionFrames_IconOnTheLeft/Wild_Champion_CardFrame Icons Icons may be used for choice layout generation or to set icons for buffs, skills and abilities @@ -1851,47 +3043,189 @@ Possible values: icons/wizard_spellcaster_gloves -Avatars +## Avatars You need to specify full path when using for layout. But only avatar name “assassin”, when used as actual avatar. Possible values: + avatars/Alchemist_01 + avatars/Alchemist_02 + avatars/Barbarian_01 + avatars/Barbarian_02 avatars/ambushers + avatars/any_scenario_queue avatars/assassin avatars/assassin_flipped + avatars/bard_01 + avatars/bard_02 avatars/broelyn avatars/broelyn__loreweaver avatars/chanting_cultist avatars/chest avatars/cleric_01 avatars/cleric_02 - avatars/cristov_s_recruits + avatars/cleric_alt_01 + avatars/cleric_alt_02 avatars/cristov__the_just + avatars/cristov_s_recruits + avatars/druid_01 + avatars/druid_02 + avatars/dwarf_cleric_female_01 + avatars/dwarf_cleric_female_02 + avatars/dwarf_cleric_male_01 + avatars/dwarf_cleric_male_02 + avatars/dwarf_fighter_female_01 + avatars/dwarf_fighter_female_02 + avatars/dwarf_fighter_male_01 + avatars/dwarf_fighter_male_02 + avatars/dwarf_ranger_female_01 + avatars/dwarf_ranger_female_02 + avatars/dwarf_ranger_male_01 + avatars/dwarf_ranger_male_02 + avatars/dwarf_thief_female_01 + avatars/dwarf_thief_female_02 + avatars/dwarf_thief_male_01 + avatars/dwarf_thief_male_02 + avatars/dwarf_wizard_female_01 + avatars/dwarf_wizard_female_02 + avatars/dwarf_wizard_male_01 + avatars/dwarf_wizard_male_02 + avatars/elf_cleric_female_01 + avatars/elf_cleric_female_02 + avatars/elf_cleric_male_01 + avatars/elf_cleric_male_02 + avatars/elf_fighter_female_01 + avatars/elf_fighter_female_02 + avatars/elf_fighter_male_01 + avatars/elf_fighter_male_02 + avatars/elf_ranger_female_01 + avatars/elf_ranger_female_02 + avatars/elf_ranger_male_01 + avatars/elf_ranger_male_02 + avatars/elf_thief_female_01 + avatars/elf_thief_female_02 + avatars/elf_thief_male_01 + avatars/elf_thief_male_02 + avatars/elf_wizard_female_01 + avatars/elf_wizard_female_02 + avatars/elf_wizard_male_01 + avatars/elf_wizard_male_02 + avatars/elf_wizard_male_03 avatars/fighter_01 avatars/fighter_02 + avatars/fighter_alt_01 + avatars/fighter_alt_02 + avatars/halfdemon_cleric_female_01 + avatars/halfdemon_cleric_female_02 + avatars/halfdemon_cleric_male_01 + avatars/halfdemon_cleric_male_02 + avatars/halfdemon_fighter_female_01 + avatars/halfdemon_fighter_female_02 + avatars/halfdemon_fighter_male_01 + avatars/halfdemon_fighter_male_02 + avatars/halfdemon_ranger_female_01 + avatars/halfdemon_ranger_female_02 + avatars/halfdemon_ranger_male_01 + avatars/halfdemon_ranger_male_02 + avatars/halfdemon_thief_female_01 + avatars/halfdemon_thief_female_02 + avatars/halfdemon_thief_male_01 + avatars/halfdemon_thief_male_02 + avatars/halfdemon_wizard_female_01 + avatars/halfdemon_wizard_female_02 + avatars/halfdemon_wizard_male_01 + avatars/halfdemon_wizard_male_02 avatars/inquisition avatars/krythos avatars/lord_callum avatars/lys__the_unseen avatars/man_at_arms + avatars/monk_01 + avatars/monk_01_2 + avatars/monk_02 avatars/monsters_in_the_dark + avatars/necromancer_01 + avatars/necromancer_02 avatars/necromancers avatars/ogre - avatars/orcs + avatars/ogre_cleric_female_01 + avatars/ogre_cleric_female_02 + avatars/ogre_cleric_male_01 + avatars/ogre_cleric_male_02 + avatars/ogre_fighter_female_01 + avatars/ogre_fighter_female_02 + avatars/ogre_fighter_male_01 + avatars/ogre_fighter_male_02 + avatars/ogre_ranger_female_01 + avatars/ogre_ranger_female_02 + avatars/ogre_ranger_male_01 + avatars/ogre_ranger_male_02 + avatars/ogre_thief_female_01 + avatars/ogre_thief_female_02 + avatars/ogre_thief_male_01 + avatars/ogre_thief_male_02 + avatars/ogre_wizard_female_01 + avatars/ogre_wizard_female_02 + avatars/ogre_wizard_male_01 + avatars/ogre_wizard_male_02 + avatars/orc_cleric_female_01 + avatars/orc_cleric_female_02 + avatars/orc_cleric_male_01 + avatars/orc_cleric_male_02 + avatars/orc_fighter_female_01 + avatars/orc_fighter_female_02 + avatars/orc_fighter_male_01 + avatars/orc_fighter_male_02 avatars/orc_raiders + avatars/orc_ranger_female_01 + avatars/orc_ranger_female_02 + avatars/orc_ranger_male_01 + avatars/orc_ranger_male_02 + avatars/orc_thief_female_01 + avatars/orc_thief_female_02 + avatars/orc_thief_male_01 + avatars/orc_thief_male_02 + avatars/orc_wizard_female_01 + avatars/orc_wizard_female_02 + avatars/orc_wizard_male_01 + avatars/orc_wizard_male_02 + avatars/orcs avatars/origins_flawless_track avatars/origins_shoulder_bash avatars/pirate avatars/profit avatars/ranger_01 avatars/ranger_02 + avatars/ranger_alt_01 + avatars/ranger_alt_02 + avatars/ranger_alt_03 avatars/rayla__endweaver avatars/rayla__endweaver_flipped avatars/robbery avatars/ruinos_zealot avatars/skeleton + avatars/smallfolk_cleric_female_01 + avatars/smallfolk_cleric_female_02 + avatars/smallfolk_cleric_male_01 + avatars/smallfolk_cleric_male_02 + avatars/smallfolk_fighter_female_01 + avatars/smallfolk_fighter_female_02 + avatars/smallfolk_fighter_male_01 + avatars/smallfolk_fighter_male_02 + avatars/smallfolk_ranger_female_01 + avatars/smallfolk_ranger_female_02 + avatars/smallfolk_ranger_male_01 + avatars/smallfolk_ranger_male_02 + avatars/smallfolk_thief_female_01 + avatars/smallfolk_thief_female_02 + avatars/smallfolk_thief_male_01 + avatars/smallfolk_thief_male_02 + avatars/smallfolk_wizard_female_01 + avatars/smallfolk_wizard_female_02 + avatars/smallfolk_wizard_male_01 + avatars/smallfolk_wizard_male_02 avatars/smugglers avatars/spider avatars/summoner @@ -1899,14 +3233,67 @@ Possible values: avatars/the_wolf_tribe avatars/thief_01 avatars/thief_02 + avatars/thief_alt_01 + avatars/thief_alt_02 avatars/troll avatars/vampire_lord avatars/wizard_01 avatars/wizard_02 + avatars/wizard_alt_01 + avatars/wizard_alt_02 avatars/wolf_shaman - - -Zoomed buffs + avatars/WarInTheWild/Bloodfang + avatars/WarInTheWild/Gorg__Orc_Shaman + avatars/WarInTheWild/Gorg__Orc_Shaman_flip + avatars/WarInTheWild/bjorn__the_centurion + avatars/WarInTheWild/bjorn__the_centurion_flip + avatars/WarInTheWild/dark_energy + avatars/WarInTheWild/death_threat + avatars/WarInTheWild/domination + avatars/WarInTheWild/droga__guild_enforcer + avatars/WarInTheWild/galok__the_vile + avatars/WarInTheWild/galok__the_vile_flip + avatars/WarInTheWild/grak__storm_giant + avatars/WarInTheWild/honed_black_arrow + avatars/WarInTheWild/kasha__the_awakener + avatars/WarInTheWild/kasha__the_awakener_cropped + avatars/WarInTheWild/kasha__the_awakener_flip + avatars/WarInTheWild/krythos_master_vampire + avatars/WarInTheWild/mobia__elf_lord + avatars/WarInTheWild/orc_grunt + avatars/WarInTheWild/raiding_party + avatars/WarInTheWild/rampage + avatars/WarInTheWild/ren__bounty_hunter + avatars/WarInTheWild/slaughterclaw + avatars/WarInTheWild/the_call + avatars/WarInTheWild/the_summoning + avatars/WarInTheWild/torgen_rocksplitter + avatars/WarInTheWild/valius__fire_dragon + avatars/WarInTheWild/wolf_form + avatars/journeys/abomination + avatars/journeys/alpha_wolf + avatars/journeys/andor_the_valiant + avatars/journeys/broken_tables_and_chairs + avatars/journeys/command + avatars/journeys/dire_wolf + avatars/journeys/droga_guild_enforcer + avatars/journeys/dwarf_thief_female + avatars/journeys/fettered_imp + avatars/journeys/halfdemon_fighter_female + avatars/journeys/hardy_hedgehog + avatars/journeys/human_necromancer_male + avatars/journeys/lenka_the_hunter + avatars/journeys/olara_the_slayer + avatars/journeys/orc_wizard_female_02 + avatars/journeys/pelleas__the_seeker + avatars/journeys/ren_bounty_hunter + avatars/journeys/robbery + avatars/journeys/smallfolk_male + avatars/journeys/veteran_squire + avatars/journeys/waking_dragon + + +## Zoomed buffs These are larger variants of some icons @@ -1943,7 +3330,51 @@ These are larger variants of some icons zoomedbuffs/wizard_spell_components -Card list +## Coop backgrounds + scenariointrobackgrounds/pirate + scenariointrobackgrounds/the_wolf_tribe + scenariointrobackgrounds/deceptions + scenariointrobackgrounds/raiding_party + scenariointrobackgrounds/dark_sign + scenariointrobackgrounds/orcs + scenariointrobackgrounds/ruinos_zealot + scenariointrobackgrounds/inquisition + scenariointrobackgrounds/test_of_mettle + scenariointrobackgrounds/the_unseen + scenariointrobackgrounds/man_at_arms + scenariointrobackgrounds/ambush_at_the_docs + scenariointrobackgrounds/profit + scenariointrobackgrounds/necromancers + scenariointrobackgrounds/smuggling_operation + scenariointrobackgrounds/journeys/enraged_bear + scenariointrobackgrounds/journeys/confiscate + scenariointrobackgrounds/journeys/reclaim_the_forest + scenariointrobackgrounds/journeys/sway + scenariointrobackgrounds/journeys/bounty_collection + scenariointrobackgrounds/journeys/thrash + scenariointrobackgrounds/journeys/enthralled_regulars + scenariointrobackgrounds/journeys/corruption + scenariointrobackgrounds/journeys/abomination + scenariointrobackgrounds/journeys/command + scenariointrobackgrounds/journeys/loot + scenariointrobackgrounds/journeys/dragon_shard + scenariointrobackgrounds/warInthewild/torgen_rocksplitter + scenariointrobackgrounds/warInthewild/krythos_master_vampire + scenariointrobackgrounds/warInthewild/raiding_party + scenariointrobackgrounds/warInthewild/the_summoning + scenariointrobackgrounds/warInthewild/dark_energy + scenariointrobackgrounds/warInthewild/Kasha__the_Awakener + scenariointrobackgrounds/warInthewild/galok__the_vile_bonus + scenariointrobackgrounds/warInthewild/mobia__elf_lord + scenariointrobackgrounds/warInthewild/rampage + scenariointrobackgrounds/warInthewild/legionnaire + scenariointrobackgrounds/warInthewild/death_threat + scenariointrobackgrounds/warInthewild/grak__storm_giant + scenariointrobackgrounds/warInthewild/orc_grunt + scenariointrobackgrounds/warInthewild/honed_black_arrow + scenariointrobackgrounds/warInthewild/domination + scenariointrobackgrounds/lys_the_unseen +# Card list Card available: @@ -2157,7 +3588,7 @@ Card available: word_of_power_carddef() -Card Subtypes +# Card Subtypes Card subtypes are set to allow special processing, like knives or arrows. You may check for it using isCardType(subType) @@ -2246,5 +3677,237 @@ You may check for it using isCardType(subType) attachmentType noKillType -- mark champions with this sybtype to avoid AI killing it (Redeemed Ruinos) -© 2022 Wise Wizard Games, LLC. - + +# Good Practices +## Revealing cards + +When you reveal cards, you move them to reveal scroller. +There are two types of reveal scrollers: + +- common reveal - both players can view card faces in it. +- personal reveal - only one player can view card face in it. Other player sees card back. + +When you move a card to common reveal, its a good practice to let players to examine cards before moving them to next location. +To do that - use following effect. + + waitForClickEffect("Text you see", "Text your opponent sees"), + +It’s also good for personal reveal, so both you and your opponent know what’s going on at the moment. + + waitForClickEffect("Top card of your deck", "Top card of opponent's deck"), + + +# Card examples +## Flipping skill + +Barbarian Eternal Rage skill as an example. +When activated, it transforms itself into berserk variant and stays expended until next turn as transform doesn’t affect expend status of a card. + + function barbarian_eternal_rage_carddef() + + return createSkillDef({ + id = "barbarian_eternal_rage", + name = "Eternal Rage", + description = "Available at level 1", + types = { barbarianType }, + abilities = { + createAbility({ + id="barbarian_eternal_rage", + trigger = uiTrigger, + effect = drawCardsEffect(1).seq(addSlotToPlayerEffect(currentPlayer(), createPlayerSlot({ + key = berserkSlotKey, + expiry = { neverExpiry } + }))). + seq(transformTarget("barbarian_eternal_rage_berserk").apply(selectSource())). + seq(fireAbilityTriggerEffect(barbarianGoneBerserkTrigger)), + cost = combineCosts({ + expendCost, + goldCost(ifInt(isSkillNotFree(), toIntExpression(2), toIntExpression(0))) + }), + activations = multipleActivations, + promptType = showPrompt, + layout = loadLayoutData("layouts/skills_abilities/barbarian/barbarian_eternal_ragev45"), + aiPriority = toIntExpression(-1) + }) + }, + layoutPath = "icons/barbarian_eternal_rage", + layout = loadLayoutData("layouts/skills_abilities/barbarian/barbarian_eternal_ragev45") + }) + end + + function barbarian_eternal_rage_berserk_carddef() + + return createSkillDef({ + id = "barbarian_eternal_rage_berserk", + name = "Eternal Rage", + description = "Available at level 1", + types = { barbarianType}, + abilities = { + createAbility({ + id = "barbarian_eternal_rage_berserk_auto_ability", + effect = ifElseEffect(countPlayerSlots(currentPid, berserkImmuneSlotKey).lte(0), damagePlayerEffect(currentPid, toIntExpression(1)), nullEffect()), + trigger = endOfTurnTrigger, + activations = multipleActivations, + }), + createAbility({ + id = "barbarian_eternal_rage_berserk_ability", + trigger = uiTrigger, + effect = removeSlotFromPlayerEffect(currentPlayer(), berserkSlotKey). + seq(transformTarget("barbarian_eternal_rage").apply(selectSource())), + cost = combineCosts({ + expendCost, + goldCost(ifInt(isSkillNotFree(), toIntExpression(2), toIntExpression(0))) + }), + activations = multipleActivations, + promptType = showPrompt, + layout = loadLayoutData("layouts/skills_abilities/barbarian/barbarian_eternal_rage_berserk"), + aiPriority = toIntExpression(-1) + }) + }, + layout = loadLayoutData("layouts/skills_abilities/barbarian/barbarian_eternal_rage_berserk"), + layoutPath = "icons/barbarian_eternal_rage_berserk" + }) + end +## Class ability + + + function bard_bold_saga_carddef() + local card_name = "bard_bold_saga" + local selector = selectLoc(centerRowLoc).Where(isChampion().And(isCardAcquirable()).And(isCardAffordable())) + local checkSelector = selectLoc(centerRowLoc).Where(isChampion().And(isCardAcquirable()) + .And(getCardCost().lte(getPlayerGold(ownerPid).add(toIntExpression(1))))) + local buffSelector = selectLoc(centerRowLoc).Where(isChampion()) + local buff = getCostDiscountBuff(card_name, 1, buffSelector) + + return createHeroAbilityDef({ + id = card_name, + name = "Bold Saga", + types = { bardType }, + tags = { bardGalleryCardTag }, + abilities = { + createAbility({ + id = card_name .. "_ability_sacrifice", + effect = createCardEffect(buff, currentBuffsLoc) + .seq(e.AcquireToTargets( + toStringExpression("Acquire a champion directly into play"), + selector, + CardLocEnum.InPlay, + { acquireIntoPLayTag, expensiveTag } + )), + cost = sacrificeSelfCost, + trigger = uiTrigger, + promptType = showPrompt, + filter = s.Where(isChampion()), + warning = "You are trying to acquire normally while Bold Saga can be used instead", + check = checkSelector.count().gte(1), + tags = { discountTag, acquireIntoPLayTag }, + aiPriority = toIntExpression(100), + layout = loadLayoutData(skillsDefaultPath .. card_name) + }) + }, + layoutPath = iconDefaultPath .. card_name, + layout = loadLayoutData(skillsDefaultPath .. card_name) + }) + end + + +## Black Spike + local function black_spike_carddef() + local cardLayout = createLayout({ + name = "Black Spike", + art = "art/t_krythos_master_vampire", + frame = "frames/coop_campaign_cardframe", + xmlText = [[ + + + + ]], + health = 0, + isGuard = false + }) + + return createChampionDef({ + id="black_spike", + name="Black Spike", + types={ noStealType }, + acquireCost=0, + health = 0, + isGuard = false, + abilities = { + createAbility({ + id="black_spike_ability", + trigger = autoTrigger, + effect = gainCombatEffect(selectSource().sum(getCardHealth())), + check = selectLoc(currentHandLoc).count().eq(0) + }), + sacrificeSelfOnLeavePlayAbility("black_spike_sacrifice") + }, + layoutPath = "icons/slaughterclaw", + layout = cardLayout + }) + end + + +## Sprout Spike Skill + local function sprout_spike_skill() + local cardLayout = createLayout({ + name = "Sprout a Spike", + art = "art/campaign/warinthewild/slaughterclaw_fixed", + frame = "frames/coop_campaign_cardframe", + text = "Instead of acquiring a card, sacrifice it and sprout a spike equal to its cost." + }) + + return createSkillDef({ + id = "sprout_spike", + name = "Sprout a Spike", + abilities = { + createAbility({ + id = "sprout_spike", + effect = incrementCounterEffect("black_spike_guard", selectLoc(loc(currentPid, discardPloc)).reverse().take(1).sum(getCardCost())) + .seq(sacrificeTarget().apply(selectLoc(loc(currentPid, discardPloc)).reverse().take(1))) + .seq(createCardEffect(black_spike_carddef(), revealLoc)) + .seq(grantHealthTarget(getCounter("black_spike_guard")).apply(selectLoc(revealLoc))) + .seq(moveTarget(currentInPlayLoc).apply(selectLoc(revealLoc))) + .seq(resetCounterEffect("black_spike_guard")), + trigger = onAcquireGlobalTrigger, + activations = multipleActivations, + promptType = showPrompt, + tags = { gainHealthTag } + }) + }, + layoutPath = "features/warinthewild/slaughterclaw_spike", + layout = cardLayout + }) + end + + +## Barbarian Roaring Cowl + function barbarian_roaring_cowl_carddef() + + return createMagicArmorDef({ + id = "barbarian_roaring_cowl", + name = "Roaring Cowl", + description = "Available at level 21", + types = { barbarianType, magicArmorType, backType }, + tags = { barbarianGalleryCardTag, devCardTag }, + level = 21, + acquireCost = 0, + abilities = { + createAbility({ + id = "barbarian_roaring_cowl_ability", + effect = prepareTarget().apply(selectLoc(currentSkillsLoc)) + .seq(addSlotToPlayerEffect(controllerPid, createPlayerIntExpressionSlot(skillCostModKey, toIntExpression(-1), { endOfTurnExpiry }))), + cost = expendCost, + check = getPlayerHealth(currentPid).gte(55).And(getAbilityActivationsThisTurn().gte(1)), + trigger = autoTrigger, + activations = singleActivation, + tags = { drawTag } + }), + }, + layoutPath = "icons/barbarian_roaring_cowl_02", + layout = loadLayoutData("layouts/barbarian/barbarian_roaring_cowl") + }) + end + +© 2024 Wise Wizard Games, LLC. + From 7d9a51debb5c49ecb0c8253cd7fc0437399d23f1 Mon Sep 17 00:00:00 2001 From: Jseph Date: Mon, 29 Dec 2025 22:10:24 -0800 Subject: [PATCH 3/9] Fix string for purchase ability. --- Jseph/purchase_reserve_cards.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index c776513..9dff5c3 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -89,7 +89,7 @@ function purchase_first_reserve_skill_def() local mainLayout = createLayout({ name = "Purchase Reserve", art = "art/T_Taxation", - text = "Acquire reserve card.\nCost: 7 gold + 2 gold per index - 1 gold per turn.", + text = "Acquire reserve card.\nCost: 8 gold + 2 gold per index - 1 gold per turn.", }) return createSkillDef({ From ea25e3c19db49822442247c275b121af341427a6 Mon Sep 17 00:00:00 2001 From: Jseph Date: Tue, 30 Dec 2025 15:13:19 -0800 Subject: [PATCH 4/9] Update SetUpGame to add for both players. Also improve ui experience and consider the purchase an acquisition. --- Jseph/purchase_reserve_cards.lua | 85 ++++++++++++++++++-------------- Jseph/trickster.lua | 7 +-- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index 9dff5c3..8f56383 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -7,11 +7,11 @@ require 'mediumai' require 'easyai' -- Buff that counts how many times the player's deck was shuffled (due to draw or otherwise) -function draw_shuffle_counter_def() +function keep_cards_in_reserve_buf_def() local layout = createLayout({ - name = "Draw Shuffle Counter", + name = "Keep Cards in Reserve", art = "art/t_cunning_of_the_wolf", - text = format("Shuffles: {0}", { getCounter("draw_shuffles") }) + text = "Reserve cards must be purchased." }) local currentReserve = loc(currentPid, reservePloc) local reserveSlot = createSlot({ @@ -32,18 +32,10 @@ function draw_shuffle_counter_def() }) return createBuffDef({ - id = "draw_shuffle_counter", - name = "Draw Shuffle Counter", + id = "keep_cards_in_reserve", + name = "Keep Cards in Reserve", layout = layout, abilities = { - createAbility({ - id = "draw_shuffle_counter_inc", - trigger = deckShuffledTrigger, - activations = multipleActivations, - effect = incrementCounterEffect("draw_shuffles", const(1)).seq(showTextExpressionEffect( - format("Your deck has been shuffled {0} times.", { getCounter("draw_shuffles") }) - )) - }), createAbility({ id = "add_slot_to_reserve_cards", trigger = autoTrigger, @@ -69,10 +61,12 @@ local function create_purchase_ability(baseCost, index, slot) layout = createLayout({ name = "Purchase Reserve", art = "art/T_Taxation", - text = format("{0}, : Move {1} to your hand.\n(-1 per turn)", { dynamicCost, getCardNameStringExpression(target) }), + text = format("{0}, : Acquire {1} to top of deck.\n(-1 per turn)", { dynamicCost, getCardNameStringExpression(target) }), }), effect = addSlotToTarget(slot).apply(target) - .seq(moveTarget(currentHandLoc).apply(target)), + .seq(acquireForFreeTarget(CardLocEnum.ExtraReveal).apply(target)) + .seq(showTextTarget("Acquired reserve card").apply(selectSource())) + .seq(moveTarget(CardLocEnum.Deck).apply(selectLoc(loc(currentPid, extraRevealPloc)).take(1))), cost = combineCosts({ expendCost, goldCost(dynamicCost) }), check = selectLoc(loc(currentPid, reservePloc)).count().gte(index) }) @@ -104,16 +98,18 @@ function purchase_first_reserve_skill_def() create_purchase_ability(15, 4, slot), }, layout = mainLayout, - layoutPath = "avatars/profit" + -- Note: Capitolization matters here. If this is not all lowercase the game + -- crashes at startup for some reason. + layoutPath = "art/t_taxation" }) end function setupGame(g) registerCards(g, { - purchase_first_reserve_skill_def() - }) + }) + standardSetup(g, { - description = "Custom no heroes game", + description = "Knights of Balance: A Community Game Balancing Effort.", playerOrder = { plid1, plid2 }, ai = ai.CreateKillSwitchAi(createAggressiveAI(), createHardAi2()), timeoutAi = createTimeoutAi(), @@ -121,49 +117,62 @@ function setupGame(g) players = { { id = plid1, - startDraw = 5, - name = "Player 1", - avatar="assassin", - health = 50, + startDraw = 3, + init = { + fromEnv = plid1 + }, cards = { + reserve = { + --{ qty = 1, card = wizard_treasure_map_carddef() } + --{ qty = 1, card = ranger_parrot_carddef() } + }, deck = { - { qty=10, card=fire_gem_carddef() }, }, - reserve = { - { qty = 1, card = wizard_treasure_map_carddef() }, - { qty = 1, card = ranger_parrot_carddef() }, - { qty = 1, card = fire_gem_carddef() }, - { qty = 1, card = gold_carddef() }, + hand = { + --{ qty = 5, card = fire_gem_carddef() }, + }, + discard = { }, skills = { - purchase_first_reserve_skill_def() + purchase_first_reserve_skill_def(), }, buffs = { drawCardsCountAtTurnEndDef(5), discardCardsAtTurnStartDef(), fatigueCount(40, 1, "FatigueP1"), - draw_shuffle_counter_def(), + keep_cards_in_reserve_buf_def(), } } }, { id = plid2, startDraw = 5, - name = "Player 2", - avatar="assassin", - health = 50, + init = { + fromEnv = plid2 + }, cards = { + reserve = { + --{ qty = 1, card = wizard_treasure_map_carddef() } + --{ qty = 1, card = ranger_parrot_carddef() } + }, deck = { - { qty=2, card=dagger_carddef() }, - { qty=8, card=gold_carddef() }, + }, + hand = { + --{ qty = 5, card = fire_gem_carddef() }, + }, + discard = { + }, + skills = { + purchase_first_reserve_skill_def(), }, buffs = { drawCardsCountAtTurnEndDef(5), discardCardsAtTurnStartDef(), - fatigueCount(40, 1, "FatigueP2") + fatigueCount(40, 1, "FatigueP2"), + keep_cards_in_reserve_buf_def(), } } - } + }, } }) end diff --git a/Jseph/trickster.lua b/Jseph/trickster.lua index 64ac0f1..899cad8 100644 --- a/Jseph/trickster.lua +++ b/Jseph/trickster.lua @@ -515,14 +515,15 @@ end + function setupMeta(meta) meta.name = "trickster" - meta.minLevel = 3 - meta.maxLevel = 3 + meta.minLevel = 0 + meta.maxLevel = 0 meta.introbackground = "" meta.introheader = "" meta.introdescription = "" - meta.path = "C:/Program Files (x86)/Steam/steamapps/common/Hero Realms/Custom Scripts/trickster.lua" + meta.path = "C:/Users/jseph/hero-realms-lua-scripts/hero-realms-lua-scripts/Jseph/trickster.lua" meta.features = { } From 79a5fc35997d22c421f99a33bb874db7c15388ca Mon Sep 17 00:00:00 2001 From: Jseph Date: Wed, 31 Dec 2025 23:44:16 -0800 Subject: [PATCH 5/9] Update to use static prices for cards. --- Jseph/purchase_reserve_cards.lua | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index 8f56383..689a39a 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -105,8 +105,21 @@ function purchase_first_reserve_skill_def() end function setupGame(g) - registerCards(g, { - }) + --testing flags + local add_all_reserve_cards_to_deck = true + local all_card_defs = {} + local class_list = { "fighter", "wizard", "cleric", "ranger", "thief", + "barbarian", "alchemist", "druid", "necromancer", "bard", "monk" } + if add_all_reserve_cards_to_deck then + for n, x in pairs(_G) do + if type(x) == "function" and string.find(n, "carddef$") then + local firstWord = string.split(n, "_")[1] + table.insert(all_card_defs, x()) + end + end + end + table.insert(all_card_defs, purchase_first_reserve_skill_def()) + registerCards(g, all_card_defs) standardSetup(g, { description = "Knights of Balance: A Community Game Balancing Effort.", From 929a7c4c258a054361f63b6fce182b1e0e2f6b98 Mon Sep 17 00:00:00 2001 From: Jseph Date: Wed, 31 Dec 2025 23:44:55 -0800 Subject: [PATCH 6/9] Push more changes for purchase_reserve_cards --- Jseph/purchase_reserve_cards.lua | 155 +++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 41 deletions(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index 689a39a..3bdd881 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -6,7 +6,17 @@ require 'hardai' require 'mediumai' require 'easyai' --- Buff that counts how many times the player's deck was shuffled (due to draw or otherwise) +--testing flags +local addAllClassCardsToDeck = false +local decreaseCostPerTurn = false +local debugStart = false + +-- constants +local cardCostOverrides = { + {"wizard_treasure_map", 2}, + {"ranger_parrot", 3} +} + function keep_cards_in_reserve_buf_def() local layout = createLayout({ name = "Keep Cards in Reserve", @@ -30,7 +40,7 @@ function keep_cards_in_reserve_buf_def() activations = multipleActivations, tags = { } }) - + return createBuffDef({ id = "keep_cards_in_reserve", name = "Keep Cards in Reserve", @@ -61,17 +71,57 @@ local function create_purchase_ability(baseCost, index, slot) layout = createLayout({ name = "Purchase Reserve", art = "art/T_Taxation", - text = format("{0}, : Acquire {1} to top of deck.\n(-1 per turn)", { dynamicCost, getCardNameStringExpression(target) }), + text = format("{0}, : Acquire {1} to hand.\n(-1 per turn)", { dynamicCost, getCardNameStringExpression(target) }), }), effect = addSlotToTarget(slot).apply(target) .seq(acquireForFreeTarget(CardLocEnum.ExtraReveal).apply(target)) .seq(showTextTarget("Acquired reserve card").apply(selectSource())) - .seq(moveTarget(CardLocEnum.Deck).apply(selectLoc(loc(currentPid, extraRevealPloc)).take(1))), + .seq(moveTarget(CardLocEnum.Hand).apply(selectLoc(loc(currentPid, extraRevealPloc)).take(1))), cost = combineCosts({ expendCost, goldCost(dynamicCost) }), check = selectLoc(loc(currentPid, reservePloc)).count().gte(index) }) end +local function basic_acquire_ability_layout() + local target = selectLoc(loc(currentPid, reservePloc)).take(1) + local cardCost = const(4) + for _, override in ipairs(cardCostOverrides) do + cardCost = ifInt( + target.where(isCardName(override[1])).count().eq(const(1)), + const(override[2]), + cardCost) + end + return createLayout({ + name = "Purchase Reserve", + art = "art/T_Taxation", + text = format(", : Acquire {1} to hand.", + { cardCost, getCardNameStringExpression(target) }), + }) +end + +local function create_basic_acquire_ability(slot) + local target = selectLoc(loc(currentPid, reservePloc)).take(1) + local cardCost = const(4) + for _, override in ipairs(cardCostOverrides) do + cardCost = ifInt( + target.where(isCardName(override[1])).count().eq(const(1)), + const(override[2]), + cardCost) + end + return createAbility({ + id = "purchase_first_reserve_card", + trigger = uiTrigger, + promptType = showPrompt, + layout = basic_acquire_ability_layout(), + effect = addSlotToTarget(slot).apply(target) + .seq(acquireForFreeTarget(CardLocEnum.ExtraReveal).apply(target)) + .seq(showTextTarget("Acquired reserve card").apply(selectSource())) + .seq(moveTarget(CardLocEnum.Hand).apply(selectLoc(loc(currentPid, extraRevealPloc)).take(1))), + cost = combineCosts({ expendCost, goldCost(cardCost) }), + check = selectLoc(loc(currentPid, reservePloc)).count().gte(1) + }) +end + -- Skill: pay dynamic gold to purchase one of the first 4 cards in your reserve function purchase_first_reserve_skill_def() local slot = createSlot({ id = "purchased_reserve_card_slot", expiresArray = { neverExpiry } }) @@ -85,18 +135,23 @@ function purchase_first_reserve_skill_def() art = "art/T_Taxation", text = "Acquire reserve card.\nCost: 8 gold + 2 gold per index - 1 gold per turn.", }) + local purchaseAbilities = {} + if decreaseCostPerTurn then + table.insert(purchaseAbilities, create_purchase_ability(9, 1, slot)) + table.insert(purchaseAbilities, create_purchase_ability(11, 2, slot)) + table.insert(purchaseAbilities, create_purchase_ability(13, 3, slot)) + table.insert(purchaseAbilities, create_purchase_ability(15, 4, slot)) + else + mainLayout = basic_acquire_ability_layout() + table.insert(purchaseAbilities, create_basic_acquire_ability(slot)) + end return createSkillDef({ id = "purchase_first_reserve_skill", name = "Purchase Reserve", cardTypeLabel = "Skill", types = { skillType }, - abilities = { - create_purchase_ability(9, 1, slot), - create_purchase_ability(11, 2, slot), - create_purchase_ability(13, 3, slot), - create_purchase_ability(15, 4, slot), - }, + abilities = purchaseAbilities, layout = mainLayout, -- Note: Capitolization matters here. If this is not all lowercase the game -- crashes at startup for some reason. @@ -105,22 +160,47 @@ function purchase_first_reserve_skill_def() end function setupGame(g) - --testing flags - local add_all_reserve_cards_to_deck = true - local all_card_defs = {} - local class_list = { "fighter", "wizard", "cleric", "ranger", "thief", + local allCardDefs = {} + local classList = { "fighter", "wizard", "cleric", "ranger", "thief", "barbarian", "alchemist", "druid", "necromancer", "bard", "monk" } - if add_all_reserve_cards_to_deck then + + local function contains(t, val) + for _, v in pairs(t) do + if v == val then return true end + end + return false + end + + if addAllClassCardsToDeck then for n, x in pairs(_G) do if type(x) == "function" and string.find(n, "carddef$") then - local firstWord = string.split(n, "_")[1] - table.insert(all_card_defs, x()) + local firstWord = string.match(n, "^([^_]+)_") + if contains(classList, firstWord) then + table.insert(allCardDefs, x()) + end end end end - table.insert(all_card_defs, purchase_first_reserve_skill_def()) - registerCards(g, all_card_defs) - + local allToRegister = { purchase_first_reserve_skill_def() } + for _, cardDef in ipairs(allCardDefs) do + table.insert(allToRegister, cardDef) + end + registerCards(g, allToRegister) + local p1Deck = {} + if addAllClassCardsToDeck then + for _, cardDef in ipairs(allCardDefs) do + table.insert(p1Deck, { qty = 1, card = cardDef }) + end + end + local p1Reserve = {} + local p1Hand = {} + if debugStart then + p1Reserve = { + { qty = 1, card = wizard_treasure_map_carddef() }, + { qty = 1, card = ranger_parrot_carddef() }, + } + p1Hand = { { qty = 5, card = fire_gem_carddef() }} + end standardSetup(g, { description = "Knights of Balance: A Community Game Balancing Effort.", playerOrder = { plid1, plid2 }, @@ -135,15 +215,9 @@ function setupGame(g) fromEnv = plid1 }, cards = { - reserve = { - --{ qty = 1, card = wizard_treasure_map_carddef() } - --{ qty = 1, card = ranger_parrot_carddef() } - }, - deck = { - }, - hand = { - --{ qty = 5, card = fire_gem_carddef() }, - }, + reserve = p1Reserve, + deck = p1Deck, + hand = p1Hand, discard = { }, skills = { @@ -193,15 +267,14 @@ end function endGame(g) end - function setupMeta(meta) - meta.name = "purchase_reserve_cards" - meta.minLevel = 0 - meta.maxLevel = 0 - meta.introbackground = "" - meta.introheader = "" - meta.introdescription = "" - meta.path = "C:/Users/jseph/hero-realms-lua-scripts/hero-realms-lua-scripts/Jseph/purchase_reserve_cards.lua" - meta.features = { -} - - end \ No newline at end of file +function setupMeta(meta) + meta.name = "purchase_reserve_cards" + meta.minLevel = 0 + meta.maxLevel = 0 + meta.introbackground = "" + meta.introheader = "" + meta.introdescription = "" + meta.path = "C:/Users/jseph/hero-realms-lua-scripts/hero-realms-lua-scripts/Jseph/purchase_reserve_cards.lua" + meta.features = { + } +end From 61794f7e957683699ddfc518fa26fbd64ebbd851 Mon Sep 17 00:00:00 2001 From: Jseph Date: Thu, 8 Jan 2026 21:45:10 -0800 Subject: [PATCH 7/9] Finish single card purchase from reserve. --- Jseph/purchase_reserve_cards.lua | 156 ++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 24 deletions(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index 3bdd881..b7bf37d 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -13,8 +13,95 @@ local debugStart = false -- constants local cardCostOverrides = { - {"wizard_treasure_map", 2}, - {"ranger_parrot", 3} + {"cleric_ship_s_bell", 4}, + {"cleric_imperial_sailor", 3}, -- This is apparently the old name of the card + {"cleric_sacred_censer", 4}, + {"cleric_holy_water", 4}, + {"cleric_morningstar", 7}, + {"cleric_enduring_follower", 6}, + {"cleric_motes_of_light", 5}, + {"cleric_benediction_beads", 7}, + {"fighter_cutlass", 3}, + {"fighter_bottle_of_rum", 3}, + {"fighter_chain", 6}, + {"fighter_javelin", 3}, + {"fighter_taunting_talisman", 4}, + {"fighter_ruinic_throwing_axe", 4}, -- The card name just says "Throwing Axe." I'm guessing that's a mistake in the card name. + {"fighter_iron_shield", 5}, + {"fighter_dazzling_ruby", 5}, + {"fighter_ruby_glaive", 5}, + {"ranger_spyglass", 3}, + {"ranger_parrot", 4}, + {"ranger_veiled_trap", 3}, + {"ranger_death_arrow", 4}, + {"ranger_hawk_pet", 3}, + {"ranger_gold_tipped_arrow", 6}, + {"ranger_trick_arrow", 3}, + {"ranger_stalking_bow", 6}, + {"thief_trick_dice", 4}, + {"thief_hook", 4}, + {"thief_kunai", 4}, + {"thief_blinding_powder", 6}, + {"thief_lockpick", 6}, + {"thief_ruby_encrusted_blade", 4}, + {"thief_parrying_dagger", 7}, + {"thief_lucky_knife", 6}, + {"wizard_ship_in_a_bottle", 4}, + {"wizard_treasure_map", 5}, + {"wizard_prestidigitation_charm", 5}, + {"wizard_clock_of_ages", 5}, + {"wizard_wand_of_wanting", 6}, + {"wizard_combust", 7}, + {"wizard_magic_scroll", 5}, + {"wizard_adept_s_components", 7}, + {"barbarian_ring_of_rage", 3}, + {"barbarian_earthshaker", 3}, + {"barbarian_caltrops", 3}, + {"barbarian_seething_spear", 4}, + {"barbarian_whip", 3}, + {"barbarian_double_bladed_hand_axe", 5}, + {"barbarian_net_of_thorns", 3}, + {"barbarian_pillage", 7}, + {"alchemist_fireworks", 4}, + {"alchemist_recalibration_crystal", 4}, + {"alchemist_kaleidoscope", 4}, + {"alchemist_vial_of_acid", 3}, + {"alchemist_recipe_book", 7}, + {"alchemist_rainbow_potion", 5}, + {"alchemist_silver_scales", 4}, + {"alchemist_diamond", 8}, + {"druid_entangling_roots", 2}, + {"druid_panther_eye_ring", 3}, + {"druid_wisdom_of_the_woods", 4}, + {"druid_sunbird", 4}, + {"druid_reclaim_the_forest", 4}, + {"druid_hardy_hedgehog", 6}, + {"druid_great_stag", 5}, + {"druid_flying_squirrel", 6}, + {"necromancer_severing_scythe", 4}, + {"necromancer_stitcher_s_kit", 5}, + {"necromancer_preserved_heart", 4}, + {"necromancer_dread_cauldron", 3}, + {"necromancer_bag_of_bones", 5}, + {"necromancer_soul_cage", 5}, + {"necromancer_withered_wand", 4}, -- Named this way instead of withering_wand for some reason + {"necromancer_company_of_corpses", 5}, + {"bard_musical_darts", 3}, + {"bard_songbook", 3}, + {"bard_muse_s_paper", 5}, + {"bard_music_box", 4}, + {"bard_whistling_rapier", 7}, + {"bard_enchanted_flute", 5}, + {"bard_tuning_fork", 5}, + {"bard_captivating_herald", 6}, + {"monk_amulet_of_resolve", 4}, + {"monk_horn_of_ascendance", 3}, + {"monk_dragon_staff", 4}, + {"monk_tranquil_wind", 4}, + {"monk_arm_bands_of_solemnity", 5}, + {"monk_void_s_eye", 4}, + {"monk_staff_of_the_phoenix", 5}, + {"monk_radiant_blossom", 4}, } function keep_cards_in_reserve_buf_def() @@ -28,18 +115,28 @@ function keep_cards_in_reserve_buf_def() id = "reserve_card_slot", expiresArray = { neverExpiry }, }) - local moveBackToReserve = createCardEffectAbility({ - id = "move_back_to_reserve", - effect = moveTarget(currentReserve).apply(selectTargets().where( + local unpurchasedReserveCardsNotInReserve = selectTargets().where( isCardWithSlot("reserve_card_slot").And( isCardWithSlot("purchased_reserve_card_slot").invert() - ))), + )).exclude(selectLoc(loc(currentPid, myRevealPloc))) + .exclude(selectLoc(loc(currentPid, reservePloc))) + local moveBackToReserve = createCardEffectAbility({ + id = "move_back_to_reserve", + effect = moveTarget(currentReserve).apply(unpurchasedReserveCardsNotInReserve), cost = noCost, trigger = locationChangedCardTrigger, check = const(1).eq(1), activations = multipleActivations, tags = { } }) + + local applyCostChangeEffect = nullEffect() + for _, override in ipairs(cardCostOverrides) do + applyCostChangeEffect = applyCostChangeEffect.seq( + addSlotToTarget(createCostChangeSlot(-override[2], { endOfTurnExpiry })) + .apply(selectLoc(currentReserve).where(isCardName(override[1]))) + ) + end return createBuffDef({ id = "keep_cards_in_reserve", @@ -48,9 +145,10 @@ function keep_cards_in_reserve_buf_def() abilities = { createAbility({ id = "add_slot_to_reserve_cards", - trigger = autoTrigger, - activations = singleActivation, + trigger = startOfTurnTrigger, + activations = multipleActivations, effect = addSlotToTarget(reserveSlot).apply(selectLoc(currentReserve)) + .seq(applyCostChangeEffect) }), }, cardEffectAbilities = { @@ -83,7 +181,7 @@ local function create_purchase_ability(baseCost, index, slot) end local function basic_acquire_ability_layout() - local target = selectLoc(loc(currentPid, reservePloc)).take(1) + local target = selectLoc(loc(ownerPid, reservePloc)).take(1) local cardCost = const(4) for _, override in ipairs(cardCostOverrides) do cardCost = ifInt( @@ -94,30 +192,40 @@ local function basic_acquire_ability_layout() return createLayout({ name = "Purchase Reserve", art = "art/T_Taxation", - text = format(", : Acquire {1} to hand.", - { cardCost, getCardNameStringExpression(target) }), + text = format(": Acquire reserve cards to hand.{0}", + { ifElseString( + target.count().eq(const(0)), + "", + format("\nNext card: {0}\n({1} gold)", {getCardNameStringExpression(target), cardCost}))}), }) end local function create_basic_acquire_ability(slot) - local target = selectLoc(loc(currentPid, reservePloc)).take(1) - local cardCost = const(4) - for _, override in ipairs(cardCostOverrides) do - cardCost = ifInt( - target.where(isCardName(override[1])).count().eq(const(1)), - const(override[2]), - cardCost) - end + local target = selectLoc(loc(currentPid, reservePloc)).take(5) return createAbility({ id = "purchase_first_reserve_card", trigger = uiTrigger, promptType = showPrompt, + activations = multipleActivations, layout = basic_acquire_ability_layout(), - effect = addSlotToTarget(slot).apply(target) - .seq(acquireForFreeTarget(CardLocEnum.ExtraReveal).apply(target)) - .seq(showTextTarget("Acquired reserve card").apply(selectSource())) - .seq(moveTarget(CardLocEnum.Hand).apply(selectLoc(loc(currentPid, extraRevealPloc)).take(1))), - cost = combineCosts({ expendCost, goldCost(cardCost) }), + effect = moveTarget(loc(currentPid, myRevealPloc)).apply(target.reverse()) + .seq(ifElseEffect( + selectLoc(loc(currentPid, myRevealPloc)).take(1).where(getCardCost().lte(getPlayerGold(ownerPid))).count().gte(const(1)), + pushTargetedEffect({ + desc = "Choose a card to purchase", + validTargets = selectLoc(loc(currentPid, myRevealPloc)).take(1).where(getCardCost().lte(getPlayerGold(ownerPid))), + min = 0, + max = 1, + targetEffect = addSlotToTarget(slot).seq(acquireTarget(0, CardLocEnum.Hand)) + .seq(showTextTarget("Acquired reserve card").apply(selectSource())) + .seq(moveTarget(CardLocEnum.Reserve).apply(selectLoc(loc(currentPid, myRevealPloc)).reverse())), + tags = { } + }), + waitForClickEffect("No affordable cards.", "") + .seq(moveTarget(CardLocEnum.Reserve).apply(selectLoc(loc(currentPid, myRevealPloc)).reverse())) + .seq(prepareTarget().apply(selectSource())) + )), + cost = expendCost, check = selectLoc(loc(currentPid, reservePloc)).count().gte(1) }) end From 076b8c054415aceedc52ae35f9d4d3bdbd8b8997 Mon Sep 17 00:00:00 2001 From: Jseph Date: Thu, 8 Jan 2026 21:50:06 -0800 Subject: [PATCH 8/9] Update doc string. --- Jseph/purchase_reserve_cards.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index b7bf37d..9449b53 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -310,7 +310,7 @@ function setupGame(g) p1Hand = { { qty = 5, card = fire_gem_carddef() }} end standardSetup(g, { - description = "Knights of Balance: A Community Game Balancing Effort.", + description = "Purchase Reserve Cards: A balancing effort by Jseph, Userkaffe and Azgalor.", playerOrder = { plid1, plid2 }, ai = ai.CreateKillSwitchAi(createAggressiveAI(), createHardAi2()), timeoutAi = createTimeoutAi(), From 184167700bc79956fa6d4896662933d7aa6b3b87 Mon Sep 17 00:00:00 2001 From: Jseph Date: Thu, 12 Feb 2026 21:30:43 -0800 Subject: [PATCH 9/9] Improve trickster healing and monkey --- Jseph/purchase_reserve_cards.lua | 121 +++++++++++-------------------- Jseph/trickster.lua | 73 +++++++++++++++++-- 2 files changed, 111 insertions(+), 83 deletions(-) diff --git a/Jseph/purchase_reserve_cards.lua b/Jseph/purchase_reserve_cards.lua index 9449b53..7e1e378 100644 --- a/Jseph/purchase_reserve_cards.lua +++ b/Jseph/purchase_reserve_cards.lua @@ -111,6 +111,7 @@ function keep_cards_in_reserve_buf_def() text = "Reserve cards must be purchased." }) local currentReserve = loc(currentPid, reservePloc) + local opponentReserve = loc(oppPid, reservePloc) local reserveSlot = createSlot({ id = "reserve_card_slot", expiresArray = { neverExpiry }, @@ -134,7 +135,8 @@ function keep_cards_in_reserve_buf_def() for _, override in ipairs(cardCostOverrides) do applyCostChangeEffect = applyCostChangeEffect.seq( addSlotToTarget(createCostChangeSlot(-override[2], { endOfTurnExpiry })) - .apply(selectLoc(currentReserve).where(isCardName(override[1]))) + .apply(selectLoc(currentReserve).union(selectLoc(opponentReserve)) + .where(isCardName(override[1]))) ) end @@ -146,7 +148,7 @@ function keep_cards_in_reserve_buf_def() createAbility({ id = "add_slot_to_reserve_cards", trigger = startOfTurnTrigger, - activations = multipleActivations, + activations = singleActivation, effect = addSlotToTarget(reserveSlot).apply(selectLoc(currentReserve)) .seq(applyCostChangeEffect) }), @@ -157,29 +159,6 @@ function keep_cards_in_reserve_buf_def() }) end --- Helper function to create purchase abilities for reserve cards -local function create_purchase_ability(baseCost, index, slot) - local target = selectLoc(loc(currentPid, reservePloc)).take(index).reverse().take(1) - local rawCost = const(baseCost).add(getTurnsPlayed(currentPid).negate()) - local dynamicCost = ifInt(rawCost.gte(const(0)), rawCost, const(0)) - return createAbility({ - id = "purchase_reserve_activate_" .. index, - trigger = uiTrigger, - promptType = showPrompt, - layout = createLayout({ - name = "Purchase Reserve", - art = "art/T_Taxation", - text = format("{0}, : Acquire {1} to hand.\n(-1 per turn)", { dynamicCost, getCardNameStringExpression(target) }), - }), - effect = addSlotToTarget(slot).apply(target) - .seq(acquireForFreeTarget(CardLocEnum.ExtraReveal).apply(target)) - .seq(showTextTarget("Acquired reserve card").apply(selectSource())) - .seq(moveTarget(CardLocEnum.Hand).apply(selectLoc(loc(currentPid, extraRevealPloc)).take(1))), - cost = combineCosts({ expendCost, goldCost(dynamicCost) }), - check = selectLoc(loc(currentPid, reservePloc)).count().gte(index) - }) -end - local function basic_acquire_ability_layout() local target = selectLoc(loc(ownerPid, reservePloc)).take(1) local cardCost = const(4) @@ -192,11 +171,7 @@ local function basic_acquire_ability_layout() return createLayout({ name = "Purchase Reserve", art = "art/T_Taxation", - text = format(": Acquire reserve cards to hand.{0}", - { ifElseString( - target.count().eq(const(0)), - "", - format("\nNext card: {0}\n({1} gold)", {getCardNameStringExpression(target), cardCost}))}), + text = ": Acquire the next reserve card to hand." }) end @@ -257,7 +232,6 @@ function purchase_first_reserve_skill_def() return createSkillDef({ id = "purchase_first_reserve_skill", name = "Purchase Reserve", - cardTypeLabel = "Skill", types = { skillType }, abilities = purchaseAbilities, layout = mainLayout, @@ -268,47 +242,10 @@ function purchase_first_reserve_skill_def() end function setupGame(g) - local allCardDefs = {} - local classList = { "fighter", "wizard", "cleric", "ranger", "thief", - "barbarian", "alchemist", "druid", "necromancer", "bard", "monk" } - - local function contains(t, val) - for _, v in pairs(t) do - if v == val then return true end - end - return false - end + registerCards(g, { + purchase_first_reserve_skill_def(), + }) - if addAllClassCardsToDeck then - for n, x in pairs(_G) do - if type(x) == "function" and string.find(n, "carddef$") then - local firstWord = string.match(n, "^([^_]+)_") - if contains(classList, firstWord) then - table.insert(allCardDefs, x()) - end - end - end - end - local allToRegister = { purchase_first_reserve_skill_def() } - for _, cardDef in ipairs(allCardDefs) do - table.insert(allToRegister, cardDef) - end - registerCards(g, allToRegister) - local p1Deck = {} - if addAllClassCardsToDeck then - for _, cardDef in ipairs(allCardDefs) do - table.insert(p1Deck, { qty = 1, card = cardDef }) - end - end - local p1Reserve = {} - local p1Hand = {} - if debugStart then - p1Reserve = { - { qty = 1, card = wizard_treasure_map_carddef() }, - { qty = 1, card = ranger_parrot_carddef() }, - } - p1Hand = { { qty = 5, card = fire_gem_carddef() }} - end standardSetup(g, { description = "Purchase Reserve Cards: A balancing effort by Jseph, Userkaffe and Azgalor.", playerOrder = { plid1, plid2 }, @@ -318,53 +255,81 @@ function setupGame(g) players = { { id = plid1, + --isAi = true, startDraw = 3, init = { fromEnv = plid1 }, cards = { - reserve = p1Reserve, - deck = p1Deck, - hand = p1Hand, + reserve = { + --{ qty = 1, card = wizard_treasure_map_carddef() }, + --{ qty = 1, card = ranger_parrot_carddef() } + }, + deck = { + }, + hand = { + --{ qty = 1, card = thief_enchanted_garrote_carddef() }, + --{ qty = 1, card = ranger_honed_black_arrow_carddef() }, + --{ qty = 2, card = cleric_follower_b_carddef() }, + --{ qty = 2, card = cleric_imperial_sailor_carddef() }, + --{ qty = 1, card = cleric_brightstar_shield_carddef() }, + --{ qty = 1, card = fighter_rallying_flag_carddef() }, + --{ qty = 1, card = barbarian_disorienting_headbutt_carddef() }, + }, discard = { + -- { qty = 2, card = torgen_rocksplitter_carddef() }, + -- { qty = 2, card = cleric_follower_b_carddef() }, + -- { qty = 1, card = cleric_follower_a_carddef() }, + -- { qty = 1, card = cleric_veteran_follower_carddef() }, + -- { qty = 1, card = cleric_redeemed_ruinos_carddef() }, }, skills = { purchase_first_reserve_skill_def(), + --{ qty = 1, card = fighter_helm_of_fury_carddef() }, + --{ qty = 1, card = alchemist_spectrum_spectacles_carddef() } }, buffs = { + keep_cards_in_reserve_buf_def(), drawCardsCountAtTurnEndDef(5), discardCardsAtTurnStartDef(), fatigueCount(40, 1, "FatigueP1"), - keep_cards_in_reserve_buf_def(), } } }, { id = plid2, + --isAi = true, startDraw = 5, init = { fromEnv = plid2 }, cards = { reserve = { - --{ qty = 1, card = wizard_treasure_map_carddef() } + --{ qty = 1, card = wizard_treasure_map_carddef() }, --{ qty = 1, card = ranger_parrot_carddef() } }, deck = { }, hand = { - --{ qty = 5, card = fire_gem_carddef() }, + --{ qty = 1, card = ranger_light_crossbow_carddef() }, + --{ qty = 1, card = ranger_honed_black_arrow_carddef() }, + --{ qty = 2, card = cleric_follower_b_carddef() }, + --{ qty = 2, card = cleric_imperial_sailor_carddef() }, + --{ qty = 1, card = cleric_brightstar_shield_carddef() }, + --{ qty = 1, card = sway_carddef() }, }, discard = { + --{ qty = 2, card = cleric_follower_b_carddef() }, }, skills = { purchase_first_reserve_skill_def(), + --{ qty = 1, card = cleric_shining_breastplate_carddef() }, }, buffs = { + keep_cards_in_reserve_buf_def(), drawCardsCountAtTurnEndDef(5), discardCardsAtTurnStartDef(), fatigueCount(40, 1, "FatigueP2"), - keep_cards_in_reserve_buf_def(), } } }, diff --git a/Jseph/trickster.lua b/Jseph/trickster.lua index 899cad8..7ba4d76 100644 --- a/Jseph/trickster.lua +++ b/Jseph/trickster.lua @@ -24,6 +24,23 @@ Decrease the cost of two random cards in the market by {gold_2} until your next ]] +pet_monkey_layout = [[ + + + + + + + + + + + + + + +]] + hidden_power_layout = [[ : Shuffle your deck then draw {1} card(s)", + text = format("{0}: Gain {0} and shuffle your deck then draw {1} card(s)", {s.Minus(s.getCounter("reshuffle_card_count"), s.Const(1)), getCounter("reshuffle_card_count")}) }), @@ -90,7 +108,7 @@ end local function reshuffle_effect() return pushTargetedEffect({ desc = - "Pay X to put X+1 cards back in your deck, shuffle, and then draw X+1 cards.", + "Pay X to gain X, put X+1 cards back in your deck, shuffle, and then draw X+1 cards.", min=1, max=ifInt( selectLoc(loc(currentPid, handPloc)).count() @@ -110,7 +128,7 @@ local function reshuffle_hand_skill() name = "Mulligan", art = "art/t_prism_rainerpetter", frame = "frames/alchemist_frames/alchemist_item_cardframe", - text = ": Pay X to put X+1 cards back in your deck, shuffle, and then draw X+1 cards." + text = ": Pay X to gain X and put X+1 cards back in your deck, shuffle, and then draw X+1 cards." }) --[[local effect_chain = ignoreTarget(showTextEffect("Listing functions!")) for name, value in pairs(_G) do @@ -262,7 +280,7 @@ local function double_or_nothing_card_def() }) end -local function pet_monkey_effect() +local function pet_monkey_effect_1() local kMoveText = "Moving to top of market deck." return pushTargetedEffect({ desc = "Select a card to swap with the top card of the market deck.", @@ -276,12 +294,56 @@ local function pet_monkey_effect() }) end +local function pet_monkey_effect_2() + return moveTarget(currentRevealLoc).apply(selectLoc(tradeDeckLoc).take(3).reverse()) + .seq(noUndoEffect()) + .seq(promptSplit({ + selector = selectLoc(currentRevealLoc), + take = const(0), -- number of cards to take for split + sort = const(0), -- number of cards to be sorted for ef2 + minTake = const(3), -- Number of cards for ef1 + ef1 = moveTarget(tradeDeckLoc), -- effect to be applied to cards left + ef2 = ignoreTarget(nullEffect()), -- effect to be applied to sorted cards + header = "Pet Monkey", -- prompt header + description = "Rearrange the top 3 cards of the market deck.", + rightPileDesc = "Rightmost on top.", + pile1Name = "Top 3 cards.", + pile2Name = "Unused.", + eff1Tags = { }, + eff2Tags = { }})) +end + +local function pet_monkey_effect() + return pushChoiceEffect({ + choices = { + { + effect = pet_monkey_effect_1(), + layout = createLayout({ + name = "Pet Monkey", + art = "art/epicart/kong", + text = "Swap any card on the market row with the top card of the market deck." + }), + }, + { + effect = pet_monkey_effect_2(), + layout = createLayout({ + name = "Pet Monkey", + art = "art/epicart/kong", + text = "Rearrange the top 3 cards of the market deck.", + check = selectLoc(tradeDeckLoc).count() + .gte(const(3)) + }), + }, + } + }) +end + local function pet_monkey_card_def() local cardLayout = createLayout({ name = "Pet Monkey", art = "art/epicart/kong", frame = "frames/alchemist_frames/alchemist_item_cardframe", - text = ": Swap any card on the market row with the top card of the market deck.", + xmlText = pet_monkey_layout, }) return createChampionDef({ id = "pet_monkey_jseph", @@ -516,6 +578,7 @@ end + function setupMeta(meta) meta.name = "trickster" meta.minLevel = 0