diff --git a/abilities.js b/abilities.js index 1e2258b..57157e1 100644 --- a/abilities.js +++ b/abilities.js @@ -27,6 +27,25 @@ var ability_dict = { } } }, + white_frost: { + name: "White Frost", + description: "Sets the strength of all cards on the 2 corresponding rows to 1 for both players.", + placed: async (card, row) => { + if(card.targetRows === "agile_cr") + card.abilities = ["frost", "fog"]; + else if (card.targetRows === "agile_cs") + card.abilities = ["frost", "rain"]; + else if (card.targetRows === "agile_rs") + card.abilities = ["fog", "rain"]; + // If ability carried by a unit/hero card, draw the weather card from the deck + if (card.isUnit() || card.hero) { + let wCard = card.holder.deck.findCard(c => c.row === "weather" && c.abilities.includes("white_frost")); + if (wCard) { + await wCard.autoplay(card.holder.deck); + } + } + } + }, fog: { name: "Impenetrable Fog", description: "Sets the strength of all Ranged Combat cards to 1 for both players. ", @@ -214,7 +233,7 @@ var ability_dict = { try { Carousel.curr.cancel(); } catch (err) { } - await ui.queueCarousel(cards, 1, (c, i) => targetCard = c.cards[i], c => true, true, false, "Choose the card to draw and play keep"); + await ui.queueCarousel(cards, 1, (c, i) => targetCard = c.cards[i], c => true, true, false, "Choose the card to draw and keep"); } cards.cards.forEach(c => { if (c === targetCard) { @@ -643,11 +662,6 @@ var ability_dict = { } }, - eredin_commander: { - description: "Double the strength of all your Close Combat units (unless a Commander's horn is also present on that row).", - activated: async card => await board.getRow(card, "close", card.holder).leaderHorn(card), - weight: (card, ai) => ai.weightHornRow(card, board.getRow(card, "close", card.holder)) - }, eredin_bringer_of_death: { name: "Eredin : Bringer of Death", description: "Restore a unit card from your discard pile to your hand.", @@ -1234,13 +1248,17 @@ var ability_dict = { let cardsCount = Math.min(3, units.length); let targetCards = []; if (card.holder.controller instanceof ControllerAI) { - targetCards = card.holder.controller.getWeights(units).sort((a, b) => (b.weight - a.weight)).slice(0, cardsCount); + targetCards = card.holder.controller.getWeights(units).sort((a, b) => (b.weight - a.weight)).slice(0, cardsCount).map(c => c.card); } else { await ui.queueCarousel({ cards: units }, cardsCount, (c, i) => targetCards.push(c.cards[i]), c => true, true, true, "Choose up to " + String(cardsCount)+" to bring back to your hand."); } targetCards.forEach(async card => { await board.toHand(card, card.holder.grave); }); + }, + weight: card => { + let units = card.holder.grave.cards.filter(c => c.isUnit() && c.destructionRound == game.roundCount); + return Math.min(15, units.length * 5); } }, ghost_tree: { @@ -1279,6 +1297,114 @@ var ability_dict = { } }, + eredin_commander: { + description: "Choose on your battlefield two or less unit and/or hero cards and take them into your hand.", + activated: async card => { + let cards = card.holder.getAllRowCards().filter(c => c.hero || c.isUnit()); + if (cards.length == 0) + return false; + let targetCards = []; + let cardsCount = Math.min(2, cards.length); + if (card.holder.controller instanceof ControllerAI) { + targetCards = card.holder.controller.selectBestCards(cards, cardsCount, { "spy": 10, "zirael": 10, "medic": 8, "sage": 4, "scorch_c": 4, "scorch_r": 4 }).map(c => c.card); + } else { + await ui.queueCarousel({ cards: cards }, cardsCount, (c, i) => targetCards.push(c.cards[i]), c => true, true, true, "Choose up to " + String(cardsCount)+" cards to take back into your hand."); + } + if (targetCards.length > 0) { + targetCards.forEach(async c => { + await board.moveTo(c, card.holder.hand, c.currentLocation); + }); + } + }, + weight: card => { + let cards = card.holder.getAllRowCards().filter(c => c.hero || c.isUnit()); + if (cards.length == 0) + return card.holder.controller.selectBestCards(cards, Math.min(2, cards.length), { "spy": 10, "medic": 8, "sage": 4, "scorch_c": 4 }).reduce((a,c) => a + c.weight,0); + return 5; + + } + }, + auberon_king: { + description: "Draw from your deck or graveyard any number of Navigator cards. Then choose in your hand the same number of cards and put them back in any place of the deck.", + activated: async card => { + let navigators = card.holder.deck.cards.filter(c => c.abilities.includes("door_o")) + .concat(card.holder.grave.cards.filter(c => c.abilities.includes("door_o"))) + .sort((a, b) => b.basePower - a.basePower); + if (navigators.length < 1) + return false; + if (card.holder.controller instanceof ControllerAI) { + // AI draws only one, the strongest + await board.toHand(navigators[0], card.currentLocation); + let targetCard = card.holder.controller.getLowestWeightCard(card.holder.hand.cards); + if (targetCard) + await board.toDeck(targetCard, card.holder.hand); + } else { + let targetCards = []; + await ui.queueCarousel({ cards: navigators }, navigators.length, (c, i) => targetCards.push(c.cards[i]), c => true, true, true, "Choose any number of cards to draw, but you'll have to put back into the deck an equal amount."); + await targetCards.forEach(async c => board.toHand(c, card.currentLocation)); + await ui.queueCarousel(card.holder.hand, targetCards.length, async (c, i) => await board.toDeck(c.cards[i], card.holder.hand), c => true, true, false, "Choose " + String(targetCards.length) +" cards to put back into your deck."); + } + }, + weight: card => { + let navigators = card.holder.deck.cards.filter(c => c.abilities.includes("door_o")).concat(card.holder.grave.cards.filter(c => c.abilities.includes("door_o"))); + if (navigators.length < 1 || card.holder.deck.cards.length < 4) + return 0; + if (card.holder.hand.cards.filter(c => c.abilities.includes("door_o")).length > 0) + return 0; + if (game.roundCount < 3) + return 15; + if (game.roundCount > 2) { + navigators = card.holder.getAllRowCards().filter(c => c.abilities.includes("door_o")); + if (navigators.length == 0 || (navigators.length == 1 && card.holder.leader.key == "wh_caranthir_navigator")) + return 15; + } + return 0; + } + }, + winter_queen: { + description: "Draw any special card from your deck and play it immediatly.", + activated: async card => { + let specials = card.holder.deck.cards.filter(c => !(c.hero || c.isUnit())); + if (specials.length < 1) + return false; + let targetCard = null; + if (card.holder.controller instanceof ControllerAI) { + targetCard = card.holder.controller.getHighestWeightCard(specials)[0]; + if (targetCard) + card.holder.getAIController().playCardDefault(targetCard, card.holder.deck); + } else { + await ui.queueCarousel({ cards: specials }, 1, (c, i) => targetCard = c.cards[i], c => true, true, false, "Choose a special card to play immediatly."); + if (targetCard) { + // let player select where to play the card + let choiceDone = false; + card.holder.selectCardDestination(targetCard, card.holder.deck, async () => { + choiceDone = true; + ui.enablePlayer(true); + }); + // We sleep until the choice is made, otherwise the turn continues as normal + await sleepUntil(() => choiceDone, 100); + } + } + }, + weight: card => { + let specials = card.holder.deck.cards.filter(c => !(c.hero || c.isUnit())); + if (specials.length < 1) + return 0; + return card.holder.controller.getWeights(specials).sort((a, b) => b.weight - a.weight)[0].weight; + } + }, + caranthir_navigator: { + description: "You can open two Dimensional Doors at the same time (but only if there is at least one navigator in each of these 2 rows).", + placed: card => { + card.holder.disableLeader(); + } + }, + imlerith_general: { + description: "Passive: Imlerith will shield Navigators from destruction.", + placed: card => { + card.holder.disableLeader(); + } + }, queen_calanthe: { description: "Play a unit then draw a card from you deck.", activated: async card => { @@ -2138,4 +2264,111 @@ var ability_dict = { description: "Place on your or opponent's battlefield on any row. Next unit card (not hero) that will be placed on this row will be destroyed and it's abilities will not work", weight: (card) => 20 }, + door: { + name: "Dimensional Door", + description: "While the door is active, at the end of each of your turns you should draw one card from your deck. If the row of the card matches the row of the door, play it immediatly, otherwise put it back into the deck. If it does not have a row, you can play it or put it back into the deck.", + weight: (card) => 0 + }, + door_o: { + name: "Dimensional Door Opening", + description: "Opens the dimensional door of the row it is in, if none other is opened", + weight: (card) => 0, + placed: async card => { + if (card.isLocked()) + return; + let openedDoors = card.holder.getAllRows().map(r => r.special).reduce((a, c) => a.concat(c.cards.filter(c => c.key === "spe_dimensional_door" && c.faceUp)), []); + if (openedDoors.length == 0 || (card.holder.leader.key === "wh_caranthir_navigator" && openedDoors.length < 2)) { + let door = card.currentLocation.special.findCard(c => c.key === "spe_dimensional_door"); + if (door && !door.faceUp) + door.flip(); + } + }, + removed: async (card) => { + // If last navigator in row, close the door + let navigators = card.currentLocation.cards.filter(c => c.abilities.includes("door_o")); + if (navigators.length == 0) { + let door = card.currentLocation.special.findCard(c => c.key === "spe_dimensional_door"); + if (door && door.faceUp) + door.flip(); + } + } + }, + sage: { + name: "Sage", + description: "Place on your battlefield and draw 4 cards from your deck. Look at them and put them back at the top or bottom of the deck in the order of your choice.", + weight: (card) => 20, + placed: async card => { + if (card.isLocked()) + return; + if (card.holder.controller instanceof ControllerAI) + return; // Meh, too tricky to implement for the AI, just skip + if (card.holder.deck.cards.length > 0) { + await ui.startDeckSorter(card.holder.deck.cards.slice(0, Math.min(4, card.holder.deck.cards.length)), card.holder, null, "Re-order the cards back into the deck", true); + } + } + }, + zirael: { + name: "Zirael", + description: "Place on your battlefield and draw 2 cards from your deck. Play them immediatly or put them back at the top or bottom of the deck in the order of your choice.", + weight: (card) => 20, + placed: async card => { + if (card.isLocked()) + return; + let cards = card.holder.deck.cards.slice(0, Math.min(2, card.holder.deck.cards.length)); + if (cards.length == 0) + return; + let targetCards = []; + if (card.holder.controller instanceof ControllerAI) { + targetCards = card.holder.controller.getWeights(cards).filter(c => c.weight > 0).map(c => c.card); + } else { + await ui.queueCarousel({ cards: cards }, Math.min(2, card.holder.deck.cards.length), (c, i) => targetCards.push(c.cards[i]), c => true, true, true, "Choose up to 2 cards to play right away."); + } + // Discarding cards left aside + cards.forEach(c => { + if (!targetCards.includes(c)) { + card.holder.deck.removeCard(c); + card.holder.deck.addCard(c); + } + }); + for (var i = 0; i < targetCards.length; i++) { + let c = targetCards[i]; + if (card.holder.controller instanceof ControllerAI) { + await card.holder.getAIController().playCardDefault(c, card.holder.deck); + } else { + // let player select where to play the card + let choiceDone = false; + card.holder.selectCardDestination(c, card.holder.deck, async () => { + choiceDone = true; + ui.enablePlayer(true); + }); + // We sleep until the choice is made, otherwise the turn continues as normal + await sleepUntil(() => choiceDone, 100); + } + } + } + }, + naglfar: { + name: "Naglfar", + description: "Place on your battlefield and draw 2 cards or less from your deck with the word 'Naglfar' in their name and play them immediatly.", + weight: (card) => 20, + placed: async card => { + if (card.isLocked()) + return; + // Find available Naglfar cards in the deck + let cards = card.holder.deck.cards.filter(c => c.name.toLowerCase().includes("naglfar")); + if (cards.length == 0) + return; + let targetCards = []; + // Select up to 2, AI takes highest weights + if (card.holder.controller instanceof ControllerAI) { + targetCards = card.holder.controller.getWeights(cards).sort((a, b) => b.weight - a.weight).slice(0, Math.min(2, cards.length)).map(c => c.card); + } else { + await ui.queueCarousel({ cards: cards }, Math.min(2, card.holder.deck.cards.length), (c, i) => targetCards.push(c.cards[i]), c => true, true, true, "Choose up to 2 cards to play right away."); + } + for (var i = 0; i < targetCards.length; i++) { + let c = targetCards[i]; + await card.holder.getAIController().playCardDefault(c, card.holder.deck); // Should be enought, all these cards play on a single row + } + } + }, }; diff --git a/cards.js b/cards.js index 756cef4..b1743d8 100644 --- a/cards.js +++ b/cards.js @@ -3357,6 +3357,346 @@ var ext_ve_cards = { }, }; +var ext_wh_cards = { + "spe_dimensional_door": { + "name": "Dimensional Door", + "deck": "special wild_hunt", + "row": "", + "strength": "", + "ability": "door", + "filename": "dimensional_door", + "count": "0", + "quote": "Very useful for travels to other dimensions and bringing apocalypse upon the world." + }, + "spe_white_frost_1": { + "name": "White Frost", + "deck": "weather wild_hunt", + "row": "agile_cr", + "strength": "", + "ability": "white_frost", + "filename": "white_frost", + "count": "1", + "quote": "It is the beginning of the end!" + }, + "spe_white_frost_2": { + "name": "White Frost", + "deck": "weather wild_hunt", + "row": "agile_cs", + "strength": "", + "ability": "white_frost", + "filename": "white_frost", + "count": "1", + "quote": "It is the beginning of the end!" + }, + "spe_white_frost_3": { + "name": "White Frost", + "deck": "weather wild_hunt", + "row": "agile_rs", + "strength": "", + "ability": "white_frost", + "filename": "white_frost", + "count": "1", + "quote": "It is the beginning of the end!" + }, + "wh_eredin_commander": { + "name": "Eredin Bréacc Glas: Commander of Dearg Ruadhri", + "deck": "wild_hunt", + "row": "leader", + "strength": "", + "ability": "eredin_commander", + "filename": "eredin_commander", + "count": "1", + "quote": "Any last words?" + }, + "wh_auberon_king": { + "name": "Auberon Muirketah: King of Aen Elle", + "deck": "wild_hunt", + "row": "leader", + "strength": "", + "ability": "auberon_king", + "filename": "auberon_king", + "count": "1", + "quote": "After having lived over six hundred and fifty years, little remains to excite." + }, + "wh_winter_queen": { + "name": "Winter Queen: Destroyer of the Worlds", + "deck": "wild_hunt", + "row": "leader", + "strength": "", + "ability": "winter_queen", + "filename": "winter_queen", + "count": "1", + "quote": "Truth is but a shard of ice." + }, + "wh_caranthir_navigator": { + "name": "Caranthir Ar-Feiniel: Sorcerer-Navigator", + "deck": "wild_hunt", + "row": "leader", + "strength": "", + "ability": "caranthir_navigator", + "filename": "caranthir_navigator", + "count": "1", + "quote": "A favorite son who chose a life of villainy." + }, + "wh_imlerith_general": { + "name": "Imlerith: General of the Wild Hunt", + "deck": "wild_hunt", + "row": "leader", + "strength": "", + "ability": "imlerith_general", + "filename": "imlerith_general", + "count": "1", + "quote": "The sisters said you would come. They saw you arrive in the water's surface." + }, + "wh_wild_hunt_warrior_1": { + "name": "Wild Hunt Warrior", + "id": 2, + "deck": "wild_hunt", + "row": "close", + "strength": "3", + "ability": "bond", + "filename": "wild_hunt_warrior_1", + "count": "3", + "target": "wh_wild_hunt_warrior", + "quote": "The White Frost is coming." + }, + "wh_wild_hunt_warrior_2": { + "name": "Wild Hunt Warrior", + "id": 2, + "deck": "wild_hunt", + "row": "close", + "strength": "4", + "ability": "bond", + "filename": "wild_hunt_warrior_2", + "count": "1", + "target": "wh_wild_hunt_warrior", + "quote": "THe considers it an excellent joke - to split the skulls of the living with the skulls of the dead. " + }, + "wh_imlerith": { + "name": "Imlerith", + "deck": "wild_hunt", + "row": "close", + "strength": "7", + "ability": "scorch_c", + "filename": "imlerith", + "count": "1", + "quote": "He boasted that no weapon would ever pierce his armor. And he was right — even in death." + }, + "wh_geralt": { + "name": "Geralt of Rivia", + "deck": "wild_hunt", + "row": "close", + "strength": "8", + "ability": "hero", + "filename": "geralt", + "count": "1", + "quote": "I'm only here for Yen." + }, + "wh_cirilla": { + "name": "Cirilla", + "deck": "wild_hunt", + "row": "close", + "strength": "0", + "ability": "hero zirael", + "filename": "cirilla", + "count": "1", + "quote": "Zireael possesses a great power she cannot control." + }, + "wh_avallach": { + "name": "Avallac'h", + "deck": "wild_hunt", + "row": "close", + "strength": "6", + "ability": "hero sage", + "filename": "avallach", + "count": "1", + "quote": "You humans have… unusual tastes." + }, + "wh_wild_hunt_rider_1": { + "name": "Wild Hunt Rider", + "deck": "wild_hunt", + "row": "agile_cr", + "strength": "5", + "ability": "", + "filename": "wild_hunt_rider_1", + "count": "2", + "quote": "First the buffalo horns atop their helms penetrate one's view, then the crest betwixt them." + }, + "wh_wild_hunt_rider_2": { + "name": "Wild Hunt Rider", + "deck": "wild_hunt", + "row": "agile_rs", + "strength": "5", + "ability": "", + "filename": "wild_hunt_rider_2", + "count": "2", + "quote": "The cavalcade of wraiths on undead horses galloping accross the sky." + }, + "wh_wild_hunt_rider_3": { + "name": "Wild Hunt Rider", + "deck": "wild_hunt", + "row": "agile_cs", + "strength": "5", + "ability": "", + "filename": "wild_hunt_rider_3", + "count": "2", + "quote": "They paint the sky with snow and the earth with fire." + }, + "wh_aen_elle_conqueror_1": { + "name": "Aen Elle Conqueror", + "id": 1, + "deck": "wild_hunt", + "row": "ranged", + "strength": "4", + "ability": "bond", + "filename": "aen_elle_conqueror_1", + "count": "1", + "quote": "The end of one world can be the beginning of another." + }, + "wh_aen_elle_conqueror_2": { + "name": "Aen Elle Conqueror", + "id": 2, + "deck": "wild_hunt", + "row": "ranged", + "strength": "4", + "ability": "bond", + "filename": "aen_elle_conqueror_2", + "count": "1", + "quote": "If they will not submit, they shall be driven out!" + }, + "wh_aen_elle_conqueror_3": { + "name": "Aen Elle Conqueror", + "id": 3, + "deck": "wild_hunt", + "row": "ranged", + "strength": "4", + "ability": "bond", + "filename": "aen_elle_conqueror_3", + "count": "1", + "quote": "For his noble kind morality is but a whim enjoyed by others. More specifically, those who lose." + }, + "wh_wild_hunt_hound_1": { + "name": "Wild Hunt Hound", + "deck": "wild_hunt", + "row": "ranged", + "strength": "7", + "ability": "", + "filename": "wild_hunt_hound_1", + "count": "1", + "quote": "Cry 'Havoc!', and let slip the dogs of war." + }, + "wh_wild_hunt_hound_2": { + "name": "Wild Hunt Hound", + "deck": "wild_hunt", + "row": "ranged", + "strength": "7", + "ability": "", + "filename": "wild_hunt_hound_2", + "count": "1", + "quote": "Who's a good boy? Well, surely not him." + }, + "wh_yennefer_captive": { + "name": "Yennefer: Eredin's Captive", + "deck": "wild_hunt", + "row": "ranged", + "strength": "5", + "ability": "hero medic", + "filename": "yennefer_captive", + "count": "1", + "quote": "I'll exchange her for your sould, witcher!" + }, + "wh_nithral": { + "name": "Nithral", + "deck": "wild_hunt", + "row": "ranged", + "strength": "7", + "ability": "hero morale", + "filename": "nithral", + "count": "1", + "quote": "Eredin's personal cavalcade includes only the most brutal and most ferocious of the Aen Elle." + }, + "wh_geels": { + "name": "Ge'els", + "deck": "wild_hunt", + "row": "ranged", + "strength": "6", + "ability": "hero sage", + "filename": "geels", + "count": "1", + "quote": "Paintings should convey emotion, not words." + }, + "wh_naglfar_taskmaster": { + "name": "Naglfar's Taskmaster", + "deck": "wild_hunt", + "row": "siege", + "strength": "4", + "ability": "medic", + "filename": "naglfar_taskmaster", + "count": "1", + "quote": "She ensures the ship is sustained by squeezing the life essence from those insignificant human vermin." + }, + "wh_naglfar_crew": { + "name": "Naglfar's Crew", + "deck": "wild_hunt", + "row": "siege", + "strength": "3", + "ability": "bond", + "filename": "naglfar_crew", + "count": "4", + "quote": "Humans don’t know what powers the Naglfar. For those that discover the truth... Well, it’s already too late for them." + }, + "wh_naglfar_cartographer": { + "name": "Naglfar's Cartographer", + "deck": "wild_hunt", + "row": "siege", + "strength": "4", + "ability": "sage", + "filename": "naglfar_cartographer", + "count": "1", + "quote": "We're almost there, my lord!" + }, + "wh_naglfar": { + "name": "Naglfar", + "deck": "wild_hunt", + "row": "siege", + "strength": "0", + "ability": "hero naglfar", + "filename": "naglfar", + "count": "1", + "quote": "Naglfar will wage the final battle of good and evil known as Ragh nar Roog." + }, + "wh_navigator_1": { + "name": "Navigator", + "deck": "wild_hunt", + "row": "agile_crs", + "strength": "3", + "ability": "door_o", + "filename": "navigator_1", + "count": "1", + "quote": "They are powerful mages who had the ability to manipulate space and to open portals between different worlds." + }, + "wh_navigator_2": { + "name": "Navigator", + "deck": "wild_hunt", + "row": "agile_crs", + "strength": "4", + "ability": "door_o", + "filename": "navigator_2", + "count": "1", + "quote": "They would guide the Riders of the Hunt along mystic pathways through time and space in order to reach other worlds." + }, + "wh_navigator_3": { + "name": "Navigator", + "deck": "wild_hunt", + "row": "agile_crs", + "strength": "5", + "ability": "door_o", + "filename": "navigator_3", + "count": "1", + "quote": "All of them are apprentices of Avallac'h" + }, +}; + var ext_wu_cards = { "wu_vilgefortz_magician_kovir": { "name": "Vilgefortz: Magician of Kovir", @@ -6789,6 +7129,7 @@ card_dict = Object.assign({}, card_dict, ext_sk_cards); card_dict = Object.assign({}, card_dict, ext_re_cards); card_dict = Object.assign({}, card_dict, ext_to_cards); card_dict = Object.assign({}, card_dict, ext_ve_cards); +card_dict = Object.assign({}, card_dict, ext_wh_cards); card_dict = Object.assign({}, card_dict, ext_wu_cards); card_dict = Object.assign({}, card_dict, ext_lr_cards); diff --git a/decks.js b/decks.js index c7b45c7..2a824b9 100644 --- a/decks.js +++ b/decks.js @@ -290,6 +290,35 @@ let default_decks = [{ ["ve_hungry_wolves_3", 1], ["ve_abandoned_girl", 1] ] + }, + { + "title": "Wild Hunt Deck 1 - Worlds Conquest", + "description": "Deck relying mostly on bond door mechanic", + "leader": "wh_winter_queen", + "faction": "wild_hunt", + "cards": [ + ["spe_decoy", 2], + ["spe_scorch", 1], + ["spe_clear", 1], + ["spe_white_frost_1", 1], + ["wh_geralt", 1], + ["wh_imlerith", 1], + ["wh_nithral", 1], + ["wh_avallach", 1], + ["wh_geels", 1], + ["wh_navigator_3", 1], + ["wh_wild_hunt_rider_1", 2], + ["wh_wild_hunt_rider_2", 2], + ["wh_wild_hunt_rider_3", 2], + ["wh_yennefer_captive", 1], + ["wh_naglfar_cartographer", 1], + ["wh_naglfar_taskmaster", 1], + ["wh_navigator_2", 1], + ["wh_naglfar_crew", 4], + ["wh_navigator_1", 1], + ["wh_cirilla", 1], + ["wh_naglfar", 1] + ] } ]; diff --git a/factions.js b/factions.js index 9904b8a..de3394f 100644 --- a/factions.js +++ b/factions.js @@ -303,7 +303,7 @@ var factions = { } if (player.controller instanceof ControllerAI) { if(targetCard) - targetCard.autoplay(player.deck); + player.controller.playCardDefault(targetCard, player.deck); // If more than 1 card was destroyed, we trigger the faction ability once more if (player.destroyedCards > 0) { await factions["velen"].factionAbilityAction(player); @@ -349,4 +349,108 @@ var factions = { }, unavailableSpecials: [] }, + wild_hunt: { + name: "Wild Hunt", + factionAbilityAction: async player => { + if (player.deck.cards.length == 0) + return; + let openedDoors = player.getAllRows().map(r => r.special).reduce((a, c) => a.concat(c.cards.filter(c => c.key === "spe_dimensional_door" && c.faceUp)), []); + if (openedDoors.length > 0) { + for (var i = 0; i < openedDoors.length; i++) { + if (player.deck.cards.length > 0) { + let door = openedDoors[i]; + let card = player.deck.cards.slice(0, 1)[0]; + ui.showPreviewVisuals(card); + await sleep(2000); + let play = false; + if (card.hero || card.isUnit()) { + if (card.getPlayableRows().filter(r => r === door.currentLocation.row).length > 0) { + play = true; + } else { + ui.helper.showMessage("Card drawn cannot be played on a row with an opened door.", 2); + } + } else { + if (!(player.controller instanceof ControllerAI)) { + play = await ui.popup("Play [E]", (p) => p.choice = true, "Discard [Q]", (p) => p.choice = false, "Play the card?", "Do you want to play this special card or put it back in the deck?"); + } else { + if (player.controller.getWeights([card])[0].weight > 0) + play = true; + } + } + ui.preview.classList.add("hide"); + ui.previewCard = null; + if (play) { + if (!(player.controller instanceof ControllerAI)) { + // let player select where to play the card + let choiceDone = false; + player.selectCardDestination(card, player.deck, async () => { + choiceDone = true; + ui.enablePlayer(true); + }); + // We sleep until the choice is made, otherwise the turn continues as normal + await sleepUntil(() => choiceDone, 100); + } else { + player.getAIController().playCardDefault(card, player.deck); + } + } else { + player.deck.removeCard(card); + player.deck.addCard(card); + } + } + } + } + }, + factionAbility: player => { + game.gameStart.push(async () => { + player.getAllRows().forEach(r => { + let c = new Card("spe_dimensional_door", card_dict["spe_dimensional_door"], player); + c.flip(); // Starts the game face down + c.noRemove = true; // Stays on the board until the end + r.special.addCard(c); + }); + // Draws an additional card + player.deck.draw(player.hand); + player.playedLeaders = [player.leader.key]; + return false; + }); + game.roundStart.push(async () => { + // We put all Dimensional Doors face down again + player.getAllRows().forEach(r => { + let door = r.special.findCard(c => c.abilities.includes("door")); + if (door && door.faceUp) + door.flip(); + }); + // Select a different leader starting from round 2 + if (game.roundCount > 1) { + let availableLeaders = Object.keys(card_dict).filter(cid => card_dict[cid].deck === "wild_hunt" && card_dict[cid].row === "leader" && !player.playedLeaders.includes(cid)) + .map(cid => new Card(cid, card_dict[cid], player)); + let targetCard = null; + if (player.controller instanceof ControllerAI) { + let rand = randomInt(availableLeaders.length); + targetCard = availableLeaders[rand]; + } else { + await ui.queueCarousel({ cards: availableLeaders }, 1, (c, i) => targetCard = c.cards[i], c => true, true, false, "Select the next leader to start the round with"); + } + if (targetCard) { + player.playedLeaders.push(targetCard.key); + player.replaceLeader(targetCard); + } + } + + return false; + }); + // On player's turn end, try to run the faction ability + game.turnEnd.push(async () => { + if(game.currPlayer === player) + await factions["wild_hunt"].factionAbilityAction(player); + }); + }, + description: "At the beginning of each round select a different Leader card. You will be able to use its ability in upcoming round before chosing another. Start the game with 11 cards instead of 10", + activeAbility: false, + abilityUses: 0, + weight: (player) => { + return 0; + }, + unavailableSpecials: ["spe_horn","spe_fog","spe_rain","spe_frost"] + }, } \ No newline at end of file diff --git a/gwent.js b/gwent.js index cb73e7f..32b8536 100644 --- a/gwent.js +++ b/gwent.js @@ -239,6 +239,23 @@ class ControllerAI { return data; } + // Analyses the list of cards provided and computes their total weight based on the mapping provided (dict: ability=>weight) + // Return best X cards found with a weight higher than 0 + selectBestCards(cards, count, weights, keepZeros=false) { + let wCards = []; + cards.forEach(c => { + let w = 0; + c.abilities.forEach(ab => { + if (ab in weights) + w += weights[ab]; + }); + wCards.push({ card: c, weight: w }); + }); + if (keepZeros) + return wCards.sort((a, b) => b.weight - a.weight).slice(0, Math.min(wCards.length, count)); + return wCards.filter(c => c.weight > 0).sort((a, b) => b.weight - a.weight).slice(0, Math.min(wCards.length, count)); + } + // Swaps a card from the hand with the deck if beneficial redraw() { let card = this.discardOrder({ @@ -350,6 +367,15 @@ class ControllerAI { await this.player.playCard(c); } + // Plays any card with a default behaviour + async playCardDefault(card,src=null) { + let data_max = this.getMaximums(); + let data_board = this.getBoardData(); + if (src && src !== card.holder.hand) + board.moveTo(card, card.holder.hand, src); + await this.playCard(card, data_max, data_board) + } + // Plays a Commander's Horn to the most beneficial row. Assumes at least one viable row. async horn(card) { let rows = this.player.getAllRows().filter(r => !r.special.containsCardByKey("spe_horn")); @@ -1221,7 +1247,7 @@ class Player { await this.playCardAction(card, async () => await board.moveTo(card, row, this.hand), endTurn); } - // Plays a card to the board + // Plays a card to the board from hand async playCard(card) { await this.playCardAction(card, async () => await card.autoplay(this.hand)); } @@ -1394,6 +1420,15 @@ class Player { await ui.viewCard(player_me.leader, async () => await player_me.activateLeader()); } + replaceLeader(newLeader) { + this.leader = newLeader; + this.elem_leader.children[0].children[0].replaceWith(this.leader.elem); + this.enableLeader(); + let ab = this.leader.abilities[0]; + if (ability_dict[ab].placed) + ability_dict[ab].placed(this.leader); + } + async activateFactionAbility() { let factionData = factions[this.deck.faction]; if (factionData.activeAbility && this.factionAbilityUses > 0) { @@ -2007,7 +2042,8 @@ class Row extends CardContainer { } this.updateState(card, false); if (runEffect) { - if (!card.decoyTarget) { + // Decoy targets do no trigger the removed effect, exept for cards holding a door opened, door closes when they leave the row + if (!card.decoyTarget || card.abilities.includes("door_o")) { for (let x of card.removed) x(card); } else { @@ -2407,6 +2443,10 @@ class Board { } } } + // For Wild Hunt faction: Imlerith protects navigators + if (card.abilities.includes("door_o") && card.holder.leader.key === "wh_imlerith_general") { + destroy = false; + } } if (destroy) { await this.moveTo(card, "grave", source); @@ -2419,7 +2459,8 @@ class Board { } } else { card.animate("comrade"); - await protectors[0].animate("comrade"); + if (protectors.length > 0) + await protectors[0].animate("comrade"); } } @@ -2921,7 +2962,9 @@ class Card { } this.abilities = (card_data.ability === "") ? [] : card_data.ability.split(" "); this.row = (this.faction === "weather") ? this.faction : card_data.row; + this.targetRows = (this.faction === "weather") ? card_data.row : ""; this.filename = card_data.filename; + this.faceUp = true; this.placed = []; this.removed = []; this.activated = []; @@ -3031,6 +3074,8 @@ class Card { this.desc += "

Hero: " + ability_dict["hero"].description + "

"; this.elem = this.createCardElem(this); + this.faceUpElem = this.elem; + this.faceDownElem = null; } // Returns the identifier for this type of card @@ -3194,6 +3239,9 @@ class Card { num.classList.add("center"); power.appendChild(num); row.style.backgroundImage = iconURL("card_row_" + card.row); + } else if (card.faction === "weather" && card.targetRows.length > 0) { + // Some weather cards can target different rows for a same weather type (such as White Frost) + row.style.backgroundImage = iconURL("card_row_" + card.targetRows); } let abi = document.createElement("div"); @@ -3233,6 +3281,36 @@ class Card { return elem; } + createCardBackElem() { + let elem = document.createElement("div"); + elem.classList.add("card"); + let f = this.faction; + if (f == "special") { + f = this.holder.leader.faction; + elem.classList.add("special"); + } + elem.style.backgroundImage = iconURL("deck_back_" + f, "jpg"); + return elem; + } + + flip() { + if (this.faceUp) { + if (!this.faceDownElem) { + this.faceDownElem = this.createCardBackElem(); + if (this.faceUpElem.style.left) + this.faceDownElem.style.left = this.faceUpElem.style.left; + } + this.elem.replaceWith(this.faceDownElem); + this.elem = this.faceDownElem; + } else { + this.elem.replaceWith(this.faceUpElem); + this.elem = this.faceUpElem; + if (this.faceDownElem.style.left) + this.faceUpElem.style.left = this.faceDownElem.style.left; + } + this.faceUp = !this.faceUp; + } + // Indicates whether or not the abilities of this card are locked isLocked() { return this.locked; @@ -4124,6 +4202,7 @@ class Carousel { if (!Carousel.elem) { Carousel.elem = document.getElementById("carousel"); + Carousel.submitBtn = document.getElementById("carousel_submit"); Carousel.elem.children[0].addEventListener("click", () => Carousel.curr.cancel(), false); window.addEventListener("keydown", function (e) { if (e.keyCode == 81) { @@ -4133,6 +4212,11 @@ class Carousel { } catch (err) { } } }); + Carousel.submitBtn.addEventListener("click", function (e) { + Carousel.curr.selection.map(async s => await Carousel.curr.action(Carousel.curr.container, s)); + Carousel.curr.selection = []; + Carousel.curr.exit(); + }); } this.elem = Carousel.elem; document.getElementsByTagName("main")[0].classList.remove("noclick"); @@ -4140,7 +4224,9 @@ class Carousel { this.elem.children[0].classList.remove("noclick"); this.previews = this.elem.getElementsByClassName("card-lg"); this.desc = this.elem.getElementsByClassName("card-description")[0]; - this.title_elem = this.elem.children[2]; + this.title_elem = document.getElementById("carousel_label"); + this.submitBtn = Carousel.submitBtn; + } // Initializes the current Carousel @@ -4164,6 +4250,12 @@ class Carousel { this.title_elem.classList.add("hide"); } + if (this.bExit) { + this.submitBtn.classList.remove("hide"); + } else { + this.submitBtn.classList.add("hide"); + } + this.elem.classList.remove("hide"); ui.enablePlayer(true); tocar("explaining", false); @@ -5511,6 +5603,9 @@ function getPreviewElem(elem, card, nb = 0) { num.classList.add("card-large-power-strength"); power.appendChild(num); row.style.backgroundImage = iconURL("card_row_" + card.row); + } else if (card.row === "weather" && card.targetRows.length > 0) { + // For rare weather cards which can target different rows for a same wheather type (such as White Frost) + row.style.backgroundImage = iconURL("card_row_" + card.targetRows); } if (c_abilities.length > 0) { @@ -5529,7 +5624,7 @@ function getPreviewElem(elem, card, nb = 0) { if (str === "shield_c" || str == "shield_r" || str === "shield_s") str = "shield"; abi.style.backgroundImage = iconURL("card_ability_" + str); - } else if (card.row.includes("agile")) { + } else if (card.row.includes("agile") && !faction.startsWith("weather")) { abi.style.backgroundImage = iconURL("card_ability_" + "agile"); } @@ -5634,12 +5729,12 @@ document.onkeydown = function (e) { var elem_principal = document.documentElement; -function openFullscreen() { +async function openFullscreen() { try { if (elem_principal.requestFullscreen) elem_principal.requestFullscreen(); else if (elem_principal.webkitRequestFullscreen) elem_principal.webkitRequestFullscreen(); else if (elem_principal.msRequestFullscreen) elem_principal.msRequestFullscreen(); - window.screen.orientation.lock("landscape"); + await window.screen.orientation.lock("landscape"); } catch (err) { } } diff --git a/img/icons/card_ability_door.png b/img/icons/card_ability_door.png new file mode 100644 index 0000000..7922f7f Binary files /dev/null and b/img/icons/card_ability_door.png differ diff --git a/img/icons/card_ability_door_o.png b/img/icons/card_ability_door_o.png new file mode 100644 index 0000000..7922f7f Binary files /dev/null and b/img/icons/card_ability_door_o.png differ diff --git a/img/icons/card_ability_naglfar.png b/img/icons/card_ability_naglfar.png new file mode 100644 index 0000000..e864002 Binary files /dev/null and b/img/icons/card_ability_naglfar.png differ diff --git a/img/icons/card_ability_sage.png b/img/icons/card_ability_sage.png new file mode 100644 index 0000000..d95a02d Binary files /dev/null and b/img/icons/card_ability_sage.png differ diff --git a/img/icons/card_ability_zirael.png b/img/icons/card_ability_zirael.png new file mode 100644 index 0000000..ee10ba0 Binary files /dev/null and b/img/icons/card_ability_zirael.png differ diff --git a/img/icons/card_special_door.png b/img/icons/card_special_door.png new file mode 100644 index 0000000..15508c6 Binary files /dev/null and b/img/icons/card_special_door.png differ diff --git a/img/icons/card_special_naglfar.png b/img/icons/card_special_naglfar.png new file mode 100644 index 0000000..750631c Binary files /dev/null and b/img/icons/card_special_naglfar.png differ diff --git a/img/icons/card_special_sage.png b/img/icons/card_special_sage.png new file mode 100644 index 0000000..231607c Binary files /dev/null and b/img/icons/card_special_sage.png differ diff --git a/img/icons/card_special_zirael.png b/img/icons/card_special_zirael.png new file mode 100644 index 0000000..e1420b4 Binary files /dev/null and b/img/icons/card_special_zirael.png differ diff --git a/img/icons/deck_back_wild_hunt.jpg b/img/icons/deck_back_wild_hunt.jpg new file mode 100644 index 0000000..09c3223 Binary files /dev/null and b/img/icons/deck_back_wild_hunt.jpg differ diff --git a/img/icons/deck_shield_wild_hunt.png b/img/icons/deck_shield_wild_hunt.png new file mode 100644 index 0000000..8cb3a45 Binary files /dev/null and b/img/icons/deck_shield_wild_hunt.png differ diff --git a/img/icons/faction-band-wild_hunt.png b/img/icons/faction-band-wild_hunt.png new file mode 100644 index 0000000..8f7df64 Binary files /dev/null and b/img/icons/faction-band-wild_hunt.png differ diff --git a/img/icons/notif_wild_hunt.png b/img/icons/notif_wild_hunt.png new file mode 100644 index 0000000..bc7987d Binary files /dev/null and b/img/icons/notif_wild_hunt.png differ diff --git a/img/icons/power_door.png b/img/icons/power_door.png new file mode 100644 index 0000000..2cad652 Binary files /dev/null and b/img/icons/power_door.png differ diff --git a/img/icons/power_white_frost.png b/img/icons/power_white_frost.png new file mode 100644 index 0000000..8edc397 Binary files /dev/null and b/img/icons/power_white_frost.png differ diff --git a/img/sm/faction_wild_hunt.jpg b/img/sm/faction_wild_hunt.jpg new file mode 100644 index 0000000..d847411 Binary files /dev/null and b/img/sm/faction_wild_hunt.jpg differ diff --git a/img/sm/special_dimensional_door.jpg b/img/sm/special_dimensional_door.jpg new file mode 100644 index 0000000..79568cf Binary files /dev/null and b/img/sm/special_dimensional_door.jpg differ diff --git a/img/sm/weather_white_frost.jpg b/img/sm/weather_white_frost.jpg new file mode 100644 index 0000000..9ce01b9 Binary files /dev/null and b/img/sm/weather_white_frost.jpg differ diff --git a/img/sm/wild_hunt_aen_elle_conqueror_1.jpg b/img/sm/wild_hunt_aen_elle_conqueror_1.jpg new file mode 100644 index 0000000..655fefa Binary files /dev/null and b/img/sm/wild_hunt_aen_elle_conqueror_1.jpg differ diff --git a/img/sm/wild_hunt_aen_elle_conqueror_2.jpg b/img/sm/wild_hunt_aen_elle_conqueror_2.jpg new file mode 100644 index 0000000..209a3aa Binary files /dev/null and b/img/sm/wild_hunt_aen_elle_conqueror_2.jpg differ diff --git a/img/sm/wild_hunt_aen_elle_conqueror_3.jpg b/img/sm/wild_hunt_aen_elle_conqueror_3.jpg new file mode 100644 index 0000000..1b55011 Binary files /dev/null and b/img/sm/wild_hunt_aen_elle_conqueror_3.jpg differ diff --git a/img/sm/wild_hunt_auberon_king.jpg b/img/sm/wild_hunt_auberon_king.jpg new file mode 100644 index 0000000..8ae5916 Binary files /dev/null and b/img/sm/wild_hunt_auberon_king.jpg differ diff --git a/img/sm/wild_hunt_avallach.jpg b/img/sm/wild_hunt_avallach.jpg new file mode 100644 index 0000000..fd6e69e Binary files /dev/null and b/img/sm/wild_hunt_avallach.jpg differ diff --git a/img/sm/wild_hunt_caranthir_navigator.jpg b/img/sm/wild_hunt_caranthir_navigator.jpg new file mode 100644 index 0000000..05c9eeb Binary files /dev/null and b/img/sm/wild_hunt_caranthir_navigator.jpg differ diff --git a/img/sm/wild_hunt_cirilla.jpg b/img/sm/wild_hunt_cirilla.jpg new file mode 100644 index 0000000..70a436f Binary files /dev/null and b/img/sm/wild_hunt_cirilla.jpg differ diff --git a/img/sm/wild_hunt_eredin_commander.jpg b/img/sm/wild_hunt_eredin_commander.jpg new file mode 100644 index 0000000..2c05549 Binary files /dev/null and b/img/sm/wild_hunt_eredin_commander.jpg differ diff --git a/img/sm/wild_hunt_geels.jpg b/img/sm/wild_hunt_geels.jpg new file mode 100644 index 0000000..e46d063 Binary files /dev/null and b/img/sm/wild_hunt_geels.jpg differ diff --git a/img/sm/wild_hunt_geralt.jpg b/img/sm/wild_hunt_geralt.jpg new file mode 100644 index 0000000..fc0e146 Binary files /dev/null and b/img/sm/wild_hunt_geralt.jpg differ diff --git a/img/sm/wild_hunt_imlerith.jpg b/img/sm/wild_hunt_imlerith.jpg new file mode 100644 index 0000000..8f073d1 Binary files /dev/null and b/img/sm/wild_hunt_imlerith.jpg differ diff --git a/img/sm/wild_hunt_imlerith_general.jpg b/img/sm/wild_hunt_imlerith_general.jpg new file mode 100644 index 0000000..0532b2c Binary files /dev/null and b/img/sm/wild_hunt_imlerith_general.jpg differ diff --git a/img/sm/wild_hunt_naglfar.jpg b/img/sm/wild_hunt_naglfar.jpg new file mode 100644 index 0000000..2fe6f15 Binary files /dev/null and b/img/sm/wild_hunt_naglfar.jpg differ diff --git a/img/sm/wild_hunt_naglfar_cartographer.jpg b/img/sm/wild_hunt_naglfar_cartographer.jpg new file mode 100644 index 0000000..d226725 Binary files /dev/null and b/img/sm/wild_hunt_naglfar_cartographer.jpg differ diff --git a/img/sm/wild_hunt_naglfar_crew.jpg b/img/sm/wild_hunt_naglfar_crew.jpg new file mode 100644 index 0000000..8b49964 Binary files /dev/null and b/img/sm/wild_hunt_naglfar_crew.jpg differ diff --git a/img/sm/wild_hunt_naglfar_taskmaster.jpg b/img/sm/wild_hunt_naglfar_taskmaster.jpg new file mode 100644 index 0000000..a3c1f90 Binary files /dev/null and b/img/sm/wild_hunt_naglfar_taskmaster.jpg differ diff --git a/img/sm/wild_hunt_navigator_1.jpg b/img/sm/wild_hunt_navigator_1.jpg new file mode 100644 index 0000000..4008be8 Binary files /dev/null and b/img/sm/wild_hunt_navigator_1.jpg differ diff --git a/img/sm/wild_hunt_navigator_2.jpg b/img/sm/wild_hunt_navigator_2.jpg new file mode 100644 index 0000000..3aecc87 Binary files /dev/null and b/img/sm/wild_hunt_navigator_2.jpg differ diff --git a/img/sm/wild_hunt_navigator_3.jpg b/img/sm/wild_hunt_navigator_3.jpg new file mode 100644 index 0000000..4a090b5 Binary files /dev/null and b/img/sm/wild_hunt_navigator_3.jpg differ diff --git a/img/sm/wild_hunt_nithral.jpg b/img/sm/wild_hunt_nithral.jpg new file mode 100644 index 0000000..08a573c Binary files /dev/null and b/img/sm/wild_hunt_nithral.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_hound_1.jpg b/img/sm/wild_hunt_wild_hunt_hound_1.jpg new file mode 100644 index 0000000..f448b50 Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_hound_1.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_hound_2.jpg b/img/sm/wild_hunt_wild_hunt_hound_2.jpg new file mode 100644 index 0000000..24f324f Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_hound_2.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_rider_1.jpg b/img/sm/wild_hunt_wild_hunt_rider_1.jpg new file mode 100644 index 0000000..925277e Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_rider_1.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_rider_2.jpg b/img/sm/wild_hunt_wild_hunt_rider_2.jpg new file mode 100644 index 0000000..b75bc97 Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_rider_2.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_rider_3.jpg b/img/sm/wild_hunt_wild_hunt_rider_3.jpg new file mode 100644 index 0000000..98ce294 Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_rider_3.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_warrior_1.jpg b/img/sm/wild_hunt_wild_hunt_warrior_1.jpg new file mode 100644 index 0000000..68bd264 Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_warrior_1.jpg differ diff --git a/img/sm/wild_hunt_wild_hunt_warrior_2.jpg b/img/sm/wild_hunt_wild_hunt_warrior_2.jpg new file mode 100644 index 0000000..f7cd9ef Binary files /dev/null and b/img/sm/wild_hunt_wild_hunt_warrior_2.jpg differ diff --git a/img/sm/wild_hunt_winter_queen.jpg b/img/sm/wild_hunt_winter_queen.jpg new file mode 100644 index 0000000..ba06a0d Binary files /dev/null and b/img/sm/wild_hunt_winter_queen.jpg differ diff --git a/img/sm/wild_hunt_yennefer_captive.jpg b/img/sm/wild_hunt_yennefer_captive.jpg new file mode 100644 index 0000000..4938e6a Binary files /dev/null and b/img/sm/wild_hunt_yennefer_captive.jpg differ diff --git a/index.html b/index.html index d739ffc..caeaf78 100644 --- a/index.html +++ b/index.html @@ -164,6 +164,7 @@

+
diff --git a/style.css b/style.css index a6d4f97..4f49d60 100644 --- a/style.css +++ b/style.css @@ -547,6 +547,7 @@ html { top: 77%; } +/* Carousel Title */ #carousel>:nth-child(3) { position: absolute; width: 100%; @@ -559,9 +560,45 @@ html { box-shadow: 0 0 1vw #ffffff54; } -/* Modal helpers */ +/* Carousel Submit Button */ +#carousel > #carousel_submit { + position: absolute; + top: 13.5%; + right: 5%; + width: 5%; + height: 5%; + text-align: center; + color: tan; + font-weight: bold; + font-size: 1.6vw; +} -#helper-box { +#carousel > #carousel_submit:hover { + color: green; + border: 1px green solid; + border-radius: 5px; + background-color: #86d986; +} + /* + + #arrangementWindow-button { + bottom: 35%; + right: -88%; + width: 5%; + height: 5%; + text-align: center; + color: tan; + font-weight: bold; + font-size: 1.6vw; +} + +#arrangementWindow-button:hover { + color: green; +} +*/ + /* Modal helpers */ + + #helper-box { position: absolute; top: 1vw; width: 100%;