-- RobScript Team - Anime Adventures ยท Robly Airflow -- Task navigation: Home, Farm, Units, Battle, Activity, Settings. -- Readable source cached for restarting and continuing after teleports. local AIRFLOW_SOURCE = [====[ -- RobScript Team - Anime Adventures โ€” Robly Airflow -- Decoded all 4,177 string constants and renamed outer modules for readability. -- Original temporary variable names remain where their meaning is uncertain. -- UI migrated to Robly Airflow; gameplay, autosaves and reporting are preserved. -- Native profiles/themes, live statuses, logs, and UUID-based fodder selection. -- Teleports use this edition's local cache instead of fetching the old UI script. -- Statically checked; not run inside Roblox. if _G.AA_SF and _G.AA_SF.alive then return end local VERSION = "2.0" local CHANGELOG = { { v = "1.0", date = "Oct 30, 2026", title = "First version", items = { { "๐Ÿ—บ๏ธ", "Picks the best mode", "Daily Challenge, the half-hour Challenge, first clears (Normal and Hard), raids, Fool's Hunt and farming, ranked by gems per minute from the game's own rewards and your real run times.", "NEW" }, { "๐Ÿ’ฐ", "Farm unit or damage?", "Buys the farm unit and its upgrades only while they pay back before the match ends and enemies die early on the road; otherwise it buys damage.", "NEW" }, { "โญ", "Traits, relics and elements", "Unit strength comes from the game's formula at full upgrade: level, trait (Unique, Golden...), stat rolls, curses, relic buffs and the mode's weak/strong elements.", "NEW" }, { "๐Ÿ“", "Smart placement", "Each unit goes where it covers the most road. Spots the server refuses are remembered per map.", "NEW" }, { "๐Ÿ›ก๏ธ", "Defense in depth", "Tracks how far down the road your units reach. When enemies get to the end of that stretch, the next unit goes further down the road. If one slips past them all, a unit goes right in its way: one enemy at your base ends the run.", "NEW" }, { "๐ŸŽ", "Free rewards", "Daily login, finished quests, battle pass and first-clear milestones are claimed for you.", "NEW" }, { "๐Ÿงพ", "Spend history", "Every gold, gem and coin spend has a floor, a stop rule and a line in the Spending tab.", "NEW" }, { "๐Ÿงช", "Battle simulator", "Plays a mode with your team before the farm spends a run on it: the game's own enemy HP, waves and yen, and the farm's own buying. It learns from every real match.", "NEW" }, { "โฉ", "Real wave timing", "The simulator plays waves the way the game does: each wave's yen ร—2.6 (the world's money boost), the early skip vote (about 2 s + 2 s per enemy, at most 30 s) and an enemy at your base taking it down in seconds.", "NEW" }, { "๐Ÿ‘ฅ", "Best team finder", "Tries your other units against each mode's weak and strong elements and shows the team that does best (Battle โ†’ Planner). It only equips it if you switch that on.", "NEW" }, { "๐ŸŒŸ", "Evolution check", "Shows which evolutions your items already cover and what each one would change in the hardest mode you play.", "NEW" }, { "๐Ÿงฌ", "Smart evolve", "Evolves your best copy of a unit once it's ready and gets stronger, but only with items no bigger goal needs and only using up spare copies (never one on your team or with a trait). Every evolve shows in the log and the Spending tab. Switch it off in the Units โ†’ Goals.", "NEW" } } } } table.insert(CHANGELOG, 1, { v = "1.5", date = "Oct 30, 2026", title = "Dungeons", items = { { "๐Ÿฐ", "Dungeons", "The dungeon joins the ranking: 20 rooms in a row, one room a run. The farm goes for Rare Chests first, keeps them (and chests) for floor 20 so every luck item bought on the way counts, keeps its coins for the shops at floors 10โ€“15, and buys chest luck in every shop (keeping coins back for it while another shop room is ahead). Rare Chests come with curses, so each curse is judged by what it does, and a curse is skipped only if the simulator says your team can't beat rooms with it. A room gives 5 tries, and clearing one refills them. Modes tab.", "NEW" }, { "โน๏ธ", "Infinite ends on time", "Past wave 50 an Infinite run pays only a few gems a wave. Once the daily gems are in and no unit is training there, the farm ends the run if another mode pays more a minute: it sells your units, the base falls and the run pays out as usual. Modes tab.", "NEW" }, { "๐Ÿ›ก๏ธ", "Fool's Hunt lessons everywhere", "Four things Fool's Hunt taught now work with your own team too (dungeons, challenges, story, raids): on the last wave each farm is sold and the yen goes into damage; once 5 units are down, at least 2 damage dealers guard the stretch before the base; once flyers show up (or a dungeon run carries the Curse of Flight), units that hit flyers get more of the yen; and short-range units get less of it, since a boss walks past them in a moment.", "IMPROVED" }, { "๐ŸŽฏ", "Training check", "A takedown-training check cut short by a teleport no longer counts as \"no\" for 6 hours: it's checked again.", "FIX" }, { "โน๏ธ", "Selling to end Infinite", "Ending an Infinite run sells every unit once and skips any the game refuses (a golden Bulby held the old loop for 14 minutes).", "FIX" }, { "๐Ÿ‘น", "Moving up to the boss", "On the last wave with only a boss or two left, new units and upgrades go where the boss is. An attacker it has walked past is sold, and when the boss would reach your base alive (a Curse of Regen boss heals 1% of its HP a second), units waiting further down the road move up to it too, so all your damage lands together.", "NEW" }, { "๐Ÿ“", "The simulator searches for spots", "At the start of a match (your own team, not Infinite or Fool's Hunt) the simulator tries spots on the map for each attacker and uses the layout that gets furthest against tough enemies. Each unit's shortlist counts the road it reaches at full range with the run's bonuses: Iron Sentinel went from reaching 72% of a road to all of it. A spot that lost on a map counts against it next time. Battle โ†’ Planner.", "NEW" }, { "โฑ๏ธ", "Stays for the boss", "The \"no progress for 6 minutes\" safety no longer walks out of a boss fight on the last wave (a final boss was left at half HP and the room started over).", "FIX" }, { "๐Ÿ‘ฅ", "Team search that counts bosses", "The team planner now also tries your damage-over-time units and every farm, and judges teams near their breaking point, where a difference shows. In a dungeon the simulator now plays each room's last-wave boss with its real HP, and damage over time stays on a lone boss instead of vanishing. With Auto-Equip Best Team on (Battle โ†’ Planner), the better team goes in before a dungeon run's first room and after a lost try.", "NEW" }, { "๐Ÿงช", "Simulator knows your items", "In a dungeon the simulator now counts what the run carries: damage, cooldown and range items, a damage type's bonus (Mage Coat), and the curses that make enemies tougher. It used to simulate your team as if it had bought nothing, and called rooms it then won a loss.", "FIX" } } }) table.insert(CHANGELOG, 2, { v = "1.4", date = "Oct 30, 2026", title = "Main goal", items = { { "๐Ÿ†", "Main goal", "Pick a unit to chase (Settings: pull goal, e.g. Chuuni, SS once evolved). The farm plays Fool's Hunt for AprilCoins while the goal needs them, pulls the April banner 10 at a time until it drops, keeps its evolve item's price, buys that item, trains its takedowns and evolves it. Units โ†’ Goals.", "NEW" }, { "๐Ÿ’ฑ", "Live currency", "Spends read your gems and event coins the moment they change, not the profile copy that only updates after a teleport.", "FIX" }, { "๐Ÿƒ", "Fool's Hunt", "Plays the team the mode deals (not yours): places each dealt card, casts Random Buff on your strongest unit, Clone and Astral Summon, picks the AprilCoin multipliers, and at 20 units sells a weak unit for a much stronger card (or discards a card that isn't worth a spot). A Refund card cashes in a farm that won't earn back its cost before the run ends, or a weak unit whose yen buys far more damage as upgrades. Extra copies of a spell card are discarded so the hand keeps room for units.", "NEW" }, { "๐Ÿ”€", "Merge stacking", "Grows your strongest Fool's Hunt unit the way the guides do: a second copy goes down next to it and isn't upgraded, the first copy gets maxed, then Merge goes on the fresh copy so it keeps 1.5ร— the maxed one's damage and gets its own upgrades on top. Repeat for more. An Idol gets maxed early (it buffs everyone and makes upgrades cheaper).", "NEW" }, { "๐Ÿ“–", "Learns the mode", "Every Fool's Hunt run records the wave reached, the coins, the dealt units that carried the damage (placed first next time) and the hidden stats of every dealt unit. Units โ†’ Goals.", "NEW" } } }) table.insert(CHANGELOG, 3, { v = "1.3", date = "Oct 30, 2026", title = "What the guides say", items = { { "๐Ÿ“š", "Community tiers", "A community tier list (Oct 2026) now rates units the game hides: an S or SS evolution counts as joining your team, and the Units โ†’ Goals lists every banner unit and your own units with their ratings.", "NEW" }, { "๐ŸŽŸ๏ธ", "Banner picks", "The April banner only spends AprilCoins while Chuuni (SS once evolved) or Vengeful Swordsman (S) is missing, not on Violentine (B) or Kansai (D).", "IMPROVED" }, { "๐ŸŒฌ๏ธ", "Buff chaining", "The guides say Windsong and Commander buffs chain up to +100%. The farm tests it once in a match (a second copy's buff while one is up) and chains them from then on only if it really adds up.", "NEW" }, { "๐ŸŽฏ", "Unit tips", "Chuuni (Delusion) targets the strongest enemy so the boss keeps her mark; Legendary Assassin (Prime) targets the closest and stands near the exit.", "NEW" }, { "๐ŸŽ", "Codes", "Redeems the promo codes from the guides in the lobby, each one your account hasn't used.", "NEW" } } }) table.insert(CHANGELOG, 4, { v = "1.2", date = "Oct 30, 2026", title = "Hidden unit stats", items = { { "๐Ÿ”", "Hidden unit stats", "The game hides the upgrades of about half its Mythic and Secret units (every April, Sakamoto and current banner unit) until they're in a match. The farm now records them when a match sends them, and until then calls those units \"strength unknown\" instead of weak: no goal value, no automatic evolve and no summons on a guess.", "FIX" } } }) table.insert(CHANGELOG, 5, { v = "1.1", date = "Oct 30, 2026", title = "Takedown training", items = { { "๐ŸŽฏ", "Takedown training", "Some evolutions need the unit's own takedowns (Shinobi (Hood) needs 7,500). The farm puts that unit on your team only for runs the simulator says you still win with 30% tougher enemies, counts its takedowns every match, and puts your team back after each run. Units โ†’ Goals.", "NEW" }, { "โ™พ๏ธ", "Infinite", "Infinite maps join the ranking: each pays gems once a day at waves 10, 25 and 50 (200 in all), and a run lasts until the base falls, so a unit in training gets far more takedowns. The first quarter of the road is kept for that unit (your other attackers stand after it), and while enemies die early the others aren't upgraded, so enemies live long enough for it to hit them. Every map sends the same number of enemies; the easy ones last longest. Modes tab.", "NEW" }, { "๐Ÿ“", "Where it stands", "A takedown needs a hit before the enemy dies, so the trainee stands where enemies arrive first and goes for the newest enemy in its range, one your other units haven't killed yet.", "NEW" } } }) table.insert(CHANGELOG, 1, { v = "1.7", date = "Oct 31, 2026", title = "A stronger team", items = { { "๐Ÿ’ช", "Units โ†’ Upgrades", "The farm now makes your 8 core units stronger with what you already have: XP food from the gold shop (gold kept for you), Stat Cubes to your grade, trait rerolls where a roll beats the trait on average, your unused relics, and a limit break once a unit is at its cap with a Divine Wish and spare units. One action at a time, each judged by how much of the team's damage it adds, with the game's own unit maths. Dry Run is on at first: it only says what it would do.", "NEW" }, { "๐ŸŽฏ", "Modes that make the team stronger", "The mode ranker now counts the XP a run gives your units, and values a Divine Wish, Star Remnants and Stat Cubes by what they'd do for your core units. The Daily Challenge is played once a day whatever the Farm โ†’ Stages says, since it pays the wish, remnants and cubes.", "NEW" }, { "๐Ÿ“Š", "Who carries", "Each match records how much of the damage each unit dealt (Iron Sentinel's drones count as its). The Units โ†’ Upgrades weighs every change by that.", "NEW" }, { "๐ŸŽฒ", "Trait rolls work", "The game only rolls a trait on a unit that isn't equipped, and said so in red. The unit now leaves your team for the roll and goes back in its slot.", "FIX" }, { "๐Ÿ’", "Team swaps with a Miracle Timepiece", "A team can hold one Miracle Timepiece, so a second unit carrying one was turned away, leaving an empty slot. It takes its relic off for that team now.", "FIX" }, { "โธ๏ธ", "The Units โ†’ Upgrades finishes first", "It can only work in the lobby, and won rooms used to chain straight into the next one, so it hardly ever got a turn. The run now goes through the lobby whenever the Units โ†’ Upgrades has something to do, and it finishes before the next room. Going through the lobby doesn't cost a choice of room.", "NEW" }, { "๐Ÿ”ฎ", "Smart Relics", "Before a run, your relics move to where they add the most damage on the team that's playing, counting what each copy really rolled: its damage types, the Timepiece's extra hit on slow attackers, crit, flyers. Once a team has them, they only move again for +2% or more. Units โ†’ Upgrades.", "NEW" }, { "๐Ÿ”ฅ", "Two fuses", "The game has two, so the Lobby tab does too. Limit Break raises the level cap. Fuse For XP feeds spare units into the one you pick, up to its level cap: only level-1 units, never locked ones, and never Mythic or better, since a limit break needs those. It stays off until you switch it on, and it can't be undone.", "NEW" }, { "โ˜€๏ธ", "Ready for the Summer update", "Update 21 changed how the game checks unlocked levels, which units hit flyers, and where it keeps trait odds and difficulty numbers. The farm reads all of them the new way, so it plays again and the team search and Units โ†’ Upgrades work.", "FIX" }, { "๐Ÿ”", "Replays with no teleport", "Replay and Next can now restart right where you are. The farm reloads itself on the spot, about 5 seconds, and can still change the team before the first wave. If the game teleports instead, it works as before.", "NEW" }, { "๐Ÿชฝ", "The new flyer rules", "Any unit can now hit flyers, for half damage and without its stun or slow unless it stands on a hill or has an anti-air attack. The simulator and the buying both play it that way.", "FIX" }, { "๐Ÿ›‘", "No more rerolls", "The farm no longer rerolls stats or traits, on the Units โ†’ Upgrades or the Lobby tab. Your Stat Cubes, Star Remnants and tokens stay yours to use by hand.", "IMPROVED" }, { "๐Ÿ–๏ธ", "The Summer Hunt", "The farm can now play the Summer Hunt: 50 waves, a new map and weather every hour. It picks each card by what it pays and what it costs your team, and claims finished bingo lines. It never spends Summer Pearls or trades gems for them. Turn on Summer Hunt Only to play it.", "NEW" }, { "๐Ÿงช", "A tougher team test", "The team search used to stop testing at enemies ร—4, so on easy maps every team looked the same and a swap could come down to seconds. It now keeps making enemies tougher until your team breaks, and a swap has to really go further over that whole test. Otherwise your team stays.", "IMPROVED" }, { "๐ŸŒ€", "Windsong takes turns properly", "A second Windsong cast adds nothing while the buff is up, and doesn't renew it either, so a cast fired a moment early was wasted. Each copy now reads the buff on your units and casts only once it has really ended.", "FIX" }, { "๐Ÿ”", "Hidden units get real numbers", "The game hides many units' upgrades and attacks until they're played. The farm now fills those gaps with community data, so the team search can judge them, and the game's own numbers replace it the first time a unit plays.", "NEW" }, { "๐ŸŽฏ", "Banner Snipe", "Pick any unit a banner can feature. Each lobby visit reads every banner, and while one features your pick it pulls there until the unit drops, or until your gems, pearls or Legacy Gems reach the Keep line you set. Banners turn over every 4 hours: a match can't read them, but the farm knows when they've changed and stops at the lobby after the match to look. It replaces Griffin Watch (a watch that was on carries on for Griffin). Units โ†’ Summon.", "NEW" }, { "๐ŸŒ€", "Portal To Play", "A new pick on the Farm โ†’ Stages. Smart plays a portal you hold when its map drops something your evolutions need, from any of the game's 38 kinds. Pick a kind to play it first whenever you hold one, or switch Portals off to keep them.", "NEW" }, { "โ„๏ธ", "Snow Reaper's Gale takes turns", "Snow Reaper (Final)'s Gale of the Moon is a switch with a bar: while it's on, enemies in her range are slowed, chilled and frozen. The farm used to flip it on and off every few seconds. Now her copies take turns: the next one goes on as the one running empties, a copy with nothing in range switches off to keep its bar, and while one copy is on a boss the others wait with theirs full.", "FIX" }, { "๐Ÿ“", "Control units stand with your damage", "A unit that freezes, stuns or slows holds what's in its own range, so it now goes where your other attackers' damage reaches the road the most, and what it holds stays under all of them. Snow Reaper's copies used to go where the road was longest, sometimes 120 studs from anyone else. Control that only comes at a later upgrade counts too now: Ghost Lady (Princess) from upgrade 3, JIO from 4, Delinquent (The Universe) from 7.", "FIX" }, { "๐ŸงŠ", "The simulator counts the Gale", "Snow Reaper (Final)'s Gale of the Moon now plays in the simulator: her aura on every enemy in range, the freezes and the slow, a third of the time per copy. Her attacks also read their real shapes (a circle, a cone, then the whole field) instead of one enemy, and so do Violentine's. On your Daily Challenge test it reads wave 24 and 925M where the real run went to wave 25 with 778M; before, it said wave 8 with 8M. The team search now weighs her like that.", "FIX" }, { "โœจ", "A cleaner window", "Every tab is reorganized and opens with what it's for. Home is the dashboard, with a line for every part. Play holds all the choices of what to play, one control per kind (Off, On or Only this) beside its map pick, where each kind used to have two switches. Summon and Fuse replace the old Lobby tab, and fine-tuning sits under Advanced at the bottom of each tab.", "IMPROVED" }, { "๐Ÿ”ฅ", "Limit Break, your way", "On the Units โ†’ Fusion: pick any unit that can still be limit broken, then tick its fodder in a table that shows each unit's rarity, level, points, evolved, trait, relic, shine and whether a goal needs it. โ˜… marks the farm's own safe picks, and a card shows level, Divine Wish and fodder points with a progress bar. Nothing ticked: the farm picks by its safe rule as before.", "NEW" }, { "๐Ÿ“…", "Keep Playing Daily plays through a rest", "A Daily that lost 2 in a row rested for 6 hours, and one that rested before you switched Keep Playing Daily on stayed rested with it on. Now the switch plays through any rest from losses, old ones included. A rest because the game refused the pad still holds, so a refusal can't turn into a loop.", "FIX" }, { "๐ŸŒ€", "Portals end at wave 15", "Shibuya's portal lists 20 waves, but every real run ends at wave 15, like a dungeon room. The simulator played all 20, with twice as much enemy health in the last 5 as in the 15 real ones, so every portal reading came out harder than the real thing. Now it plays 15, and in the match wave 15 is the last one: your farm units are cashed in for damage there, where the farm used to wait for a wave 20 that never came. A portal whose real runs go past 15 plays its whole list.", "FIX" }, { "๐ŸŒ€", "Portal retries wait out the game's 30 s", "The game opens one portal every 30 seconds, and a portal that gave no answer could still count. The farm tried again 12 s later, the game said no, and the try after that went in. Now it waits for a slow portal until the 30 s are nearly up and plays it if it opens, and a retry goes once they're over. In the match, a start vote the game errors on is sent again: twice the match after a retry stood for over a minute before its first wave.", "FIX" }, { "๐ŸŒ€", "Portals never rest", "A portal that didn't open set every portal aside for half an hour, and a long Shibuya Infinite run took the turn with 16 portals waiting. Now portals never rest: one that doesn't open or start is set aside on its own, and the next try uses another portal you hold. Losing streaks don't rest them either.", "FIX" }, { "โšก", "Faster team searches", "A search ran some teams twice at the same strength (12 of 118 runs on Shibuya Infinite) and worked out each unit's damage again every simulated second. Now it remembers both. The search before a match's first wave or in an opened portal takes about 12 s instead of 18, trying as many teams as before and picking the same one. Searches in the lobby keep their time and try more teams in it. Blizzard runs also give the same answer every time now, so the search compares teams fairly there.", "IMPROVED" }, { "๐Ÿ”", "A team search before every match", "Replays restart in place without the lobby, so they kept the team that went in, even when a Summer Hunt replay brought a new map and weather. Now a replay gets a short search before its first wave, on the match's own map and weather, like the next story act already did. A map where the simulator is far off real runs still sends back the team that won there last, and the log now says that's what happened.", "IMPROVED" }, { "๐ŸŒ€", "Portals: Off, On or Only this", "Portals had a plain switch where every other kind on the Farm โ†’ Stages has three choices. Now it has the same three: On plays the portals you hold ahead of other kinds when a goal needs their drops (or your pick whenever you hold one), Only this plays just portals, and Off never opens one.", "IMPROVED" }, { "โ„๏ธ", "Blizzard at its real strength", "In the Summer Hunt's Blizzard, half the enemies are covered in Ice. The simulator used to guess that made every enemy 1.5ร— tougher. Now every other enemy comes in Ice and takes a third of the damage from anything but fire, as measured in real runs. On average that's about 2ร— tougher, and 3ร— on the Ice half.", "FIX" }, { "๐Ÿค", "Matchmaking for the Daily", "The Daily Challenge now goes through the game's matchmaking first, since its enemies get no tougher with more players. If nobody joins within the wait (2 minutes to start with), it plays alone. Both are under Play โ†’ Challenges.", "NEW" }, { "๐Ÿ”ฅ", "Fire, Soaring, Armored and Ice enemies", "The simulator now knows Update 21's other enemy types: nothing slows or stuns a Fire enemy, only hill units can hit a Soaring one, and Armored and Ice enemies take less damage. How much less, the farm measures from the game's own damage numbers, and counts it only once the readings agree (Ice enemies: a third, from everything but fire). The Flying Enemies and Armored Enemies challenges count too.", "NEW" }, { "๐Ÿชฝ", "Flying enemies are back in the simulator", "Update 21 moved where the game marks an enemy as flying, and since then the simulator played every flyer as a ground enemy. It reads the new place now, so team picks and predictions know again that units without anti-air do half damage to flyers and can't slow or stun them.", "FIX" }, { "๐Ÿ“…", "Daily Challenge modifiers at their real strength", "The Daily's weaknesses and resistances now count at the numbers the game really gives its enemies, the same ones its Level Modifiers panel shows (for example ร—1.66 physical, and magic cut to ร—0.43), where the farm used to guess ร—1.5 and ร—0.5.", "FIX" }, { "๐Ÿ“ฆ", "Everyone inside the kill box", "In a kill box every attacker now starts on a spot that really reaches the road's last stretch. A unit that stands on hills, like Iron Sentinel, has few spots near the end, and its list used to be filled up with mid-road ones: on the Summer Hunt it stood 100 studs from the other three, reaching 30-64% of the road and none of the box. The fast start works the same way now.", "FIX" }, { "๐Ÿ“ˆ", "Legend Stage progress", "Legend Stage To Play now has Progress too: the next legend stage you haven't beaten, in the game's order.", "NEW" }, { "โฉ", "Auto Summon gets all its pulls", "The game refuses a pull that comes a few seconds after the last one, and the farm used to stop there for 10 minutes. It now waits a moment and carries on.", "FIX" }, { "๐Ÿ”ฅ", "Smarter Limit Break", "Fuses now use the tier list's Fusion Fodder first. They never use your best supports, control units or farm, anything rated A or higher, a unit that evolves into one of those, or your 10 strongest units.", "NEW" }, { "๐Ÿง ", "The search team comes first", "The farm looks for the best team before every run and plays whatever it ranks first. Relics now move once, after the team is settled, and a tiny relic gain no longer costs a trip to the lobby.", "FIX" }, { "๐Ÿ“Œ", "Always on top", "The window and its round button now stay above the game's own screens.", "NEW" }, { "๐Ÿงน", "Only events that are on", "The panel now shows an event's switches only while the game runs it. Fool's Hunt and the April banner are out of the game, so their switches are hidden. If they come back, their switches do too. The same goes for the Summer Hunt when it ends.", "FIX" }, { "๐Ÿ”ฅ", "Legend Stages count their weaknesses", "Enemies on Legend Stages and some raids take much more damage from certain types, up to ร—3.5 for some. The team search, the simulator and the buying now all count that, so a fire and ice stage gets your fire and ice units.", "FIX" }, { "๐Ÿชผ", "Elyssia (Abyssal) can be picked", "Her upgrades were hidden, so the team search skipped her. They now come from her wiki page, and the simulator plays her jellies and eels. Every unit's numbers were checked against its live page too.", "FIX" }, { "๐Ÿ“", "Three ways to place your team", "Before the first wave the simulator searches the best spots on the map for three layouts and picks by how strong your team is there. Beats enemies at 1.5ร— strength from the road's start: a fast clear. Beats a bit more than normal strength: spread over the road. Normal strength or lower: a kill box at the road's end, now with its own best spots. It works in every mode, dungeon rooms too, except Infinite and modes that deal your team. A layout that loses steps down to a safer one on the next try, and a room's last life is always a kill box.", "NEW" }, { "๐Ÿชœ", "Tougher team test in dungeons", "A dungeon team swap now has to go further up the same rising test as every other mode, up to 24ร— enemy strength. A tie keeps your team.", "IMPROVED" }, { "๐ŸŒฌ๏ธ", "Windsongs where they're needed", "A buffer now gets only the copies that keep its buff up all the time. For Windsong that's 2 for each group of attackers, taking turns, so a force at the back gets its own pair. The rest of the yen goes to your attackers.", "FIX" }, { "๐Ÿ”Ž", "A fresh team search before every run", "Auto-Equip Best Team used to go by a saved plan, even one made hours ago with different units. Now it searches before every run with the units and levels you have right then.", "FIX" }, { "โœจ", "A new look", "Fewer tabs, one home for every control, shorter descriptions and a new neon theme. Home shows what it's doing and what to play, Goals what it's working toward, Team what makes your units stronger, Lobby what you choose by hand.", "IMPROVED" }, { "๐ŸŽฏ", "Choose what to evolve next", "The Units โ†’ Goals lists your 10 strongest evolutions, by the guides' rating and then the score at level 100. Pick one to focus on and its items and takedowns come first, or leave it on Smart Play and the farm chooses.", "NEW" }, { "๐Ÿ”€", "Team swaps without the lobby", "A dungeon room that wants a different team now gets it as the room loads, before the first wave, instead of a trip to the lobby. No wave starts until all six are right, and if the swap fails the run goes to the lobby, which doesn't cost a try.", "NEW" }, { "โญ๏ธ", "Next between story acts", "After a win the farm presses Next instead of going back to the lobby. The team is sorted out at the start of the next match, before the first wave: your training unit stays in if the simulator says the team still wins there, otherwise your own team goes back. It still stops at the lobby when the Units โ†’ Upgrades has work.", "NEW" }, { "๐Ÿ—บ๏ธ", "Story progress means new maps", "Farm Story Only now plays only acts you've never beaten. Hard runs of acts you've already beaten on Normal wait, and come back when the switch is off.", "FIX" }, { "๐Ÿงฑ", "The simulator plays stacked rooms", "In dungeon rooms your attackers stand together on the road's last stretch, and now the simulator plays them there too. It no longer calls a room lost at wave 7 when your team really gets to the boss, so its team picks and room picks go by what actually happens. Whether to take a curse is still judged the careful way.", "FIX" } } }) table.insert(CHANGELOG, 2, { v = "1.6", date = "Oct 31, 2026", title = "A simpler window", items = { { "๐ŸŒพ", "Farm โ†’ Stages", "One switch to play, and five you can use to play only raids, story progress, challenges, April Coins or dungeons. All off: it plays whatever pays best.", "NEW" }, { "๐Ÿฐ", "Lobby tab", "Pick a banner to summon on, a unit to fuse to, a unit and grade for its stats, and a unit to evolve (it farms the items and takedowns first). Each stays off until you switch it on.", "NEW" }, { "โš™๏ธ", "Everything else", "The old tabs are one switch away: Settings, Show All Settings.", "IMPROVED" }, { "๐Ÿ‘ฅ", "Team swaps", "A swap of two units could fail without a word and leave the wrong team on. The farm now checks and tries again.", "FIX" }, { "๐Ÿง ", "A smarter simulator", "It now counts how many times an attack hits (shields lose a point per hit), plays slows and stuns on the road, and stops regen on bleeding enemies, so slow units like Mochi get the credit they earn.", "IMPROVED" }, { "๐ŸŽฒ", "Auto Stats waits for Worthiness", "Stat rolls are better at 100% Worthiness or more, so Auto Stats waits for it. Lobby tab, Wait For Worthiness.", "NEW" }, { "๐Ÿฐ", "Best team for every dungeon room", "Before each room the farm tries your other units against that room. A new team goes on only if it holds up clearly better, and the rooms a team has really won or lost count too.", "IMPROVED" }, { "โœจ", "Abilities in the simulator", "It now plays Chuuni's marks, Iron Sentinel's drones, time stops, cooldown buffs, shield breaks and built-in crits, so those units get the credit they earn when it picks teams. Summons aren't played yet.", "IMPROVED" }, { "๐Ÿฉธ", "Enemy abilities in the simulator", "Enemies that heal, enemies that split when they die and enemies with barriers are now played too, so it sees the rooms that are really hard.", "FIX" }, { "๐Ÿงฒ", "Slows get bought", "A slow or a stun now counts for what it does for your other units, not just its own damage, so Mochi goes down early. And the simulator weighs upgrades the way the farm does, so it buys in nearly the same order.", "IMPROVED" }, { "๐Ÿงฟ", "Curses, more carefully", "A curse lasts to room 20, and a death loses every chest in the run. Past room 10, or with chests riding on the run, a curse is taken only if the simulator says your team still beats the rooms after it; past room 15, or with many chests, it has to beat them against 30% tougher enemies. A curse the simulator can't play (Chaos, Eruption, Armor, Burst) is passed when chests are riding. Shrines follow the same rule.", "IMPROVED" }, { "๐Ÿ›’", "Shop buys for your team", "Items that boost one damage type are now judged by how much of your damage is that type, not how many units are. An item that does nothing for your team isn't bought at any price.", "FIX" }, { "๐Ÿ›ก๏ธ", "Illusionist's base guard", "Illusionist (Final)'s ability guards the base for 10 seconds and throws back what touches it, so the farm now fires it as an enemy reaches the base. It used to fire at 90% of the road, which spent nearly all of it before the enemy arrived.", "FIX" } } }) table.insert(CHANGELOG, 1, { v = "1.8", date = "Nov 3, 2026", title = "Card Pick and Go To Lobby", items = { { "๐Ÿ ", "Go To Lobby", "Two new buttons on the Settings โ†’ General. Go To Lobby leaves the match at once, and the farm starts no other match until you press Start Farming Again. Summons, fuses and stars you switched on keep running in the lobby. A match that was already starting when you pressed it comes straight back.", "NEW" }, { "๐Ÿƒ", "Card Pick for the Summer Hunt", "Choose how the farm takes the Hunt's cards, on the Farm โ†’ Stages. Secret Luck (the default) takes the card with the most secret luck, then the most pay. Gems takes the card that pays the most. Easiest Win takes the one that makes your team strongest. Smart weighs pay against strength, as before. Except on Easiest Win, a card that would leave your team too weak to win is never taken.", "NEW" }, { "๐Ÿบ", "Artifact Stage", "The Artifact Stage (a new one every 14 days) is played now, alone, ahead of other kinds while your team has a chance there, until its 10 clears are in: 30 gems a clear, and at 3, 5 and 10 clears 15 Limited Star Remnants and 250 gems in all, claimed for you. The simulator reads its enemies 2.1ร— tougher, as the stage says, until real runs measure them. Losses rest it like any mode. Farm โ†’ Stages: Off, On or Only this.", "NEW" }, { "๐ŸงŸ", "Zombie (Risen)'s blessing", "Every summon on the map does +25% damage and reaches 10% further for each Zombie (Risen) placed, ร—2 with all 4 down. The simulator now plays it on Iron Sentinel's drones and Elyssia's jellyfish and eels, and on the dead enemies Violentine (Brutal Angel) raises once a run has measured how much of hers they deal (the DAMAGE line now says what each unit's summons dealt), and both it and the match value another Zombie by a quarter of what your summons deal, measured live from their real damage. So the team search can pick it next to your summoners, and a match places all 4 when your summons are worth it.", "NEW" }, { "๐Ÿ”ฅ", "Fuse For XP uses everything unlocked", "Fuse For XP now uses every unit up to the rarity you pick that isn't locked, including shiny, traited, levelled and farm units. It still never takes your team, your picks, a unit in takedown training or one wearing a relic. Lock a unit in the game to keep it.", "IMPROVED" }, { "โšก", "Faster summons and Fuse For XP", "Auto Summon pulls the next 10 as soon as the last ones land, where it waited 5 s between pulls, and looks every 30 s instead of every 2 minutes. Fuse For XP feeds 50 units at a time, batch after batch, where it fed 20 every 20 s (20 a time if the game won't take 50). Your Keep lines, Summons Per Visit and the level cap still hold.", "IMPROVED" }, { "๐Ÿ‘ฅ", "Auto-Equip Best Team on by default", "The farm now equips the best team it finds before each run, unless you switched Auto-Equip Best Team off yourself (Battle โ†’ Planner). Your own team goes back after.", "IMPROVED" }, { "๐Ÿšช", "Violentine at the entrance", "Violentine (Brutal Angel) raises the last enemy she hit when it dies, so she now stands at the road's entrance, where enemies come before your other units reach them, the spot a unit in takedown training gets. Her raised enemies take Zombie (Risen)'s blessing too.", "IMPROVED" }, { "๐Ÿงฉ", "Team search on a new account", "The team search leaves out units under level 50, so a weak unit doesn't take a slot. On a new account every unit is under 50, so the search had nothing to pick from and kept whatever team you set. That rule now waits until you own 6 kinds of attacker at level 50 or more. Before that, the search picks your best units at any level, and the farm equips what it finds.", "FIX" }, { "๐Ÿ–๏ธ", "The Summer Hunt for new teams", "A team that can't win the Hunt yet no longer replays it over and over. What only a win pays (a new bingo square, a new weather, the first Explosion Mage) now counts at the real chance of winning, where it used to count on every run, lost ones too. The pearls still count in full, as they come on a loss too.", "FIX" }, { "๐Ÿ“‹", "Unit pickers always fill in", "If the window opened before the game had loaded your units, the unit pickers on the Units โ†’ Fusion and Units โ†’ Summon could stay empty, and so could the fodder table. They now fill in as soon as your units are there, in a match too.", "FIX" } } }) table.insert(CHANGELOG, 1, { v = "1.9", date = "Nov 3, 2026", title = "Study A Unit and portal tiers", items = { { "๐Ÿ‘ฅ", "A full team wins a tie", "When the team search's pick and your own team came out even, your own team stayed, even with empty slots: a farm with 3 of 6 slots filled got a full team for a few runs, then went back to its 3. A tie now goes to the team that fills more slots.", "FIX" }, { "โœจ", "Studies use the ability", "In a study run, the unit studied now uses its ability as soon as it's ready, so its report says what the ability does. Abilities used to wait for a boss, a crowd or an enemy at your base, which an easy study map never brings: Illusionist (Final)'s was used 0 times in 2 runs.", "FIX" }, { "๐ŸŒ€", "Portal tiers", "Portals come in tiers, and each one you hold is now read the way its Portal Analysis shows it: Tier, how much tougher its enemies are, its reward multiplier, lanes, the host's Limited Star Remnants, and its own weak and strong types. Each tier you hold is judged on its own, simulated at its own strength, and its payout counts its reward multiplier. Within a tier, the farm opens the portal whose types suit your team best.", "NEW" }, { "๐ŸŽฏ", "No runs on modes a team can't win", "Before a mode has any real runs, the farm used to give it a hopeful chance, so it got one run to check the simulator. A weak team then tried every big-reward mode once and lost them all. Now, when the simulator's team loses even against enemies at a quarter of their health, the farm skips that mode, and wins elsewhere instead.", "FIX" }, } }) table.insert(CHANGELOG, 1, { v = "2.0", date = "Nov 4, 2026", title = "Charms, matchmaking and buff rotations", items = { { "๐Ÿ”ฎ", "Simulator card", "Home tab: what the simulator expects of the run you're playing (a win or a loss, and at which wave), how the run is going against that at the start of every wave (ahead, on track or behind), how your units stand and why, the lane, and who does the damage. In the lobby, what it reads for the next mode.", "NEW" }, { "๐Ÿ’", "Smart Charm Equip", "Summer Charms give bonuses in the Summer Hunt and Sky Portals, one worn at a time. Before either, the farm puts on the one that helps most: more rewards where your team wins with room to spare, more damage where it might lose. Charms only change in the lobby, so a replay goes through it once when a better one is waiting. Farm โ†’ Events.", "NEW" }, { "๐Ÿค", "Matchmaking in every mode that has it", "Farm โ†’ Party: a switch for each mode the game matchmakes. The Daily (on, as before), raids (up to 6 players), Infinite and Legend Stages (up to 4) and the Summer Hunt (up to 6). Other players' units help, and outside the Daily the enemies get tougher with each player. If nobody joins in time, it plays alone.", "NEW" }, { "๐ŸŽถ", "Buffs that stack keep stacking", "Windsong's buff adds up: +30% with one, +69% with two, +100% with three. The farm used to fire its buffers whenever units needed one, so they went off together and ran out together, and your units sat at +30% most of the time. Now the copies take turns, one every few seconds, so as many buffs stay up as your copies can hold (6 Windsongs: +100%). It learns which buffers stack from its own readings. Windsong buffs magic units only.", "IMPROVED" }, { "๐Ÿ›ฃ๏ธ", "Portals on one lane", "A portal tier can open on fewer lanes than its map has, and the game picks which each match. Units used to be placed for every lane, so some stood where no enemy came. Now the farm waits for the first enemies, places on their lane only, and its first unit goes down sooner.", "FIX" }, { "๐ŸŒช๏ธ", "Portal weaknesses", "A portal's weakness (Air Weakness x1.5) counted as no bonus at all, and air is only ever a unit's second type. Now the bonus counts for every unit that deals that type, so the team search can bring them.", "FIX" }, { "๐Ÿ‘ฅ", "The team search sees buffers", "Units whose worth goes to the rest of the team, like buffers, Zombie (Risen)'s blessing on summons and a Gale over the road, are now tried in every slot of the team search. They used to rank by their own damage and were mostly never tried.", "IMPROVED" }, { "๐ŸŽฏ", "Portals you can win go first", "A portal you hold used to go first whatever its chances. Now only one the simulator gives 25% or more, or one your team has already won, goes ahead of other modes.", "FIX" }, { "๐Ÿƒ", "Training checks the real tier", "Takedown training checked its swap on a portal's plain level, not its tier's tougher enemies. Now it checks the tier it will play.", "FIX" }, { "๐ŸŒ ", "Starfall", "In the Summer Hunt's Starfall weather, Star Golems fall in, and the simulator didn't play them. Now each Starfall run counts them, and the simulator adds them from then on.", "NEW" }, { "๐Ÿ“จ", "Reports go out once", "A match record or study report could be sent twice when a study run went to the lobby right after its match. Now the farm saves at once that it went.", "FIX" }, { "โš”๏ธ", "A study's last wave", "On a study run's last wave the whole team now fights.", "FIX" }, { "๐Ÿ”", "Matchmaking every run", "With a mode's matchmaking switch on, the farm goes back to the lobby and matchmakes again after every run. A replay used to start the next run alone, without the players who'd joined.", "FIX" }, { "๐Ÿงฑ", "Buffers in a block", "A buffer that stacks is now worth the step its copies make together: Windsong +30% with 2 copies, +69% with 4, +100% with 6. The farm used to stop at 3 and hold +30%. Now it puts the block down early, next to your main damage dealers.", "IMPROVED" }, { "๐Ÿ“…", "The Daily with others", "When the simulator says your team can't win the Daily alone, the farm plays it only with other players: if nobody joins, it tries matchmaking again in 10 minutes instead of losing it alone. On the morning this came in, 11 of 11 Dailies played alone were lost and 5 of 5 played with others were won. With Matchmake The Daily off, it waits for a team that can win it.", "IMPROVED" }, { "๐ŸŒ€", "Play Portals You Can't Beat", "Farm โ†’ Stages: your portals go first even when the simulator says your team will lose there. Off by default.", "NEW" }, { "๐Ÿ”„", "Your other portals", "When the portal kind you picked runs out (or Only Portals finds nothing on Smart), the farm plays your other portals, best first, instead of waiting.", "FIX" }, { "๐ŸŽญ", "Summer skins counted once", "In the Summer Hunt the game already puts a Summer skin's damage boost into the unit's damage, and the simulator added it a second time. A Spirit Reaper in its +5000% skin read 51 times too strong, and Hunts the simulator called wins were lost at wave 13-24. Now it counts once.", "FIX" } } }) local App = { alive = true, conns = { }, loadId = string.format("%06x", math.random(0, 0xFFFFFF)) } _G.AA_SF = App if not game:IsLoaded() then game.Loaded:Wait() end local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local HttpService = game:GetService("HttpService") local LocalPlayer = Players.LocalPlayer while not LocalPlayer do task.wait(0.25) LocalPlayer = Players.LocalPlayer end local Core = { } local Config = { } local Brain = { } local Status = { } local LogBuffer = { lines = { }, pending = { } } local Utils = { } local Units = { } local Modes = { } local FarmHub = { } local Match = { } local Spending = { } local Reports = { } local Economy = { } local Simulator = { } local Adventure = { } local Training = { } local Dungeon = { } local Strategy = { } local Hunt = { } local GUI = { UI = { }, LIVE = { } } Core.FOLDER = "AA_SmartFarm" Core.SETTINGS = Core.FOLDER .. "/settings.json" Core.BRAIN = Core.FOLDER .. "/brain.json" Core.LOGFILE = Core.FOLDER .. "/log.txt" Core.SELF = "RobScript_Team_Anime_Adventures.lua" Core.AIRFLOW_URL = "https://raw.githubusercontent.com/bobloscript/Robly_Airflow/main/Source.luau" Core.DISCORD_INVITE = "" Core.HIVE_URL = "" Core.WEBHOOK_URL = "" Core.DEFAULTS = { master = true, rewards = true, goldShop = true, goldFloor = 300000, summonApril = false, aprilFloor = 0, aprilTargets = { "dokuro" }, pullGoal = "", goalCoinValue = 1.0, summonGems = false, gemFloor = 30000, summonsPerVisit = 20, play = true, dailyChallenge = true, challenge = true, firstClears = true, raids = true, event = true, farm = true, dailyLoop = false, dailyTeam = { }, dailyBox = false, dailyMatch = true, dailyMatchWait = 120, mmRaid = false, mmInfinite = false, mmLegend = false, mmEvent = false, infinite = true, infiniteStop = true, trainLeave = true, dungeon = true, dungeonStart = true, dungeonValue = 150, unlockValue = 20, aprilValue = 0.35, pearlValue = 0.35, studyUnit = "", studyRuns = 2, smartCharm = true, huntCards = "Secret Luck", skinSmart = false, starOpen = false, pearlFloor = 105000, exploreBonus = 0.2, restAfterLosses = 2, battle = true, place = true, upgrade = true, actives = true, autoStart = true, farmUnits = true, farmSpare = 2, safeReach = 0.45, clearSec = 20, dangerReach = 0.7, abilityHold = 20, smartTarget = true, saveRatio = 0.6, sim = true, simSpots = true, simTeams = true, autoTeam = true, autoTeamChosen = false, goals = true, goalValue = 3000, raidShopGoals = true, smartEvolve = true, trainTakedowns = true, community = true, codes = true, onlyRaids = false, onlyStory = false, onlyChallenges = false, onlyCoins = false, onlyDungeons = false, onlyLegend = false, onlyInfinite = false, onlyPortals = false, onlyArtifact = false, pickRaid = "", pickStory = "", pickLegend = "", pickInfinite = "", portals = true, pickPortal = "smart", portalsAny = false, artifact = true, chSkip = { }, chSmart = false, chMinP = 0.5, skipSummonFx = false, autoSummon = false, summonBanner = "", tokenFloor = 0, snipeOn = false, snipeUnit = "", snipeGemFloor = 30000, snipePearlFloor = 105000, snipeLegacyFloor = 0, autoFuse = false, fuseUnit = "", fuseArmSig = "", fuseArmAt = 0, fuseFodder = { }, autoFuseXp = false, fuseXpUnit = "", fuseXpId = "", fuseXpRarity = "Epic", fuseXpOnAt = 0, team = true, teamXp = true, teamBuyXp = true, xpGoldFloor = 100000, teamFuse = true, teamRelics = true, teamCoreDays = 7, strengthValue = 300, strDry = true, autoEvolve = false, evolveUnit = "", focusNext = "", focusDone = "", simpleGui = false, antiAfk = true, logConsole = false, logLevel = 1, shareStats = false, useHive = false, shareChosen = false, shareNoticed = false } Core.ITEM_VALUE = { StarFruit = 50, StarFruitGreen = 70, StarFruitRed = 70, StarFruitPink = 70, StarFruitBlue = 70, StarFruitEpic = 300, StarFruitCapsule = 120, star_remnant = 40, star_remnant_limited = 25, reroll_stat_all = 120, reroll_stat_specific = 250, divine_wish = 400, LuckPotion = 50 } Core.ITEM_DEFAULT = 8 Core.GOLD_VALUE = 0.02 Core.TIERS = { overall = { SS = "Fused Hero (Vegito)|Supreme Being (Sovereign)|Griffin (Darkness)|Legendary Assassin (Prime)|Illusionist (Transcended)|Chuuni (Delusion)|Berserker (Rage)|Shinobi (Eternal)", S = "Spider (Immolation)|Dark Mage (Fallen Star)|Vengeful Swordsman (Sky Reaver)|Shadow Sorcerer (Incident)|Snow Reaper (Final)|Sorcerer Killer (Heavenly Body)|Dracula (Unholy King)|Stain (Hero Slayer)|Death Ninja (Immortal Butcher)|Daky (Obi)|Gas (Toxic)|Shadowgirl (Time Traveller)|Griffin (Reincarnation)|Leafy (Sylph)|Wenda|Commander|Shalta (Bloodfallen)|Puchi (Heaven)", A = "Elyssia (Abyssal)|Ghost-kun (Bound)|Usoap (God)|Elf Mage (Aura)|Siren (Harpy)|Vego (Mage II)|Izo (Black Fire)|Fox Ninja (Sage)|Asushi (Moonlight Beast)|Noruto (Six Tails)|Itochi (Susanoo)|Shigaruko (Symbol of Fear)|Reliable Student|JIO (Over Heaven)|Kiro (Bites the Dust)|Koichy (Echoes)|Lao (ROOM)|Dazai (No Longer Human)|Power (Fiend)|Zombie (Risen)|Switchblade (Deception)|Bulby", B = "Gilgamesh (King of Heroes)|Poseidon (Tyrant of the Seas)|Hubris (The One)|Zid (Shadow)|Bubblegum (Ultimate)|Carrot (Ultra)|Captain (God)|Tango (God of Flashiness)|Archer (Heroic Spirit)|Golden Freezo (Emperor)|Aizo (Final)|Demiurge (Demon Emperor)|Cream (Void)|Honey (Hive)|Violentine (Brutal Angel)|Demihuman (Gamer)|Gambler (Jackpot)|Killstreak (Speedrun)|Kisoko (Bankai)|Lulu (Geass)|Red Scar (Conqueror)|Diavoro|Pain (Divine)|Tatsumiki (Tornado)|Emili|Oshy (Idol)|C.E.O.", C = "Komon (Paper Angel)|Medae (Witch of Betrayal)|Luffo (Gear IV)|Chuyo (Corruption)|Fiend Girl (Butterfly)|Conrod (Wizard King)|Roger (Pirate King)|Saber (Promised Sword)|Albedo (Overseer)|Izu (Demon Snow)|Lancer (Child of Light)|Illy (Homunculus)|Aku (Rashamon)|Senbodu (Buddha)|Flamingo (Awakened)|Kiroto (Dual)|Heathcliff (Admin)|Avdo (Magician's Red)|Josuka (Unbreakable)|Skull Knight (King)|Somber (Buzzsaw)|Jirayo (Toad Sage)", D = "Kansai (Daydream)|Merlyn (Infinity)|Megomu (Chimera Shadow)|Sukuno|Mochi Charlot|Toad Sensei|Iron Knight|Hex|Jose (Shining Gem)|Delinquent" }, story = { SS = "Fused Hero (Vegito)|Berserker (Rage)|Supreme Being (Sovereign)|Griffin (Darkness)|Legendary Assassin (Prime)|Shadowgirl (Time Traveller)|Shinobi (Eternal)", S = "Elf Mage (Aura)|Siren (Harpy)|Cream (Void)|Reliable Student|Honey (Hive)|Usoap (God)|Spider (Immolation)|Daky (Obi)|Fox Ninja (Sage)|Izo (Black Fire)|Dracula (Unholy King)", A = "Dark Mage (Fallen Star)|Chuuni (Delusion)|Snow Reaper (Final)|JIO (Over Heaven)|Dazai (No Longer Human)|Lulu (Geass)|Kisoko (Bankai)|Poseidon (Tyrant of the Seas)|Gilgamesh (King of Heroes)|Hubris (The One)|Zid (Shadow)|Killstreak (Speedrun)|Switchblade (Deception)|Bulby", B = "Roger (Pirate King)|Carrot (Ultra)|Skull Knight (King)|Golden Freezo (Emperor)|Tango (God of Flashiness)|Commander|Leafy (Sylph)|Wenda|Gambler (Jackpot)|Demihuman (Gamer)|Oshy (Idol)|C.E.O.", C = "Merlyn (Infinity)|Flamingo (Awakened)|Emili|Pain (Divine)|Red Scar (Conqueror)|Toad Sensei|Iron Knight|Hex|Delinquent" }, infinite = { SS = "Fused Hero (Vegito)|Supreme Being (Sovereign)|Griffin (Darkness)|Chuuni (Delusion)|Illusionist (Transcended)|Legendary Assassin (Prime)|Berserker (Rage)|Shinobi (Eternal)|Griffin (Reincarnation)|Leafy (Sylph)|Wenda|Commander", S = "Spider (Immolation)|Dark Mage (Fallen Star)|Shadow Sorcerer (Incident)|Snow Reaper (Final)|Vengeful Swordsman (Sky Reaver)|Dracula (Unholy King)|Stain (Hero Slayer)|Death Ninja (Immortal Butcher)|Daky (Obi)|Gas (Toxic)|Sorcerer Killer (Heavenly Body)|Shalta (Bloodfallen)|Puchi (Heaven)|JIO (Over Heaven)|Shadowgirl (Time Traveller)|Kiro (Bites the Dust)|Lao (ROOM)", A = "Elyssia (Abyssal)|Ghost-kun (Bound)|Usoap (God)|Vego (Mage II)|Asushi (Moonlight Beast)|Noruto (Six Tails)|Itochi (Susanoo)|Shigaruko (Symbol of Fear)|Koichy (Echoes)|Dazai (No Longer Human)|Power (Fiend)|Zombie (Risen)|Switchblade (Deception)|Kisoko (Bankai)|Lulu (Geass)|Red Scar (Conqueror)", B = "Gilgamesh (King of Heroes)|Poseidon (Tyrant of the Seas)|Hubris (The One)|Bubblegum (Ultimate)|Carrot (Ultra)|Cream (Void)|Violentine (Brutal Angel)|Demihuman (Gamer)|Roger (Pirate King)|Skull Knight (King)|Gambler (Jackpot)|Pain (Divine)|Tatsumiki (Tornado)|Diavoro|Emili|Oshy (Idol)", C = "Kansai (Daydream)|Honey (Hive)|Aizo (Final)|Medae (Witch of Betrayal)|Somber (Buzzsaw)|Senbodu (Buddha)|Kiroto (Dual)|Jirayo (Toad Sage)|Mochi Charlot|Sukuno" } } Core.TIERS_EXTRA = { story = { S = "Violentine (Brutal Angel)|Elyssia (Abyssal)|Carrot (Ultra)|Dark Mage (Fallen Star)|Snow Reaper (Final)" }, castle = { S = "Chuuni (Delusion)|Switchblade (Deception)", A = "Vengeful Swordsman (Sky Reaver)|Sorcerer Killer (Heavenly Body)" } } Core.TIERS_USER = { overall = { S = "Elyssia (Abyssal)|Violentine (Brutal Angel)" } } Core.TIER_ALIAS = { ["Wenda"] = "wendy", ["Aizo (Final)"] = "aizen_hog_evolved", ["Tatsumiki (Tornado)"] = "tatsumaki_evolved", ["Leafy (Sylph)"] = "leafa_evolved", ["Demihuman (Gamer)"] = "sato_evolved", ["Asushi (Moonlight Beast)"] = "atsushi_evolved", ["Noruto (Six Tails)"] = "naruto_6tails", ["Poseidon (Tyrant of the Seas)"] = "poseidon_evolved", ["Pain (Divine)"] = "pain_2_evolved", ["Demiurge (Demon Emperor)"] = "demiurge_evolved", ["Medae (Witch of Betrayal)"] = "medea_evolved", ["Conrod (Wizard King)"] = "conrad_evolved", ["Lancer (Child of Light)"] = "lancer_evolved", ["Illy (Homunculus)"] = "illya_evolved", ["Kiroto (Dual)"] = "kirito_evolved", ["Heathcliff (Admin)"] = "heathcliff_evolved", ["Josuka (Unbreakable)"] = "josuke_2_evolved", ["Jirayo (Toad Sage)"] = "jiraiya_evolved", ["Flamingo (Awakened)"] = "doflamingo_evolved", ["Puchi (Heaven)"] = "pucci_heaven", ["Skull Knight (King)"] = "skull_knight_evolved", ["Koichy (Echoes)"] = "koichi_evolved", ["Aku (Rashamon)"] = "akutagawa_evolved", ["Albedo (Overseer)"] = "albedo_evolved", ["Archer (Heroic Spirit)"] = "archer_evolved", ["Avdo (Magician's Red)"] = "avdol_evolved", ["Chuyo (Corruption)"] = "chuya_evolved", ["Daky (Obi)"] = "daki_evolved", ["Dazai (No Longer Human)"] = "dazai_evolved", ["Diavoro"] = "diavolo", ["Emili"] = "emilia", ["Fiend Girl (Butterfly)"] = "hinami_evolved", ["Fox Ninja (Sage)"] = "naruto_sage", ["Fused Hero (Vegito)"] = "vegito", ["Gilgamesh (King of Heroes)"] = "gilgamesh_evolved", ["Golden Freezo (Emperor)"] = "frieza_golden_evolved", ["Iron Knight"] = "metal_knight", ["Itochi (Susanoo)"] = "itachi_2_evolved", ["Izu (Demon Snow)"] = "izumi_evolved", ["Kiro (Bites the Dust)"] = "kira_evolved", ["Kisoko (Bankai)"] = "kisuke_evolved", ["Komon (Paper Angel)"] = "konan_unit_evolved", ["Lao (ROOM)"] = "law_2_evolved", ["Lulu (Geass)"] = "lelouch_evolved", ["Luffo (Gear IV)"] = "luffy_g4_evolved", ["Megomu (Chimera Shadow)"] = "megumi_evolved", ["Merlyn (Infinity)"] = "merlin_evolved", ["Mochi Charlot"] = "katakuri_evolved", ["Oshy (Idol)"] = "hoshino_evolved", ["Power (Fiend)"] = "power_evolved", ["Roger (Pirate King)"] = "roger_evolved", ["Saber (Promised Sword)"] = "saber_evolved", ["Senbodu (Buddha)"] = "sengoku_unit_evolved", ["Shalta (Bloodfallen)"] = "shalltear_evolved", ["Shigaruko (Symbol of Fear)"] = "shigaraki_evolved", ["Sorcerer Killer (Heavenly Body)"] = "toji_evolved", ["Stain (Hero Slayer)"] = "stain_evolved", ["Sukuno"] = "sukuna", ["Tango (God of Flashiness)"] = "tengen_2_evolved", ["Usoap (God)"] = "usopp_ts_evolved", ["Zid (Shadow)"] = "cid_evolved" } Core.KEEP_ROLES = { support = { "griffith_reincarnation", "leafa_evolved", "wendy", "erwin", "shalltear_evolved", "pucci_heaven" }, control = { "koichi", "dio_heaven", "kira_evolved", "koichi_evolved", "law_2_evolved", "dazai_evolved", "power_evolved", "giselle_evolved", "nagumo_evolved" }, farm = { "bulma" }, wanted = { "griffith", "femto_egg", "femto" } } Core.FILL_UNITS = { ["ainz"] = { primary_attack_no_upgrades = "ainz|ainz:ball", spawn_cap = 1, upgrade = { { cost = 5000, damage = 4500, attack_cooldown = 7, range = 25.5 }, { cost = 7500, damage = 5500, attack_cooldown = 7, range = 26 }, { cost = 12500, damage = 6000, attack_cooldown = 6.5, range = 27, primary_attack = "ainz|ainz:lightning" }, { cost = 15000, damage = 7500, attack_cooldown = 6, range = 28 }, { cost = 20000, damage = 9000, attack_cooldown = 5.5, range = 29 }, { cost = 25000, damage = 12000, attack_cooldown = 5, range = 30 } } }, ["ainz_evolved"] = { primary_attack_no_upgrades = "ainz_evolved|ainz:ball", spawn_cap = 1, upgrade = { { cost = 5000, damage = 6750, attack_cooldown = 7, range = 25.5 }, { cost = 7500, damage = 8250, attack_cooldown = 7, range = 26 }, { cost = 12500, damage = 9000, attack_cooldown = 6.5, range = 27, primary_attack = "ainz_evolved|ainz:lightning" }, { cost = 15000, damage = 11250, attack_cooldown = 6, range = 28 }, { cost = 20000, damage = 13500, attack_cooldown = 5.5, range = 29 }, { cost = 25000, damage = 18000, attack_cooldown = 5, range = 30 }, { cost = 35000, damage = 22500, attack_cooldown = 12, range = 35, primary_attack = "ainz_evolved|ainz:fallen_down" }, { cost = 75000, damage = 30000, attack_cooldown = 11, range = 37 }, { cost = 100000, damage = 37500, attack_cooldown = 10, range = 40 } } }, ["aizen_christmas"] = { primary_attack_no_upgrades = "aizen_christmas|aizen_transcended:one", spawn_cap = 1, upgrade = { { cost = 30000, damage = 20000, attack_cooldown = 7, range = 27 }, { cost = 50000, damage = 25000, attack_cooldown = 6.5, range = 30, primary_attack = "aizen_christmas|aizen_transcended:two" }, { cost = 65000, damage = 35000, attack_cooldown = 6, range = 33, primary_attack = "aizen_christmas|aizen_transcended:three" }, { cost = 80000, damage = 45000, attack_cooldown = 6, range = 35 }, { cost = 100000, damage = 50000, attack_cooldown = 8, range = 38, primary_attack = "aizen_christmas|aizen_transcended:four" }, { cost = 150000, damage = 75000, attack_cooldown = 7, range = 40 } } }, ["akutagawa"] = { primary_attack_no_upgrades = "akutagawa|akutagawa:slash", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1120, attack_cooldown = 7, range = 23 }, { cost = 3500, damage = 1848, attack_cooldown = 7, range = 24, primary_attack = "akutagawa|akutagawa:ribbon" }, { cost = 5500, damage = 2800, attack_cooldown = 6.5, range = 24.5 }, { cost = 8000, damage = 4480, attack_cooldown = 6.5, range = 25 }, { cost = 10000, damage = 7280, attack_cooldown = 6.5, range = 27, primary_attack = "akutagawa|akutagawa:spikes" }, { cost = 12000, damage = 8960, attack_cooldown = 6, range = 28 }, { cost = 15500, damage = 11200, attack_cooldown = 6, range = 29 } } }, ["akutagawa_evolved"] = { primary_attack_no_upgrades = "akutagawa_evolved|akutagawa:slash", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1568, attack_cooldown = 7, range = 23 }, { cost = 3500, damage = 2587.2, attack_cooldown = 7, range = 24, primary_attack = "akutagawa_evolved|akutagawa:ribbon" }, { cost = 5500, damage = 3920, attack_cooldown = 6.5, range = 24.5 }, { cost = 8000, damage = 6272, attack_cooldown = 6.5, range = 25 }, { cost = 10000, damage = 10192, attack_cooldown = 6.5, range = 27, primary_attack = "akutagawa_evolved|akutagawa:spikes" }, { cost = 12000, damage = 12544, attack_cooldown = 6, range = 28 }, { cost = 15500, damage = 15680, attack_cooldown = 6, range = 29 }, { cost = 18000, damage = 32928, attack_cooldown = 11, range = 33, primary_attack = "akutagawa_evolved|akutagawa:bite" }, { cost = 24000, damage = 39200, attack_cooldown = 10, range = 35 } } }, ["albedo"] = { primary_attack_no_upgrades = "albedo|albedo:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2000, attack_cooldown = 6, range = 16 }, { cost = 4500, damage = 4000, attack_cooldown = 7, range = 17, primary_attack = "albedo|albedo:two" }, { cost = 6000, damage = 6000, attack_cooldown = 6.5, range = 18 }, { cost = 8000, damage = 7500, attack_cooldown = 6, range = 20 }, { cost = 9000, damage = 8000, attack_cooldown = 8, range = 21, primary_attack = "albedo|albedo:three" }, { cost = 13500, damage = 9000, attack_cooldown = 7.5, range = 22 }, { cost = 15000, damage = 11000, attack_cooldown = 7, range = 23 } } }, ["albedo_evolved"] = { primary_attack_no_upgrades = "albedo_evolved|albedo:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 3000, attack_cooldown = 6, range = 16 }, { cost = 4500, damage = 6000, attack_cooldown = 7, range = 17, primary_attack = "albedo_evolved|albedo:two" }, { cost = 6000, damage = 9000, attack_cooldown = 6.5, range = 18 }, { cost = 8000, damage = 11250, attack_cooldown = 6, range = 20 }, { cost = 9000, damage = 12000, attack_cooldown = 8, range = 21, primary_attack = "albedo_evolved|albedo:three" }, { cost = 13500, damage = 13500, attack_cooldown = 7.5, range = 22 }, { cost = 15000, damage = 16500, attack_cooldown = 7, range = 23 }, { cost = 20000, damage = 20250, attack_cooldown = 8, range = 25, primary_attack = "albedo_evolved|albedo:four" }, { cost = 30000, damage = 22500, attack_cooldown = 7, range = 28 } } }, ["alucard"] = { primary_attack_no_upgrades = "alucard|alucard:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2000, attack_cooldown = 9, range = 22 }, { cost = 3500, damage = 3300, attack_cooldown = 9, range = 23 }, { cost = 4500, damage = 4000, attack_cooldown = 8.5, range = 24, primary_attack = "alucard|alucard:two" }, { cost = 5500, damage = 6000, attack_cooldown = 8, range = 25 }, { cost = 7000, damage = 8000, attack_cooldown = 7.5, range = 26 }, { cost = 9000, damage = 9000, attack_cooldown = 7, range = 27 } } }, ["alucard_evolved"] = { primary_attack_no_upgrades = "alucard_evolved|alucard:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2800, attack_cooldown = 9, range = 22 }, { cost = 3500, damage = 4620, attack_cooldown = 9, range = 23 }, { cost = 4500, damage = 5600, attack_cooldown = 8.5, range = 24, primary_attack = "alucard_evolved|alucard:two" }, { cost = 5500, damage = 8400, attack_cooldown = 8, range = 25 }, { cost = 7000, damage = 11200, attack_cooldown = 7.5, range = 26 }, { cost = 9000, damage = 12600, attack_cooldown = 7, range = 27 }, { cost = 12500, damage = 28000, attack_cooldown = 11, range = 29, primary_attack = "alucard_evolved|alucard:three" }, { cost = 16000, damage = 35000, attack_cooldown = 10, range = 30 }, { cost = 25000, damage = 38500, attack_cooldown = 9.5, range = 32 }, { cost = 30000, damage = 42000, attack_cooldown = 9, range = 33 } } }, ["archer"] = { primary_attack_no_upgrades = "archer|archer:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1100, attack_cooldown = 7, range = 23 }, { cost = 2200, damage = 1300, attack_cooldown = 7, range = 25, primary_attack = "archer|archer:two" }, { cost = 3500, damage = 1900, attack_cooldown = 6.5, range = 26 }, { cost = 4500, damage = 2600, attack_cooldown = 6.5, range = 27 }, { cost = 6000, damage = 5800, attack_cooldown = 8, range = 30, primary_attack = "archer|archer:three" }, { cost = 7500, damage = 7500, attack_cooldown = 8, range = 32 }, { cost = 10000, damage = 10000, attack_cooldown = 8, range = 35 } } }, ["asuna"] = { primary_attack_no_upgrades = "asuna|asuna:jab", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1375, attack_cooldown = 7, range = 22 }, { cost = 2400, damage = 2125, attack_cooldown = 6.5, range = 25, primary_attack = "asuna|asuna:stabs" }, { cost = 3500, damage = 3000, attack_cooldown = 6.5, range = 27 }, { cost = 4250, damage = 3125, attack_cooldown = 6, range = 29, primary_attack = "asuna|asuna:barrage" }, { cost = 7000, damage = 5000, attack_cooldown = 6, range = 30 }, { cost = 7500, damage = 5625, attack_cooldown = 6, range = 31 } } }, ["asuna_evolved"] = { primary_attack_no_upgrades = "asuna_evolved|asuna:jab", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1925, attack_cooldown = 7, range = 22 }, { cost = 2400, damage = 2975, attack_cooldown = 6.5, range = 25, primary_attack = "asuna_evolved|asuna:stabs" }, { cost = 3500, damage = 4200, attack_cooldown = 6.5, range = 27 }, { cost = 4250, damage = 4375, attack_cooldown = 6, range = 29, primary_attack = "asuna_evolved|asuna:barrage" }, { cost = 7000, damage = 7000, attack_cooldown = 6, range = 30 }, { cost = 7500, damage = 7875, attack_cooldown = 6, range = 31 }, { cost = 8500, damage = 8750, attack_cooldown = 5, range = 33, primary_attack = "asuna_evolved|asuna:final" }, { cost = 9500, damage = 8750, attack_cooldown = 4.5, range = 35 }, { cost = 10000, damage = 10500, attack_cooldown = 4.5, range = 37 } } }, ["atsushi"] = { primary_attack_no_upgrades = "atsushi|atsushi:one", spawn_cap = 4, upgrade = { { cost = 1700, damage = 1100, attack_cooldown = 7, range = 20 }, { cost = 2400, damage = 1750, attack_cooldown = 7, range = 21 }, { cost = 3300, damage = 3000, attack_cooldown = 8, range = 22, primary_attack = "atsushi|atsushi:two" }, { cost = 4400, damage = 4000, attack_cooldown = 7.5, range = 24 }, { cost = 5500, damage = 4700, attack_cooldown = 7.5, range = 25 } } }, ["atsushi_evolved"] = { primary_attack_no_upgrades = "atsushi_evolved|atsushi:one", spawn_cap = 4, upgrade = { { cost = 1700, damage = 1650, attack_cooldown = 7, range = 20 }, { cost = 2400, damage = 2625, attack_cooldown = 7, range = 21 }, { cost = 3300, damage = 4500, attack_cooldown = 8, range = 22, primary_attack = "atsushi_evolved|atsushi:two" }, { cost = 4400, damage = 6000, attack_cooldown = 7.5, range = 24 }, { cost = 5500, damage = 7050, attack_cooldown = 7.5, range = 25 }, { cost = 7500, damage = 7500, attack_cooldown = 7, range = 26, primary_attack = "atsushi_evolved|atsushi:three" }, { cost = 12000, damage = 11250, attack_cooldown = 6.5, range = 27 }, { cost = 18000, damage = 15000, attack_cooldown = 6, range = 28 } } }, ["avdol"] = { primary_attack_no_upgrades = "avdol|avdol:one", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1000, attack_cooldown = 7.5, range = 19 }, { cost = 3500, damage = 1600, attack_cooldown = 7, range = 20 }, { cost = 4500, damage = 2400, attack_cooldown = 7, range = 21, primary_attack = "avdol|avdol:two" }, { cost = 5500, damage = 3300, attack_cooldown = 7, range = 22 }, { cost = 7500, damage = 4200, attack_cooldown = 6.5, range = 23 }, { cost = 10000, damage = 5200, attack_cooldown = 6.5, range = 25 }, { cost = 12000, damage = 7000, attack_cooldown = 6.5, range = 26 } } }, ["avdol_evolved"] = { primary_attack_no_upgrades = "avdol_evolved|live", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1350, attack_cooldown = 7.5, range = 19 }, { cost = 3500, damage = 2160, attack_cooldown = 7, range = 20 }, { cost = 4500, damage = 3240, attack_cooldown = 7, range = 21 }, { cost = 5500, damage = 4455, attack_cooldown = 7, range = 22 }, { cost = 7500, damage = 5670, attack_cooldown = 6.5, range = 23 }, { cost = 10000, damage = 7020, attack_cooldown = 6.5, range = 25 }, { cost = 12000, damage = 10800, attack_cooldown = 9, range = 27, primary_attack = "avdol_evolved|live7" }, { cost = 15000, damage = 12825, attack_cooldown = 8.5, range = 28 }, { cost = 20000, damage = 16875, attack_cooldown = 8.5, range = 29 } } }, ["bambietta"] = { primary_attack_no_upgrades = "bambietta|bambietta:one", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1000, attack_cooldown = 8, range = 22 }, { cost = 3000, damage = 1700, attack_cooldown = 7.5, range = 22.5 }, { cost = 3700, damage = 2200, attack_cooldown = 7.5, range = 23, primary_attack = "bambietta|bambietta:two" }, { cost = 4500, damage = 2900, attack_cooldown = 7.5, range = 24 }, { cost = 6000, damage = 4000, attack_cooldown = 7.5, range = 24.5 }, { cost = 7500, damage = 5000, attack_cooldown = 7, range = 25 }, { cost = 10000, damage = 6000, attack_cooldown = 7, range = 26, primary_attack = "bambietta|bambietta:three" }, { cost = 15000, damage = 8000, attack_cooldown = 7, range = 28 }, { cost = 20000, damage = 10000, attack_cooldown = 6.5, range = 30 } } }, ["bee_girl"] = { primary_attack_no_upgrades = "bee_girl|honey:one", spawn_cap = 5, upgrade = { { cost = 1250, damage = 250, attack_cooldown = 1, range = 25 }, { cost = 2000, damage = 450, attack_cooldown = 1, range = 26 }, { cost = 3000, damage = 600, attack_cooldown = 1, range = 27 }, { cost = 4500, damage = 1000, attack_cooldown = 1, range = 26 } } }, ["bee_girl_evolved"] = { primary_attack_no_upgrades = "bee_girl_evolved|live", spawn_cap = 5, upgrade = { { cost = 1250, damage = 337.5, attack_cooldown = 1, range = 25 }, { cost = 2000, damage = 607.5, attack_cooldown = 1, range = 26 }, { cost = 3000, damage = 810, attack_cooldown = 1, range = 27 }, { cost = 4500, damage = 1350, attack_cooldown = 1, range = 26 }, { cost = 7500, damage = 2025, attack_cooldown = 1, range = 27 }, { cost = 11000, damage = 2700, attack_cooldown = 1, range = 28 } } }, ["boxxo"] = { primary_attack_no_upgrades = "boxxo|boxxo:one", spawn_cap = 5, upgrade = { { cost = 2500, damage = 1000, attack_cooldown = 5, range = 23 }, { cost = 5000, damage = 2500, attack_cooldown = 5, range = 24 }, { cost = 10000, damage = 5000, attack_cooldown = 5, range = 25 } } }, ["buddha"] = { primary_attack_no_upgrades = "buddha|buddha:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2000, attack_cooldown = 5, range = 24 }, { cost = 5500, damage = 3300, attack_cooldown = 4, range = 25 }, { cost = 12000, damage = 5000, attack_cooldown = 4, range = 30 }, { cost = 20000, damage = 8500, attack_cooldown = 4, range = 32 } } }, ["buddha_evolved"] = { primary_attack_no_upgrades = "buddha_evolved|buddha:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2200, attack_cooldown = 5, range = 24 }, { cost = 5500, damage = 3630, attack_cooldown = 4, range = 25 }, { cost = 12000, damage = 5500, attack_cooldown = 4, range = 30 }, { cost = 20000, damage = 9350, attack_cooldown = 4, range = 32 }, { cost = 25000, damage = 13750, attack_cooldown = 4, range = 35 }, { cost = 30000, damage = 19250, attack_cooldown = 4, range = 37 }, { cost = 0, damage = 19250, attack_cooldown = 6, range = 37, primary_attack = "buddha_evolved|buddha:bleed" }, { cost = 0, damage = 28875, attack_cooldown = 8, range = 55.5, primary_attack = "buddha_evolved|buddha:bleed" } } }, ["buggy"] = { primary_attack_no_upgrades = "buggy|buggy:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1200, attack_cooldown = 6, range = 16 }, { cost = 3500, damage = 2000, attack_cooldown = 6, range = 17 }, { cost = 5000, damage = 4000, attack_cooldown = 7, range = 22, primary_attack = "buggy|buggy:two" }, { cost = 6000, damage = 5500, attack_cooldown = 7, range = 23 }, { cost = 12500, damage = 6500, attack_cooldown = 7, range = 24 }, { cost = 25000, damage = 7000, attack_cooldown = 7, range = 25, primary_attack = "buggy|buggy:three" } } }, ["buu"] = { primary_attack_no_upgrades = "buu|buu:one", spawn_cap = 3, upgrade = { { cost = 2400, damage = 1800, attack_cooldown = 7, range = 24 }, { cost = 3000, damage = 3200, attack_cooldown = 7, range = 24.5 }, { cost = 5000, damage = 4700, attack_cooldown = 6.5, range = 25, primary_attack = "buu|buu:two" }, { cost = 7000, damage = 7000, attack_cooldown = 6.5, range = 26 }, { cost = 10000, damage = 9500, attack_cooldown = 6, range = 27 }, { cost = 12000, damage = 10500, attack_cooldown = 6, range = 28 } } }, ["buu_evolved"] = { primary_attack_no_upgrades = "buu_evolved|buu:one", spawn_cap = 3, upgrade = { { cost = 2400, damage = 2340, attack_cooldown = 7, range = 24 }, { cost = 3000, damage = 4160, attack_cooldown = 7, range = 24.5 }, { cost = 5000, damage = 6110, attack_cooldown = 6.5, range = 25, primary_attack = "buu_evolved|buu:two" }, { cost = 7000, damage = 9100, attack_cooldown = 6.5, range = 26 }, { cost = 10000, damage = 12350, attack_cooldown = 6, range = 27 }, { cost = 12000, damage = 13650, attack_cooldown = 6, range = 28 }, { cost = 15000, damage = 13910, attack_cooldown = 9, range = 31, primary_attack = "buu_evolved|buu:three" }, { cost = 25000, damage = 18200, attack_cooldown = 8.5, range = 32 }, { cost = 30000, damage = 23400, attack_cooldown = 8, range = 33 } } }, ["caesar"] = { primary_attack_no_upgrades = "caesar|caesar:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 900, attack_cooldown = 7, range = 16 }, { cost = 2000, damage = 1600, attack_cooldown = 7, range = 17 }, { cost = 3000, damage = 2500, attack_cooldown = 6, range = 23, primary_attack = "caesar|caesar:two" }, { cost = 4500, damage = 3500, attack_cooldown = 6, range = 24 }, { cost = 6000, damage = 4500, attack_cooldown = 6, range = 25 }, { cost = 8000, damage = 5000, attack_cooldown = 7, range = 33, primary_attack = "caesar|caesar:three" }, { cost = 12000, damage = 7000, attack_cooldown = 7, range = 35 } } }, ["caesarclown"] = { primary_attack_no_upgrades = "caesarclown|caesarclown:one", spawn_cap = 4, upgrade = { { cost = 3000, damage = 1000, attack_cooldown = 12, range = 24 }, { cost = 4500, damage = 1800, attack_cooldown = 12, range = 25 }, { cost = 5000, damage = 2400, attack_cooldown = 12, range = 26, primary_attack = "caesarclown|caesarclown:two" }, { cost = 6000, damage = 3000, attack_cooldown = 12, range = 27 }, { cost = 7500, damage = 4000, attack_cooldown = 12, range = 28 } } }, ["caesarclown_evolved"] = { primary_attack_no_upgrades = "caesarclown_evolved|live", spawn_cap = 4, upgrade = { { cost = 3000, damage = 1350, attack_cooldown = 12, range = 24 }, { cost = 4500, damage = 2430, attack_cooldown = 12, range = 25 }, { cost = 5000, damage = 3240, attack_cooldown = 12, range = 26 }, { cost = 6000, damage = 4050, attack_cooldown = 12, range = 27 }, { cost = 7500, damage = 5400, attack_cooldown = 12, range = 28 }, { cost = 9000, damage = 8100, attack_cooldown = 12, range = 30, primary_attack = "caesarclown_evolved|live6" }, { cost = 14000, damage = 10800, attack_cooldown = 11, range = 30 } } }, ["carrot"] = { primary_attack_no_upgrades = "carrot|carrot:two", spawn_cap = 4, upgrade = { { cost = 1750, damage = 800, attack_cooldown = 8, range = 18 }, { cost = 2500, damage = 1500, attack_cooldown = 8, range = 19 }, { cost = 3500, damage = 2000, attack_cooldown = 8, range = 20, primary_attack = "carrot|carrot:one" }, { cost = 4500, damage = 2500, attack_cooldown = 8, range = 23 }, { cost = 6000, damage = 3500, attack_cooldown = 8, range = 24 }, { cost = 7500, damage = 4500, attack_cooldown = 8, range = 30, primary_attack = "carrot|carrot:three" }, { cost = 10000, damage = 5500, attack_cooldown = 8, range = 33 }, { cost = 15000, damage = 7000, attack_cooldown = 8, range = 35 } } }, ["carrot_evolved"] = { primary_attack_no_upgrades = "carrot_evolved|carrot_evolved:one", spawn_cap = 4, upgrade = { { cost = 1750, damage = 840, attack_cooldown = 8, range = 12 }, { cost = 2500, damage = 1680, attack_cooldown = 8, range = 13 }, { cost = 3500, damage = 2800, attack_cooldown = 8, range = 20, primary_attack = "carrot_evolved|carrot_evolved:two" }, { cost = 4500, damage = 3500, attack_cooldown = 8, range = 23 }, { cost = 6000, damage = 4900, attack_cooldown = 8, range = 24 }, { cost = 7500, damage = 6300, attack_cooldown = 8, range = 30, primary_attack = "carrot_evolved|carrot_evolved:three" }, { cost = 10000, damage = 7700, attack_cooldown = 8, range = 33 }, { cost = 15000, damage = 9800, attack_cooldown = 8, range = 35 } } }, ["chad"] = { primary_attack_no_upgrades = "chad|chad:one", spawn_cap = 4, upgrade = { { cost = 2300, damage = 1000, attack_cooldown = 6.5, range = 23 }, { cost = 3100, damage = 1500, attack_cooldown = 6, range = 25 }, { cost = 4500, damage = 1700, attack_cooldown = 6, range = 26, primary_attack = "chad|chad:two" }, { cost = 5000, damage = 2200, attack_cooldown = 6, range = 28 }, { cost = 6500, damage = 3000, attack_cooldown = 6, range = 29 }, { cost = 8000, damage = 3500, attack_cooldown = 5.5, range = 30 } } }, ["chad_evolved"] = { primary_attack_no_upgrades = "chad_evolved|chad:one", spawn_cap = 4, upgrade = { { cost = 2300, damage = 1350, attack_cooldown = 6.5, range = 23 }, { cost = 3100, damage = 2025, attack_cooldown = 6, range = 25 }, { cost = 4500, damage = 2295, attack_cooldown = 6, range = 26, primary_attack = "chad_evolved|chad:two" }, { cost = 5000, damage = 2970, attack_cooldown = 6, range = 28 }, { cost = 6500, damage = 4050, attack_cooldown = 6, range = 29 }, { cost = 8000, damage = 4725, attack_cooldown = 5.5, range = 30 }, { cost = 10000, damage = 6075, attack_cooldown = 5.5, range = 31, primary_attack = "chad_evolved|chad:three" }, { cost = 12000, damage = 6750, attack_cooldown = 5, range = 33 } } }, ["charmy"] = { primary_attack_no_upgrades = "charmy|charmy:ball", spawn_cap = 5, upgrade = { { cost = 2000, damage = 600, attack_cooldown = 7.5, range = 24 }, { cost = 2500, damage = 1000, attack_cooldown = 7, range = 25 }, { cost = 3000, damage = 1250, attack_cooldown = 7, range = 29, primary_attack = "charmy|charmy:sheep" }, { cost = 4500, damage = 2000, attack_cooldown = 7, range = 30 }, { cost = 6000, damage = 2500, attack_cooldown = 6.5, range = 32 }, { cost = 7500, damage = 3000, attack_cooldown = 6.5, range = 33 } } }, ["charmy_evolved"] = { primary_attack_no_upgrades = "charmy_evolved|charmy:ball", spawn_cap = 5, upgrade = { { cost = 2000, damage = 780, attack_cooldown = 7.5, range = 24 }, { cost = 2500, damage = 1300, attack_cooldown = 7, range = 25 }, { cost = 3000, damage = 1625, attack_cooldown = 7, range = 29, primary_attack = "charmy_evolved|charmy:sheep" }, { cost = 4500, damage = 2600, attack_cooldown = 7, range = 30 }, { cost = 6000, damage = 3250, attack_cooldown = 6.5, range = 32 }, { cost = 7500, damage = 3900, attack_cooldown = 6.5, range = 33 }, { cost = 10000, damage = 4550, attack_cooldown = 8, range = 34, primary_attack = "charmy_evolved|charmy:sheep2" }, { cost = 12500, damage = 5200, attack_cooldown = 8, range = 35 } } }, ["chihiro"] = { primary_attack_no_upgrades = "chihiro|rokuhira:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1500, attack_cooldown = 7.5, range = 27 }, { cost = 3500, damage = 2200, attack_cooldown = 7, range = 30 }, { cost = 4000, damage = 2700, attack_cooldown = 8, range = 31, primary_attack = "chihiro|rokuhira:two" }, { cost = 4500, damage = 3750, attack_cooldown = 7.5, range = 32 }, { cost = 5000, damage = 4500, attack_cooldown = 7.5, range = 33 }, { cost = 7500, damage = 5000, attack_cooldown = 7, range = 36, primary_attack = "chihiro|rokuhira:three" }, { cost = 10000, damage = 7000, attack_cooldown = 7, range = 37 }, { cost = 15000, damage = 9000, attack_cooldown = 7, range = 38 } } }, ["chihiro_evolved"] = { primary_attack_no_upgrades = "chihiro_evolved|rokuhira:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2100, attack_cooldown = 7.5, range = 27 }, { cost = 3500, damage = 3080, attack_cooldown = 7, range = 30 }, { cost = 4000, damage = 3780, attack_cooldown = 8, range = 31, primary_attack = "chihiro_evolved|rokuhira:two" }, { cost = 4500, damage = 5250, attack_cooldown = 7.5, range = 32 }, { cost = 5000, damage = 6300, attack_cooldown = 7.5, range = 33 }, { cost = 7500, damage = 7000, attack_cooldown = 7, range = 36, primary_attack = "chihiro_evolved|rokuhira:three" }, { cost = 10000, damage = 9800, attack_cooldown = 7, range = 37 }, { cost = 15000, damage = 12600, attack_cooldown = 7, range = 38 }, { cost = 20000, damage = 15400, attack_cooldown = 6.5, range = 39.5, primary_attack = "chihiro_evolved|rokuhira:four" }, { cost = 25000, damage = 21000, attack_cooldown = 6, range = 40 } } }, ["chika"] = { primary_attack_no_upgrades = "chika|chika:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 700, attack_cooldown = 5, range = 31 }, { cost = 3000, damage = 1200, attack_cooldown = 5, range = 32 }, { cost = 4000, damage = 2500, attack_cooldown = 7, range = 33, primary_attack = "chika|chika:two" }, { cost = 5000, damage = 3300, attack_cooldown = 6.5, range = 34 }, { cost = 6000, damage = 4000, attack_cooldown = 6, range = 35 } } }, ["chika_evolved"] = { primary_attack_no_upgrades = "chika_evolved|chika:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 910, attack_cooldown = 5, range = 31 }, { cost = 3000, damage = 1560, attack_cooldown = 5, range = 32 }, { cost = 4000, damage = 3250, attack_cooldown = 7, range = 33, primary_attack = "chika_evolved|chika:two" }, { cost = 5000, damage = 4290, attack_cooldown = 6.5, range = 34 }, { cost = 6000, damage = 5200, attack_cooldown = 6, range = 35 }, { cost = 7500, damage = 7800, attack_cooldown = 8.5, range = 40, primary_attack = "chika_evolved|chika:three" }, { cost = 10000, damage = 10400, attack_cooldown = 8, range = 42 }, { cost = 12500, damage = 13000, attack_cooldown = 8, range = 45 } } }, ["chuya"] = { primary_attack_no_upgrades = "chuya|chuya:one", spawn_cap = 4, upgrade = { { cost = 1350, damage = 800, attack_cooldown = 7, range = 23 }, { cost = 1750, damage = 1600, attack_cooldown = 8, range = 24, primary_attack = "chuya|chuya:two" }, { cost = 2200, damage = 2500, attack_cooldown = 8, range = 24.5 }, { cost = 3500, damage = 4000, attack_cooldown = 8, range = 25 }, { cost = 5000, damage = 5500, attack_cooldown = 8.5, range = 27, primary_attack = "chuya|chuya:three" }, { cost = 6000, damage = 7500, attack_cooldown = 8.5, range = 28 } } }, ["chuya_evolved"] = { primary_attack_no_upgrades = "chuya_evolved|chuya:one", spawn_cap = 4, upgrade = { { cost = 1350, damage = 1080, attack_cooldown = 7, range = 23 }, { cost = 1750, damage = 2160, attack_cooldown = 8, range = 24, primary_attack = "chuya_evolved|chuya:two" }, { cost = 2200, damage = 3375, attack_cooldown = 8, range = 24.5 }, { cost = 3500, damage = 5400, attack_cooldown = 8, range = 25 }, { cost = 5000, damage = 7425, attack_cooldown = 8.5, range = 27, primary_attack = "chuya_evolved|chuya:three" }, { cost = 6000, damage = 10125, attack_cooldown = 8.5, range = 28 }, { cost = 7500, damage = 13500, attack_cooldown = 8, range = 29 }, { cost = 9000, damage = 18900, attack_cooldown = 9.5, range = 33, primary_attack = "chuya_evolved|chuya:four" }, { cost = 12500, damage = 20250, attack_cooldown = 9, range = 40 } } }, ["cid"] = { primary_attack_no_upgrades = "cid|cid:one", spawn_cap = 3, upgrade = { { cost = 3500, damage = 3000, attack_cooldown = 6.5, range = 18 }, { cost = 5000, damage = 4800, attack_cooldown = 6, range = 20 }, { cost = 7000, damage = 7200, attack_cooldown = 8.5, range = 23, primary_attack = "cid|cid:two" }, { cost = 9000, damage = 9000, attack_cooldown = 8, range = 24 }, { cost = 14000, damage = 10800, attack_cooldown = 8, range = 25 } } }, ["cid_evolved"] = { primary_attack_no_upgrades = "cid_evolved|cid:one", spawn_cap = 3, upgrade = { { cost = 3500, damage = 4200, attack_cooldown = 6.5, range = 18 }, { cost = 5000, damage = 6720, attack_cooldown = 6, range = 20 }, { cost = 7000, damage = 10080, attack_cooldown = 8.5, range = 23, primary_attack = "cid_evolved|cid:two" }, { cost = 9000, damage = 12600, attack_cooldown = 8, range = 24 }, { cost = 14000, damage = 15120, attack_cooldown = 8, range = 25 }, { cost = 19000, damage = 30240, attack_cooldown = 12, range = 30, primary_attack = "cid_evolved|cid:three" }, { cost = 23000, damage = 36820, attack_cooldown = 11, range = 33 }, { cost = 25000, damage = 42000, attack_cooldown = 10, range = 35 } } }, ["conrad"] = { primary_attack_no_upgrades = "conrad|conrad:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 1000, attack_cooldown = 8, range = 23 }, { cost = 5000, damage = 2000, attack_cooldown = 7, range = 24 }, { cost = 6000, damage = 4000, attack_cooldown = 10, range = 25, primary_attack = "conrad|conrad:two" }, { cost = 7500, damage = 5000, attack_cooldown = 10, range = 27 }, { cost = 9000, damage = 6000, attack_cooldown = 10, range = 28 } } }, ["conrad_evolved"] = { primary_attack_no_upgrades = "conrad_evolved|conrad:one", spawn_cap = 3, upgrade = { { cost = 3000, damage = 1400, attack_cooldown = 8, range = 23 }, { cost = 5000, damage = 2800, attack_cooldown = 7, range = 24 }, { cost = 6000, damage = 5600, attack_cooldown = 10, range = 25, primary_attack = "conrad_evolved|conrad:two" }, { cost = 7500, damage = 7000, attack_cooldown = 10, range = 27 }, { cost = 9000, damage = 8400, attack_cooldown = 10, range = 28 }, { cost = 12000, damage = 11200, attack_cooldown = 10, range = 33, primary_attack = "conrad_evolved|conrad:three" }, { cost = 25000, damage = 17500, attack_cooldown = 10, range = 34 }, { cost = 35000, damage = 25200, attack_cooldown = 10, range = 35 } } }, ["cooler"] = { primary_attack_no_upgrades = "cooler|cooler:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 800, attack_cooldown = 6, range = 22 }, { cost = 2500, damage = 1300, attack_cooldown = 6, range = 23 }, { cost = 3000, damage = 2500, attack_cooldown = 8, range = 24, primary_attack = "cooler|cooler:two" }, { cost = 3500, damage = 3500, attack_cooldown = 8, range = 25 }, { cost = 4500, damage = 4500, attack_cooldown = 8, range = 26 }, { cost = 6000, damage = 6000, attack_cooldown = 8, range = 27 }, { cost = 7500, damage = 8000, attack_cooldown = 10, range = 30, primary_attack = "cooler|cooler:three" }, { cost = 9000, damage = 10000, attack_cooldown = 10, range = 33 }, { cost = 12500, damage = 12500, attack_cooldown = 10, range = 35 }, { cost = 20000, damage = 17000, attack_cooldown = 10, range = 37 } } }, ["cranel"] = { primary_attack_no_upgrades = "cranel|cranel:one", spawn_cap = 4, upgrade = { { cost = 1750, damage = 1200, attack_cooldown = 7, range = 20 }, { cost = 2500, damage = 1750, attack_cooldown = 6.5, range = 21 }, { cost = 3000, damage = 2250, attack_cooldown = 6.5, range = 22, primary_attack = "cranel|cranel:two" }, { cost = 4500, damage = 3000, attack_cooldown = 6, range = 24 }, { cost = 6000, damage = 4500, attack_cooldown = 6, range = 25 } } }, ["cranel_evolved"] = { primary_attack_no_upgrades = "cranel_evolved|cranel:one", spawn_cap = 4, upgrade = { { cost = 1750, damage = 1800, attack_cooldown = 7, range = 20 }, { cost = 2500, damage = 2625, attack_cooldown = 6.5, range = 21 }, { cost = 3000, damage = 3375, attack_cooldown = 6.5, range = 22, primary_attack = "cranel_evolved|cranel:two" }, { cost = 4500, damage = 4500, attack_cooldown = 6, range = 24 }, { cost = 6000, damage = 6750, attack_cooldown = 6, range = 25 }, { cost = 8000, damage = 9750, attack_cooldown = 8, range = 26, primary_attack = "cranel_evolved|cranel:three" }, { cost = 10000, damage = 12000, attack_cooldown = 8, range = 27 }, { cost = 15000, damage = 15000, attack_cooldown = 8, range = 28 } } }, ["daki"] = { primary_attack_no_upgrades = "daki|daki:slash", spawn_cap = 4, upgrade = { { cost = 2200, damage = 600, attack_cooldown = 6, range = 22 }, { cost = 2800, damage = 1200, attack_cooldown = 6, range = 23, primary_attack = "daki|daki:slam" }, { cost = 3500, damage = 1500, attack_cooldown = 5.5, range = 25 }, { cost = 5000, damage = 1750, attack_cooldown = 5.5, range = 27 }, { cost = 6500, damage = 2000, attack_cooldown = 5.5, range = 28, primary_attack = "daki|daki:spike" }, { cost = 7000, damage = 2500, attack_cooldown = 5, range = 29 }, { cost = 8000, damage = 3000, attack_cooldown = 5, range = 30 } } }, ["daki_evolved"] = { primary_attack_no_upgrades = "daki_evolved|live", spawn_cap = 4, upgrade = { { cost = 2200, damage = 840, attack_cooldown = 6, range = 22 }, { cost = 2800, damage = 1680, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 2100, attack_cooldown = 5.5, range = 25 }, { cost = 5000, damage = 2450, attack_cooldown = 5.5, range = 27 }, { cost = 6500, damage = 2800, attack_cooldown = 5.5, range = 28, primary_attack = "daki_evolved|live5" }, { cost = 7000, damage = 3500, attack_cooldown = 5, range = 29 }, { cost = 8000, damage = 4200, attack_cooldown = 5, range = 30 }, { cost = 10000, damage = 7700, attack_cooldown = 8, range = 34, primary_attack = "daki_evolved|live8" }, { cost = 12500, damage = 9450, attack_cooldown = 8, range = 35 } } }, ["dazai"] = { primary_attack_no_upgrades = "dazai|dazai:shoot", spawn_cap = 3, upgrade = { { cost = 2400, damage = 1500, attack_cooldown = 7, range = 21 }, { cost = 3800, damage = 3000, attack_cooldown = 7, range = 21.5 }, { cost = 5500, damage = 4500, attack_cooldown = 7, range = 22, primary_attack = "dazai|dazai:punch" }, { cost = 8000, damage = 6000, attack_cooldown = 6.5, range = 22.5 }, { cost = 10000, damage = 9000, attack_cooldown = 6.5, range = 23 }, { cost = 12000, damage = 11000, attack_cooldown = 6.5, range = 24 } } }, ["dazai_evolved"] = { primary_attack_no_upgrades = "dazai_evolved|dazai:shoot", spawn_cap = 3, upgrade = { { cost = 2400, damage = 2025, attack_cooldown = 7, range = 21 }, { cost = 3800, damage = 4050, attack_cooldown = 7, range = 21.5 }, { cost = 5500, damage = 6075, attack_cooldown = 7, range = 22, primary_attack = "dazai_evolved|dazai:punch" }, { cost = 8000, damage = 8100, attack_cooldown = 6.5, range = 22.5 }, { cost = 10000, damage = 12150, attack_cooldown = 6.5, range = 23 }, { cost = 15000, damage = 16200, attack_cooldown = 12, range = 24, primary_attack = "dazai_evolved|dazai:full_aoe" }, { cost = 20000, damage = 20250, attack_cooldown = 11.5, range = 24.5 }, { cost = 24000, damage = 27000, attack_cooldown = 11, range = 25 } } }, ["deku_vigilante"] = { primary_attack_no_upgrades = "deku_vigilante|deku_vigilante:axe_kick", spawn_cap = 4, upgrade = { { cost = 2000, damage = 600, attack_cooldown = 5, range = 23 }, { cost = 2500, damage = 900, attack_cooldown = 7, range = 25, primary_attack = "deku_vigilante|deku_vigilante:blackwhip" }, { cost = 3500, damage = 1250, attack_cooldown = 7, range = 26 }, { cost = 4500, damage = 2000, attack_cooldown = 7, range = 27 }, { cost = 6000, damage = 3500, attack_cooldown = 7, range = 28, primary_attack = "deku_vigilante|deku_vigilante:whip_punch" }, { cost = 7500, damage = 4500, attack_cooldown = 6.5, range = 29 }, { cost = 9000, damage = 6000, attack_cooldown = 6, range = 30 } } }, ["demiurge"] = { primary_attack_no_upgrades = "demiurge|demiurge:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 700, attack_cooldown = 7.5, range = 23 }, { cost = 2500, damage = 1100, attack_cooldown = 7.5, range = 25 }, { cost = 3500, damage = 1400, attack_cooldown = 7, range = 27, primary_attack = "demiurge|demiurge:two" }, { cost = 5000, damage = 2050, attack_cooldown = 7, range = 28 }, { cost = 6500, damage = 2900, attack_cooldown = 7, range = 29 }, { cost = 8000, damage = 3800, attack_cooldown = 7, range = 30 }, { cost = 10000, damage = 4300, attack_cooldown = 7, range = 32 } } }, ["demiurge_evolved"] = { primary_attack_no_upgrades = "demiurge_evolved|live", spawn_cap = 4, upgrade = { { cost = 2000, damage = 910, attack_cooldown = 7.5, range = 23 }, { cost = 2500, damage = 1430, attack_cooldown = 7.5, range = 25 }, { cost = 3500, damage = 1820, attack_cooldown = 7, range = 27 }, { cost = 5000, damage = 2665, attack_cooldown = 7, range = 28 }, { cost = 6500, damage = 3770, attack_cooldown = 7, range = 29 }, { cost = 8000, damage = 4940, attack_cooldown = 7, range = 30 }, { cost = 12000, damage = 11050, attack_cooldown = 13, range = 33 }, { cost = 15000, damage = 13650, attack_cooldown = 12.5, range = 34 }, { cost = 17500, damage = 17290, attack_cooldown = 12, range = 35 } } }, ["doflamingo"] = { primary_attack_no_upgrades = "doflamingo|doflamingo:rainbow", spawn_cap = 3, upgrade = { { cost = 3500, damage = 1150, attack_cooldown = 6, range = 24 }, { cost = 5000, damage = 2350, attack_cooldown = 6, range = 25 }, { cost = 6000, damage = 3250, attack_cooldown = 7.5, range = 26, primary_attack = "doflamingo|doflamingo:overheat" }, { cost = 7500, damage = 4650, attack_cooldown = 7.5, range = 27 }, { cost = 8500, damage = 6000, attack_cooldown = 7, range = 28 }, { cost = 10000, damage = 7500, attack_cooldown = 7, range = 29, primary_attack = "doflamingo|doflamingo:spikes" }, { cost = 15000, damage = 8125, attack_cooldown = 7, range = 30 } } }, ["doflamingo_evolved"] = { primary_attack_no_upgrades = "doflamingo_evolved|doflamingo:rainbow", spawn_cap = 3, upgrade = { { cost = 3500, damage = 1610, attack_cooldown = 6, range = 24 }, { cost = 5000, damage = 3290, attack_cooldown = 7.5, range = 26, primary_attack = "doflamingo_evolved|doflamingo:overheat" }, { cost = 6000, damage = 4550, attack_cooldown = 7, range = 27 }, { cost = 7500, damage = 6510, attack_cooldown = 7, range = 28, primary_attack = "doflamingo_evolved|doflamingo:spikes" }, { cost = 8500, damage = 8400, attack_cooldown = 7, range = 29 }, { cost = 10000, damage = 10500, attack_cooldown = 7, range = 30, primary_attack = "doflamingo_evolved|doflamingo:birdcage" }, { cost = 15000, damage = 11375, attack_cooldown = 6.5, range = 33 }, { cost = 17500, damage = 14280, attack_cooldown = 6.5, range = 34 }, { cost = 20000, damage = 21350, attack_cooldown = 8, range = 36, primary_attack = "doflamingo_evolved|doflamingo:bullets" }, { cost = 25000, damage = 31500, attack_cooldown = 8, range = 38 } } }, ["dokuro"] = { primary_attack_no_upgrades = "dokuro|dokuro:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1750, attack_cooldown = 7, range = 16 }, { cost = 2500, damage = 3000, attack_cooldown = 7, range = 18 }, { cost = 4000, damage = 4000, attack_cooldown = 6.5, range = 22, primary_attack = "dokuro|dokuro:two" }, { cost = 9000, damage = 8000, attack_cooldown = 6.5, range = 25 }, { cost = 16000, damage = 10000, attack_cooldown = 6, range = 27 }, { cost = 29000, damage = 15000, attack_cooldown = 6, range = 29 } } }, ["dokuro_evolved"] = { primary_attack_no_upgrades = "dokuro_evolved|dokuro:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 2275, attack_cooldown = 7, range = 16 }, { cost = 2500, damage = 3900, attack_cooldown = 7, range = 18 }, { cost = 4000, damage = 5200, attack_cooldown = 6.5, range = 22, primary_attack = "dokuro_evolved|dokuro:two" }, { cost = 9000, damage = 10400, attack_cooldown = 6.5, range = 25 }, { cost = 16000, damage = 13000, attack_cooldown = 6, range = 27 }, { cost = 29000, damage = 19500, attack_cooldown = 6, range = 29 } } }, ["elyssia"] = { primary_attack_no_upgrades = "elyssia|live", spawn_cap = 4, upgrade = { { cost = 5000, damage = 5000, attack_cooldown = 4, range = 12 }, { cost = 10000, damage = 10000, attack_cooldown = 4, range = 12 } } }, ["elyssia_evolved"] = { primary_attack_no_upgrades = "elyssia_evolved|live", spawn_cap = 2, upgrade = { { cost = 5000, damage = 7000, attack_cooldown = 4, range = 12 }, { cost = 10000, damage = 14000, attack_cooldown = 4, range = 12 }, { cost = 35000, damage = 21000, attack_cooldown = 4, range = 15 }, { cost = 70000, damage = 42000, attack_cooldown = 4, range = 15 } } }, ["evileye"] = { primary_attack_no_upgrades = "evileye|evileye:one", spawn_cap = 5, upgrade = { { cost = 1800, damage = 1210, attack_cooldown = 8, range = 23.5 }, { cost = 2200, damage = 1980, attack_cooldown = 7.5, range = 24 }, { cost = 3500, damage = 2970, attack_cooldown = 7.5, range = 25, primary_attack = "evileye|evileye:two" }, { cost = 4000, damage = 3850, attack_cooldown = 7, range = 27 }, { cost = 5000, damage = 4620, attack_cooldown = 7, range = 30 }, { cost = 6500, damage = 7700, attack_cooldown = 12, range = 33 } } }, ["evileye_evolved"] = { primary_attack_no_upgrades = "evileye_evolved|evileye:one", spawn_cap = 5, upgrade = { { cost = 1800, damage = 1694, attack_cooldown = 8, range = 23.5 }, { cost = 2200, damage = 2772, attack_cooldown = 7.5, range = 24 }, { cost = 3500, damage = 4158, attack_cooldown = 7.5, range = 25, primary_attack = "evileye_evolved|evileye:two" }, { cost = 4000, damage = 5390, attack_cooldown = 7, range = 27 }, { cost = 5000, damage = 6468, attack_cooldown = 7, range = 30 }, { cost = 6500, damage = 10780, attack_cooldown = 12, range = 33, primary_attack = "evileye_evolved|evileye:three" }, { cost = 7500, damage = 12600, attack_cooldown = 11, range = 34 }, { cost = 10000, damage = 16800, attack_cooldown = 10, range = 35 } } }, ["feitan"] = { primary_attack_no_upgrades = "feitan|feitan:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2500, attack_cooldown = 6, range = 20 }, { cost = 3000, damage = 4000, attack_cooldown = 6, range = 22 }, { cost = 4000, damage = 6000, attack_cooldown = 6, range = 23, primary_attack = "feitan|feitan:two" }, { cost = 5000, damage = 7000, attack_cooldown = 5.5, range = 25 }, { cost = 7500, damage = 10000, attack_cooldown = 5.5, range = 26 }, { cost = 9000, damage = 12000, attack_cooldown = 5.5, range = 28 } } }, ["feitan_evolved"] = { primary_attack_no_upgrades = "feitan_evolved|feitan:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 3500, attack_cooldown = 6, range = 20 }, { cost = 3000, damage = 5600, attack_cooldown = 6, range = 22 }, { cost = 4000, damage = 8400, attack_cooldown = 6, range = 23, primary_attack = "feitan_evolved|feitan:two" }, { cost = 5000, damage = 9800, attack_cooldown = 5.5, range = 25 }, { cost = 7500, damage = 14000, attack_cooldown = 5.5, range = 26 }, { cost = 21000, damage = 19600, attack_cooldown = 16, range = 30, primary_attack = "feitan_evolved|feitan:three" }, { cost = 25000, damage = 22400, attack_cooldown = 14, range = 33 }, { cost = 30000, damage = 25200, attack_cooldown = 12, range = 35 } } }, ["frieren"] = { primary_attack_no_upgrades = "frieren|frieren:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1400, attack_cooldown = 7, range = 26 }, { cost = 2200, damage = 2300, attack_cooldown = 7, range = 28 }, { cost = 3200, damage = 3500, attack_cooldown = 7, range = 30, primary_attack = "frieren|frieren:two" }, { cost = 4500, damage = 4800, attack_cooldown = 6.5, range = 32 }, { cost = 7000, damage = 6500, attack_cooldown = 6.5, range = 35 }, { cost = 10000, damage = 8200, attack_cooldown = 6.5, range = 40 } } }, ["frieren_evolved"] = { primary_attack_no_upgrades = "frieren_evolved|live", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1890, attack_cooldown = 7, range = 26 }, { cost = 2200, damage = 3105, attack_cooldown = 7, range = 28 }, { cost = 3200, damage = 4725, attack_cooldown = 7, range = 30 }, { cost = 4500, damage = 6480, attack_cooldown = 6.5, range = 32 }, { cost = 7000, damage = 8775, attack_cooldown = 6.5, range = 35 }, { cost = 10000, damage = 11070, attack_cooldown = 6.5, range = 40, primary_attack = "frieren_evolved|live6" }, { cost = 11000, damage = 13230, attack_cooldown = 6, range = 42 }, { cost = 17000, damage = 17550, attack_cooldown = 6, range = 45 } } }, ["fugo"] = { primary_attack_no_upgrades = "fugo|haze:bulb_throw", spawn_cap = 3, upgrade = { { cost = 3500, damage = 800, attack_cooldown = 12, range = 24 }, { cost = 5000, damage = 1200, attack_cooldown = 11, range = 25, primary_attack = "fugo|haze:bulb_shoot" }, { cost = 6000, damage = 1750, attack_cooldown = 11, range = 26 }, { cost = 8000, damage = 2500, attack_cooldown = 11, range = 27 }, { cost = 12500, damage = 3000, attack_cooldown = 11, range = 28 }, { cost = 20000, damage = 3500, attack_cooldown = 10, range = 30, primary_attack = "fugo|haze:slam" }, { cost = 25000, damage = 5000, attack_cooldown = 10, range = 33 } } }, ["fugo_evolved"] = { primary_attack_no_upgrades = "fugo_evolved|haze:bulb_throw", spawn_cap = 3, upgrade = { { cost = 3500, damage = 1080, attack_cooldown = 12, range = 24 }, { cost = 5000, damage = 1620, attack_cooldown = 11, range = 25, primary_attack = "fugo_evolved|haze:bulb_shoot" }, { cost = 6000, damage = 2362.5, attack_cooldown = 11, range = 26 }, { cost = 8000, damage = 3375, attack_cooldown = 11, range = 27 }, { cost = 12500, damage = 4050, attack_cooldown = 11, range = 28 }, { cost = 20000, damage = 4725, attack_cooldown = 10, range = 30, primary_attack = "fugo_evolved|haze:slam" }, { cost = 25000, damage = 6750, attack_cooldown = 10, range = 33 } } }, ["fujitora"] = { primary_attack_no_upgrades = "fujitora|fujitora:touch", spawn_cap = 4, upgrade = { { cost = 2100, damage = 1050, attack_cooldown = 8.5, range = 25 }, { cost = 3500, damage = 1900, attack_cooldown = 8, range = 26 }, { cost = 4500, damage = 2900, attack_cooldown = 8, range = 28, primary_attack = "fujitora|fujitora:meteor" }, { cost = 6000, damage = 3800, attack_cooldown = 8, range = 30 }, { cost = 7500, damage = 4600, attack_cooldown = 7.5, range = 32 }, { cost = 8500, damage = 5600, attack_cooldown = 7.5, range = 34 }, { cost = 9500, damage = 7200, attack_cooldown = 7.5, range = 35 } } }, ["fujitora_evolved"] = { primary_attack_no_upgrades = "fujitora_evolved|live", spawn_cap = 4, upgrade = { { cost = 2100, damage = 1470, attack_cooldown = 8.5, range = 25 }, { cost = 3500, damage = 2660, attack_cooldown = 8, range = 26 }, { cost = 4500, damage = 4060, attack_cooldown = 8, range = 28, primary_attack = "fujitora_evolved|live3" }, { cost = 6000, damage = 5320, attack_cooldown = 8, range = 30 }, { cost = 7500, damage = 6440, attack_cooldown = 7.5, range = 32 }, { cost = 8500, damage = 7840, attack_cooldown = 7.5, range = 34 }, { cost = 9000, damage = 13300, attack_cooldown = 12, range = 36, primary_attack = "fujitora_evolved|live7" }, { cost = 11000, damage = 14000, attack_cooldown = 11.5, range = 38 }, { cost = 13000, damage = 17080, attack_cooldown = 11, range = 40 } } }, ["gabimaru"] = { primary_attack_no_upgrades = "gabimaru|gabimaru:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1100, attack_cooldown = 8, range = 20 }, { cost = 3000, damage = 1800, attack_cooldown = 7.5, range = 21 }, { cost = 4500, damage = 2700, attack_cooldown = 7.5, range = 22, primary_attack = "gabimaru|gabimaru:two" }, { cost = 5000, damage = 3500, attack_cooldown = 7.5, range = 23 }, { cost = 6500, damage = 4500, attack_cooldown = 7.5, range = 25 }, { cost = 7500, damage = 5000, attack_cooldown = 7, range = 27 } } }, ["gabimaru_evolved"] = { primary_attack_no_upgrades = "gabimaru_evolved|gabimaru:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1430, attack_cooldown = 8, range = 20 }, { cost = 3000, damage = 2340, attack_cooldown = 7.5, range = 21 }, { cost = 4500, damage = 3510, attack_cooldown = 7.5, range = 22, primary_attack = "gabimaru_evolved|gabimaru:two" }, { cost = 5000, damage = 4550, attack_cooldown = 7.5, range = 23 }, { cost = 6500, damage = 5850, attack_cooldown = 7.5, range = 25 }, { cost = 9000, damage = 6500, attack_cooldown = 7, range = 27, primary_attack = "gabimaru_evolved|gabimaru:three" }, { cost = 10000, damage = 7800, attack_cooldown = 7, range = 28 }, { cost = 14000, damage = 10010, attack_cooldown = 7, range = 30 } } }, ["gaku"] = { primary_attack_no_upgrades = "gaku|gaku:one", spawn_cap = 3, upgrade = { { cost = 1500, damage = 1000, attack_cooldown = 4.5, range = 25 }, { cost = 2500, damage = 2000, attack_cooldown = 4, range = 26 }, { cost = 3000, damage = 3500, attack_cooldown = 6, range = 27, primary_attack = "gaku|gaku:two" }, { cost = 5000, damage = 5000, attack_cooldown = 5.5, range = 30 }, { cost = 8000, damage = 7500, attack_cooldown = 5.5, range = 32 }, { cost = 10000, damage = 12000, attack_cooldown = 5.5, range = 35 } } }, ["gaku_evolved"] = { primary_attack_no_upgrades = "gaku_evolved|gaku:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1250, attack_cooldown = 4.5, range = 25 }, { cost = 2500, damage = 2500, attack_cooldown = 4, range = 26 }, { cost = 3000, damage = 4375, attack_cooldown = 6, range = 27, primary_attack = "gaku_evolved|gaku:two" }, { cost = 5000, damage = 6250, attack_cooldown = 5.5, range = 30 }, { cost = 8000, damage = 9375, attack_cooldown = 5.5, range = 32 }, { cost = 12000, damage = 18750, attack_cooldown = 8.5, range = 37, primary_attack = "gaku_evolved|gaku:three" }, { cost = 20000, damage = 25025, attack_cooldown = 8, range = 40 } } }, ["ghostkun"] = { primary_attack_no_upgrades = "ghostkun|hanako_kun:one", spawn_cap = 3, upgrade = { { cost = 1750, damage = 400, attack_cooldown = 5, range = 26 }, { cost = 3300, damage = 1200, attack_cooldown = 8, range = 27, primary_attack = "ghostkun|hanako_kun:two" }, { cost = 5000, damage = 2000, attack_cooldown = 7.5, range = 27 }, { cost = 7500, damage = 2400, attack_cooldown = 7, range = 28 }, { cost = 10000, damage = 4000, attack_cooldown = 6.5, range = 29 } } }, ["ghostkun_evolved"] = { primary_attack_no_upgrades = "ghostkun_evolved|live", spawn_cap = 3, upgrade = { { cost = 1750, damage = 560, attack_cooldown = 5, range = 26 }, { cost = 3300, damage = 1680, attack_cooldown = 8, range = 27, primary_attack = "ghostkun_evolved|live2" }, { cost = 5000, damage = 2800, attack_cooldown = 7.5, range = 27 }, { cost = 7500, damage = 3360, attack_cooldown = 7, range = 28 }, { cost = 10000, damage = 5600, attack_cooldown = 6.5, range = 29 }, { cost = 12500, damage = 10080, attack_cooldown = 12, range = 30, primary_attack = "ghostkun_evolved|live6" }, { cost = 17500, damage = 13440, attack_cooldown = 12, range = 33 }, { cost = 20000, damage = 16800, attack_cooldown = 12, range = 35 } } }, ["gilgamesh"] = { primary_attack_no_upgrades = "gilgamesh|gilgamesh:wheel_gates", spawn_cap = 3, upgrade = { { cost = 3000, damage = 1200, attack_cooldown = 7, range = 24 }, { cost = 3500, damage = 2600, attack_cooldown = 7, range = 25, primary_attack = "gilgamesh|gilgamesh:collapse" }, { cost = 5000, damage = 3500, attack_cooldown = 6.5, range = 26 }, { cost = 6000, damage = 5500, attack_cooldown = 6.5, range = 27 }, { cost = 7000, damage = 8500, attack_cooldown = 9.5, range = 29, primary_attack = "gilgamesh|gilgamesh:jump_and_gates" }, { cost = 9000, damage = 10000, attack_cooldown = 9, range = 30 }, { cost = 12000, damage = 13000, attack_cooldown = 9, range = 31 } } }, ["gilgamesh_evolved"] = { primary_attack_no_upgrades = "gilgamesh_evolved|gilgamesh:wheel_gates", spawn_cap = 3, upgrade = { { cost = 3000, damage = 1680, attack_cooldown = 7, range = 24 }, { cost = 3500, damage = 3640, attack_cooldown = 7, range = 25, primary_attack = "gilgamesh_evolved|gilgamesh:collapse" }, { cost = 5000, damage = 4900, attack_cooldown = 6.5, range = 26 }, { cost = 6000, damage = 7700, attack_cooldown = 6.5, range = 27 }, { cost = 7000, damage = 11900, attack_cooldown = 9.5, range = 29, primary_attack = "gilgamesh_evolved|gilgamesh:jump_and_gates" }, { cost = 9000, damage = 14000, attack_cooldown = 9, range = 30 }, { cost = 12000, damage = 18200, attack_cooldown = 9, range = 31 }, { cost = 15000, damage = 32200, attack_cooldown = 14, range = 33, primary_attack = "gilgamesh_evolved|gilgamesh:enuma_elish" }, { cost = 25000, damage = 42000, attack_cooldown = 13, range = 34 }, { cost = 30000, damage = 49000, attack_cooldown = 12, range = 35 } } }, ["giselle"] = { primary_attack_no_upgrades = "giselle|giselle:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1400, attack_cooldown = 7, range = 23 }, { cost = 2500, damage = 3000, attack_cooldown = 7, range = 25, primary_attack = "giselle|giselle:two" }, { cost = 4000, damage = 4000, attack_cooldown = 6.5, range = 27 }, { cost = 6000, damage = 6500, attack_cooldown = 6.5, range = 28 }, { cost = 7500, damage = 8000, attack_cooldown = 6.5, range = 29 } } }, ["giselle_evolved"] = { primary_attack_no_upgrades = "giselle_evolved|giselle:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1750, attack_cooldown = 7, range = 23 }, { cost = 2500, damage = 3750, attack_cooldown = 7, range = 25, primary_attack = "giselle_evolved|giselle:two" }, { cost = 4000, damage = 5000, attack_cooldown = 6.5, range = 27 }, { cost = 6000, damage = 8125, attack_cooldown = 6.5, range = 28 }, { cost = 9000, damage = 11250, attack_cooldown = 6.5, range = 29, primary_attack = "giselle_evolved|giselle:three" }, { cost = 16000, damage = 18750, attack_cooldown = 6, range = 30 }, { cost = 29000, damage = 31250, attack_cooldown = 6, range = 32 } } }, ["goku_ssj3"] = { primary_attack_no_upgrades = "goku_ssj3|goku_ssj3:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1400, attack_cooldown = 7, range = 23 }, { cost = 2500, damage = 2500, attack_cooldown = 8, range = 24, primary_attack = "goku_ssj3|goku_ssj3:two" }, { cost = 3500, damage = 4000, attack_cooldown = 8, range = 25 }, { cost = 5000, damage = 5000, attack_cooldown = 7.5, range = 26 }, { cost = 7500, damage = 7000, attack_cooldown = 7.5, range = 27 }, { cost = 9000, damage = 9000, attack_cooldown = 7.5, range = 30 } } }, ["goku_ssj3_evolved"] = { primary_attack_no_upgrades = "goku_ssj3_evolved|live", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1890, attack_cooldown = 7, range = 23 }, { cost = 2500, damage = 3375, attack_cooldown = 8, range = 24 }, { cost = 3500, damage = 5400, attack_cooldown = 8, range = 25 }, { cost = 5000, damage = 6750, attack_cooldown = 7.5, range = 26 }, { cost = 7500, damage = 9450, attack_cooldown = 7.5, range = 27 }, { cost = 9000, damage = 12150, attack_cooldown = 10, range = 33 }, { cost = 12000, damage = 16200, attack_cooldown = 9, range = 35 }, { cost = 17500, damage = 21600, attack_cooldown = 8, range = 37 } } }, ["gremmy"] = { primary_attack_no_upgrades = "gremmy|gremmy:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1700, attack_cooldown = 8, range = 22 }, { cost = 3000, damage = 2500, attack_cooldown = 7, range = 25 }, { cost = 4500, damage = 4000, attack_cooldown = 10, range = 30, primary_attack = "gremmy|gremmy:two" }, { cost = 5000, damage = 6000, attack_cooldown = 10, range = 33 }, { cost = 6500, damage = 8000, attack_cooldown = 9, range = 34 }, { cost = 7500, damage = 12000, attack_cooldown = 9, range = 35 } } }, ["gremmy_evolved"] = { primary_attack_no_upgrades = "gremmy_evolved|live", spawn_cap = 4, upgrade = { { cost = 2000, damage = 2295, attack_cooldown = 8, range = 22 }, { cost = 3000, damage = 3375, attack_cooldown = 7, range = 25 }, { cost = 4500, damage = 5400, attack_cooldown = 10, range = 30, primary_attack = "gremmy_evolved|live3" }, { cost = 5000, damage = 8100, attack_cooldown = 10, range = 33 }, { cost = 6500, damage = 10800, attack_cooldown = 9, range = 34 }, { cost = 7500, damage = 16200, attack_cooldown = 9, range = 35 }, { cost = 20000, damage = 32400, attack_cooldown = 15, range = 37, primary_attack = "gremmy_evolved|live7" }, { cost = 25000, damage = 36450, attack_cooldown = 15, range = 40 } } }, ["griffith"] = { primary_attack_no_upgrades = "griffith|griffith:combo", spawn_cap = 4, upgrade = { { cost = 2100, damage = 1050, attack_cooldown = 7, range = 20 }, { cost = 3200, damage = 2000, attack_cooldown = 7, range = 21 }, { cost = 5000, damage = 3000, attack_cooldown = 6.5, range = 22 }, { cost = 6000, damage = 4200, attack_cooldown = 6.5, range = 23, primary_attack = "griffith|griffith:stabs" }, { cost = 7000, damage = 4900, attack_cooldown = 6, range = 25 }, { cost = 9500, damage = 6500, attack_cooldown = 6, range = 26 }, { cost = 12000, damage = 8500, attack_cooldown = 6, range = 27 } } }, ["grimmjow_res"] = { primary_attack_no_upgrades = "grimmjow_res|rebirth_grimmjow:one", spawn_cap = 3, upgrade = { { cost = 2200, damage = 2000, attack_cooldown = 8, range = 16 }, { cost = 1750, damage = 2500, attack_cooldown = 9, range = 25, primary_attack = "grimmjow_res|rebirth_grimmjow:two" }, { cost = 5000, damage = 5000, attack_cooldown = 9, range = 27 }, { cost = 6500, damage = 7500, attack_cooldown = 9, range = 29 }, { cost = 10000, damage = 10000, attack_cooldown = 9, range = 30 } } }, ["grimmjow_res_evolved"] = { primary_attack_no_upgrades = "grimmjow_res_evolved|rebirth_grimmjow:one", spawn_cap = 3, upgrade = { { cost = 2200, damage = 3000, attack_cooldown = 8, range = 16 }, { cost = 1750, damage = 3750, attack_cooldown = 9, range = 25, primary_attack = "grimmjow_res_evolved|rebirth_grimmjow:two" }, { cost = 5000, damage = 7500, attack_cooldown = 9, range = 27 }, { cost = 6500, damage = 11250, attack_cooldown = 9, range = 29 }, { cost = 10000, damage = 15000, attack_cooldown = 9, range = 30 }, { cost = 15000, damage = 22500, attack_cooldown = 11, range = 31, primary_attack = "grimmjow_res_evolved|rebirth_grimmjow:three" }, { cost = 22500, damage = 30000, attack_cooldown = 10, range = 32 }, { cost = 25000, damage = 37500, attack_cooldown = 9, range = 33 } } }, ["guts"] = { primary_attack_no_upgrades = "guts|guts:slash", spawn_cap = 3, upgrade = { { cost = 2000, damage = 750, attack_cooldown = 8, range = 18 }, { cost = 3500, damage = 2000, attack_cooldown = 7, range = 19, primary_attack = "guts|guts:cannon" }, { cost = 4500, damage = 2500, attack_cooldown = 7, range = 20 }, { cost = 6000, damage = 3000, attack_cooldown = 7, range = 21 }, { cost = 7000, damage = 6000, attack_cooldown = 12, range = 22, primary_attack = "guts|guts:combo" }, { cost = 9000, damage = 7000, attack_cooldown = 11, range = 23 }, { cost = 12500, damage = 9000, attack_cooldown = 10, range = 24 } } }, ["guts_berserker"] = { primary_attack_no_upgrades = "guts_berserker|live", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1125, attack_cooldown = 6, range = 18 }, { cost = 3500, damage = 3000, attack_cooldown = 8, range = 19, primary_attack = "guts_berserker|live2" }, { cost = 4500, damage = 3750, attack_cooldown = 7.5, range = 20 }, { cost = 6000, damage = 4500, attack_cooldown = 7, range = 21 }, { cost = 7000, damage = 9000, attack_cooldown = 12, range = 22, primary_attack = "guts_berserker|live5" }, { cost = 9000, damage = 10500, attack_cooldown = 11, range = 23 }, { cost = 12500, damage = 13500, attack_cooldown = 10, range = 24 }, { cost = 20000, damage = 18750, attack_cooldown = 14, range = 25, primary_attack = "guts_berserker|live8" }, { cost = 25000, damage = 21000, attack_cooldown = 13.5, range = 27 }, { cost = 35000, damage = 28500, attack_cooldown = 13, range = 28 } } }, ["gyutaro"] = { primary_attack_no_upgrades = "gyutaro|gyutaro:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 700, attack_cooldown = 6.5, range = 22 }, { cost = 2500, damage = 1000, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 1200, attack_cooldown = 6, range = 25, primary_attack = "gyutaro|gyutaro:two" }, { cost = 4200, damage = 1500, attack_cooldown = 5.5, range = 26 }, { cost = 5500, damage = 2400, attack_cooldown = 5.5, range = 27 }, { cost = 6000, damage = 3000, attack_cooldown = 5.5, range = 28 } } }, ["gyutaro_evolved"] = { primary_attack_no_upgrades = "gyutaro_evolved|live", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1015, attack_cooldown = 6.5, range = 22 }, { cost = 2500, damage = 1450, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 1740, attack_cooldown = 6, range = 25 }, { cost = 4200, damage = 2175, attack_cooldown = 5.5, range = 26 }, { cost = 5500, damage = 3840, attack_cooldown = 5.5, range = 27 }, { cost = 6000, damage = 4350, attack_cooldown = 5.5, range = 28 }, { cost = 6750, damage = 8700, attack_cooldown = 8, range = 30 }, { cost = 12500, damage = 10875, attack_cooldown = 8, range = 33 }, { cost = 17500, damage = 13775, attack_cooldown = 8, range = 35 } } }, ["hakari"] = { primary_attack_no_upgrades = "hakari|hakari:one", spawn_cap = 3, upgrade = { { cost = 2100, damage = 700, attack_cooldown = 6, range = 22 }, { cost = 2800, damage = 1100, attack_cooldown = 6, range = 24 }, { cost = 3500, damage = 2000, attack_cooldown = 5.5, range = 26, primary_attack = "hakari|hakari:two" }, { cost = 5500, damage = 2800, attack_cooldown = 5.5, range = 28 }, { cost = 8000, damage = 4000, attack_cooldown = 5.5, range = 30 }, { cost = 14000, damage = 5000, attack_cooldown = 5.5, range = 32 } } }, ["hakari_evolved"] = { primary_attack_no_upgrades = "hakari_evolved|live", spawn_cap = 3, upgrade = { { cost = 2100, damage = 875, attack_cooldown = 6, range = 22 }, { cost = 2800, damage = 1375, attack_cooldown = 6, range = 24 }, { cost = 3500, damage = 2500, attack_cooldown = 5.5, range = 26 }, { cost = 5500, damage = 3500, attack_cooldown = 5.5, range = 28 }, { cost = 8000, damage = 5000, attack_cooldown = 5.5, range = 30 }, { cost = 20000, damage = 6937.5, attack_cooldown = 5, range = 32, primary_attack = "hakari_evolved|live6" }, { cost = 23000, damage = 10000, attack_cooldown = 5, range = 34 }, { cost = 27500, damage = 12500, attack_cooldown = 4.5, range = 35 } } }, ["harribel"] = { primary_attack_no_upgrades = "harribel|harribel:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1250, attack_cooldown = 7, range = 24 }, { cost = 3500, damage = 2000, attack_cooldown = 7, range = 25 }, { cost = 5000, damage = 2500, attack_cooldown = 8, range = 29, primary_attack = "harribel|harribel:two" }, { cost = 6000, damage = 3500, attack_cooldown = 8, range = 30 }, { cost = 7500, damage = 4500, attack_cooldown = 8, range = 32 }, { cost = 9000, damage = 6000, attack_cooldown = 8, range = 33 } } }, ["harribel_evolved"] = { primary_attack_no_upgrades = "harribel_evolved|harribel:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1625, attack_cooldown = 7, range = 24 }, { cost = 3500, damage = 2600, attack_cooldown = 7, range = 25 }, { cost = 5000, damage = 3250, attack_cooldown = 8, range = 29, primary_attack = "harribel_evolved|harribel:two" }, { cost = 6000, damage = 4550, attack_cooldown = 8, range = 30 }, { cost = 7500, damage = 5850, attack_cooldown = 8, range = 32 }, { cost = 9000, damage = 7800, attack_cooldown = 8, range = 33 }, { cost = 13000, damage = 10400, attack_cooldown = 7, range = 35, primary_attack = "harribel_evolved|harribel:three" }, { cost = 16000, damage = 13000, attack_cooldown = 7, range = 36 } } }, ["heathcliff"] = { primary_attack_no_upgrades = "heathcliff|heathcliff:shield_bash", spawn_cap = 3, upgrade = { { cost = 2700, damage = 2900, attack_cooldown = 9, range = 18 }, { cost = 3800, damage = 4500, attack_cooldown = 9, range = 19, primary_attack = "heathcliff|heathcliff:stabs" }, { cost = 5000, damage = 5333, attack_cooldown = 8, range = 20 }, { cost = 7000, damage = 6400, attack_cooldown = 8, range = 22, primary_attack = "heathcliff|heathcliff:bash_and_slash" }, { cost = 8000, damage = 8000, attack_cooldown = 8, range = 23 }, { cost = 10000, damage = 10667, attack_cooldown = 8, range = 25 } } }, ["heathcliff_evolved"] = { primary_attack_no_upgrades = "heathcliff_evolved|heathcliff:shield_bash", spawn_cap = 3, upgrade = { { cost = 2700, damage = 4350, attack_cooldown = 9, range = 18 }, { cost = 3800, damage = 6750, attack_cooldown = 9, range = 19, primary_attack = "heathcliff_evolved|heathcliff:stabs" }, { cost = 5000, damage = 7999.5, attack_cooldown = 8, range = 20 }, { cost = 7000, damage = 9600, attack_cooldown = 8, range = 22, primary_attack = "heathcliff_evolved|heathcliff:bash_and_slash" }, { cost = 8000, damage = 12000, attack_cooldown = 8, range = 23 }, { cost = 10000, damage = 16000.5, attack_cooldown = 8, range = 25 }, { cost = 11000, damage = 22000.5, attack_cooldown = 10, range = 28, primary_attack = "heathcliff_evolved|heathcliff:full_sweep" }, { cost = 13000, damage = 25999.5, attack_cooldown = 10, range = 29 }, { cost = 14000, damage = 31999.5, attack_cooldown = 10, range = 30 } } }, ["hei"] = { primary_attack_no_upgrades = "hei|hei:one", spawn_cap = 4, upgrade = { { cost = 1700, damage = 1200, attack_cooldown = 6.5, range = 18 }, { cost = 2000, damage = 2000, attack_cooldown = 6.5, range = 19 }, { cost = 3000, damage = 2400, attack_cooldown = 6.5, range = 20, primary_attack = "hei|hei:two" }, { cost = 4000, damage = 3200, attack_cooldown = 6, range = 21 }, { cost = 7000, damage = 4500, attack_cooldown = 6, range = 22 } } }, ["hei_evolved"] = { primary_attack_no_upgrades = "hei_evolved|hei:one", spawn_cap = 4, upgrade = { { cost = 1700, damage = 1560, attack_cooldown = 6.5, range = 18 }, { cost = 2000, damage = 2600, attack_cooldown = 6.5, range = 19 }, { cost = 3000, damage = 3120, attack_cooldown = 6.5, range = 20, primary_attack = "hei_evolved|hei:two" }, { cost = 4000, damage = 4160, attack_cooldown = 6, range = 21 }, { cost = 7000, damage = 5850, attack_cooldown = 6, range = 22 }, { cost = 11000, damage = 6500, attack_cooldown = 8.5, range = 24, primary_attack = "hei_evolved|hei:three" }, { cost = 15000, damage = 8840, attack_cooldown = 8.5, range = 25 }, { cost = 18000, damage = 11700, attack_cooldown = 8, range = 26 } } }, ["hidan_unit"] = { primary_attack_no_upgrades = "hidan_unit|hidan_unit:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 750, attack_cooldown = 6, range = 20 }, { cost = 3500, damage = 1250, attack_cooldown = 6, range = 22 }, { cost = 4000, damage = 2000, attack_cooldown = 6, range = 23, primary_attack = "hidan_unit|hidan_unit:two" }, { cost = 5500, damage = 2500, attack_cooldown = 7, range = 24 }, { cost = 7500, damage = 3500, attack_cooldown = 7, range = 25 }, { cost = 9000, damage = 4000, attack_cooldown = 7, range = 26 } } }, ["hidan_unit_evolved"] = { primary_attack_no_upgrades = "hidan_unit_evolved|live", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1050, attack_cooldown = 6, range = 20 }, { cost = 3500, damage = 1750, attack_cooldown = 6, range = 22 }, { cost = 4000, damage = 2800, attack_cooldown = 6, range = 23, primary_attack = "hidan_unit_evolved|live3" }, { cost = 5500, damage = 3500, attack_cooldown = 7, range = 24 }, { cost = 7500, damage = 4900, attack_cooldown = 7, range = 25 }, { cost = 9000, damage = 5600, attack_cooldown = 7, range = 26, primary_attack = "hidan_unit_evolved|live6" }, { cost = 13000, damage = 6300, attack_cooldown = 6.5, range = 27 }, { cost = 16500, damage = 7700, attack_cooldown = 6.5, range = 29 }, { cost = 20000, damage = 10500, attack_cooldown = 6.5, range = 30 } } }, ["hinami"] = { primary_attack_no_upgrades = "hinami|hinami:one", spawn_cap = 4, upgrade = { { cost = 1700, damage = 1200, attack_cooldown = 7, range = 21 }, { cost = 2400, damage = 2000, attack_cooldown = 7, range = 22 }, { cost = 3300, damage = 3000, attack_cooldown = 8, range = 23, primary_attack = "hinami|hinami:two" }, { cost = 4400, damage = 4000, attack_cooldown = 7.5, range = 24 }, { cost = 5500, damage = 5500, attack_cooldown = 7.5, range = 25 }, { cost = 7500, damage = 7000, attack_cooldown = 7, range = 30 } } }, ["hinami_evolved"] = { primary_attack_no_upgrades = "hinami_evolved|hinami:one", spawn_cap = 4, upgrade = { { cost = 1700, damage = 1800, attack_cooldown = 7, range = 21 }, { cost = 2400, damage = 3000, attack_cooldown = 7, range = 22 }, { cost = 3300, damage = 4500, attack_cooldown = 8, range = 23, primary_attack = "hinami_evolved|hinami:two" }, { cost = 4400, damage = 6000, attack_cooldown = 7.5, range = 24 }, { cost = 5500, damage = 8250, attack_cooldown = 7.5, range = 25 }, { cost = 7500, damage = 10500, attack_cooldown = 7, range = 30, primary_attack = "hinami_evolved|hinami:three" }, { cost = 12000, damage = 13500, attack_cooldown = 7, range = 33 }, { cost = 18000, damage = 18000, attack_cooldown = 7, range = 35 } } }, ["homura"] = { primary_attack_no_upgrades = "homura|homura:shoot", spawn_cap = 3, upgrade = { { cost = 2000, damage = 900, attack_cooldown = 5, range = 16 }, { cost = 2500, damage = 1500, attack_cooldown = 5, range = 17 }, { cost = 3500, damage = 2200, attack_cooldown = 6, range = 20, primary_attack = "homura|homura:grenade" }, { cost = 4500, damage = 2750, attack_cooldown = 5.5, range = 23 }, { cost = 6000, damage = 3500, attack_cooldown = 5, range = 24 }, { cost = 8000, damage = 4000, attack_cooldown = 4.5, range = 25 } } }, ["homura_evolved"] = { primary_attack_no_upgrades = "homura_evolved|live", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1350, attack_cooldown = 5, range = 16 }, { cost = 2500, damage = 2250, attack_cooldown = 5, range = 17 }, { cost = 3500, damage = 3300, attack_cooldown = 6, range = 20, primary_attack = "homura_evolved|live3" }, { cost = 4500, damage = 4125, attack_cooldown = 5.5, range = 23 }, { cost = 6000, damage = 5250, attack_cooldown = 5, range = 24 }, { cost = 8000, damage = 6000, attack_cooldown = 4.5, range = 25 }, { cost = 11000, damage = 7800, attack_cooldown = 7, range = 30, primary_attack = "homura_evolved|live7" }, { cost = 15000, damage = 9750, attack_cooldown = 6.5, range = 31 }, { cost = 20000, damage = 12510, attack_cooldown = 6, range = 32 } } }, ["hoshino"] = { spawn_cap = 1, upgrade = { { cost = 2500 }, { cost = 5000 } } }, ["hoshino_2"] = { primary_attack_no_upgrades = "hoshino_2|live", spawn_cap = 1, upgrade = { { cost = 2500 }, { cost = 5000 } } }, ["hoshino_2_evolved"] = { primary_attack_no_upgrades = "hoshino_2_evolved|live", spawn_cap = 1, upgrade = { { cost = 2500 }, { cost = 5000 }, { cost = 7500 } } }, ["illya"] = { primary_attack_no_upgrades = "illya|illya:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1500, attack_cooldown = 6, range = 15 }, { cost = 3500, damage = 2000, attack_cooldown = 6, range = 19 }, { cost = 5000, damage = 5000, attack_cooldown = 8.5, range = 20, primary_attack = "illya|illya:two" }, { cost = 7500, damage = 6000, attack_cooldown = 8, range = 22 }, { cost = 9000, damage = 8000, attack_cooldown = 8, range = 23 }, { cost = 13500, damage = 12500, attack_cooldown = 11, range = 24 } } }, ["ippo"] = { primary_attack_no_upgrades = "ippo|ippo:basic", spawn_cap = 5, upgrade = { { cost = 1250, damage = 900, attack_cooldown = 5, range = 16 }, { cost = 2000, damage = 1400, attack_cooldown = 5, range = 17 }, { cost = 2500, damage = 6000, attack_cooldown = 15, range = 18, primary_attack = "ippo|ippo:ult" }, { cost = 3500, damage = 7500, attack_cooldown = 14, range = 19 }, { cost = 4000, damage = 9000, attack_cooldown = 13, range = 20 }, { cost = 5500, damage = 10000, attack_cooldown = 12, range = 22 } } }, ["isagi"] = { primary_attack_no_upgrades = "isagi|isagi:one", spawn_cap = 5, upgrade = { { cost = 1500, damage = 800, attack_cooldown = 4.5, range = 16 }, { cost = 2000, damage = 1250, attack_cooldown = 4, range = 17 }, { cost = 3000, damage = 2500, attack_cooldown = 6, range = 22, primary_attack = "isagi|isagi:two" }, { cost = 4500, damage = 3500, attack_cooldown = 5.5, range = 23 }, { cost = 6000, damage = 4500, attack_cooldown = 5, range = 24 }, { cost = 8000, damage = 6000, attack_cooldown = 9, range = 30, primary_attack = "isagi|isagi:three" }, { cost = 12000, damage = 7000, attack_cooldown = 8, range = 33 } } }, ["issei"] = { primary_attack_no_upgrades = "issei|issei:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1200, attack_cooldown = 8, range = 21 }, { cost = 3000, damage = 2000, attack_cooldown = 7.5, range = 22 }, { cost = 4000, damage = 2500, attack_cooldown = 7.5, range = 23, primary_attack = "issei|issei:two" }, { cost = 4500, damage = 3000, attack_cooldown = 7, range = 24 }, { cost = 6000, damage = 3500, attack_cooldown = 7, range = 25 } } }, ["issei_evolved"] = { primary_attack_no_upgrades = "issei_evolved|issei:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1680, attack_cooldown = 8, range = 21 }, { cost = 3000, damage = 2800, attack_cooldown = 7.5, range = 22 }, { cost = 4000, damage = 3500, attack_cooldown = 7.5, range = 23, primary_attack = "issei_evolved|issei:two" }, { cost = 4500, damage = 4200, attack_cooldown = 7, range = 24 }, { cost = 6000, damage = 4900, attack_cooldown = 7, range = 25 }, { cost = 7500, damage = 8400, attack_cooldown = 9, range = 50, primary_attack = "issei_evolved|issei:three" }, { cost = 9000, damage = 12600, attack_cooldown = 9, range = 53 }, { cost = 12500, damage = 16800, attack_cooldown = 9, range = 55 } } }, ["izumi"] = { primary_attack_no_upgrades = "izumi|izumi:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1089, attack_cooldown = 6.5, range = 25 }, { cost = 3500, damage = 1936, attack_cooldown = 6.5, range = 25.5 }, { cost = 5000, damage = 3630, attack_cooldown = 8, range = 26, primary_attack = "izumi|izumi:two" }, { cost = 8000, damage = 5808, attack_cooldown = 8, range = 27 }, { cost = 10500, damage = 7986, attack_cooldown = 7.5, range = 28 }, { cost = 15000, damage = 11495, attack_cooldown = 7.5, range = 29 } } }, ["izumi_evolved"] = { primary_attack_no_upgrades = "izumi_evolved|izumi:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1524.6, attack_cooldown = 6.5, range = 25 }, { cost = 3500, damage = 2710.4, attack_cooldown = 6.5, range = 25.5 }, { cost = 5000, damage = 5082, attack_cooldown = 8, range = 26, primary_attack = "izumi_evolved|izumi:two" }, { cost = 8000, damage = 8131.2, attack_cooldown = 8, range = 27 }, { cost = 10500, damage = 11180.4, attack_cooldown = 7.5, range = 28 }, { cost = 15000, damage = 16093, attack_cooldown = 7.5, range = 29 }, { cost = 17500, damage = 33880, attack_cooldown = 12.5, range = 31, primary_attack = "izumi_evolved|izumi:three" }, { cost = 20000, damage = 40656, attack_cooldown = 12, range = 33 } } }, ["jiraiya"] = { primary_attack_no_upgrades = "jiraiya|jiraiya:one_unevo", spawn_cap = 4, upgrade = { { cost = 2400, damage = 900, attack_cooldown = 8, range = 25 }, { cost = 3300, damage = 1700, attack_cooldown = 7.5, range = 26 }, { cost = 4500, damage = 3300, attack_cooldown = 7.5, range = 27, primary_attack = "jiraiya|jiraiya:two_unevo" }, { cost = 5000, damage = 4000, attack_cooldown = 7, range = 29 }, { cost = 8000, damage = 6000, attack_cooldown = 7, range = 30 }, { cost = 10000, damage = 8000, attack_cooldown = 7, range = 31 } } }, ["jiraiya_evolved"] = { primary_attack_no_upgrades = "jiraiya_evolved|live", spawn_cap = 4, upgrade = { { cost = 2400, damage = 1170, attack_cooldown = 8, range = 25 }, { cost = 3300, damage = 2210, attack_cooldown = 7.5, range = 26 }, { cost = 4500, damage = 4290, attack_cooldown = 7.5, range = 27, primary_attack = "jiraiya_evolved|live3" }, { cost = 5000, damage = 5200, attack_cooldown = 7, range = 29 }, { cost = 8000, damage = 7800, attack_cooldown = 7, range = 30 }, { cost = 10000, damage = 10400, attack_cooldown = 7, range = 31 }, { cost = 14000, damage = 13000, attack_cooldown = 8, range = 32, primary_attack = "jiraiya_evolved|live7" }, { cost = 23000, damage = 15600, attack_cooldown = 8, range = 33 } } }, ["josuke_2"] = { primary_attack_no_upgrades = "josuke_2|josuke_2:one", spawn_cap = 3, upgrade = { { cost = 1600, damage = 1250, attack_cooldown = 11, range = 22 }, { cost = 2500, damage = 2500, attack_cooldown = 11, range = 23 }, { cost = 4500, damage = 3500, attack_cooldown = 10.5, range = 25, primary_attack = "josuke_2|josuke_2:two" }, { cost = 6500, damage = 5500, attack_cooldown = 10.5, range = 26 }, { cost = 8000, damage = 7000, attack_cooldown = 10.5, range = 28 } } }, ["josuke_2_evolved"] = { primary_attack_no_upgrades = "josuke_2_evolved|live", spawn_cap = 3, upgrade = { { cost = 1600, damage = 1750, attack_cooldown = 11, range = 22 }, { cost = 2500, damage = 3500, attack_cooldown = 11, range = 23 }, { cost = 4500, damage = 4900, attack_cooldown = 10.5, range = 25, primary_attack = "josuke_2_evolved|live3" }, { cost = 6500, damage = 7700, attack_cooldown = 10.5, range = 26 }, { cost = 8000, damage = 9800, attack_cooldown = 10.5, range = 28 }, { cost = 11000, damage = 14000, attack_cooldown = 10, range = 32, primary_attack = "josuke_2_evolved|live6" }, { cost = 16000, damage = 19600, attack_cooldown = 10, range = 30 }, { cost = 25000, damage = 26600, attack_cooldown = 10, range = 33 } } }, ["judar"] = { primary_attack_no_upgrades = "judar|judar:one", spawn_cap = 3, upgrade = { { cost = 2400, damage = 2500, attack_cooldown = 7, range = 23 }, { cost = 4500, damage = 5000, attack_cooldown = 6.5, range = 24, primary_attack = "judar|judar:two" }, { cost = 6000, damage = 7500, attack_cooldown = 6, range = 25 }, { cost = 7500, damage = 11000, attack_cooldown = 7.5, range = 26, primary_attack = "judar|judar:three" }, { cost = 11000, damage = 15000, attack_cooldown = 7, range = 27 }, { cost = 15000, damage = 22000, attack_cooldown = 7, range = 28 } } }, ["judar_evolved"] = { primary_attack_no_upgrades = "judar_evolved|live", spawn_cap = 3, upgrade = { { cost = 2400, damage = 3750, attack_cooldown = 7, range = 23 }, { cost = 4500, damage = 7500, attack_cooldown = 6.5, range = 24, primary_attack = "judar_evolved|live2" }, { cost = 6000, damage = 11250, attack_cooldown = 6, range = 25 }, { cost = 7500, damage = 16500, attack_cooldown = 7.5, range = 26, primary_attack = "judar_evolved|live4" }, { cost = 11000, damage = 22500, attack_cooldown = 7, range = 27 }, { cost = 15000, damage = 33000, attack_cooldown = 7, range = 28 }, { cost = 18000, damage = 40500, attack_cooldown = 14, range = 33 }, { cost = 35000, damage = 49500, attack_cooldown = 13, range = 35 } } }, ["kenshin"] = { primary_attack_no_upgrades = "kenshin|kenshin:one", spawn_cap = 5, upgrade = { { cost = 1500, damage = 600, attack_cooldown = 4.5, range = 16 }, { cost = 2500, damage = 1000, attack_cooldown = 4.5, range = 17 }, { cost = 3000, damage = 1300, attack_cooldown = 4, range = 18, primary_attack = "kenshin|kenshin:two" }, { cost = 4500, damage = 1750, attack_cooldown = 4, range = 19 }, { cost = 5500, damage = 2400, attack_cooldown = 3.5, range = 20 }, { cost = 6000, damage = 4500, attack_cooldown = 6, range = 22, primary_attack = "kenshin|kenshin:three" }, { cost = 7500, damage = 5500, attack_cooldown = 6, range = 23 }, { cost = 8000, damage = 6500, attack_cooldown = 6, range = 24 } } }, ["kimimaro"] = { primary_attack_no_upgrades = "kimimaro|kimimaro:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1000, attack_cooldown = 5, range = 16 }, { cost = 2500, damage = 1500, attack_cooldown = 5, range = 17 }, { cost = 3000, damage = 2000, attack_cooldown = 6, range = 25, primary_attack = "kimimaro|kimimaro:two" }, { cost = 4500, damage = 2500, attack_cooldown = 6, range = 27 }, { cost = 6000, damage = 3500, attack_cooldown = 6, range = 29 }, { cost = 6500, damage = 4500, attack_cooldown = 7, range = 33, primary_attack = "kimimaro|kimimaro:three" }, { cost = 7500, damage = 5500, attack_cooldown = 7, range = 35 }, { cost = 9000, damage = 6500, attack_cooldown = 7, range = 30 } } }, ["kira"] = { primary_attack_no_upgrades = "kira|kira:coin_toss", spawn_cap = 3, upgrade = { { cost = 2100, damage = 1500, attack_cooldown = 8, range = 21 }, { cost = 3500, damage = 2500, attack_cooldown = 7.5, range = 22 }, { cost = 4500, damage = 4000, attack_cooldown = 7.5, range = 23 }, { cost = 6000, damage = 5000, attack_cooldown = 7, range = 25, primary_attack = "kira|kira:heart_attack" }, { cost = 7000, damage = 6500, attack_cooldown = 7, range = 27 }, { cost = 9000, damage = 8000, attack_cooldown = 7, range = 29 }, { cost = 11000, damage = 9000, attack_cooldown = 6.5, range = 30 } } }, ["kira_evolved"] = { primary_attack_no_upgrades = "kira_evolved|live", spawn_cap = 3, upgrade = { { cost = 2100, damage = 2100, attack_cooldown = 8, range = 21 }, { cost = 3500, damage = 3500, attack_cooldown = 7.5, range = 22 }, { cost = 4500, damage = 5600, attack_cooldown = 7.5, range = 23 }, { cost = 6000, damage = 7000, attack_cooldown = 7, range = 25 }, { cost = 7000, damage = 9100, attack_cooldown = 7, range = 27 }, { cost = 9000, damage = 11200, attack_cooldown = 7, range = 29 }, { cost = 11000, damage = 15400, attack_cooldown = 8, range = 30, primary_attack = "kira_evolved|live7" }, { cost = 15000, damage = 18900, attack_cooldown = 8, range = 31 }, { cost = 20000, damage = 23800, attack_cooldown = 8, range = 33 }, { cost = 25000, damage = 31080, attack_cooldown = 8, range = 35 } } }, ["kirito"] = { primary_attack_no_upgrades = "kirito|kirito:stab", spawn_cap = 4, upgrade = { { cost = 2250, damage = 1500, attack_cooldown = 7, range = 19 }, { cost = 3500, damage = 2400, attack_cooldown = 6.5, range = 22, primary_attack = "kirito|kirito:double" }, { cost = 4250, damage = 3500, attack_cooldown = 6.5, range = 23 }, { cost = 5000, damage = 4500, attack_cooldown = 6, range = 25, primary_attack = "kirito|kirito:square" }, { cost = 5500, damage = 5500, attack_cooldown = 6, range = 26 }, { cost = 6200, damage = 6000, attack_cooldown = 6, range = 27 }, { cost = 7500, damage = 6200, attack_cooldown = 6, range = 28 } } }, ["kirito_evolved"] = { primary_attack_no_upgrades = "kirito_evolved|kirito:stab", spawn_cap = 4, upgrade = { { cost = 2250, damage = 1950, attack_cooldown = 7, range = 19 }, { cost = 3500, damage = 3120, attack_cooldown = 6.5, range = 22, primary_attack = "kirito_evolved|kirito:double" }, { cost = 4250, damage = 4550, attack_cooldown = 6.5, range = 23 }, { cost = 5000, damage = 5850, attack_cooldown = 6, range = 25, primary_attack = "kirito_evolved|kirito:square" }, { cost = 5500, damage = 7150, attack_cooldown = 6, range = 26 }, { cost = 6200, damage = 7800, attack_cooldown = 6, range = 27, primary_attack = "kirito_evolved|kirito_evolved:slice" }, { cost = 7500, damage = 8060, attack_cooldown = 5, range = 28 }, { cost = 9000, damage = 9360, attack_cooldown = 5, range = 29 }, { cost = 12500, damage = 19500, attack_cooldown = 9, range = 30, primary_attack = "kirito_evolved|kirito_evolved:stream" }, { cost = 15000, damage = 24700, attack_cooldown = 9, range = 32 } } }, ["kisame_unit"] = { primary_attack_no_upgrades = "kisame_unit|kisame:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 900, attack_cooldown = 7, range = 22 }, { cost = 2500, damage = 1250, attack_cooldown = 7, range = 25 }, { cost = 3500, damage = 2000, attack_cooldown = 6, range = 27, primary_attack = "kisame_unit|kisame:two" }, { cost = 4500, damage = 2500, attack_cooldown = 6, range = 29 }, { cost = 7000, damage = 3500, attack_cooldown = 5.5, range = 30 }, { cost = 8000, damage = 4500, attack_cooldown = 5.5, range = 33 } } }, ["kisame_unit_evolved"] = { primary_attack_no_upgrades = "kisame_unit_evolved|kisame:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1260, attack_cooldown = 7, range = 22 }, { cost = 2500, damage = 1750, attack_cooldown = 7, range = 25 }, { cost = 3500, damage = 2800, attack_cooldown = 6, range = 27, primary_attack = "kisame_unit_evolved|kisame:two" }, { cost = 4500, damage = 3500, attack_cooldown = 6, range = 29 }, { cost = 7000, damage = 4900, attack_cooldown = 5.5, range = 30 }, { cost = 8000, damage = 6300, attack_cooldown = 5.5, range = 33, primary_attack = "kisame_unit_evolved|kisame:three" }, { cost = 9000, damage = 7700, attack_cooldown = 5, range = 34 }, { cost = 12000, damage = 9800, attack_cooldown = 4.5, range = 35 } } }, ["klein"] = { primary_attack_no_upgrades = "klein|klein:slash", spawn_cap = 5, upgrade = { { cost = 2000, damage = 1200, attack_cooldown = 7, range = 18 }, { cost = 2500, damage = 1800, attack_cooldown = 6.5, range = 19 }, { cost = 3000, damage = 2200, attack_cooldown = 6, range = 21, primary_attack = "klein|klein:stab" }, { cost = 4000, damage = 2500, attack_cooldown = 6, range = 23 }, { cost = 5000, damage = 3000, attack_cooldown = 5.5, range = 25 }, { cost = 6200, damage = 3500, attack_cooldown = 5.5, range = 26 } } }, ["klein_evolved"] = { primary_attack_no_upgrades = "klein_evolved|klein:slash", spawn_cap = 5, upgrade = { { cost = 2000, damage = 1800, attack_cooldown = 7, range = 18 }, { cost = 2500, damage = 2700, attack_cooldown = 6.5, range = 19 }, { cost = 3000, damage = 3300, attack_cooldown = 6, range = 21, primary_attack = "klein_evolved|klein:stab" }, { cost = 4000, damage = 3750, attack_cooldown = 6, range = 23 }, { cost = 5000, damage = 4500, attack_cooldown = 5.5, range = 25 }, { cost = 6500, damage = 5850, attack_cooldown = 5.5, range = 27, primary_attack = "klein_evolved|klein:final" }, { cost = 7000, damage = 6000, attack_cooldown = 5, range = 30 }, { cost = 8000, damage = 7500, attack_cooldown = 5, range = 31 } } }, ["koichi"] = { primary_attack_no_upgrades = "koichi|koichi:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1500, attack_cooldown = 8, range = 17 }, { cost = 2000, damage = 1750, attack_cooldown = 8, range = 20, primary_attack = "koichi|koichi:two" }, { cost = 3000, damage = 2200, attack_cooldown = 7.5, range = 22 }, { cost = 4500, damage = 3300, attack_cooldown = 7.5, range = 24 } } }, ["koichi_evolved"] = { primary_attack_no_upgrades = "koichi_evolved|live", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1875, attack_cooldown = 8, range = 17 }, { cost = 2000, damage = 2187.5, attack_cooldown = 8, range = 20 }, { cost = 3000, damage = 2750, attack_cooldown = 7.5, range = 22 }, { cost = 4500, damage = 4125, attack_cooldown = 7.5, range = 24 }, { cost = 6000, damage = 4625, attack_cooldown = 7.5, range = 27 }, { cost = 7500, damage = 5625, attack_cooldown = 7, range = 28 }, { cost = 10000, damage = 7500, attack_cooldown = 7, range = 29 }, { cost = 20000, damage = 11250, attack_cooldown = 7, range = 30 } } }, ["konan_unit"] = { primary_attack_no_upgrades = "konan_unit|konan_unit:one", spawn_cap = 4, upgrade = { { cost = 1250, damage = 1200, attack_cooldown = 7, range = 25 }, { cost = 2500, damage = 2300, attack_cooldown = 7.5, range = 26, primary_attack = "konan_unit|konan_unit:two" }, { cost = 3000, damage = 3500, attack_cooldown = 7.5, range = 27 }, { cost = 4000, damage = 4500, attack_cooldown = 7, range = 30 }, { cost = 4500, damage = 5000, attack_cooldown = 7, range = 32, primary_attack = "konan_unit|konan_unit:three" }, { cost = 4750, damage = 6000, attack_cooldown = 6.5, range = 33 } } }, ["konan_unit_evolved"] = { primary_attack_no_upgrades = "konan_unit_evolved|live", spawn_cap = 4, upgrade = { { cost = 1250, damage = 1620, attack_cooldown = 7, range = 25 }, { cost = 2500, damage = 3105, attack_cooldown = 7.5, range = 26 }, { cost = 3000, damage = 4725, attack_cooldown = 7.5, range = 27 }, { cost = 4000, damage = 6075, attack_cooldown = 7, range = 30 }, { cost = 4500, damage = 6750, attack_cooldown = 7, range = 32, primary_attack = "konan_unit_evolved|live5" }, { cost = 4750, damage = 8100, attack_cooldown = 6.5, range = 33 }, { cost = 5000, damage = 8775, attack_cooldown = 6.5, range = 35 }, { cost = 7000, damage = 10800, attack_cooldown = 6.5, range = 37, primary_attack = "konan_unit_evolved|live8" }, { cost = 8000, damage = 13500, attack_cooldown = 6, range = 40 } } }, ["kyoko"] = { primary_attack_no_upgrades = "kyoko|kyoko:stab", spawn_cap = 4, upgrade = { { cost = 2500, damage = 700, attack_cooldown = 6.5, range = 23 }, { cost = 3500, damage = 1200, attack_cooldown = 6, range = 24 }, { cost = 4000, damage = 2000, attack_cooldown = 6, range = 25 }, { cost = 5500, damage = 3000, attack_cooldown = 7.5, range = 30, primary_attack = "kyoko|kyoko:combo" }, { cost = 6000, damage = 4000, attack_cooldown = 7.5, range = 32 }, { cost = 7500, damage = 5500, attack_cooldown = 7, range = 33 }, { cost = 8000, damage = 7000, attack_cooldown = 7, range = 34 } } }, ["kyoko_evolved"] = { primary_attack_no_upgrades = "kyoko_evolved|kyoko:stab", spawn_cap = 4, upgrade = { { cost = 2500, damage = 910, attack_cooldown = 6.5, range = 23 }, { cost = 3500, damage = 1560, attack_cooldown = 6, range = 24 }, { cost = 4000, damage = 2600, attack_cooldown = 6, range = 25 }, { cost = 5500, damage = 3900, attack_cooldown = 7.5, range = 30, primary_attack = "kyoko_evolved|kyoko:combo" }, { cost = 6000, damage = 5200, attack_cooldown = 7.5, range = 32 }, { cost = 7500, damage = 7150, attack_cooldown = 7, range = 33 }, { cost = 8000, damage = 9100, attack_cooldown = 7, range = 34 }, { cost = 9000, damage = 13000, attack_cooldown = 9, range = 35, primary_attack = "kyoko_evolved|kyoko:final" }, { cost = 12500, damage = 15600, attack_cooldown = 8, range = 37 }, { cost = 20000, damage = 18200, attack_cooldown = 8, range = 40 } } }, ["lancer"] = { primary_attack_no_upgrades = "lancer|lancer:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1750, attack_cooldown = 8, range = 18 }, { cost = 3000, damage = 3000, attack_cooldown = 8, range = 20, primary_attack = "lancer|lancer:two" }, { cost = 4000, damage = 4000, attack_cooldown = 7.5, range = 23 }, { cost = 5000, damage = 5000, attack_cooldown = 7.5, range = 24, primary_attack = "lancer|lancer:three" }, { cost = 6500, damage = 7000, attack_cooldown = 8, range = 25 }, { cost = 7000, damage = 9000, attack_cooldown = 8, range = 26 } } }, ["lancer_evolved"] = { primary_attack_no_upgrades = "lancer_evolved|lancer:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 2275, attack_cooldown = 8, range = 18 }, { cost = 3000, damage = 3900, attack_cooldown = 8, range = 20, primary_attack = "lancer_evolved|lancer:two" }, { cost = 4000, damage = 5200, attack_cooldown = 7.5, range = 23 }, { cost = 5000, damage = 6500, attack_cooldown = 7.5, range = 24, primary_attack = "lancer_evolved|lancer:three" }, { cost = 6500, damage = 9100, attack_cooldown = 8, range = 25 }, { cost = 7000, damage = 11700, attack_cooldown = 8, range = 26 }, { cost = 9500, damage = 18200, attack_cooldown = 9, range = 28, primary_attack = "lancer_evolved|lancer:four" }, { cost = 10000, damage = 22750, attack_cooldown = 8, range = 30 } } }, ["law_2"] = { primary_attack_no_upgrades = "law_2|law_2:shot", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1600, attack_cooldown = 8, range = 19 }, { cost = 3400, damage = 2300, attack_cooldown = 8, range = 22, primary_attack = "law_2|law_2:radio" }, { cost = 4000, damage = 3200, attack_cooldown = 7.5, range = 23 }, { cost = 5200, damage = 4200, attack_cooldown = 7.5, range = 24 }, { cost = 6250, damage = 5200, attack_cooldown = 7.5, range = 25, primary_attack = "law_2|law_2:shock" }, { cost = 7500, damage = 6000, attack_cooldown = 7.5, range = 27 }, { cost = 8000, damage = 7000, attack_cooldown = 7, range = 28 }, { cost = 9000, damage = 7500, attack_cooldown = 7, range = 29 } } }, ["law_2_evolved"] = { primary_attack_no_upgrades = "law_2_evolved|live", spawn_cap = 3, upgrade = { { cost = 2200, damage = 2240, attack_cooldown = 8, range = 19 }, { cost = 3400, damage = 3220, attack_cooldown = 8, range = 22 }, { cost = 4000, damage = 4480, attack_cooldown = 7.5, range = 23 }, { cost = 5200, damage = 5880, attack_cooldown = 7.5, range = 24 }, { cost = 6250, damage = 7280, attack_cooldown = 7.5, range = 25 }, { cost = 7500, damage = 8400, attack_cooldown = 7.5, range = 27 }, { cost = 8000, damage = 9800, attack_cooldown = 7, range = 28 }, { cost = 9500, damage = 10500, attack_cooldown = 7, range = 31, primary_attack = "law_2_evolved|live8" }, { cost = 12000, damage = 11900, attack_cooldown = 6.5, range = 32 }, { cost = 13500, damage = 14000, attack_cooldown = 6.5, range = 33 } } }, ["leafa"] = { primary_attack_no_upgrades = "leafa|leafa:one", spawn_cap = 6, upgrade = { { cost = 1500, damage = 600, attack_cooldown = 10, range = 25 }, { cost = 2000, damage = 900, attack_cooldown = 10, range = 25 }, { cost = 3500, damage = 1200, attack_cooldown = 8, range = 25, primary_attack = "leafa|leafa:two" }, { cost = 4500, damage = 1500, attack_cooldown = 8, range = 25 }, { cost = 7500, damage = 2000, attack_cooldown = 8, range = 25 }, { cost = 10000, damage = 3000, attack_cooldown = 8, range = 25 } } }, ["leafa_evolved"] = { primary_attack_no_upgrades = "leafa_evolved|live", spawn_cap = 6, upgrade = { { cost = 1500, damage = 900, attack_cooldown = 10, range = 25 }, { cost = 2000, damage = 1350, attack_cooldown = 10, range = 25 }, { cost = 3500, damage = 1800, attack_cooldown = 8, range = 25, primary_attack = "leafa_evolved|live3" }, { cost = 4500, damage = 2250, attack_cooldown = 8, range = 25 }, { cost = 7500, damage = 3000, attack_cooldown = 8, range = 25 }, { cost = 10000, damage = 4500, attack_cooldown = 8, range = 25 } } }, ["luffy_g4"] = { primary_attack_no_upgrades = "luffy_g4|luffy_g4:punch", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1540, attack_cooldown = 7, range = 19 }, { cost = 3750, damage = 3300, attack_cooldown = 8, range = 20, primary_attack = "luffy_g4|luffy_g4:organ" }, { cost = 4500, damage = 3850, attack_cooldown = 8, range = 22 }, { cost = 5500, damage = 5225, attack_cooldown = 8, range = 24 }, { cost = 6500, damage = 7150, attack_cooldown = 8, range = 25, primary_attack = "luffy_g4|luffy_g4:gatling" }, { cost = 7500, damage = 7700, attack_cooldown = 7.5, range = 27 }, { cost = 8250, damage = 8800, attack_cooldown = 7.5, range = 29 }, { cost = 9000, damage = 9900, attack_cooldown = 7.5, range = 30 } } }, ["luffy_g4_evolved"] = { primary_attack_no_upgrades = "luffy_g4_evolved|luffy_g4:punch", spawn_cap = 4, upgrade = { { cost = 2500, damage = 2002, attack_cooldown = 7, range = 19 }, { cost = 3750, damage = 4290, attack_cooldown = 8, range = 20, primary_attack = "luffy_g4_evolved|luffy_g4:organ" }, { cost = 4500, damage = 5005, attack_cooldown = 8, range = 22 }, { cost = 5500, damage = 6792.5, attack_cooldown = 8, range = 24 }, { cost = 6500, damage = 9295, attack_cooldown = 8, range = 25, primary_attack = "luffy_g4_evolved|luffy_g4:gatling" }, { cost = 7500, damage = 10010, attack_cooldown = 7.5, range = 27 }, { cost = 8250, damage = 11440, attack_cooldown = 7.5, range = 29 }, { cost = 9500, damage = 13520, attack_cooldown = 7.5, range = 31, primary_attack = "luffy_g4_evolved|luffy_g4:final" }, { cost = 10000, damage = 15600, attack_cooldown = 7.5, range = 33 }, { cost = 12000, damage = 17550, attack_cooldown = 7, range = 35 } } }, ["madoka"] = { primary_attack_no_upgrades = "madoka|madoka:one", spawn_cap = 4, upgrade = { { cost = 2200, damage = 950, attack_cooldown = 8, range = 30 }, { cost = 3500, damage = 1650, attack_cooldown = 7.5, range = 32 }, { cost = 4500, damage = 2500, attack_cooldown = 7.5, range = 34 }, { cost = 5500, damage = 3200, attack_cooldown = 7.5, range = 35, primary_attack = "madoka|madoka:two" }, { cost = 6500, damage = 3900, attack_cooldown = 7, range = 36 }, { cost = 7750, damage = 4800, attack_cooldown = 7, range = 38 }, { cost = 8500, damage = 5200, attack_cooldown = 7, range = 39 } } }, ["madoka_evolved"] = { primary_attack_no_upgrades = "madoka_evolved|madoka:one", spawn_cap = 4, upgrade = { { cost = 2200, damage = 1235, attack_cooldown = 8, range = 30 }, { cost = 3500, damage = 2145, attack_cooldown = 7.5, range = 32 }, { cost = 4500, damage = 3250, attack_cooldown = 7.5, range = 34 }, { cost = 5500, damage = 4160, attack_cooldown = 7.5, range = 35, primary_attack = "madoka_evolved|madoka:two" }, { cost = 6500, damage = 5070, attack_cooldown = 7, range = 36 }, { cost = 7750, damage = 6240, attack_cooldown = 7, range = 38 }, { cost = 8500, damage = 7540, attack_cooldown = 7, range = 40, primary_attack = "madoka_evolved|madoka:three" }, { cost = 10000, damage = 8320, attack_cooldown = 6.5, range = 43 }, { cost = 13000, damage = 9620, attack_cooldown = 6.5, range = 45 } } }, ["mami"] = { primary_attack_no_upgrades = "mami|mami:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1750, attack_cooldown = 6, range = 16 }, { cost = 4000, damage = 3300, attack_cooldown = 6, range = 18 }, { cost = 5500, damage = 4500, attack_cooldown = 7, range = 22, primary_attack = "mami|mami:two" }, { cost = 6000, damage = 7000, attack_cooldown = 7, range = 24 }, { cost = 7500, damage = 8000, attack_cooldown = 7, range = 25 }, { cost = 9000, damage = 9000, attack_cooldown = 7, range = 27 } } }, ["mami_evolved"] = { primary_attack_no_upgrades = "mami_evolved|mami:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2450, attack_cooldown = 6, range = 16 }, { cost = 4000, damage = 4620, attack_cooldown = 6, range = 18 }, { cost = 5500, damage = 6300, attack_cooldown = 7, range = 22, primary_attack = "mami_evolved|mami:two" }, { cost = 6000, damage = 9800, attack_cooldown = 7, range = 24 }, { cost = 7500, damage = 11200, attack_cooldown = 7, range = 25 }, { cost = 9000, damage = 12600, attack_cooldown = 7, range = 27 }, { cost = 12500, damage = 15400, attack_cooldown = 9, range = 30, primary_attack = "mami_evolved|mami:three" }, { cost = 17500, damage = 18200, attack_cooldown = 9, range = 33 }, { cost = 30000, damage = 21000, attack_cooldown = 9, range = 35 } } }, ["mash"] = { primary_attack_no_upgrades = "mash|mash:punches", spawn_cap = 4, upgrade = { { cost = 2200, damage = 650, attack_cooldown = 6.5, range = 25 }, { cost = 2500, damage = 1000, attack_cooldown = 6, range = 25.5 }, { cost = 3500, damage = 1400, attack_cooldown = 6, range = 26, primary_attack = "mash|mash:throw" }, { cost = 5000, damage = 2000, attack_cooldown = 6, range = 26.5 }, { cost = 6500, damage = 2900, attack_cooldown = 6, range = 27 }, { cost = 8000, damage = 3900, attack_cooldown = 6, range = 28 } } }, ["mash_evolved"] = { primary_attack_no_upgrades = "mash_evolved|mash:punches", spawn_cap = 4, upgrade = { { cost = 2200, damage = 845, attack_cooldown = 6.5, range = 25 }, { cost = 2500, damage = 1300, attack_cooldown = 6, range = 25.5 }, { cost = 3500, damage = 1820, attack_cooldown = 6, range = 26, primary_attack = "mash_evolved|mash:throw" }, { cost = 5000, damage = 2600, attack_cooldown = 6, range = 26.5 }, { cost = 6500, damage = 3770, attack_cooldown = 6, range = 27 }, { cost = 8000, damage = 5070, attack_cooldown = 6, range = 28 }, { cost = 9500, damage = 6500, attack_cooldown = 7, range = 29, primary_attack = "mash_evolved|mash:counters" }, { cost = 10000, damage = 7800, attack_cooldown = 7, range = 30 }, { cost = 12000, damage = 9100, attack_cooldown = 6.5, range = 31 } } }, ["mechamaru"] = { primary_attack_no_upgrades = "mechamaru|mechamaru:one", spawn_cap = 3, upgrade = { { cost = 1400, damage = 1500, attack_cooldown = 6.5, range = 21 }, { cost = 2000, damage = 2200, attack_cooldown = 6.5, range = 23 }, { cost = 3000, damage = 2700, attack_cooldown = 6, range = 25, primary_attack = "mechamaru|mechamaru:two" }, { cost = 6000, damage = 4200, attack_cooldown = 6, range = 28 }, { cost = 9000, damage = 7000, attack_cooldown = 6, range = 30 }, { cost = 15000, damage = 14000, attack_cooldown = 10, range = 36, primary_attack = "mechamaru|mechamaru:three" }, { cost = 23000, damage = 22000, attack_cooldown = 9.5, range = 38 }, { cost = 26000, damage = 29000, attack_cooldown = 9, range = 40 } } }, ["medea"] = { primary_attack_no_upgrades = "medea|medea:one", spawn_cap = 5, upgrade = { { cost = 1250, damage = 800, attack_cooldown = 7, range = 26 }, { cost = 2500, damage = 1500, attack_cooldown = 7, range = 27 }, { cost = 3000, damage = 2000, attack_cooldown = 8.5, range = 30, primary_attack = "medea|medea:two" }, { cost = 4000, damage = 3000, attack_cooldown = 8.5, range = 33 }, { cost = 4500, damage = 4000, attack_cooldown = 8, range = 34 }, { cost = 5500, damage = 5000, attack_cooldown = 8, range = 35 } } }, ["megumi2"] = { primary_attack_no_upgrades = "megumi2|megumi_2:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 950, attack_cooldown = 6.5, range = 23 }, { cost = 3000, damage = 1600, attack_cooldown = 6.5, range = 24 }, { cost = 5000, damage = 2170, attack_cooldown = 6, range = 25, primary_attack = "megumi2|megumi_2:two" }, { cost = 8000, damage = 3500, attack_cooldown = 6, range = 26 }, { cost = 11000, damage = 5200, attack_cooldown = 6, range = 27 }, { cost = 15000, damage = 6500, attack_cooldown = 6, range = 28 } } }, ["megumi2_evolved"] = { primary_attack_no_upgrades = "megumi2_evolved|megumi_2:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1330, attack_cooldown = 6.5, range = 23 }, { cost = 3000, damage = 2240, attack_cooldown = 6.5, range = 24 }, { cost = 5000, damage = 3038, attack_cooldown = 6, range = 25, primary_attack = "megumi2_evolved|megumi_2:two" }, { cost = 8000, damage = 4900, attack_cooldown = 6, range = 26 }, { cost = 11000, damage = 7280, attack_cooldown = 6, range = 27 }, { cost = 21000, damage = 10920, attack_cooldown = 6, range = 28, primary_attack = "megumi2_evolved|megumi_2:three" }, { cost = 25000, damage = 15400, attack_cooldown = 6, range = 29 }, { cost = 30000, damage = 19600, attack_cooldown = 5.5, range = 29 } } }, ["minato"] = { primary_attack_no_upgrades = "minato|minato:first", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1250, attack_cooldown = 7, range = 23 }, { cost = 3000, damage = 2000, attack_cooldown = 7, range = 25 }, { cost = 4500, damage = 2500, attack_cooldown = 6.5, range = 26, primary_attack = "minato|minato:second" }, { cost = 5000, damage = 3500, attack_cooldown = 6.5, range = 28 }, { cost = 7500, damage = 4500, attack_cooldown = 6, range = 29 }, { cost = 9000, damage = 7500, attack_cooldown = 9, range = 30, primary_attack = "minato|minato:third" }, { cost = 12000, damage = 9000, attack_cooldown = 8.5, range = 34 }, { cost = 15000, damage = 12500, attack_cooldown = 8, range = 35 } } }, ["mirko"] = { primary_attack_no_upgrades = "mirko|mirko:kick", spawn_cap = 4, upgrade = { { cost = 2100, damage = 750, attack_cooldown = 6.5, range = 22 }, { cost = 2750, damage = 1200, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 1600, attack_cooldown = 6, range = 25, primary_attack = "mirko|mirko:spin" }, { cost = 5000, damage = 2300, attack_cooldown = 6, range = 27 }, { cost = 6500, damage = 2900, attack_cooldown = 5.5, range = 28 }, { cost = 7000, damage = 3500, attack_cooldown = 5.5, range = 30 }, { cost = 7500, damage = 3800, attack_cooldown = 5.5, range = 31, primary_attack = "mirko|mirko:final" } } }, ["mirko_evolved"] = { primary_attack_no_upgrades = "mirko_evolved|mirko:kick", spawn_cap = 4, upgrade = { { cost = 2100, damage = 1012.5, attack_cooldown = 6.5, range = 22 }, { cost = 2750, damage = 1366.88, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 2160, attack_cooldown = 6, range = 25, primary_attack = "mirko_evolved|mirko:spin" }, { cost = 5000, damage = 3105, attack_cooldown = 6, range = 27 }, { cost = 6500, damage = 3915, attack_cooldown = 5.5, range = 28 }, { cost = 7000, damage = 4725, attack_cooldown = 5.5, range = 30 }, { cost = 7500, damage = 5130, attack_cooldown = 5, range = 31, primary_attack = "mirko_evolved|mirko:final" }, { cost = 9000, damage = 6210, attack_cooldown = 5, range = 32 }, { cost = 10000, damage = 6615, attack_cooldown = 4.5, range = 33 } } }, ["monet"] = { primary_attack_no_upgrades = "monet|monet:one", spawn_cap = 4, upgrade = { { cost = 1500, damage = 1750, attack_cooldown = 8.5, range = 18 }, { cost = 1750, damage = 2500, attack_cooldown = 8, range = 20 }, { cost = 2500, damage = 3000, attack_cooldown = 8, range = 22, primary_attack = "monet|monet:two" }, { cost = 4000, damage = 4000, attack_cooldown = 7.5, range = 25 }, { cost = 5000, damage = 5000, attack_cooldown = 7.5, range = 27 } } }, ["monet_evolved"] = { primary_attack_no_upgrades = "monet_evolved|live", spawn_cap = 4, upgrade = { { cost = 1500, damage = 2275, attack_cooldown = 8.5, range = 18 }, { cost = 1750, damage = 3250, attack_cooldown = 8, range = 20 }, { cost = 2500, damage = 3900, attack_cooldown = 8, range = 22 }, { cost = 4000, damage = 5200, attack_cooldown = 7.5, range = 25 }, { cost = 5000, damage = 6500, attack_cooldown = 7.5, range = 27 }, { cost = 7500, damage = 7800, attack_cooldown = 7, range = 29, primary_attack = "monet_evolved|live6" }, { cost = 9000, damage = 9750, attack_cooldown = 7, range = 30 }, { cost = 17500, damage = 15600, attack_cooldown = 7, range = 33 } } }, ["nagumo"] = { primary_attack_no_upgrades = "nagumo|nagumo:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1500, attack_cooldown = 6, range = 27 }, { cost = 2500, damage = 2400, attack_cooldown = 6, range = 28 }, { cost = 3500, damage = 3900, attack_cooldown = 7.5, range = 30, primary_attack = "nagumo|nagumo:two" }, { cost = 4000, damage = 4800, attack_cooldown = 7.5, range = 32 }, { cost = 5000, damage = 6000, attack_cooldown = 7, range = 33 }, { cost = 8000, damage = 8000, attack_cooldown = 7, range = 35 } } }, ["nagumo_evolved"] = { primary_attack_no_upgrades = "nagumo_evolved|live", spawn_cap = 3, upgrade = { { cost = 2000, damage = 2250, attack_cooldown = 6, range = 27 }, { cost = 2500, damage = 3600, attack_cooldown = 6, range = 28 }, { cost = 3500, damage = 5850, attack_cooldown = 7.5, range = 30, primary_attack = "nagumo_evolved|live3" }, { cost = 4000, damage = 7200, attack_cooldown = 7.5, range = 32 }, { cost = 5000, damage = 9000, attack_cooldown = 7, range = 33 }, { cost = 8000, damage = 12000, attack_cooldown = 7, range = 35 }, { cost = 12000, damage = 40500, attack_cooldown = 14, range = 40 }, { cost = 18000, damage = 48000, attack_cooldown = 12, range = 42 }, { cost = 22000, damage = 58500, attack_cooldown = 11, range = 45 } } }, ["narberal"] = { primary_attack_no_upgrades = "narberal|narberal:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 700, attack_cooldown = 5.5, range = 27 }, { cost = 3500, damage = 1000, attack_cooldown = 5, range = 30 }, { cost = 4500, damage = 2000, attack_cooldown = 5, range = 33, primary_attack = "narberal|narberal:two" }, { cost = 6000, damage = 2500, attack_cooldown = 4.5, range = 34 }, { cost = 7500, damage = 3000, attack_cooldown = 4, range = 35 } } }, ["narberal_evolved"] = { primary_attack_no_upgrades = "narberal_evolved|live", spawn_cap = 3, upgrade = { { cost = 2500, damage = 910, attack_cooldown = 5.5, range = 27 }, { cost = 3500, damage = 1300, attack_cooldown = 5, range = 30 }, { cost = 4500, damage = 2600, attack_cooldown = 5, range = 33 }, { cost = 6000, damage = 3250, attack_cooldown = 4.5, range = 34 }, { cost = 7500, damage = 3900, attack_cooldown = 4, range = 35 }, { cost = 9000, damage = 7800, attack_cooldown = 7, range = 37 }, { cost = 12500, damage = 10400, attack_cooldown = 6.5, range = 38 }, { cost = 25000, damage = 15600, attack_cooldown = 6.5, range = 39 } } }, ["naruto_sage"] = { primary_attack_no_upgrades = "naruto_sage|naruto_sage:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1200, attack_cooldown = 5, range = 19 }, { cost = 3500, damage = 2000, attack_cooldown = 5, range = 20 }, { cost = 6000, damage = 5000, attack_cooldown = 8, range = 23, primary_attack = "naruto_sage|naruto_sage:two" }, { cost = 9000, damage = 6500, attack_cooldown = 7.5, range = 24 }, { cost = 12500, damage = 9000, attack_cooldown = 7, range = 25 } } }, ["nejire"] = { primary_attack_no_upgrades = "nejire|nejire:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 800, attack_cooldown = 6, range = 22 }, { cost = 3500, damage = 1600, attack_cooldown = 6.5, range = 23, primary_attack = "nejire|nejire:two" }, { cost = 4000, damage = 2000, attack_cooldown = 6.5, range = 25 }, { cost = 5500, damage = 2750, attack_cooldown = 6.5, range = 27 }, { cost = 6000, damage = 3500, attack_cooldown = 6, range = 28, primary_attack = "nejire|nejire:three" }, { cost = 7500, damage = 4500, attack_cooldown = 6, range = 29 }, { cost = 9000, damage = 6500, attack_cooldown = 7, range = 30 }, { cost = 12500, damage = 8500, attack_cooldown = 7, range = 30 } } }, ["nokotan"] = { primary_attack_no_upgrades = "nokotan|nokotan:one", spawn_cap = 5, upgrade = { { cost = 1200, damage = 1200, attack_cooldown = 7, range = 19 }, { cost = 1800, damage = 1900, attack_cooldown = 6.5, range = 20 }, { cost = 2500, damage = 2000, attack_cooldown = 6.5, range = 21, primary_attack = "nokotan|nokotan:two" }, { cost = 4000, damage = 2500, attack_cooldown = 6.5, range = 22 }, { cost = 5000, damage = 3200, attack_cooldown = 6, range = 23 }, { cost = 7000, damage = 5000, attack_cooldown = 5.5, range = 30, primary_attack = "nokotan|nokotan:three" }, { cost = 10000, damage = 6000, attack_cooldown = 5.5, range = 32 } } }, ["osaka"] = { primary_attack_no_upgrades = "osaka|kasuga:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 2000, attack_cooldown = 7, range = 24 }, { cost = 3000, damage = 3000, attack_cooldown = 7, range = 25 }, { cost = 3500, damage = 3300, attack_cooldown = 9, range = 30, primary_attack = "osaka|kasuga:two" }, { cost = 5000, damage = 4000, attack_cooldown = 8, range = 33 }, { cost = 7700, damage = 5000, attack_cooldown = 7, range = 35 } } }, ["osaka_evolved"] = { primary_attack_no_upgrades = "osaka_evolved|kasuga:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 3000, attack_cooldown = 7, range = 24 }, { cost = 3000, damage = 4500, attack_cooldown = 7, range = 25 }, { cost = 3500, damage = 4950, attack_cooldown = 9, range = 30, primary_attack = "osaka_evolved|kasuga:two" }, { cost = 5000, damage = 6000, attack_cooldown = 8, range = 33 }, { cost = 7700, damage = 7500, attack_cooldown = 7, range = 35 }, { cost = 9000, damage = 22500, attack_cooldown = 15, range = 40, primary_attack = "osaka_evolved|kasuga:three" }, { cost = 12500, damage = 30000, attack_cooldown = 14.5, range = 43 }, { cost = 20000, damage = 37500, attack_cooldown = 14, range = 45 } } }, ["osaragi"] = { primary_attack_no_upgrades = "osaragi|osaragi:one", spawn_cap = 4, upgrade = { { cost = 1800, damage = 1584, attack_cooldown = 6, range = 25 }, { cost = 2500, damage = 2376, attack_cooldown = 6, range = 27 }, { cost = 4000, damage = 3696, attack_cooldown = 8, range = 30, primary_attack = "osaragi|osaragi:two" }, { cost = 5000, damage = 4224, attack_cooldown = 7, range = 32 }, { cost = 6500, damage = 5412, attack_cooldown = 7, range = 35 }, { cost = 8000, damage = 6506, attack_cooldown = 7, range = 37 } } }, ["osaragi_evolved"] = { primary_attack_no_upgrades = "osaragi_evolved|osaragi:one", spawn_cap = 4, upgrade = { { cost = 1800, damage = 2217.6, attack_cooldown = 6, range = 25 }, { cost = 2500, damage = 3326.4, attack_cooldown = 6, range = 27 }, { cost = 4000, damage = 5174.4, attack_cooldown = 8, range = 30, primary_attack = "osaragi_evolved|osaragi:two" }, { cost = 5000, damage = 5913.6, attack_cooldown = 7, range = 32 }, { cost = 6500, damage = 7576.8, attack_cooldown = 7, range = 35 }, { cost = 8000, damage = 20328, attack_cooldown = 16, range = 55, primary_attack = "osaragi_evolved|osaragi:three" }, { cost = 15000, damage = 25872, attack_cooldown = 15, range = 60 }, { cost = 24000, damage = 32340, attack_cooldown = 14, range = 70 } } }, ["pain_2"] = { primary_attack_no_upgrades = "pain_2|pain_2:one", spawn_cap = 3, upgrade = { { cost = 1750, damage = 1000, attack_cooldown = 7, range = 21 }, { cost = 2300, damage = 1500, attack_cooldown = 7, range = 22 }, { cost = 3300, damage = 2000, attack_cooldown = 6, range = 23, primary_attack = "pain_2|pain_2:two" }, { cost = 3500, damage = 3500, attack_cooldown = 6, range = 25 }, { cost = 6000, damage = 4500, attack_cooldown = 6, range = 27 } } }, ["pain_2_evolved"] = { primary_attack_no_upgrades = "pain_2_evolved|live", spawn_cap = 3, upgrade = { { cost = 1750, damage = 1350, attack_cooldown = 7, range = 21 }, { cost = 2300, damage = 2025, attack_cooldown = 7, range = 22 }, { cost = 3300, damage = 2700, attack_cooldown = 6, range = 23, primary_attack = "pain_2_evolved|live3" }, { cost = 3500, damage = 4725, attack_cooldown = 6, range = 25 }, { cost = 6000, damage = 6075, attack_cooldown = 6, range = 27 }, { cost = 9000, damage = 16200, attack_cooldown = 15, range = 29 }, { cost = 12500, damage = 20250, attack_cooldown = 14, range = 30 }, { cost = 20000, damage = 27000, attack_cooldown = 13, range = 31, primary_attack = "pain_2_evolved|live8" }, { cost = 30000, damage = 36450, attack_cooldown = 13, range = 33 } } }, ["poseidon"] = { primary_attack_no_upgrades = "poseidon|poseidon:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2000, attack_cooldown = 9, range = 16 }, { cost = 3500, damage = 3000, attack_cooldown = 8, range = 17 }, { cost = 5000, damage = 5000, attack_cooldown = 8, range = 18 }, { cost = 8000, damage = 6500, attack_cooldown = 10, range = 19, primary_attack = "poseidon|poseidon:two" }, { cost = 9000, damage = 7500, attack_cooldown = 9.5, range = 20 }, { cost = 12500, damage = 8500, attack_cooldown = 9, range = 23 } } }, ["poseidon_evolved"] = { primary_attack_no_upgrades = "poseidon_evolved|poseidon:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 3000, attack_cooldown = 9, range = 16 }, { cost = 3500, damage = 4500, attack_cooldown = 8, range = 17 }, { cost = 5000, damage = 7500, attack_cooldown = 8, range = 18 }, { cost = 8000, damage = 9750, attack_cooldown = 10, range = 19, primary_attack = "poseidon_evolved|poseidon:two" }, { cost = 9000, damage = 11250, attack_cooldown = 9.5, range = 20 }, { cost = 12500, damage = 12750, attack_cooldown = 9, range = 23 }, { cost = 20000, damage = 21000, attack_cooldown = 14, range = 25, primary_attack = "poseidon_evolved|poseidon:three" }, { cost = 25000, damage = 24750, attack_cooldown = 12, range = 26 }, { cost = 30000, damage = 27000, attack_cooldown = 10, range = 27 } } }, ["ragna"] = { primary_attack_no_upgrades = "ragna|ragna:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1200, attack_cooldown = 6, range = 22 }, { cost = 3000, damage = 1750, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 2000, attack_cooldown = 6, range = 25, primary_attack = "ragna|ragna:two" }, { cost = 5500, damage = 3000, attack_cooldown = 5.5, range = 26 }, { cost = 7700, damage = 4000, attack_cooldown = 5.5, range = 28 } } }, ["ragna_evolved"] = { primary_attack_no_upgrades = "ragna_evolved|ragna:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 1800, attack_cooldown = 6, range = 22 }, { cost = 3000, damage = 2625, attack_cooldown = 6, range = 23 }, { cost = 3500, damage = 3000, attack_cooldown = 6, range = 25, primary_attack = "ragna_evolved|ragna:two" }, { cost = 5500, damage = 4500, attack_cooldown = 5.5, range = 26 }, { cost = 7700, damage = 6000, attack_cooldown = 5.5, range = 28 }, { cost = 9000, damage = 13500, attack_cooldown = 8.5, range = 30, primary_attack = "ragna_evolved|ragna:three" }, { cost = 12500, damage = 18750, attack_cooldown = 8.5, range = 30 }, { cost = 20000, damage = 22500, attack_cooldown = 8, range = 30 } } }, ["rayleigh"] = { primary_attack_no_upgrades = "rayleigh|rayleigh:attack_1", spawn_cap = 4, upgrade = { { cost = 2000, damage = 924, attack_cooldown = 7.5, range = 20 }, { cost = 2500, damage = 1440, attack_cooldown = 7, range = 22, primary_attack = "rayleigh|rayleigh:attack_2" }, { cost = 3500, damage = 1980, attack_cooldown = 7, range = 24 }, { cost = 5000, damage = 2400, attack_cooldown = 6.5, range = 25 }, { cost = 6500, damage = 3360, attack_cooldown = 6.5, range = 27, primary_attack = "rayleigh|rayleigh:final" }, { cost = 8000, damage = 6000, attack_cooldown = 9, range = 28 }, { cost = 9000, damage = 7320, attack_cooldown = 8.5, range = 29 }, { cost = 11000, damage = 9600, attack_cooldown = 8.5, range = 30 } } }, ["rikka"] = { primary_attack_no_upgrades = "rikka|live", spawn_cap = 3, upgrade = { { cost = 2200, damage = 1250, attack_cooldown = 7, range = 23 }, { cost = 3000, damage = 2500, attack_cooldown = 7, range = 24 }, { cost = 4000, damage = 4000, attack_cooldown = 8, range = 25, primary_attack = "rikka|live3" }, { cost = 5000, damage = 6000, attack_cooldown = 8, range = 25.5 }, { cost = 7500, damage = 8000, attack_cooldown = 7, range = 26 } } }, ["roger"] = { primary_attack_no_upgrades = "roger|roger:slashes", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2000, attack_cooldown = 7.5, range = 18 }, { cost = 4500, damage = 3500, attack_cooldown = 7, range = 20 }, { cost = 6000, damage = 5000, attack_cooldown = 7, range = 22 }, { cost = 8500, damage = 6000, attack_cooldown = 7, range = 23, primary_attack = "roger|roger:projectile_slash" }, { cost = 10000, damage = 7500, attack_cooldown = 7, range = 24 }, { cost = 14000, damage = 9000, attack_cooldown = 7, range = 25 } } }, ["roger_evolved"] = { primary_attack_no_upgrades = "roger_evolved|live", spawn_cap = 3, upgrade = { { cost = 3000, damage = 2800, attack_cooldown = 7.5, range = 18 }, { cost = 4500, damage = 4900, attack_cooldown = 7, range = 20 }, { cost = 6000, damage = 7000, attack_cooldown = 7, range = 22 }, { cost = 8500, damage = 8400, attack_cooldown = 7, range = 23 }, { cost = 10000, damage = 10500, attack_cooldown = 7, range = 24 }, { cost = 14000, damage = 12600, attack_cooldown = 7, range = 25 }, { cost = 16500, damage = 14000, attack_cooldown = 6, range = 30 }, { cost = 20000, damage = 21000, attack_cooldown = 6, range = 33 }, { cost = 25000, damage = 24500, attack_cooldown = 5.5, range = 35 } } }, ["rohan"] = { primary_attack_no_upgrades = "rohan|rohan:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1500, attack_cooldown = 7, range = 18 }, { cost = 3000, damage = 2500, attack_cooldown = 7, range = 19 }, { cost = 4000, damage = 3400, attack_cooldown = 6.5, range = 20, primary_attack = "rohan|rohan:two" }, { cost = 5000, damage = 4500, attack_cooldown = 6.5, range = 22 }, { cost = 6500, damage = 6000, attack_cooldown = 6.5, range = 24 }, { cost = 7000, damage = 6500, attack_cooldown = 6.5, range = 25 } } }, ["rohan_evolved"] = { primary_attack_no_upgrades = "rohan_evolved|rohan:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1875, attack_cooldown = 7, range = 18 }, { cost = 3000, damage = 3125, attack_cooldown = 7, range = 19 }, { cost = 4000, damage = 4250, attack_cooldown = 6.5, range = 20, primary_attack = "rohan_evolved|rohan:two" }, { cost = 5000, damage = 5625, attack_cooldown = 6.5, range = 22 }, { cost = 6500, damage = 7500, attack_cooldown = 6.5, range = 24 }, { cost = 7000, damage = 8125, attack_cooldown = 6, range = 25, primary_attack = "rohan_evolved|rohan:three" }, { cost = 9500, damage = 9375, attack_cooldown = 6, range = 27 }, { cost = 10000, damage = 10625, attack_cooldown = 6, range = 29 } } }, ["roshi"] = { primary_attack_no_upgrades = "roshi|roshi:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 900, attack_cooldown = 7.5, range = 19 }, { cost = 3000, damage = 1500, attack_cooldown = 7, range = 20 }, { cost = 4500, damage = 2500, attack_cooldown = 7, range = 23, primary_attack = "roshi|roshi:two" }, { cost = 6000, damage = 4000, attack_cooldown = 6.5, range = 24 }, { cost = 7500, damage = 5000, attack_cooldown = 6.5, range = 25 }, { cost = 9000, damage = 6000, attack_cooldown = 9, range = 30, primary_attack = "roshi|roshi:three" }, { cost = 12500, damage = 13000, attack_cooldown = 8.5, range = 33 }, { cost = 20000, damage = 16000, attack_cooldown = 8.5, range = 35 } } }, ["rukia_bankai"] = { primary_attack_no_upgrades = "rukia_bankai|rukia_2:one", spawn_cap = 3, upgrade = { { cost = 2200, damage = 2000, attack_cooldown = 6.5, range = 22 }, { cost = 2600, damage = 3100, attack_cooldown = 6.5, range = 23 }, { cost = 3200, damage = 3600, attack_cooldown = 6, range = 25, primary_attack = "rukia_bankai|rukia_2:two" }, { cost = 6000, damage = 5500, attack_cooldown = 6, range = 26 }, { cost = 10000, damage = 7000, attack_cooldown = 6, range = 27 }, { cost = 14000, damage = 10000, attack_cooldown = 6, range = 28 } } }, ["rukia_bankai_evolved"] = { primary_attack_no_upgrades = "rukia_bankai_evolved|rukia_2:one", spawn_cap = 3, upgrade = { { cost = 2200, damage = 2800, attack_cooldown = 6.5, range = 22 }, { cost = 2600, damage = 4340, attack_cooldown = 6.5, range = 23 }, { cost = 3200, damage = 5040, attack_cooldown = 6, range = 25, primary_attack = "rukia_bankai_evolved|rukia_2:two" }, { cost = 6000, damage = 7700, attack_cooldown = 6, range = 26 }, { cost = 10000, damage = 9800, attack_cooldown = 6, range = 27 }, { cost = 16000, damage = 11200, attack_cooldown = 7, range = 28, primary_attack = "rukia_bankai_evolved|rukia_2:three" }, { cost = 25000, damage = 15400, attack_cooldown = 6.5, range = 29 }, { cost = 30000, damage = 22400, attack_cooldown = 6.5, range = 32 } } }, ["saber"] = { primary_attack_no_upgrades = "saber|saber:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 1750, attack_cooldown = 7, range = 21 }, { cost = 3500, damage = 3000, attack_cooldown = 7, range = 23 }, { cost = 4400, damage = 5000, attack_cooldown = 8, range = 25, primary_attack = "saber|saber:two" }, { cost = 5500, damage = 6500, attack_cooldown = 8, range = 27 }, { cost = 6000, damage = 7000, attack_cooldown = 7.5, range = 30 }, { cost = 7000, damage = 8000, attack_cooldown = 7.5, range = 32, primary_attack = "saber|saber:three" }, { cost = 8500, damage = 8500, attack_cooldown = 7, range = 33 }, { cost = 9000, damage = 10000, attack_cooldown = 7, range = 35 } } }, ["sabo"] = { primary_attack_no_upgrades = "sabo|sabo:fireball", spawn_cap = 5, upgrade = { { cost = 1500, damage = 750, attack_cooldown = 9, range = 25 }, { cost = 2000, damage = 1200, attack_cooldown = 9, range = 26 }, { cost = 3000, damage = 1500, attack_cooldown = 8.5, range = 27, primary_attack = "sabo|sabo:leap" }, { cost = 4200, damage = 2200, attack_cooldown = 8.5, range = 30 }, { cost = 5000, damage = 2750, attack_cooldown = 8.5, range = 32 }, { cost = 6500, damage = 3300, attack_cooldown = 8, range = 33 } } }, ["sabo_evolved"] = { primary_attack_no_upgrades = "sabo_evolved|live", spawn_cap = 5, upgrade = { { cost = 1500, damage = 1012.5, attack_cooldown = 9, range = 25 }, { cost = 2000, damage = 1620, attack_cooldown = 9, range = 26 }, { cost = 3000, damage = 2025, attack_cooldown = 8.5, range = 27 }, { cost = 4200, damage = 2970, attack_cooldown = 8.5, range = 30 }, { cost = 5000, damage = 3712.5, attack_cooldown = 8.5, range = 32 }, { cost = 6500, damage = 4455, attack_cooldown = 8, range = 33 }, { cost = 8000, damage = 6075, attack_cooldown = 8, range = 35 }, { cost = 9500, damage = 8775, attack_cooldown = 8, range = 36 } } }, ["sakamoto"] = { primary_attack_no_upgrades = "sakamoto|sakamoto:one", spawn_cap = 3, upgrade = { { cost = 1500, damage = 1600, attack_cooldown = 6.5, range = 18 }, { cost = 2700, damage = 2900, attack_cooldown = 6.5, range = 21, primary_attack = "sakamoto|sakamoto:two" }, { cost = 4000, damage = 4500, attack_cooldown = 6.5, range = 23 }, { cost = 7000, damage = 5700, attack_cooldown = 6, range = 25, primary_attack = "sakamoto|sakamoto:three" }, { cost = 10000, damage = 8500, attack_cooldown = 6, range = 27 }, { cost = 12000, damage = 9500, attack_cooldown = 5.5, range = 30, primary_attack = "sakamoto|sakamoto:four" }, { cost = 17000, damage = 12000, attack_cooldown = 5.5, range = 33 } } }, ["sakamoto_evolved"] = { primary_attack_no_upgrades = "sakamoto_evolved|live", spawn_cap = 3, upgrade = { { cost = 1500, damage = 2000, attack_cooldown = 6.5, range = 18 }, { cost = 2700, damage = 3625, attack_cooldown = 6.5, range = 21, primary_attack = "sakamoto_evolved|live2" }, { cost = 4000, damage = 5625, attack_cooldown = 6.5, range = 23 }, { cost = 7000, damage = 7125, attack_cooldown = 6, range = 25, primary_attack = "sakamoto_evolved|live4" }, { cost = 10000, damage = 10625, attack_cooldown = 6, range = 27 }, { cost = 12000, damage = 11875, attack_cooldown = 5.5, range = 30, primary_attack = "sakamoto_evolved|live6" }, { cost = 17000, damage = 15000, attack_cooldown = 5.5, range = 33 }, { cost = 30000, damage = 28750, attack_cooldown = 8.5, range = 35, primary_attack = "sakamoto_evolved|live8" }, { cost = 35000, damage = 37500, attack_cooldown = 8.5, range = 37 } } }, ["sasuke_3"] = { primary_attack_no_upgrades = "sasuke_3|sasuke_3:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 500, attack_cooldown = 7, range = 24 }, { cost = 3300, damage = 1000, attack_cooldown = 7, range = 25, primary_attack = "sasuke_3|sasuke_3:two" }, { cost = 3750, damage = 1250, attack_cooldown = 6.5, range = 26 }, { cost = 4500, damage = 2000, attack_cooldown = 6.5, range = 27 }, { cost = 5500, damage = 3400, attack_cooldown = 6.5, range = 28 } } }, ["sasuke_3_evolved"] = { primary_attack_no_upgrades = "sasuke_3_evolved|live", spawn_cap = 3, upgrade = { { cost = 2000, damage = 700, attack_cooldown = 7, range = 24 }, { cost = 3300, damage = 1400, attack_cooldown = 7, range = 25 }, { cost = 3750, damage = 1750, attack_cooldown = 6.5, range = 26 }, { cost = 4500, damage = 2800, attack_cooldown = 6.5, range = 27 }, { cost = 5500, damage = 4200, attack_cooldown = 9, range = 28, primary_attack = "sasuke_3_evolved|live5" }, { cost = 7000, damage = 5180, attack_cooldown = 8.5, range = 29 }, { cost = 9000, damage = 7000, attack_cooldown = 8, range = 30 }, { cost = 12500, damage = 8400, attack_cooldown = 10, range = 33, primary_attack = "sasuke_3_evolved|live8" }, { cost = 15000, damage = 12600, attack_cooldown = 9.5, range = 35 }, { cost = 20000, damage = 16800, attack_cooldown = 9, range = 37 } } }, ["sato"] = { primary_attack_no_upgrades = "sato|sato:one", spawn_cap = 3, upgrade = { { cost = 1500, damage = 1400, attack_cooldown = 7, range = 19 }, { cost = 2100, damage = 2200, attack_cooldown = 7, range = 22, primary_attack = "sato|sato:two" }, { cost = 3000, damage = 3000, attack_cooldown = 6.5, range = 23 }, { cost = 5000, damage = 4500, attack_cooldown = 6.5, range = 24 }, { cost = 11000, damage = 7000, attack_cooldown = 6, range = 26, primary_attack = "sato|sato:three" }, { cost = 15000, damage = 10000, attack_cooldown = 6, range = 28 } } }, ["sayaka"] = { primary_attack_no_upgrades = "sayaka|sayaka:slash", spawn_cap = 5, upgrade = { { cost = 1800, damage = 994, attack_cooldown = 7, range = 20 }, { cost = 2000, damage = 1633, attack_cooldown = 7, range = 20.5 }, { cost = 3000, damage = 2130, attack_cooldown = 6.5, range = 21, primary_attack = "sayaka|sayaka:dash" }, { cost = 3500, damage = 2983, attack_cooldown = 6.5, range = 21.5 }, { cost = 4000, damage = 3693, attack_cooldown = 6, range = 22 }, { cost = 5500, damage = 4687, attack_cooldown = 6, range = 22.5 }, { cost = 6500, damage = 5400, attack_cooldown = 6, range = 23 } } }, ["sayaka_evolved"] = { primary_attack_no_upgrades = "sayaka_evolved|sayaka:slash", spawn_cap = 5, upgrade = { { cost = 1800, damage = 1341.9, attack_cooldown = 7, range = 20 }, { cost = 2000, damage = 2204.55, attack_cooldown = 7, range = 20.5 }, { cost = 3000, damage = 2875.5, attack_cooldown = 6.5, range = 21, primary_attack = "sayaka_evolved|sayaka:dash" }, { cost = 3500, damage = 4027.05, attack_cooldown = 6.5, range = 21.5 }, { cost = 4000, damage = 4985.55, attack_cooldown = 6, range = 22 }, { cost = 5500, damage = 6327.45, attack_cooldown = 6, range = 22.5 }, { cost = 6500, damage = 6327.45, attack_cooldown = 5, range = 23, primary_attack = "sayaka_evolved|sayaka:final" }, { cost = 7500, damage = 7669.35, attack_cooldown = 5, range = 24 }, { cost = 9000, damage = 8436.15, attack_cooldown = 4.5, range = 25 } } }, ["sengoku_unit"] = { primary_attack_no_upgrades = "sengoku_unit|sengoku:slams", spawn_cap = 3, upgrade = { { cost = 2100, damage = 1650, attack_cooldown = 11, range = 26.5 }, { cost = 2600, damage = 2420, attack_cooldown = 10, range = 27 }, { cost = 4200, damage = 3520, attack_cooldown = 10, range = 29 }, { cost = 5500, damage = 4180, attack_cooldown = 9, range = 30, primary_attack = "sengoku_unit|sengoku:kamehameha" }, { cost = 7500, damage = 6050, attack_cooldown = 9, range = 32 }, { cost = 9000, damage = 8250, attack_cooldown = 9, range = 33 }, { cost = 12000, damage = 9500, attack_cooldown = 9, range = 35 } } }, ["sengoku_unit_evolved"] = { primary_attack_no_upgrades = "sengoku_unit_evolved|sengoku:slams", spawn_cap = 3, upgrade = { { cost = 2100, damage = 2145, attack_cooldown = 11, range = 26.5 }, { cost = 2600, damage = 3146, attack_cooldown = 10, range = 27 }, { cost = 4200, damage = 4576, attack_cooldown = 10, range = 29 }, { cost = 5500, damage = 5434, attack_cooldown = 9, range = 30, primary_attack = "sengoku_unit_evolved|sengoku:kamehameha" }, { cost = 7500, damage = 7865, attack_cooldown = 9, range = 32 }, { cost = 9000, damage = 10725, attack_cooldown = 9, range = 33 }, { cost = 12000, damage = 25740, attack_cooldown = 17, range = 35, primary_attack = "sengoku_unit_evolved|sengoku:shockwave" }, { cost = 15000, damage = 31460, attack_cooldown = 16, range = 37 }, { cost = 18000, damage = 40040, attack_cooldown = 15, range = 38 } } }, ["shalltear"] = { primary_attack_no_upgrades = "shalltear|shalltear:one", spawn_cap = 4, upgrade = { { cost = 2000, damage = 750, attack_cooldown = 7, range = 20 }, { cost = 3500, damage = 1250, attack_cooldown = 6.5, range = 21 }, { cost = 5500, damage = 2500, attack_cooldown = 6.5, range = 23, primary_attack = "shalltear|shalltear:two" }, { cost = 7000, damage = 3500, attack_cooldown = 6, range = 24 }, { cost = 9000, damage = 5000, attack_cooldown = 6, range = 25 } } }, ["shalltear_evolved"] = { primary_attack_no_upgrades = "shalltear_evolved|live", spawn_cap = 4, upgrade = { { cost = 2000, damage = 1012.5, attack_cooldown = 7, range = 20 }, { cost = 3500, damage = 1687.5, attack_cooldown = 6.5, range = 21 }, { cost = 5500, damage = 3375, attack_cooldown = 6.5, range = 23 }, { cost = 7000, damage = 4725, attack_cooldown = 6, range = 24 }, { cost = 9000, damage = 6750, attack_cooldown = 6, range = 25 }, { cost = 12500, damage = 10800, attack_cooldown = 9, range = 27, primary_attack = "shalltear_evolved|live6" }, { cost = 20000, damage = 12150, attack_cooldown = 8, range = 30 } } }, ["shidou"] = { primary_attack_no_upgrades = "shidou|shidou:one", spawn_cap = 4, upgrade = { { cost = 1300, damage = 600, attack_cooldown = 6, range = 24 }, { cost = 2000, damage = 1050, attack_cooldown = 6, range = 26 }, { cost = 3500, damage = 2100, attack_cooldown = 6, range = 30, primary_attack = "shidou|shidou:two" }, { cost = 5000, damage = 2900, attack_cooldown = 5.5, range = 32 }, { cost = 8000, damage = 4600, attack_cooldown = 5.5, range = 33 }, { cost = 15000, damage = 7500, attack_cooldown = 5.5, range = 35 } } }, ["skull_knight"] = { primary_attack_no_upgrades = "skull_knight|skull_knight:one", spawn_cap = 2, upgrade = { { cost = 3250, damage = 1900, attack_cooldown = 8, range = 24 }, { cost = 4500, damage = 3200, attack_cooldown = 7.5, range = 25 }, { cost = 5500, damage = 5000, attack_cooldown = 7.5, range = 27 }, { cost = 8000, damage = 6000, attack_cooldown = 7.5, range = 29, primary_attack = "skull_knight|skull_knight:two" }, { cost = 12000, damage = 7500, attack_cooldown = 7, range = 31 }, { cost = 15000, damage = 9500, attack_cooldown = 7, range = 33 }, { cost = 17500, damage = 11000, attack_cooldown = 7, range = 34 } } }, ["skull_knight_evolved"] = { primary_attack_no_upgrades = "skull_knight_evolved|skull_knight:one", spawn_cap = 2, upgrade = { { cost = 3250, damage = 2660, attack_cooldown = 8, range = 24 }, { cost = 4500, damage = 4480, attack_cooldown = 7.5, range = 25 }, { cost = 5500, damage = 7000, attack_cooldown = 7.5, range = 27 }, { cost = 8000, damage = 8400, attack_cooldown = 7.5, range = 29, primary_attack = "skull_knight_evolved|skull_knight:two" }, { cost = 12000, damage = 10500, attack_cooldown = 7, range = 31 }, { cost = 15000, damage = 13300, attack_cooldown = 7, range = 33 }, { cost = 17500, damage = 18200, attack_cooldown = 7, range = 37, primary_attack = "skull_knight_evolved|skull_knight:three" }, { cost = 25000, damage = 23800, attack_cooldown = 6.5, range = 39 }, { cost = 30000, damage = 30800, attack_cooldown = 6.5, range = 41 } } }, ["smoker_unit"] = { primary_attack_no_upgrades = "smoker_unit|smoker:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 800, attack_cooldown = 6, range = 19 }, { cost = 3000, damage = 1250, attack_cooldown = 5, range = 20 }, { cost = 5000, damage = 2500, attack_cooldown = 8, range = 23, primary_attack = "smoker_unit|smoker:two" }, { cost = 6000, damage = 3000, attack_cooldown = 7.5, range = 24 }, { cost = 7500, damage = 4000, attack_cooldown = 7, range = 25 } } }, ["smoker_unit_evolved"] = { primary_attack_no_upgrades = "smoker_unit_evolved|smoker:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1120, attack_cooldown = 6, range = 19 }, { cost = 3000, damage = 1750, attack_cooldown = 5, range = 20 }, { cost = 5000, damage = 3500, attack_cooldown = 8, range = 23, primary_attack = "smoker_unit_evolved|smoker:two" }, { cost = 6000, damage = 4200, attack_cooldown = 7.5, range = 24 }, { cost = 7500, damage = 5600, attack_cooldown = 7, range = 25 }, { cost = 9000, damage = 9800, attack_cooldown = 13, range = 33, primary_attack = "smoker_unit_evolved|smoker:three" }, { cost = 15000, damage = 12600, attack_cooldown = 12, range = 35 }, { cost = 20000, damage = 15400, attack_cooldown = 11, range = 37 } } }, ["stain"] = { primary_attack_no_upgrades = "stain|stain:one", spawn_cap = 3, upgrade = { { cost = 2100, damage = 692, attack_cooldown = 6, range = 20 }, { cost = 3000, damage = 1154, attack_cooldown = 6, range = 22 }, { cost = 3500, damage = 1538, attack_cooldown = 6, range = 24 }, { cost = 5000, damage = 2154, attack_cooldown = 7, range = 26, primary_attack = "stain|stain:two" }, { cost = 6500, damage = 2923, attack_cooldown = 7, range = 27 }, { cost = 8000, damage = 3462, attack_cooldown = 7, range = 28 }, { cost = 10000, damage = 4077, attack_cooldown = 6.5, range = 30 } } }, ["stain_evolved"] = { primary_attack_no_upgrades = "stain_evolved|live", spawn_cap = 3, upgrade = { { cost = 2100, damage = 968.8, attack_cooldown = 6, range = 20 }, { cost = 3000, damage = 1615.6, attack_cooldown = 6, range = 22 }, { cost = 3500, damage = 2153.2, attack_cooldown = 6, range = 24 }, { cost = 5000, damage = 3015.6, attack_cooldown = 7, range = 26, primary_attack = "stain_evolved|live4" }, { cost = 6500, damage = 4092.2, attack_cooldown = 7, range = 27 }, { cost = 8000, damage = 4846.8, attack_cooldown = 7, range = 28 }, { cost = 10000, damage = 5707.8, attack_cooldown = 6.5, range = 30 }, { cost = 12000, damage = 6784.4, attack_cooldown = 6.5, range = 31 }, { cost = 15000, damage = 8615.6, attack_cooldown = 6.5, range = 33 } } }, ["starlia"] = { primary_attack_no_upgrades = "starlia|starlia:one", spawn_cap = 4, upgrade = { { cost = 1750, damage = 900, attack_cooldown = 4.5, range = 25 }, { cost = 2500, damage = 1200, attack_cooldown = 4.5, range = 26 }, { cost = 3000, damage = 2000, attack_cooldown = 4.5, range = 27, primary_attack = "starlia|starlia:two" }, { cost = 4500, damage = 3000, attack_cooldown = 4.5, range = 28 }, { cost = 6000, damage = 4000, attack_cooldown = 4, range = 30 } } }, ["starlia_evolved"] = { primary_attack_no_upgrades = "starlia_evolved|starlia:one", spawn_cap = 4, upgrade = { { cost = 1750, damage = 1215, attack_cooldown = 4.5, range = 25 }, { cost = 2500, damage = 1620, attack_cooldown = 4.5, range = 26 }, { cost = 3000, damage = 2700, attack_cooldown = 4.5, range = 27, primary_attack = "starlia_evolved|starlia:two" }, { cost = 4500, damage = 4050, attack_cooldown = 4.5, range = 28 }, { cost = 6000, damage = 5400, attack_cooldown = 4, range = 30 }, { cost = 7500, damage = 7425, attack_cooldown = 7, range = 33 }, { cost = 10000, damage = 9450, attack_cooldown = 6.5, range = 34 }, { cost = 12500, damage = 10800, attack_cooldown = 6, range = 35 } } }, ["tengen_2"] = { primary_attack_no_upgrades = "tengen_2|tengen_2:slam", spawn_cap = 3, upgrade = { { cost = 2100, damage = 1417.5, attack_cooldown = 7.5, range = 20.5 }, { cost = 3250, damage = 2362.5, attack_cooldown = 7.5, range = 21, primary_attack = "tengen_2|tengen_2:combo_1" }, { cost = 4000, damage = 3240, attack_cooldown = 7, range = 23 }, { cost = 5500, damage = 4725, attack_cooldown = 7, range = 23.5 }, { cost = 6250, damage = 6075, attack_cooldown = 7, range = 24, primary_attack = "tengen_2|tengen_2:combo_2" }, { cost = 8500, damage = 7560, attack_cooldown = 7, range = 24 }, { cost = 10000, damage = 8505, attack_cooldown = 6.5, range = 24.5 } } }, ["tengen_2_evolved"] = { primary_attack_no_upgrades = "tengen_2_evolved|live", spawn_cap = 3, upgrade = { { cost = 2100, damage = 1913.63, attack_cooldown = 7.5, range = 20.5 }, { cost = 3250, damage = 3189.38, attack_cooldown = 7.5, range = 21, primary_attack = "tengen_2_evolved|live2" }, { cost = 4000, damage = 4374, attack_cooldown = 7, range = 23 }, { cost = 5500, damage = 6378.75, attack_cooldown = 7, range = 23.5 }, { cost = 6250, damage = 8201.25, attack_cooldown = 7, range = 24, primary_attack = "tengen_2_evolved|live5" }, { cost = 8500, damage = 13778.1, attack_cooldown = 7, range = 24 }, { cost = 10000, damage = 11481.75, attack_cooldown = 6.5, range = 24.5 }, { cost = 14500, damage = 20047.5, attack_cooldown = 13, range = 24.5, primary_attack = "tengen_2_evolved|live8" }, { cost = 17000, damage = 22781.25, attack_cooldown = 12.5, range = 24.5 }, { cost = 20000, damage = 27337.5, attack_cooldown = 12, range = 25 } } }, ["toji"] = { primary_attack_no_upgrades = "toji|toji:one", spawn_cap = 3, upgrade = { { cost = 2000, damage = 2700, attack_cooldown = 7.5, range = 23 }, { cost = 2500, damage = 4500, attack_cooldown = 7.5, range = 25, primary_attack = "toji|toji:two" }, { cost = 3500, damage = 6500, attack_cooldown = 7, range = 27 }, { cost = 4500, damage = 8200, attack_cooldown = 7, range = 28 }, { cost = 7500, damage = 12000, attack_cooldown = 7, range = 30, primary_attack = "toji|toji:three" }, { cost = 10000, damage = 16000, attack_cooldown = 7, range = 31 }, { cost = 15000, damage = 24000, attack_cooldown = 7, range = 33 } } }, ["toji_evolved"] = { primary_attack_no_upgrades = "toji_evolved|live", spawn_cap = 3, upgrade = { { cost = 2000, damage = 4050, attack_cooldown = 7.5, range = 23 }, { cost = 2500, damage = 6750, attack_cooldown = 7, range = 25, primary_attack = "toji_evolved|live2" }, { cost = 3500, damage = 9750, attack_cooldown = 7, range = 27 }, { cost = 4500, damage = 12300, attack_cooldown = 6.5, range = 28, primary_attack = "toji_evolved|live4" }, { cost = 7500, damage = 18000, attack_cooldown = 6.5, range = 30 }, { cost = 10000, damage = 24000, attack_cooldown = 6.5, range = 31 }, { cost = 15000, damage = 36000, attack_cooldown = 6.5, range = 33 }, { cost = 24000, damage = 45000, attack_cooldown = 6.5, range = 35, primary_attack = "toji_evolved|live8" }, { cost = 28000, damage = 60000, attack_cooldown = 6.5, range = 37 } } }, ["tsuna"] = { primary_attack_no_upgrades = "tsuna|tsuna:two", spawn_cap = 4, upgrade = { { cost = 2100, damage = 700, attack_cooldown = 7.5, range = 22 }, { cost = 2500, damage = 1000, attack_cooldown = 7, range = 23 }, { cost = 3600, damage = 1700, attack_cooldown = 7, range = 24, primary_attack = "tsuna|tsuna:one" }, { cost = 4500, damage = 2300, attack_cooldown = 7, range = 25 }, { cost = 7000, damage = 3200, attack_cooldown = 7, range = 27 }, { cost = 8500, damage = 3750, attack_cooldown = 6.5, range = 28 }, { cost = 9500, damage = 3900, attack_cooldown = 6.5, range = 29 } } }, ["tsuna_evolved"] = { primary_attack_no_upgrades = "tsuna_evolved|tsuna:two", spawn_cap = 4, upgrade = { { cost = 2100, damage = 945, attack_cooldown = 7.5, range = 22 }, { cost = 2500, damage = 1350, attack_cooldown = 7, range = 23 }, { cost = 3600, damage = 2295, attack_cooldown = 7, range = 24, primary_attack = "tsuna_evolved|tsuna:one" }, { cost = 4500, damage = 3105, attack_cooldown = 7, range = 25 }, { cost = 7000, damage = 4320, attack_cooldown = 7, range = 27 }, { cost = 8500, damage = 5062.5, attack_cooldown = 6.5, range = 28 }, { cost = 10000, damage = 5400, attack_cooldown = 6.5, range = 30, primary_attack = "tsuna_evolved|tsuna:three" }, { cost = 12000, damage = 6750, attack_cooldown = 6.5, range = 31 }, { cost = 15000, damage = 8100, attack_cooldown = 6, range = 32 } } }, ["usopp_ts"] = { primary_attack_no_upgrades = "usopp_ts|usopp_ts:trap", spawn_cap = 3, upgrade = { { cost = 1500, damage = 2000, attack_cooldown = 7, range = 10 }, { cost = 2500, damage = 3500, attack_cooldown = 6.5, range = 10 }, { cost = 4000, damage = 5000, attack_cooldown = 6.5, range = 10, primary_attack = "usopp_ts|usopp_ts:trap2" }, { cost = 6500, damage = 7500, attack_cooldown = 6, range = 10 }, { cost = 8000, damage = 10000, attack_cooldown = 5.5, range = 10 } } }, ["usopp_ts_evolved"] = { primary_attack_no_upgrades = "usopp_ts_evolved|live", spawn_cap = 3, upgrade = { { cost = 1500, damage = 3000, attack_cooldown = 7, range = 10 }, { cost = 2500, damage = 5250, attack_cooldown = 6.5, range = 10 }, { cost = 4000, damage = 7500, attack_cooldown = 6.5, range = 10 }, { cost = 6500, damage = 11250, attack_cooldown = 6, range = 10 }, { cost = 8000, damage = 15000, attack_cooldown = 5.5, range = 10 }, { cost = 10000, damage = 21000, attack_cooldown = 5, range = 10 }, { cost = 35000, damage = 45000, attack_cooldown = 5, range = 10 } } }, ["vanilla_ice"] = { primary_attack_no_upgrades = "vanilla_ice|vanilla_ice:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2200, attack_cooldown = 7, range = 21 }, { cost = 3500, damage = 3800, attack_cooldown = 7, range = 23 }, { cost = 5000, damage = 5000, attack_cooldown = 6.5, range = 25, primary_attack = "vanilla_ice|vanilla_ice:two" }, { cost = 7500, damage = 7500, attack_cooldown = 6.5, range = 26 }, { cost = 11000, damage = 11000, attack_cooldown = 6.5, range = 27 }, { cost = 13500, damage = 14000, attack_cooldown = 6.5, range = 28 } } }, ["vanilla_ice_evolved"] = { primary_attack_no_upgrades = "vanilla_ice_evolved|live", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2860, attack_cooldown = 7, range = 21 }, { cost = 3500, damage = 4940, attack_cooldown = 7, range = 23 }, { cost = 5000, damage = 6500, attack_cooldown = 6.5, range = 25 }, { cost = 7500, damage = 9750, attack_cooldown = 6.5, range = 26 }, { cost = 11000, damage = 14300, attack_cooldown = 6.5, range = 27 }, { cost = 14000, damage = 15000, attack_cooldown = 11, range = 29, primary_attack = "vanilla_ice_evolved|live6" }, { cost = 17500, damage = 24700, attack_cooldown = 10, range = 30 }, { cost = 22000, damage = 29250, attack_cooldown = 9, range = 33 } } }, ["vegeta_majin"] = { primary_attack_no_upgrades = "vegeta_majin|vegeta_majin:one", spawn_cap = 3, upgrade = { { cost = 2500, damage = 2500, attack_cooldown = 7, range = 20 }, { cost = 4000, damage = 4500, attack_cooldown = 6.5, range = 21 }, { cost = 5500, damage = 7000, attack_cooldown = 6.5, range = 22, primary_attack = "vegeta_majin|vegeta_majin:two" }, { cost = 7500, damage = 9000, attack_cooldown = 6, range = 23 }, { cost = 11000, damage = 12500, attack_cooldown = 6, range = 25 }, { cost = 14000, damage = 15000, attack_cooldown = 6, range = 28 } } }, ["vegito"] = { primary_attack_no_upgrades = "vegito|vegito:one", spawn_cap = 3, upgrade = { { cost = 5000, damage = 33000, attack_cooldown = 5.5, range = 34, primary_attack = "vegito|vegito:two" }, { cost = 9000, damage = 35000, attack_cooldown = 6, range = 35, primary_attack = "vegito|vegito:four" }, { cost = 12500, damage = 45000, attack_cooldown = 7, range = 38, primary_attack = "vegito|vegito:three" }, { cost = 25000, damage = 47500, attack_cooldown = 6.5, range = 39 }, { cost = 50000, damage = 55000, attack_cooldown = 6, range = 40 }, { cost = 80000, damage = 70000, attack_cooldown = 6, range = 42 } } }, ["yoshino"] = { primary_attack_no_upgrades = "yoshino|yoshino:icicles", spawn_cap = 4, upgrade = { { cost = 2500, damage = 800, attack_cooldown = 7.5, range = 19 }, { cost = 3000, damage = 1200, attack_cooldown = 7, range = 22 }, { cost = 4000, damage = 1500, attack_cooldown = 7, range = 25, primary_attack = "yoshino|yoshino:tornado" }, { cost = 5000, damage = 2250, attack_cooldown = 7, range = 27 }, { cost = 7500, damage = 3000, attack_cooldown = 6.5, range = 28 }, { cost = 9000, damage = 4000, attack_cooldown = 6.5, range = 30 } } }, ["yoshino_evolved"] = { primary_attack_no_upgrades = "yoshino_evolved|yoshino:icicles", spawn_cap = 4, upgrade = { { cost = 2500, damage = 800, attack_cooldown = 7.5, range = 19 }, { cost = 3000, damage = 1200, attack_cooldown = 7, range = 22 }, { cost = 4000, damage = 1500, attack_cooldown = 7, range = 25, primary_attack = "yoshino_evolved|yoshino:tornado" }, { cost = 5000, damage = 2250, attack_cooldown = 7, range = 27 }, { cost = 7500, damage = 3000, attack_cooldown = 6.5, range = 28 }, { cost = 9000, damage = 4000, attack_cooldown = 6.5, range = 30 }, { cost = 12000, damage = 5000, attack_cooldown = 8, range = 35, primary_attack = "yoshino_evolved|yoshino:summon" }, { cost = 17500, damage = 6000, attack_cooldown = 8, range = 37 } } }, ["yuma"] = { primary_attack_no_upgrades = "yuma|yuma:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 900, attack_cooldown = 5, range = 25 }, { cost = 3300, damage = 1500, attack_cooldown = 5, range = 26 }, { cost = 4000, damage = 3000, attack_cooldown = 7, range = 27, primary_attack = "yuma|yuma:two" }, { cost = 6000, damage = 3500, attack_cooldown = 6.5, range = 28 }, { cost = 7000, damage = 5000, attack_cooldown = 6, range = 29 } } }, ["yuma_evolved"] = { primary_attack_no_upgrades = "yuma_evolved|yuma:one", spawn_cap = 4, upgrade = { { cost = 2500, damage = 1350, attack_cooldown = 5, range = 25 }, { cost = 3300, damage = 2250, attack_cooldown = 5, range = 26 }, { cost = 4000, damage = 4500, attack_cooldown = 7, range = 27, primary_attack = "yuma_evolved|yuma:two" }, { cost = 6000, damage = 5250, attack_cooldown = 6.5, range = 28 }, { cost = 7000, damage = 7500, attack_cooldown = 6, range = 29 }, { cost = 9000, damage = 11250, attack_cooldown = 8.5, range = 30, primary_attack = "yuma_evolved|yuma:three" }, { cost = 12500, damage = 13500, attack_cooldown = 8, range = 33 }, { cost = 15000, damage = 16500, attack_cooldown = 8, range = 35 } } } } Core.FILL_ATTACKS = { ["ainz_evolved|ainz:ball"] = { k = "c", r = 7 }, ["ainz_evolved|ainz:fallen_down"] = { k = "f", h = 3 }, ["ainz_evolved|ainz:lightning"] = { k = "c", r = 10 }, ["ainz|ainz:ball"] = { k = "c", r = 7 }, ["ainz|ainz:lightning"] = { k = "c", r = 10 }, ["aizen_christmas|aizen_transcended:four"] = { k = "f", h = 2 }, ["aizen_christmas|aizen_transcended:one"] = { k = "c", r = 8, h = 3 }, ["aizen_christmas|aizen_transcended:three"] = { k = "n", r = 60, h = 3 }, ["aizen_christmas|aizen_transcended:two"] = { k = "n", r = 60, h = 2 }, ["akutagawa_evolved|akutagawa:bite"] = { k = "c", r = 12 }, ["akutagawa_evolved|akutagawa:ribbon"] = { k = "n", r = 60 }, ["akutagawa_evolved|akutagawa:slash"] = { k = "c", r = 8 }, ["akutagawa_evolved|akutagawa:spikes"] = { k = "c", r = 10 }, ["akutagawa|akutagawa:ribbon"] = { k = "n", r = 60 }, ["akutagawa|akutagawa:slash"] = { k = "c", r = 8 }, ["akutagawa|akutagawa:spikes"] = { k = "c", r = 10 }, ["albedo_evolved|albedo:four"] = { k = "f" }, ["albedo_evolved|albedo:one"] = { k = "c", r = 7 }, ["albedo_evolved|albedo:three"] = { k = "l" }, ["albedo_evolved|albedo:two"] = { k = "c", r = 8, h = 3 }, ["albedo|albedo:one"] = { k = "c", r = 7 }, ["albedo|albedo:three"] = { k = "l" }, ["albedo|albedo:two"] = { k = "c", r = 8, h = 3 }, ["alucard_evolved|alucard:one"] = { k = "c", r = 8, h = 3 }, ["alucard_evolved|alucard:three"] = { k = "c", r = 12, h = 3 }, ["alucard_evolved|alucard:two"] = { k = "c", r = 10 }, ["alucard|alucard:one"] = { k = "c", r = 8, h = 3 }, ["alucard|alucard:two"] = { k = "c", r = 10 }, ["archer:four"] = { k = "c", r = 11, h = 3 }, ["archer:one"] = { k = "c", r = 5 }, ["archer:three"] = { k = "c", r = 9.5, h = 5 }, ["archer:two"] = { k = "l", h = 2 }, ["archer|archer:one"] = { k = "c", r = 5 }, ["archer|archer:three"] = { k = "c", r = 9.5, h = 5 }, ["archer|archer:two"] = { k = "l", h = 2 }, ["asuna_evolved|asuna:barrage"] = { k = "c", r = 8, h = 3 }, ["asuna_evolved|asuna:final"] = { k = "c", r = 10, h = 3 }, ["asuna_evolved|asuna:jab"] = { k = "n", r = 40 }, ["asuna_evolved|asuna:stabs"] = { k = "c", r = 6, h = 3 }, ["asuna|asuna:barrage"] = { k = "c", r = 8, h = 3 }, ["asuna|asuna:jab"] = { k = "n", r = 40 }, ["asuna|asuna:stabs"] = { k = "c", r = 6, h = 3 }, ["atsushi_evolved|atsushi:one"] = { k = "c", r = 7, h = 3, dot = "bleed", ticks = 3, rate = 0.083 }, ["atsushi_evolved|atsushi:three"] = { k = "c", r = 15, h = 2, dot = "bleed", ticks = 3, rate = 0.083 }, ["atsushi_evolved|atsushi:two"] = { k = "c", r = 9, h = 3, dot = "bleed", ticks = 3, rate = 0.083 }, ["atsushi|atsushi:one"] = { k = "c", r = 7, h = 3, dot = "bleed", ticks = 3, rate = 0.083 }, ["atsushi|atsushi:two"] = { k = "c", r = 9, h = 3, dot = "bleed", ticks = 3, rate = 0.083 }, ["avdol:one"] = { k = "n", r = 45, h = 2, dot = "burn", ticks = 5, rate = 0.06 }, ["avdol:three"] = { k = "c", r = 14, h = 4, dot = "burn", ticks = 5, rate = 0.06 }, ["avdol:two"] = { k = "c", r = 10, dot = "burn", ticks = 5, rate = 0.06 }, ["avdol_evolved|live"] = { k = "n", r = 45, h = 2 }, ["avdol_evolved|live7"] = { k = "n", r = 45, h = 4 }, ["avdol|avdol:one"] = { k = "n", r = 45, h = 2, dot = "burn", ticks = 5, rate = 0.06 }, ["avdol|avdol:two"] = { k = "c", r = 10, dot = "burn", ticks = 5, rate = 0.06 }, ["bambietta|bambietta:one"] = { k = "c", r = 8 }, ["bambietta|bambietta:three"] = { k = "f" }, ["bambietta|bambietta:two"] = { k = "c", r = 12, h = 3 }, ["bee_girl:one"] = { k = "s" }, ["bee_girl_evolved|live"] = { k = "s" }, ["bee_girl|honey:one"] = { k = "s" }, ["boxxo|boxxo:one"] = { k = "c", r = 10, h = 3 }, ["buddha_evolved|buddha:bleed"] = { k = "c", r = 18, dot = "bleed", ticks = 3, rate = 0.1 }, ["buddha_evolved|buddha:one"] = { k = "c", r = 10 }, ["buddha|buddha:one"] = { k = "c", r = 10 }, ["buggy|buggy:one"] = { k = "l" }, ["buggy|buggy:three"] = { k = "f" }, ["buggy|buggy:two"] = { k = "c", r = 7 }, ["buu_evolved|buu:one"] = { k = "c", r = 9 }, ["buu_evolved|buu:three"] = { k = "f", h = 3 }, ["buu_evolved|buu:two"] = { k = "c", r = 11 }, ["buu|buu:one"] = { k = "c", r = 9 }, ["buu|buu:two"] = { k = "c", r = 11 }, ["caesarclown:one"] = { k = "c", r = 6, dot = "poison", ticks = 56, rate = 0.05 }, ["caesarclown:three"] = { k = "c", r = 13, h = 3, dot = "poison", ticks = 56, rate = 0.05 }, ["caesarclown:two"] = { k = "c", r = 9, dot = "poison", ticks = 56, rate = 0.05 }, ["caesarclown_evolved|live"] = { k = "c", r = 6 }, ["caesarclown_evolved|live6"] = { k = "c", r = 6, h = 3 }, ["caesarclown|caesarclown:one"] = { k = "c", r = 6, dot = "poison", ticks = 56, rate = 0.05 }, ["caesarclown|caesarclown:two"] = { k = "c", r = 9, dot = "poison", ticks = 56, rate = 0.05 }, ["caesar|caesar:one"] = { k = "c", r = 5 }, ["caesar|caesar:three"] = { k = "c", r = 12, h = 3 }, ["caesar|caesar:two"] = { k = "c", r = 8, h = 3 }, ["carrot_evolved|carrot_evolved:one"] = { k = "f", stun = 1 }, ["carrot_evolved|carrot_evolved:three"] = { k = "c", r = 9, stun = 1 }, ["carrot_evolved|carrot_evolved:two"] = { k = "c", r = 7, stun = 1 }, ["carrot|carrot:one"] = { k = "c", r = 7 }, ["carrot|carrot:three"] = { k = "c", r = 8 }, ["chad_evolved|chad:one"] = { k = "c", r = 7 }, ["chad_evolved|chad:three"] = { k = "l" }, ["chad_evolved|chad:two"] = { k = "l", h = 3 }, ["chad|chad:one"] = { k = "c", r = 7 }, ["chad|chad:two"] = { k = "l", h = 3 }, ["charmy_evolved|charmy:ball"] = { k = "c", r = 6, slow = 0.5, t = 2 }, ["charmy_evolved|charmy:sheep"] = { k = "c", r = 7.5, slow = 0.5, t = 2 }, ["charmy_evolved|charmy:sheep2"] = { k = "c", r = 11, h = 3, slow = 0.5, t = 2 }, ["charmy|charmy:ball"] = { k = "c", r = 6, slow = 0.5, t = 2 }, ["charmy|charmy:sheep"] = { k = "c", r = 7.5, slow = 0.5, t = 2 }, ["chihiro_evolved|rokuhira:four"] = { k = "c", r = 18 }, ["chihiro_evolved|rokuhira:one"] = { k = "c", r = 10, h = 3 }, ["chihiro_evolved|rokuhira:three"] = { k = "c", r = 14 }, ["chihiro_evolved|rokuhira:two"] = { k = "c", r = 12, h = 3 }, ["chihiro|rokuhira:one"] = { k = "c", r = 10, h = 3 }, ["chihiro|rokuhira:three"] = { k = "c", r = 14 }, ["chihiro|rokuhira:two"] = { k = "c", r = 12, h = 3 }, ["chika_evolved|chika:one"] = { k = "l" }, ["chika_evolved|chika:three"] = { k = "c", r = 13 }, ["chika_evolved|chika:two"] = { k = "c", r = 9, h = 3 }, ["chika|chika:one"] = { k = "l" }, ["chika|chika:two"] = { k = "c", r = 9, h = 3 }, ["chuya_evolved|chuya:four"] = { k = "c", r = 13, h = 4 }, ["chuya_evolved|chuya:one"] = { k = "l" }, ["chuya_evolved|chuya:three"] = { k = "c", r = 11, h = 3 }, ["chuya_evolved|chuya:two"] = { k = "c", r = 9, h = 3 }, ["chuya|chuya:one"] = { k = "l" }, ["chuya|chuya:three"] = { k = "c", r = 11, h = 3 }, ["chuya|chuya:two"] = { k = "c", r = 9, h = 3 }, ["cid_evolved|cid:one"] = { k = "c", r = 6, h = 3 }, ["cid_evolved|cid:three"] = { k = "f", h = 3 }, ["cid_evolved|cid:two"] = { k = "c", r = 12, h = 2 }, ["cid|cid:one"] = { k = "c", r = 6, h = 3 }, ["cid|cid:two"] = { k = "c", r = 12, h = 2 }, ["conrad_evolved|conrad:one"] = { k = "c", r = 9, h = 3 }, ["conrad_evolved|conrad:three"] = { k = "c", r = 15 }, ["conrad_evolved|conrad:two"] = { k = "l" }, ["conrad|conrad:one"] = { k = "c", r = 9, h = 3 }, ["conrad|conrad:two"] = { k = "l" }, ["cooler|cooler:one"] = { k = "c", r = 8 }, ["cooler|cooler:three"] = { k = "c", r = 15, h = 3 }, ["cooler|cooler:two"] = { k = "c", r = 10 }, ["cranel_evolved|cranel:one"] = { k = "c", r = 4.5 }, ["cranel_evolved|cranel:three"] = { k = "c", r = 7 }, ["cranel_evolved|cranel:two"] = { k = "c", r = 6 }, ["cranel|cranel:one"] = { k = "c", r = 4.5 }, ["cranel|cranel:two"] = { k = "c", r = 6 }, ["daki:slam"] = { k = "c", r = 6, dot = "bleed", ticks = 4, rate = 0.1 }, ["daki:slash"] = { k = "c", r = 5, dot = "bleed", ticks = 4, rate = 0.1 }, ["daki:spike"] = { k = "c", r = 7, h = 2, dot = "bleed", ticks = 4, rate = 0.1 }, ["daki:ult"] = { k = "c", r = 14, h = 3, dot = "bleed", ticks = 4, rate = 0.1 }, ["daki_evolved|live"] = { k = "c", r = 5 }, ["daki_evolved|live5"] = { k = "c", r = 5, h = 2 }, ["daki_evolved|live8"] = { k = "c", r = 5, h = 3 }, ["daki|daki:slam"] = { k = "c", r = 6, dot = "bleed", ticks = 4, rate = 0.1 }, ["daki|daki:slash"] = { k = "c", r = 5, dot = "bleed", ticks = 4, rate = 0.1 }, ["daki|daki:spike"] = { k = "c", r = 7, h = 2, dot = "bleed", ticks = 4, rate = 0.1 }, ["dazai_evolved|dazai:full_aoe"] = { k = "f" }, ["dazai_evolved|dazai:punch"] = { k = "c", r = 10 }, ["dazai_evolved|dazai:shoot"] = { k = "c", r = 8, h = 3 }, ["dazai|dazai:punch"] = { k = "c", r = 10 }, ["dazai|dazai:shoot"] = { k = "c", r = 8, h = 3 }, ["deku_vigilante:axe_kick"] = { k = "c", r = 6 }, ["deku_vigilante:blackwhip"] = { k = "c", r = 9 }, ["deku_vigilante:slingshot"] = { k = "l" }, ["deku_vigilante:whip_punch"] = { k = "l" }, ["deku_vigilante|deku_vigilante:axe_kick"] = { k = "c", r = 6 }, ["deku_vigilante|deku_vigilante:blackwhip"] = { k = "c", r = 9 }, ["deku_vigilante|deku_vigilante:whip_punch"] = { k = "l" }, ["demiurge:one"] = { k = "c", r = 8, dot = "burn", ticks = 4, rate = 0.1 }, ["demiurge:three"] = { k = "c", r = 12, dot = "burn", ticks = 4, rate = 0.1 }, ["demiurge:two"] = { k = "c", r = 10, dot = "burn", ticks = 4, rate = 0.1 }, ["demiurge_evolved|live"] = { k = "c", r = 8 }, ["demiurge|demiurge:one"] = { k = "c", r = 8, dot = "burn", ticks = 4, rate = 0.1 }, ["demiurge|demiurge:two"] = { k = "c", r = 10, dot = "burn", ticks = 4, rate = 0.1 }, ["doflamingo_evolved|doflamingo:birdcage"] = { k = "c", r = 10 }, ["doflamingo_evolved|doflamingo:bullets"] = { k = "c", r = 12, h = 3 }, ["doflamingo_evolved|doflamingo:overheat"] = { k = "c", r = 8 }, ["doflamingo_evolved|doflamingo:rainbow"] = { k = "c", r = 6.5 }, ["doflamingo_evolved|doflamingo:spikes"] = { k = "c", r = 9 }, ["doflamingo|doflamingo:overheat"] = { k = "c", r = 8 }, ["doflamingo|doflamingo:rainbow"] = { k = "c", r = 6.5 }, ["doflamingo|doflamingo:spikes"] = { k = "c", r = 9 }, ["dokuro_evolved|dokuro:one"] = { k = "c", r = 8 }, ["dokuro_evolved|dokuro:two"] = { k = "c", r = 13 }, ["dokuro|dokuro:one"] = { k = "c", r = 8 }, ["dokuro|dokuro:two"] = { k = "c", r = 13 }, ["elyssia_evolved|live"] = { k = "c", r = 12, h = 2 }, ["elyssia|live"] = { k = "s" }, ["evileye_evolved|evileye:one"] = { k = "c", r = 6 }, ["evileye_evolved|evileye:three"] = { k = "c", r = 13, h = 3 }, ["evileye_evolved|evileye:two"] = { k = "c", r = 7 }, ["evileye|evileye:one"] = { k = "c", r = 6 }, ["evileye|evileye:two"] = { k = "c", r = 7 }, ["feitan_evolved|feitan:one"] = { k = "c", r = 6 }, ["feitan_evolved|feitan:three"] = { k = "f", h = 6, dot = "burn", ticks = 6, rate = 0.0667 }, ["feitan_evolved|feitan:two"] = { k = "c", r = 8, h = 4 }, ["feitan|feitan:one"] = { k = "c", r = 6 }, ["feitan|feitan:two"] = { k = "c", r = 8, h = 4 }, ["frieren:one"] = { k = "c", r = 8 }, ["frieren:three"] = { k = "c", r = 11, h = 4 }, ["frieren:two"] = { k = "c", r = 9 }, ["frieren_evolved|live"] = { k = "c", r = 8 }, ["frieren_evolved|live6"] = { k = "c", r = 8, h = 4 }, ["frieren|frieren:one"] = { k = "c", r = 8 }, ["frieren|frieren:two"] = { k = "c", r = 9 }, ["fugo_evolved|haze:bulb_shoot"] = { k = "c", r = 8, h = 3, dot = "poison", ticks = 20, rate = 0.15 }, ["fugo_evolved|haze:bulb_throw"] = { k = "c", r = 6, h = 3, dot = "poison", ticks = 20, rate = 0.15 }, ["fugo_evolved|haze:slam"] = { k = "c", r = 10, h = 3, dot = "poison", ticks = 20, rate = 0.15 }, ["fugo|haze:bulb_shoot"] = { k = "c", r = 8, h = 3, dot = "poison", ticks = 20, rate = 0.15 }, ["fugo|haze:bulb_throw"] = { k = "c", r = 6, h = 3, dot = "poison", ticks = 20, rate = 0.15 }, ["fugo|haze:slam"] = { k = "c", r = 10, h = 3, dot = "poison", ticks = 20, rate = 0.15 }, ["fujitora:meteor"] = { k = "c", r = 10 }, ["fujitora:meteors"] = { k = "c", r = 16, h = 3 }, ["fujitora:touch"] = { k = "c", r = 8, h = 2 }, ["fujitora_evolved|live"] = { k = "c", r = 8, h = 2 }, ["fujitora_evolved|live3"] = { k = "c", r = 8 }, ["fujitora_evolved|live7"] = { k = "c", r = 8, h = 3 }, ["fujitora|fujitora:meteor"] = { k = "c", r = 10 }, ["fujitora|fujitora:touch"] = { k = "c", r = 8, h = 2 }, ["gabimaru_evolved|gabimaru:one"] = { k = "c", r = 7, dot = "burn", ticks = 4, rate = 0.1 }, ["gabimaru_evolved|gabimaru:three"] = { k = "c", r = 12, h = 3, dot = "burn", ticks = 4, rate = 0.1 }, ["gabimaru_evolved|gabimaru:two"] = { k = "c", r = 9, dot = "burn", ticks = 4, rate = 0.1 }, ["gabimaru|gabimaru:one"] = { k = "c", r = 7, dot = "burn", ticks = 4, rate = 0.1 }, ["gabimaru|gabimaru:two"] = { k = "c", r = 9, dot = "burn", ticks = 4, rate = 0.1 }, ["gaku_evolved|gaku:one"] = { k = "c", r = 8 }, ["gaku_evolved|gaku:three"] = { k = "c", r = 15, h = 2 }, ["gaku_evolved|gaku:two"] = { k = "c", r = 13 }, ["gaku|gaku:one"] = { k = "c", r = 8 }, ["gaku|gaku:two"] = { k = "c", r = 13 }, ["ghostkun:one"] = { k = "c", r = 8, dot = "burn", ticks = 4, rate = 0.125 }, ["ghostkun:three"] = { k = "n", r = 60, h = 9, dot = "burn", ticks = 4, rate = 0.125 }, ["ghostkun:two"] = { k = "c", r = 11, h = 3, dot = "burn", ticks = 4, rate = 0.125 }, ["ghostkun_evolved|live"] = { k = "c", r = 8 }, ["ghostkun_evolved|live2"] = { k = "c", r = 8, h = 3 }, ["ghostkun_evolved|live6"] = { k = "c", r = 8, h = 9 }, ["ghostkun|hanako_kun:one"] = { k = "c", r = 8, dot = "burn", ticks = 4, rate = 0.125 }, ["ghostkun|hanako_kun:two"] = { k = "c", r = 11, h = 3, dot = "burn", ticks = 4, rate = 0.125 }, ["gilgamesh_evolved|gilgamesh:collapse"] = { k = "c", r = 9 }, ["gilgamesh_evolved|gilgamesh:enuma_elish"] = { k = "c", r = 16, h = 5 }, ["gilgamesh_evolved|gilgamesh:jump_and_gates"] = { k = "c", r = 12, h = 4 }, ["gilgamesh_evolved|gilgamesh:wheel_gates"] = { k = "l", h = 3 }, ["gilgamesh|gilgamesh:collapse"] = { k = "c", r = 9 }, ["gilgamesh|gilgamesh:jump_and_gates"] = { k = "c", r = 12, h = 4 }, ["gilgamesh|gilgamesh:wheel_gates"] = { k = "l", h = 3 }, ["giselle_evolved|giselle:one"] = { k = "l" }, ["giselle_evolved|giselle:three"] = { k = "s" }, ["giselle_evolved|giselle:two"] = { k = "c", r = 4 }, ["giselle|giselle:one"] = { k = "l" }, ["giselle|giselle:two"] = { k = "c", r = 4 }, ["goku_ssj3:one"] = { k = "c", r = 8, h = 3 }, ["goku_ssj3:three"] = { k = "c", r = 15, h = 3 }, ["goku_ssj3:two"] = { k = "c", r = 12, h = 3 }, ["goku_ssj3_evolved|live"] = { k = "c", r = 8, h = 3 }, ["goku_ssj3|goku_ssj3:one"] = { k = "c", r = 8, h = 3 }, ["goku_ssj3|goku_ssj3:two"] = { k = "c", r = 12, h = 3 }, ["gremmy:one"] = { k = "n", r = 60, h = 3 }, ["gremmy:three"] = { k = "c", r = 20 }, ["gremmy:two"] = { k = "c", r = 14, h = 2 }, ["gremmy_evolved|live"] = { k = "n", r = 60, h = 3 }, ["gremmy_evolved|live3"] = { k = "n", r = 60, h = 2 }, ["gremmy_evolved|live7"] = { k = "n", r = 60 }, ["gremmy|gremmy:one"] = { k = "n", r = 60, h = 3 }, ["gremmy|gremmy:two"] = { k = "c", r = 14, h = 2 }, ["griffith|griffith:combo"] = { k = "n", r = 50, h = 4 }, ["griffith|griffith:stabs"] = { k = "n", r = 50, h = 4 }, ["grimmjow_res_evolved|rebirth_grimmjow:one"] = { k = "n", r = 45, h = 3 }, ["grimmjow_res_evolved|rebirth_grimmjow:three"] = { k = "n", r = 50, h = 3 }, ["grimmjow_res_evolved|rebirth_grimmjow:two"] = { k = "c", r = 10, h = 2 }, ["grimmjow_res|rebirth_grimmjow:one"] = { k = "n", r = 45, h = 3 }, ["grimmjow_res|rebirth_grimmjow:two"] = { k = "c", r = 10, h = 2 }, ["guts_berserker:four"] = { k = "f", h = 5 }, ["guts_berserker:one"] = { k = "n", r = 60, h = 3, dot = "bleed", ticks = 4, rate = 0.0625 }, ["guts_berserker:three"] = { k = "n", r = 60, h = 6, dot = "bleed", ticks = 4, rate = 0.0625 }, ["guts_berserker:two"] = { k = "n", r = 60, h = 2, dot = "bleed", ticks = 4, rate = 0.0625 }, ["guts_berserker|live"] = { k = "n", r = 60, h = 2 }, ["guts_berserker|live2"] = { k = "n", r = 60, h = 3 }, ["guts_berserker|live5"] = { k = "n", r = 60, h = 6 }, ["guts_berserker|live8"] = { k = "n", r = 60, h = 5 }, ["guts|guts:cannon"] = { k = "c", r = 8, dot = "bleed", ticks = 3, rate = 0.08 }, ["guts|guts:combo"] = { k = "n", r = 60, h = 4 }, ["guts|guts:slash"] = { k = "c", r = 7, dot = "bleed", ticks = 3, rate = 0.08 }, ["gyutaro:one"] = { k = "c", r = 5, h = 3 }, ["gyutaro:three"] = { k = "n", r = 50, h = 3 }, ["gyutaro:two"] = { k = "c", r = 8, h = 3 }, ["gyutaro_evolved|live"] = { k = "c", r = 5, h = 3 }, ["gyutaro|gyutaro:one"] = { k = "c", r = 5, h = 3 }, ["gyutaro|gyutaro:two"] = { k = "c", r = 8, h = 3 }, ["hakari:one"] = { k = "l" }, ["hakari:three"] = { k = "s", h = 3 }, ["hakari:two"] = { k = "c", r = 4 }, ["hakari_evolved|live"] = { k = "l" }, ["hakari_evolved|live6"] = { k = "l", h = 3 }, ["hakari|hakari:one"] = { k = "l" }, ["hakari|hakari:two"] = { k = "c", r = 4 }, ["harribel_evolved|harribel:one"] = { k = "c", r = 6, h = 3 }, ["harribel_evolved|harribel:three"] = { k = "c", r = 9, h = 3 }, ["harribel_evolved|harribel:two"] = { k = "c", r = 8 }, ["harribel|harribel:one"] = { k = "c", r = 6, h = 3 }, ["harribel|harribel:two"] = { k = "c", r = 8 }, ["heathcliff_evolved|heathcliff:bash_and_slash"] = { k = "c", r = 10, h = 2 }, ["heathcliff_evolved|heathcliff:full_sweep"] = { k = "c", r = 15, h = 3 }, ["heathcliff_evolved|heathcliff:shield_bash"] = { k = "n", r = 40 }, ["heathcliff_evolved|heathcliff:stabs"] = { k = "c", r = 8, h = 3 }, ["heathcliff|heathcliff:bash_and_slash"] = { k = "c", r = 10, h = 2 }, ["heathcliff|heathcliff:shield_bash"] = { k = "n", r = 40 }, ["heathcliff|heathcliff:stabs"] = { k = "c", r = 8, h = 3 }, ["hei_evolved|hei:one"] = { k = "c", r = 7 }, ["hei_evolved|hei:three"] = { k = "f", h = 4 }, ["hei_evolved|hei:two"] = { k = "c", r = 10, h = 2 }, ["hei|hei:one"] = { k = "c", r = 7 }, ["hei|hei:two"] = { k = "c", r = 10, h = 2 }, ["hidan_unit:one"] = { k = "c", r = 8, dot = "bleed", ticks = 3, rate = 0.1 }, ["hidan_unit:three"] = { k = "n", r = 60, h = 3, dot = "bleed", ticks = 3, rate = 0.1 }, ["hidan_unit:two"] = { k = "c", r = 12, h = 2, dot = "bleed", ticks = 3, rate = 0.1 }, ["hidan_unit_evolved|live"] = { k = "c", r = 8 }, ["hidan_unit_evolved|live3"] = { k = "c", r = 8, h = 2 }, ["hidan_unit_evolved|live6"] = { k = "c", r = 8, h = 3 }, ["hidan_unit|hidan_unit:one"] = { k = "c", r = 8, dot = "bleed", ticks = 3, rate = 0.1 }, ["hidan_unit|hidan_unit:two"] = { k = "c", r = 12, h = 2, dot = "bleed", ticks = 3, rate = 0.1 }, ["hinami_evolved|hinami:one"] = { k = "n", r = 45 }, ["hinami_evolved|hinami:three"] = { k = "c", r = 12 }, ["hinami_evolved|hinami:two"] = { k = "c", r = 10 }, ["hinami|hinami:one"] = { k = "n", r = 45 }, ["hinami|hinami:two"] = { k = "c", r = 10 }, ["homura:grenade"] = { k = "c", r = 7 }, ["homura:shoot"] = { k = "c", r = 5, h = 3 }, ["homura:ult"] = { k = "c", r = 14, h = 3 }, ["homura_evolved|live"] = { k = "c", r = 5, h = 3 }, ["homura_evolved|live3"] = { k = "c", r = 5 }, ["homura_evolved|live7"] = { k = "c", r = 5, h = 3 }, ["homura|homura:grenade"] = { k = "c", r = 7 }, ["homura|homura:shoot"] = { k = "c", r = 5, h = 3 }, ["hoshino_2_evolved|live"] = { k = "s" }, ["hoshino_2|live"] = { k = "s" }, ["illya:one"] = { k = "c", r = 6 }, ["illya:three"] = { k = "c", r = 14, h = 3 }, ["illya:two"] = { k = "c", r = 10 }, ["illya|illya:one"] = { k = "c", r = 6 }, ["illya|illya:two"] = { k = "c", r = 10 }, ["ippo|ippo:basic"] = { k = "n", r = 40, h = 4 }, ["ippo|ippo:ult"] = { k = "n", r = 50, h = 7 }, ["isagi|isagi:one"] = { k = "l" }, ["isagi|isagi:three"] = { k = "l" }, ["isagi|isagi:two"] = { k = "c", r = 6 }, ["issei_evolved|issei:one"] = { k = "c", r = 6 }, ["issei_evolved|issei:three"] = { k = "n", r = 45, h = 3 }, ["issei_evolved|issei:two"] = { k = "l", h = 2 }, ["issei|issei:one"] = { k = "c", r = 6 }, ["issei|issei:two"] = { k = "l", h = 2 }, ["izumi_evolved|izumi:one"] = { k = "c", r = 8 }, ["izumi_evolved|izumi:three"] = { k = "c", r = 10, h = 4 }, ["izumi_evolved|izumi:two"] = { k = "c", r = 9, h = 3 }, ["izumi|izumi:one"] = { k = "c", r = 8 }, ["izumi|izumi:two"] = { k = "c", r = 9, h = 3 }, ["jiraiya:one"] = { k = "l", h = 3, slow = 0.5, t = 4 }, ["jiraiya:three"] = { k = "n", r = 60, h = 3, slow = 0.5, t = 4 }, ["jiraiya:two"] = { k = "c", r = 12, slow = 0.5, t = 4 }, ["jiraiya_evolved|live"] = { k = "l", h = 3 }, ["jiraiya_evolved|live3"] = { k = "l" }, ["jiraiya_evolved|live7"] = { k = "l", h = 3 }, ["jiraiya|jiraiya:one_unevo"] = { k = "l", h = 3 }, ["jiraiya|jiraiya:two_unevo"] = { k = "c", r = 12 }, ["josuke_2:one"] = { k = "c", r = 6, stun = 1 }, ["josuke_2:three"] = { k = "c", r = 12, h = 2, stun = 1 }, ["josuke_2:two"] = { k = "c", r = 8, h = 4, stun = 1 }, ["josuke_2_evolved|live"] = { k = "c", r = 6 }, ["josuke_2_evolved|live3"] = { k = "c", r = 6, h = 4 }, ["josuke_2_evolved|live6"] = { k = "c", r = 6, h = 2 }, ["josuke_2|josuke_2:one"] = { k = "c", r = 6, stun = 1 }, ["josuke_2|josuke_2:two"] = { k = "c", r = 8, h = 4, stun = 1 }, ["judar:four"] = { k = "f", h = 3 }, ["judar:one"] = { k = "c", r = 8, h = 2 }, ["judar:three"] = { k = "n", r = 50, h = 3 }, ["judar:two"] = { k = "c", r = 12 }, ["judar_evolved|live"] = { k = "c", r = 8, h = 2 }, ["judar_evolved|live2"] = { k = "c", r = 8 }, ["judar_evolved|live4"] = { k = "c", r = 8, h = 3 }, ["judar|judar:one"] = { k = "c", r = 8, h = 2 }, ["judar|judar:three"] = { k = "n", r = 50, h = 3 }, ["judar|judar:two"] = { k = "c", r = 12 }, ["kenshin|kenshin:one"] = { k = "c", r = 7 }, ["kenshin|kenshin:three"] = { k = "c", r = 10, h = 2 }, ["kenshin|kenshin:two"] = { k = "c", r = 8 }, ["kimimaro|kimimaro:one"] = { k = "l" }, ["kimimaro|kimimaro:three"] = { k = "c", r = 12 }, ["kimimaro|kimimaro:two"] = { k = "c", r = 8 }, ["kira:bubble"] = { k = "c", r = 9 }, ["kira:coin_toss"] = { k = "c", r = 6 }, ["kira:heart_attack"] = { k = "c", r = 8 }, ["kira:triple_bomb"] = { k = "c", r = 12, h = 3 }, ["kira_evolved|live"] = { k = "c", r = 6 }, ["kira_evolved|live7"] = { k = "c", r = 6, h = 3 }, ["kira|kira:coin_toss"] = { k = "c", r = 6 }, ["kira|kira:heart_attack"] = { k = "c", r = 8 }, ["kirito_evolved|kirito:double"] = { k = "n", r = 40, h = 2 }, ["kirito_evolved|kirito:square"] = { k = "c", r = 8, h = 3 }, ["kirito_evolved|kirito:stab"] = { k = "n", r = 40 }, ["kirito_evolved|kirito_evolved:slice"] = { k = "c", r = 6 }, ["kirito_evolved|kirito_evolved:stream"] = { k = "c", r = 12, h = 6 }, ["kirito|kirito:double"] = { k = "n", r = 40, h = 2 }, ["kirito|kirito:square"] = { k = "c", r = 8, h = 3 }, ["kirito|kirito:stab"] = { k = "n", r = 40 }, ["kisame_unit_evolved|kisame:one"] = { k = "l" }, ["kisame_unit_evolved|kisame:three"] = { k = "c", r = 11 }, ["kisame_unit_evolved|kisame:two"] = { k = "c", r = 8 }, ["kisame_unit|kisame:one"] = { k = "l" }, ["kisame_unit|kisame:two"] = { k = "c", r = 8 }, ["klein_evolved|klein:final"] = { k = "c", r = 10, h = 3 }, ["klein_evolved|klein:slash"] = { k = "c", r = 7 }, ["klein_evolved|klein:stab"] = { k = "n", r = 45 }, ["klein|klein:slash"] = { k = "c", r = 7 }, ["klein|klein:stab"] = { k = "n", r = 45 }, ["koichi:one"] = { k = "c", r = 5, h = 3, slow = 0.5, t = 2.5 }, ["koichi:three"] = { k = "c", r = 12, h = 3, slow = 0.8, t = 2.5 }, ["koichi:two"] = { k = "c", r = 8, slow = 0.65, t = 2.5 }, ["koichi_evolved|live"] = { k = "c", r = 5, h = 3 }, ["koichi|koichi:one"] = { k = "c", r = 5, h = 3 }, ["koichi|koichi:two"] = { k = "c", r = 8 }, ["konan_unit:four"] = { k = "n", r = 50, h = 4 }, ["konan_unit:one"] = { k = "c", r = 10 }, ["konan_unit:three"] = { k = "n", r = 50, h = 3 }, ["konan_unit:two"] = { k = "c", r = 10 }, ["konan_unit_evolved|live"] = { k = "c", r = 10 }, ["konan_unit_evolved|live5"] = { k = "c", r = 10, h = 3 }, ["konan_unit_evolved|live8"] = { k = "c", r = 10, h = 4 }, ["konan_unit|konan_unit:one"] = { k = "c", r = 10 }, ["konan_unit|konan_unit:three"] = { k = "n", r = 50, h = 3 }, ["konan_unit|konan_unit:two"] = { k = "c", r = 10 }, ["kyoko_evolved|kyoko:combo"] = { k = "c", r = 8, h = 4 }, ["kyoko_evolved|kyoko:final"] = { k = "c", r = 12, h = 2 }, ["kyoko_evolved|kyoko:stab"] = { k = "l" }, ["kyoko|kyoko:combo"] = { k = "c", r = 8, h = 4 }, ["kyoko|kyoko:stab"] = { k = "l" }, ["lancer_evolved|lancer:four"] = { k = "c", r = 9, h = 3 }, ["lancer_evolved|lancer:one"] = { k = "c", r = 7 }, ["lancer_evolved|lancer:three"] = { k = "c", r = 8, h = 4 }, ["lancer_evolved|lancer:two"] = { k = "c", r = 12 }, ["lancer|lancer:one"] = { k = "c", r = 7 }, ["lancer|lancer:three"] = { k = "c", r = 8, h = 4 }, ["lancer|lancer:two"] = { k = "c", r = 12 }, ["law_2:gamma"] = { k = "c", r = 11, h = 3 }, ["law_2:radio"] = { k = "c", r = 7 }, ["law_2:shock"] = { k = "c", r = 9 }, ["law_2:shot"] = { k = "n", r = 30 }, ["law_2_evolved|live"] = { k = "n", r = 30 }, ["law_2_evolved|live8"] = { k = "n", r = 30, h = 3 }, ["law_2|law_2:radio"] = { k = "c", r = 7 }, ["law_2|law_2:shock"] = { k = "c", r = 9 }, ["law_2|law_2:shot"] = { k = "n", r = 30 }, ["leafa:one"] = { k = "c", r = 6 }, ["leafa:two"] = { k = "c", r = 8, h = 3 }, ["leafa_evolved|live"] = { k = "c", r = 6 }, ["leafa_evolved|live3"] = { k = "c", r = 6, h = 3 }, ["leafa|leafa:one"] = { k = "c", r = 6 }, ["leafa|leafa:two"] = { k = "c", r = 8, h = 3 }, ["luffy_g4_evolved|luffy_g4:final"] = { k = "c", r = 15 }, ["luffy_g4_evolved|luffy_g4:gatling"] = { k = "c", r = 12, h = 4 }, ["luffy_g4_evolved|luffy_g4:organ"] = { k = "c", r = 9, h = 4 }, ["luffy_g4_evolved|luffy_g4:punch"] = { k = "c", r = 7.5 }, ["luffy_g4|luffy_g4:gatling"] = { k = "c", r = 12, h = 4 }, ["luffy_g4|luffy_g4:organ"] = { k = "c", r = 9, h = 4 }, ["luffy_g4|luffy_g4:punch"] = { k = "c", r = 7.5 }, ["madoka_evolved|madoka:one"] = { k = "c", r = 7 }, ["madoka_evolved|madoka:three"] = { k = "c", r = 11 }, ["madoka_evolved|madoka:two"] = { k = "c", r = 9, h = 3 }, ["madoka|madoka:one"] = { k = "c", r = 7 }, ["madoka|madoka:two"] = { k = "c", r = 9, h = 3 }, ["mami_evolved|mami:one"] = { k = "n", r = 40 }, ["mami_evolved|mami:three"] = { k = "c", r = 15, h = 3 }, ["mami_evolved|mami:two"] = { k = "c", r = 8 }, ["mami|mami:one"] = { k = "n", r = 40 }, ["mami|mami:two"] = { k = "c", r = 8 }, ["mash_evolved|mash:counters"] = { k = "c", r = 12, h = 3 }, ["mash_evolved|mash:punches"] = { k = "c", r = 6, h = 2 }, ["mash_evolved|mash:throw"] = { k = "c", r = 8 }, ["mash|mash:punches"] = { k = "c", r = 6, h = 2 }, ["mash|mash:throw"] = { k = "c", r = 8 }, ["mechamaru|mechamaru:one"] = { k = "c", r = 9 }, ["mechamaru|mechamaru:three"] = { k = "n", r = 60, h = 3 }, ["mechamaru|mechamaru:two"] = { k = "c", r = 12, h = 3 }, ["medea:one"] = { k = "c", r = 7 }, ["medea:three"] = { k = "c", r = 9, h = 3 }, ["medea:two"] = { k = "c", r = 8, h = 3 }, ["medea|medea:one"] = { k = "c", r = 7 }, ["medea|medea:two"] = { k = "c", r = 8, h = 3 }, ["megumi2_evolved|megumi_2:one"] = { k = "c", r = 4, amp = 0.3 }, ["megumi2_evolved|megumi_2:three"] = { k = "s", amp = 0.3 }, ["megumi2_evolved|megumi_2:two"] = { k = "s", h = 3, amp = 0.3 }, ["megumi2|megumi_2:one"] = { k = "c", r = 4, amp = 0.3 }, ["megumi2|megumi_2:two"] = { k = "s", h = 3, amp = 0.3 }, ["minato|minato:first"] = { k = "c", r = 7 }, ["minato|minato:second"] = { k = "c", r = 9 }, ["minato|minato:third"] = { k = "c", r = 12, h = 3 }, ["mirko_evolved|mirko:final"] = { k = "c", r = 10 }, ["mirko_evolved|mirko:kick"] = { k = "c", r = 8 }, ["mirko_evolved|mirko:spin"] = { k = "c", r = 9, h = 2 }, ["mirko|mirko:final"] = { k = "c", r = 10 }, ["mirko|mirko:kick"] = { k = "c", r = 8 }, ["mirko|mirko:spin"] = { k = "c", r = 9, h = 2 }, ["monet:one"] = { k = "n", r = 45, slow = 0.5, t = 4 }, ["monet:three"] = { k = "c", r = 12, h = 2, slow = 0.5, t = 4 }, ["monet:two"] = { k = "c", r = 9, slow = 0.5, t = 4 }, ["monet_evolved|live"] = { k = "n", r = 45 }, ["monet_evolved|live6"] = { k = "n", r = 45, h = 2 }, ["monet|monet:one"] = { k = "n", r = 45, slow = 0.5, t = 4 }, ["monet|monet:two"] = { k = "c", r = 9, slow = 0.5, t = 4 }, ["nagumo:one"] = { k = "c", r = 8, amp = 0.25, keep = true }, ["nagumo:three"] = { k = "c", r = 12, h = 3, amp = 0.25, keep = true }, ["nagumo:two"] = { k = "c", r = 10, h = 3, amp = 0.25, keep = true }, ["nagumo_evolved|live"] = { k = "c", r = 8 }, ["nagumo_evolved|live3"] = { k = "c", r = 8, h = 3 }, ["nagumo|nagumo:one"] = { k = "c", r = 8 }, ["nagumo|nagumo:two"] = { k = "c", r = 10, h = 3 }, ["narberal:one"] = { k = "l" }, ["narberal:three"] = { k = "c", r = 9 }, ["narberal:two"] = { k = "c", r = 7 }, ["narberal_evolved|live"] = { k = "l" }, ["narberal|narberal:one"] = { k = "l" }, ["narberal|narberal:two"] = { k = "c", r = 7 }, ["naruto_6tails:one"] = { k = "c", r = 9 }, ["naruto_6tails:three"] = { k = "c", r = 13 }, ["naruto_6tails:two"] = { k = "c", r = 11, h = 3 }, ["naruto_sage|naruto_sage:one"] = { k = "c", r = 9 }, ["naruto_sage|naruto_sage:two"] = { k = "c", r = 11 }, ["nejire|nejire:one"] = { k = "n", r = 30 }, ["nejire|nejire:three"] = { k = "n", r = 30 }, ["nejire|nejire:two"] = { k = "n", r = 30 }, ["nokotan|nokotan:one"] = { k = "c", r = 7, h = 2 }, ["nokotan|nokotan:three"] = { k = "c", r = 14, h = 3 }, ["nokotan|nokotan:two"] = { k = "l", h = 4 }, ["osaka_evolved|kasuga:one"] = { k = "c", r = 6 }, ["osaka_evolved|kasuga:three"] = { k = "c", r = 12 }, ["osaka_evolved|kasuga:two"] = { k = "l", h = 3 }, ["osaka|kasuga:one"] = { k = "c", r = 6 }, ["osaka|kasuga:two"] = { k = "l", h = 3 }, ["osaragi_evolved|osaragi:one"] = { k = "c", r = 7, dot = "bleed", ticks = 6, rate = 0.0583 }, ["osaragi_evolved|osaragi:three"] = { k = "c", r = 15, h = 5, dot = "bleed", ticks = 6, rate = 0.0583 }, ["osaragi_evolved|osaragi:two"] = { k = "n", r = 45, h = 5, dot = "bleed", ticks = 6, rate = 0.0583 }, ["osaragi|osaragi:one"] = { k = "c", r = 7, dot = "bleed", ticks = 6, rate = 0.0583 }, ["osaragi|osaragi:two"] = { k = "n", r = 45, h = 5, dot = "bleed", ticks = 6, rate = 0.0583 }, ["pain_2:four"] = { k = "c", r = 19, h = 4 }, ["pain_2:one"] = { k = "c", r = 8, h = 2 }, ["pain_2:three"] = { k = "c", r = 14, knock = 1 }, ["pain_2:two"] = { k = "n", r = 45 }, ["pain_2_evolved|live"] = { k = "c", r = 8, h = 2 }, ["pain_2_evolved|live3"] = { k = "c", r = 8 }, ["pain_2_evolved|live8"] = { k = "c", r = 8, h = 4 }, ["pain_2|pain_2:one"] = { k = "c", r = 8, h = 2 }, ["pain_2|pain_2:two"] = { k = "n", r = 45 }, ["poseidon_evolved|poseidon:one"] = { k = "n", r = 50, h = 3 }, ["poseidon_evolved|poseidon:three"] = { k = "f", h = 4 }, ["poseidon_evolved|poseidon:two"] = { k = "c", r = 8, h = 3 }, ["poseidon|poseidon:one"] = { k = "n", r = 50, h = 3 }, ["poseidon|poseidon:two"] = { k = "c", r = 8, h = 3 }, ["ragna_evolved|ragna:one"] = { k = "c", r = 8, h = 3 }, ["ragna_evolved|ragna:three"] = { k = "c", r = 4, h = 3 }, ["ragna_evolved|ragna:two"] = { k = "l", h = 3 }, ["ragna|ragna:one"] = { k = "c", r = 8, h = 3 }, ["ragna|ragna:two"] = { k = "l", h = 3 }, ["rayleigh|rayleigh:attack_1"] = { k = "c", r = 6, h = 3 }, ["rayleigh|rayleigh:attack_2"] = { k = "c", r = 8, h = 3 }, ["rayleigh|rayleigh:final"] = { k = "c", r = 10, h = 3 }, ["rikka:one"] = { k = "c", r = 12 }, ["rikka:three"] = { k = "f", h = 4 }, ["rikka:two"] = { k = "c", r = 16, h = 4 }, ["rikka|live"] = { k = "c", r = 12 }, ["rikka|live3"] = { k = "c", r = 12, h = 4 }, ["roger:projectile_slash"] = { k = "c", r = 9, stun = 1 }, ["roger:slashes"] = { k = "c", r = 8, h = 3, stun = 1 }, ["roger:ult"] = { k = "c", r = 11, h = 3, stun = 1 }, ["roger_evolved|live"] = { k = "c", r = 8, h = 3 }, ["roger|roger:projectile_slash"] = { k = "c", r = 9 }, ["roger|roger:slashes"] = { k = "c", r = 8, h = 3 }, ["rohan_evolved|rohan:one"] = { k = "c", r = 5 }, ["rohan_evolved|rohan:three"] = { k = "c", r = 6, h = 3 }, ["rohan_evolved|rohan:two"] = { k = "c", r = 5.5, h = 3 }, ["rohan|rohan:one"] = { k = "c", r = 5 }, ["rohan|rohan:two"] = { k = "c", r = 5.5, h = 3 }, ["roshi|roshi:one"] = { k = "n", r = 40 }, ["roshi|roshi:three"] = { k = "c", r = 9, h = 3 }, ["roshi|roshi:two"] = { k = "c", r = 7 }, ["rukia_bankai_evolved|rukia_2:one"] = { k = "c", r = 9, h = 3 }, ["rukia_bankai_evolved|rukia_2:three"] = { k = "f", h = 2 }, ["rukia_bankai_evolved|rukia_2:two"] = { k = "n", r = 60, h = 2 }, ["rukia_bankai|rukia_2:one"] = { k = "c", r = 9, h = 3 }, ["rukia_bankai|rukia_2:two"] = { k = "n", r = 60, h = 2 }, ["saber:four"] = { k = "c", r = 12 }, ["saber:one"] = { k = "c", r = 7.5 }, ["saber:three"] = { k = "n", r = 50, h = 3 }, ["saber:two"] = { k = "c", r = 9 }, ["saber|saber:one"] = { k = "c", r = 7.5 }, ["saber|saber:three"] = { k = "n", r = 50, h = 3 }, ["saber|saber:two"] = { k = "c", r = 9 }, ["sabo:fireball"] = { k = "c", r = 9, dot = "burn", ticks = 4, rate = 0.1 }, ["sabo:leap"] = { k = "c", r = 9, dot = "burn", ticks = 4, rate = 0.1 }, ["sabo:three"] = { k = "c", r = 11, dot = "burn", ticks = 4, rate = 0.1 }, ["sabo_evolved|live"] = { k = "c", r = 9 }, ["sabo|sabo:fireball"] = { k = "c", r = 9, dot = "burn", ticks = 4, rate = 0.1 }, ["sabo|sabo:leap"] = { k = "c", r = 9, dot = "burn", ticks = 4, rate = 0.1 }, ["sakamoto:five"] = { k = "c", r = 16, h = 3 }, ["sakamoto:four"] = { k = "c", r = 12, h = 4 }, ["sakamoto:one"] = { k = "c", r = 7, h = 3 }, ["sakamoto:three"] = { k = "n", r = 60 }, ["sakamoto:two"] = { k = "c", r = 10 }, ["sakamoto_evolved|live"] = { k = "n", r = 60 }, ["sakamoto_evolved|live2"] = { k = "n", r = 60, h = 3 }, ["sakamoto_evolved|live4"] = { k = "n", r = 60 }, ["sakamoto_evolved|live6"] = { k = "n", r = 60, h = 4 }, ["sakamoto_evolved|live8"] = { k = "n", r = 60, h = 3 }, ["sakamoto|sakamoto:four"] = { k = "c", r = 12, h = 4 }, ["sakamoto|sakamoto:one"] = { k = "n", r = 60 }, ["sakamoto|sakamoto:three"] = { k = "c", r = 10 }, ["sakamoto|sakamoto:two"] = { k = "c", r = 7, h = 3 }, ["sasuke_3:four"] = { k = "c", r = 15, h = 4, dot = "burn", ticks = 5, rate = 0.2 }, ["sasuke_3:one"] = { k = "c", r = 9, h = 3, dot = "burn", ticks = 5, rate = 0.2 }, ["sasuke_3:three"] = { k = "c", r = 13, dot = "burn", ticks = 5, rate = 0.2 }, ["sasuke_3:two"] = { k = "c", r = 11, h = 3, dot = "burn", ticks = 5, rate = 0.2 }, ["sasuke_3_evolved|live"] = { k = "c", r = 9, h = 3 }, ["sasuke_3_evolved|live5"] = { k = "c", r = 9 }, ["sasuke_3_evolved|live8"] = { k = "c", r = 9, h = 4 }, ["sasuke_3|sasuke_3:one"] = { k = "c", r = 9, h = 3, dot = "burn", ticks = 5, rate = 0.2 }, ["sasuke_3|sasuke_3:two"] = { k = "c", r = 11, h = 3, dot = "burn", ticks = 5, rate = 0.2 }, ["sato|sato:one"] = { k = "n", r = 45, h = 3 }, ["sato|sato:three"] = { k = "s", h = 3 }, ["sato|sato:two"] = { k = "c", r = 4 }, ["sayaka_evolved|sayaka:dash"] = { k = "c", r = 7 }, ["sayaka_evolved|sayaka:final"] = { k = "c", r = 9, h = 2 }, ["sayaka_evolved|sayaka:slash"] = { k = "c", r = 6 }, ["sayaka|sayaka:dash"] = { k = "c", r = 7 }, ["sayaka|sayaka:slash"] = { k = "c", r = 6 }, ["sengoku_unit_evolved|sengoku:kamehameha"] = { k = "n", r = 75 }, ["sengoku_unit_evolved|sengoku:shockwave"] = { k = "c", r = 20, h = 3 }, ["sengoku_unit_evolved|sengoku:slams"] = { k = "n", r = 60, h = 2 }, ["sengoku_unit|sengoku:kamehameha"] = { k = "n", r = 75 }, ["sengoku_unit|sengoku:slams"] = { k = "n", r = 60, h = 2 }, ["shalltear:one"] = { k = "n", r = 50 }, ["shalltear:three"] = { k = "c", r = 12, h = 3 }, ["shalltear:two"] = { k = "c", r = 9 }, ["shalltear_evolved|live"] = { k = "n", r = 50 }, ["shalltear_evolved|live6"] = { k = "n", r = 50, h = 3 }, ["shalltear|shalltear:one"] = { k = "n", r = 50 }, ["shalltear|shalltear:two"] = { k = "c", r = 9 }, ["shidou|shidou:one"] = { k = "l" }, ["shidou|shidou:two"] = { k = "c", r = 15 }, ["skull_knight_evolved|skull_knight:one"] = { k = "c", r = 7 }, ["skull_knight_evolved|skull_knight:three"] = { k = "c", r = 12, h = 3 }, ["skull_knight_evolved|skull_knight:two"] = { k = "c", r = 10 }, ["skull_knight|skull_knight:one"] = { k = "c", r = 7 }, ["skull_knight|skull_knight:two"] = { k = "c", r = 10 }, ["smoker_unit_evolved|smoker:one"] = { k = "c", r = 8, h = 3 }, ["smoker_unit_evolved|smoker:three"] = { k = "c", r = 12, h = 3 }, ["smoker_unit_evolved|smoker:two"] = { k = "c", r = 10, h = 3 }, ["smoker_unit|smoker:one"] = { k = "c", r = 8, h = 3 }, ["smoker_unit|smoker:two"] = { k = "c", r = 10, h = 3 }, ["stain:one"] = { k = "c", r = 8, dot = "bleed", ticks = 3, rate = 0.1 }, ["stain:three"] = { k = "c", r = 15, h = 3, dot = "bleed", ticks = 3, rate = 0.1 }, ["stain:two"] = { k = "c", r = 12, h = 3, dot = "bleed", ticks = 3, rate = 0.1 }, ["stain_evolved|live"] = { k = "c", r = 8 }, ["stain_evolved|live4"] = { k = "c", r = 8, h = 3 }, ["stain|stain:one"] = { k = "c", r = 8, dot = "bleed", ticks = 3, rate = 0.1 }, ["stain|stain:two"] = { k = "c", r = 12, h = 3, dot = "bleed", ticks = 3, rate = 0.1 }, ["starlia_evolved|starlia:one"] = { k = "c", r = 8 }, ["starlia_evolved|starlia:two"] = { k = "c", r = 10 }, ["starlia|starlia:one"] = { k = "c", r = 8 }, ["starlia|starlia:two"] = { k = "c", r = 10 }, ["tengen_2:combo_1"] = { k = "c", r = 8, h = 3 }, ["tengen_2:combo_2"] = { k = "c", r = 9, h = 4 }, ["tengen_2:slam"] = { k = "c", r = 7 }, ["tengen_2:ult"] = { k = "f", h = 5 }, ["tengen_2_evolved|live"] = { k = "c", r = 7 }, ["tengen_2_evolved|live2"] = { k = "c", r = 7, h = 3 }, ["tengen_2_evolved|live5"] = { k = "c", r = 7, h = 4 }, ["tengen_2_evolved|live8"] = { k = "c", r = 7, h = 5 }, ["tengen_2|tengen_2:combo_1"] = { k = "c", r = 8, h = 3 }, ["tengen_2|tengen_2:combo_2"] = { k = "c", r = 9, h = 4 }, ["tengen_2|tengen_2:slam"] = { k = "c", r = 7 }, ["toji_evolved|live"] = { k = "c", r = 4, h = 3 }, ["toji_evolved|live2"] = { k = "c", r = 4, h = 2 }, ["toji_evolved|live4"] = { k = "c", r = 4, h = 3 }, ["toji_evolved|live8"] = { k = "c", r = 4 }, ["toji|toji:one"] = { k = "c", r = 4, h = 3 }, ["toji|toji:three"] = { k = "s", h = 3 }, ["toji|toji:two"] = { k = "s", h = 2 }, ["tsuna_evolved|tsuna:one"] = { k = "c", r = 9, h = 3, dot = "burn", ticks = 5, rate = 0.08 }, ["tsuna_evolved|tsuna:three"] = { k = "n", r = 60, h = 2, dot = "burn", ticks = 5, rate = 0.08 }, ["tsuna_evolved|tsuna:two"] = { k = "n", r = 50, dot = "burn", ticks = 5, rate = 0.08 }, ["tsuna|tsuna:one"] = { k = "c", r = 9, h = 3, dot = "burn", ticks = 5, rate = 0.08 }, ["tsuna|tsuna:two"] = { k = "n", r = 50, dot = "burn", ticks = 5, rate = 0.08 }, ["usopp_ts:trap"] = { k = "c", r = 4 }, ["usopp_ts:trap2"] = { k = "c", r = 6 }, ["usopp_ts:trap3"] = { k = "c", r = 8 }, ["usopp_ts_evolved|live"] = { k = "c", r = 4 }, ["usopp_ts|usopp_ts:trap"] = { k = "c", r = 4 }, ["usopp_ts|usopp_ts:trap2"] = { k = "c", r = 6 }, ["vanilla_ice:one"] = { k = "c", r = 10 }, ["vanilla_ice:three"] = { k = "f", h = 5 }, ["vanilla_ice:two"] = { k = "n", r = 60 }, ["vanilla_ice_evolved|live"] = { k = "c", r = 10 }, ["vanilla_ice_evolved|live6"] = { k = "c", r = 10, h = 5 }, ["vanilla_ice|vanilla_ice:one"] = { k = "c", r = 10 }, ["vanilla_ice|vanilla_ice:two"] = { k = "n", r = 60 }, ["vegeta_majin:one"] = { k = "c", r = 8.5 }, ["vegeta_majin:three"] = { k = "f", h = 3 }, ["vegeta_majin:two"] = { k = "c", r = 10 }, ["vegeta_majin|vegeta_majin:one"] = { k = "c", r = 8.5 }, ["vegeta_majin|vegeta_majin:two"] = { k = "c", r = 10 }, ["vegito|vegito:four"] = { k = "l" }, ["vegito|vegito:one"] = { k = "c", r = 8 }, ["vegito|vegito:three"] = { k = "n", r = 60, h = 3 }, ["vegito|vegito:two"] = { k = "c", r = 12 }, ["yoshino_evolved|yoshino:icicles"] = { k = "n", r = 60, slow = 0.5, t = 2 }, ["yoshino_evolved|yoshino:summon"] = { k = "l", slow = 0.5, t = 2 }, ["yoshino_evolved|yoshino:tornado"] = { k = "c", r = 9, slow = 0.5, t = 2 }, ["yoshino|yoshino:icicles"] = { k = "n", r = 60, slow = 0.5, t = 2 }, ["yoshino|yoshino:tornado"] = { k = "c", r = 9, slow = 0.5, t = 2 }, ["yuma_evolved|yuma:one"] = { k = "c", r = 8 }, ["yuma_evolved|yuma:three"] = { k = "c", r = 12, h = 12 }, ["yuma_evolved|yuma:two"] = { k = "c", r = 10, h = 4 }, ["yuma|yuma:one"] = { k = "c", r = 8 }, ["yuma|yuma:two"] = { k = "c", r = 10, h = 4 } } Core.TIPS = { rikka_evolved = { target = "strongest", why = "Chuuni (Delusion)'s mark stays on the boss" }, sakamoto_evolved = { target = "closest", spot = "exit", why = "Legendary Assassin (Prime) doubles damage on the closest enemy, a wall near the exit" } } Core.CODES = { "AACOMEBACK", "UPDATESOON5189", "DUNGEONS", "APRILFOOLS", "MILLIONFAVES", "2BILLIONAA", "UPD20INCOMING" } function Utils.now() return os.clock() end function Utils.json(gb) local hb, ib = pcall(HttpService.JSONEncode, HttpService, gb) return hb and ib or "{}" end function Utils.unjson(jb) local kb, mb = pcall(HttpService.JSONDecode, HttpService, jb) return kb and mb or nil end function Utils.readJson(nb) local ob, pb = pcall(function() return isfile(nb) and readfile(nb) or nil end) return ob and pb and Utils.unjson(pb) or nil end function Utils.writeJson(qb, rb) pcall(function() if not isfolder(Core.FOLDER) then makefolder(Core.FOLDER) end end) pcall(writefile, qb, Utils.json(rb)) end function Utils.fmt(sb) sb = tonumber(sb) or 0 if sb >= 1e6 then return string.format("%.2fM", sb / 1e6) end if sb >= 1e4 then return string.format("%.1fk", sb / 1e3) end return tostring(math.floor(sb + 0.5)) end function Utils.clamp(tb, ub, vb) return math.max(ub, math.min(vb, tb)) end function Utils.count(wb) local xb = 0 for yb in pairs(wb or { }) do xb = xb + 1 end return xb end function Utils.list(zb) if type(zb) ~= "table" then return { } end if # zb > 0 then return zb end local Ab = { } for Bb in pairs(zb) do table.insert(Ab, Bb) end table.sort(Ab, function(Cb, Db) return (tonumber(Cb) or 0) < (tonumber(Db) or 0) end) local Eb = { } for Fb, Gb in ipairs(Ab) do table.insert(Eb, zb [Gb]) end return Eb end function Utils.flat(Hb) return Vector3.new(Hb.X, 0, Hb.Z) end function Utils.plain(Ib, Jb) local Kb = type(Ib) if Kb == "number" or Kb == "string" or Kb == "boolean" then return Ib end if Kb ~= "table" or (Jb or 0) > 6 then return nil end local Lb = { } for Mb, Nb in pairs(Ib) do if type(Mb) == "number" or type(Mb) == "string" then Lb [Mb] = Utils.plain(Nb, (Jb or 0) + 1) end end return Lb end function App.log(Ob, Pb, Qb) if Ob > (Config.logLevel or 1) then return end local Rb = string.format("[%s] %-7s %s", os.date("%H:%M:%S"), Pb, tostring(Qb)) table.insert(LogBuffer.lines, Rb) if GUI.activityLog then pcall(function() GUI.activityLog:Push(GUI.escape(Rb), Ob == 0 and "Error" or nil) end) end if # LogBuffer.lines > 250 then table.remove(LogBuffer.lines, 1) end table.insert(LogBuffer.pending, Rb) local Sb = tostring(Qb):lower() if Sb:find("refused") or Sb:find("error") or Sb:find("timeout") or Pb == "FATAL" then LogBuffer.problems = LogBuffer.problems or { } table.insert(LogBuffer.problems, Rb) if # LogBuffer.problems > 30 then table.remove(LogBuffer.problems, 1) end end if Config.logConsole then print("[AA] " .. Rb) end end function App.status(Tb, Ub) Status [Tb] = Ub end function App.flush() if # LogBuffer.pending == 0 then return end local Vb = table.concat(LogBuffer.pending, "\n") .. "\n" table.clear(LogBuffer.pending) pcall(function() if not isfolder(Core.FOLDER) then makefolder(Core.FOLDER) end if appendfile and isfile(Core.LOGFILE) then appendfile(Core.LOGFILE, Vb) else writefile(Core.LOGFILE, Vb) end end) end task.spawn(function() while App.alive do task.wait(3) App.flush() end end) do local Wb = Utils.readJson(Core.SETTINGS) or { } if Wb.shareChosen ~= true then Wb.shareStats = nil end if Wb.autoTeamChosen ~= true then Wb.autoTeam = nil end for Xb, Yb in pairs(Core.DEFAULTS) do if Wb [Xb] ~= nil and type(Wb [Xb]) == type(Yb) then Config [Xb] = Wb [Xb] else Config [Xb] = Yb end end Config.seenVersion = type(Wb.seenVersion) == "string" and Wb.seenVersion or nil if Wb.griffinWatch == true and Wb.snipeUnit == nil then Config.snipeOn, Config.snipeUnit = true, "griffith" end if Config.pickPortal == "off" then Config.portals, Config.pickPortal = false, "smart" end Config.useHive, Config.shareStats = false, false Config.cfgv = 1 if not Config.simpleGui then Config.simpleGui = true if not Config.play then Config.master = false end Config.play = true for Zb, _b in ipairs({ "dailyChallenge", "challenge", "firstClears", "raids", "event", "farm", "infinite", "dungeon" }) do Config [_b] = true end end end function App.saveSoon() App.saveAt = Utils.now() + 1.5 end function App.saveNow() Utils.writeJson(Core.SETTINGS, Config) Utils.writeJson(Core.BRAIN, Brain) App.flush() end do local ac = Utils.readJson(Core.BRAIN) or { } local function bc(cc) return type(cc) == "table" and cc or nil end Brain.v = 1 Brain.loads = (tonumber(ac.loads) or 0) + 1 Brain.modes = bc(ac.modes) or { } Brain.testTeam = bc(ac.testTeam) Brain.blocked = bc(ac.blocked) or { } Brain.caps = bc(ac.caps) or { } Brain.enemyMods = bc(ac.enemyMods) or { } Brain.learn = bc(ac.learn) or { } Brain.learnMarks = ac.learnMarks if not Brain.learnMarks then Brain.learn.rikka_evolved = nil Brain.learnMarks = true end Brain.spend = bc(ac.spend) or { } Brain.totals = bc(ac.totals) or { wins = 0, losses = 0, gems = 0, coins = 0, matches = 0 } Brain.pending = bc(ac.pending) Brain.live = bc(ac.live) or { } Brain.lastResult = bc(ac.lastResult) Brain.fuseInfo = bc(ac.fuseInfo) Brain.history = bc(ac.history) or { } Brain.lastMatch = bc(ac.lastMatch) Brain.claims = bc(ac.claims) or { } Brain.portalPay = bc(ac.portalPay) or { } Brain.watch = bc(ac.watch) Brain.portalMods = type(ac.portalMods) == "string" and ac.portalMods or nil Brain.cleared = bc(ac.cleared) or { } Brain.simCal = bc(ac.simCal) or { } Brain.infHard = ac.infHard if not Brain.infHard then for dc, ec in pairs(Brain.simCal) do if type(ec) == "table" and tostring(dc):match("^inf:") and tonumber(ec.hpCal) then ec.hpCal = ec.hpCal / 2 end end Brain.infHard = true end Brain.maps = bc(ac.maps) or { } local fc = 3 local gc = tonumber(ac.simVer) == fc Brain.simVer = fc Brain.simRes = gc and bc(ac.simRes) or { } Brain.simTeam = gc and bc(ac.simTeam) or { } Brain.simEvo = gc and bc(ac.simEvo) or { } Brain.trainSim = gc and bc(ac.trainSim) or { } Brain.train = bc(ac.train) or { } Brain.train.real = bc(Brain.train.real) or { } Brain.train.bad = bc(Brain.train.bad) or { } Brain.train.log = bc(Brain.train.log) or { } Brain.train.cal = bc(Brain.train.cal) or { real = 0, sim = 0, n = 0 } Brain.savedTeam = bc(ac.savedTeam) Brain.goals = bc(ac.goals) or { } Brain.goalsTop = bc(ac.goalsTop) or { } Brain.goalItems = bc(ac.goalItems) or { } Brain.goalNeed = bc(ac.goalNeed) or { } Brain.raidShop = bc(ac.raidShop) or { } Brain.lb = bc(ac.lb) Brain.hidden = bc(ac.hidden) or { } Brain.unitData = bc(ac.unitData) or { } Brain.codes = bc(ac.codes) or { } Brain.stack = bc(ac.stack) or { } Brain.ownSwap = bc(ac.ownSwap) Brain.snipe = bc(ac.snipe) or { } Brain.snipe.seen = bc(Brain.snipe.seen) or { } if type(Brain.snipe.clock) == "table" and Brain.snipe.clock.src ~= "changed" then Brain.snipe.clock = nil end Brain.zoneRec = bc(ac.zoneRec) or { } Brain.fh = bc(ac.fh) or { } Brain.hunt = bc(ac.hunt) or { } Brain.fh.runs = bc(Brain.fh.runs) or { } Brain.fh.units = bc(Brain.fh.units) or { } Brain.nextMode = type(ac.nextMode) == "string" and ac.nextMode or nil Brain.infResults = tonumber(ac.infResults) or 0 Brain.infLive = ac.infLive == true or nil Brain.dg = bc(ac.dg) or { } Brain.dg.sim = bc(Brain.dg.sim) or { } Brain.dg.depth = bc(Brain.dg.depth) or { } Brain.dg.log = bc(Brain.dg.log) or { } Brain.dg.seen = bc(Brain.dg.seen) or { } Brain.dg.runs = bc(Brain.dg.runs) or { } Brain.str = bc(ac.str) or { } Brain.str.share = bc(Brain.str.share) or { } Brain.str.core = bc(Brain.str.core) or { } Brain.str.log = bc(Brain.str.log) or { } Brain.str.noWork = bc(Brain.str.noWork) or { } Brain.lastRun = bc(ac.lastRun) Brain.holdLobby = bc(ac.holdLobby) Brain.relicOff = bc(ac.relicOff) or { } Brain.buyStorage = bc(ac.buyStorage) Brain.buyOnce = bc(ac.buyOnce) Brain.toggles = bc(ac.toggles) or { } Brain.unitSent = bc(ac.unitSent) or { } Brain.startTeam = bc(ac.startTeam) Brain.trial = bc(ac.trial) Brain.study = bc(ac.study) Brain.studyQ = bc(ac.studyQ) or { } Brain.studySent = tonumber(ac.studySent) or 0 Brain.charm = bc(ac.charm) or { } Brain.studied = bc(ac.studied) or { } Brain.studiedSeeded = ac.studiedSeeded == true or nil Brain.lastSuggest = tonumber(ac.lastSuggest) or 0 Brain.anonId = type(ac.anonId) == "string" and ac.anonId or HttpService:GenerateGUID(false):gsub("-", ""):sub(1, 10):lower() Brain.hiveQueue = bc(ac.hiveQueue) or { } Brain.hiveN = tonumber(ac.hiveN) or 0 Brain.aaHiveQ = bc(ac.aaHiveQ) or { } Brain.hiveSent = tonumber(ac.hiveSent) or 0 end task.spawn(function() while App.alive do task.wait(1) if App.saveAt and Utils.now() >= App.saveAt then App.saveAt = nil App.saveNow() end end end) do local hc = ReplicatedStorage:WaitForChild("src", 60) Core.Loader = require(hc:WaitForChild("Loader")) Core.Util = require(hc:WaitForChild("Util")) local ic = Utils.now() while App.alive and Utils.now() - ic < 90 do local jc, kc = pcall(Core.Loader.load_client_service, nil, "GUIService") if jc and type(kc) == "table" and kc.session and kc.session._initialized then break end task.wait(0.5) end end function Core.svc(lc) local mc, nc = pcall(Core.Loader.load_client_service, nil, lc) return mc and nc or nil end function Core.core(oc) local pc, qc = pcall(Core.Loader.load_core_service, nil, oc) return pc and qc or nil end function Core.data(rc) local tc, uc = pcall(Core.Loader.load_data, nil, rc) return tc and uc or nil end Core.EP = Core.svc("EndpointsClient") Core.GUIS = Core.svc("GUIService") Core.USC = Core.core("UnitServiceCore") Core.PSC = Core.core("ProgressionServiceCore") Core.QCC = Core.core("QuestClassesCore") Core.PC = Core.core("PlacementCore") Core.PATH = Core.core("PathService") Core.LU = Core.core("LevelUtil") or (function() local vc = { 1, 1.524259, 2.3760452, 3.7038265, 5.7735984, 9.9, 13.6269783, 16.313749, 21.4345692, 21.4345692, 56.325588, 84.488382, 84.488382, 168.976764, 253.465146, 337.953528, 375, 400, 450, 500, 550, 650 } local wc = { 1, 1.0447, 1.09139809, 1.1401835846, 1.1911497909, 1.3366844723 } local xc = { hxhant = 0.5, magnolia = 0.6, jjk = 0.7 } local yc = { } function yc.calculate_level_diff_scale(zc, Ac, Bc) if Bc and (Bc._use_max or Bc._is_daily_challenge or Bc._is_event_challenge) then return wc [6] end if Ac == "story" or Ac == "challenge" then return wc [zc or 6] or wc [6] end return wc [6] end function yc.calculate_world_diff_scale(Cc, Dc, Ec, Fc, Gc) local Hc = vc [# vc] * 1.5 if Gc and Gc._is_event_challenge then return 0.5 * Hc end if Gc and (Gc._use_max or Gc._is_daily_challenge) then return Hc end local Ic = vc [Cc or # vc] or vc [# vc] if Dc == "story" or Dc == "raid" then Ic = Ic * ((Cc or # vc) < 12 and 0.6 or 0.7) end return Ic * 1.5 * (xc [Fc] or 1) end function yc.calculate_world_resource_scale(Jc, Kc) local Lc = 1.7411011266 if Jc and Jc <= 5 and (Kc == "story" or Kc == "infinite" or Kc == "challenge") then Lc = ({ 1, 1.148698355, 1.3195079108, 1.5157165665, 1.7411011266 }) [Jc] end return Lc * 1.5 end function yc.calculate_multiplayer_diff_scale(Mc, Nc, Oc) if Oc._event_contract then return 1 end if Oc._override_multiplayer_diff_scale then return 1 + (Nc - 1) * Oc._override_multiplayer_diff_scale end if Oc._is_daily_challenge then return 4 end if Oc._is_event_challenge or Oc._no_multiplayer_scale then return 1 end if Mc == "story" or Mc == "challenge" or Mc == "infinite" or Mc == "infinite_tower" then return 1 + (Nc - 1) * (Oc.world == "bleach_legend" and 1 / 6 or (Oc.world == "jojo_legend" and 0.3 or 0.6)) end if Mc == "raid" then if Oc.id == "jojo_portal_pucci" then return 0.3 end if Oc.portal_group or Oc._raid_multiplayer_scale then return 1 + (Nc - 1) * 0.3 end return Oc.id ~= "namek_anniversary" and 8 or 8 * (1 + (Nc - 1) * 0.5) end return 1 end local Pc = { opm_infinite = 0.8, jojo_infinite = 0.7, sao_infinite = 0.7, berserk_infinite = 0.7, overlord_infinite = 0.7, fate_infinite = 0.7 } yc.Infinite = { infinite_max_unit_wave = 100, initial_wave_template = { wave_reward = 1500, wave_diff_scale = 1 }, infinite_diff_scale = function(Qc, Rc) local Sc if Qc <= 20 then Sc = math.exp(0.165 * Qc) * 0.257116 + 0.187431 elseif Qc <= 50 then if Rc == "april2_event" then Sc = math.pow(Qc - 1, 2.9) / 500 + 2 elseif Rc == "halloween2_event" or Rc == "christmas_event" then Sc = math.pow(Qc - 1, 2.4) / 400 + 4.2 else Sc = math.pow(Qc - 1, 2.48) / 400 + 4.2 end else Sc = math.max(32, math.pow(Qc - 1, 2.8) / 400 - 92) end if Pc [Rc] then return Sc * Pc [Rc] end if Rc == "halloween2_event" or Rc == "christmas_event" or Rc == "april2_event" then Sc = Sc * 0.85 if Rc == "christmas_event" then Sc = Sc * 0.75 end end return Sc end, infinite_amt_scale = function(Tc, Uc) local Vc if Tc <= 1 then Vc = 3 elseif Tc < 9 then Vc = math.pow(Tc - 1, 2) / 5 + 5 else Vc = math.pow(Tc - 1, 1.4) / 5 + 15 end if Uc and Uc._tournament and Uc._tournament.tournament_id == "3" then Vc = math.min(Vc / 3, 5) end return math.min(Vc, 60) end, infinite_get_normal_unit_chance = function(Wc, Xc) if Wc.min_inf_wave and Xc < Wc.min_inf_wave then return 0 end local Yc = Wc.spawn_tier_normal if not Yc then return 0 end local Zc = math.min(Xc - 1, 100) / 100 local _c, bd if Yc <= 2 then _c, bd = 5, 5 elseif Yc == 3 then _c, bd = 3, 6 elseif Yc == 4 then _c, bd = 1, 6 elseif Yc <= 7 then _c, bd = 0, 5 else _c, bd = 0, 2 end return _c + (bd - _c) * Zc end, infinite_get_boss_unit_chance = function(dd, ed) if dd.min_inf_wave and ed < dd.min_inf_wave then return 0 end if ed <= 10 then return dd.spawn_tier_boss == 1 and 1 or 0 end return dd.spawn_tier_boss and 1 or 0 end } local fd = Core.core("LevelUtilCore") yc.xp_items_order = fd and fd.xp_items_order return yc end)() Core.FLY_GROUND = (Core.data("GameplaySettings") or { }).FLYING_GROUND_DAMAGE or 0.5 function Core.traitInfo(gd) local hd = Core.data("Traits") or { } return (hd.info or hd) [gd] end Core.CAR = Core.data("ChallengeAndRewards") or { } Core.Units = Core.data("Units") or { } Core.Levels = Core.data("Levels") or { } Core.Worlds = Core.data("Worlds") or { } Core.Waves = Core.data("Waves") or { } Core.Attacks = Core.data("Attacks") or { } Core.Items = Core.data("Items") or { } Core.GS = Core.data("GameplaySettings") or { } Core.WLO = Core.data("WorldLevelOrder") or { } Core.Banners = Core.data("Banners") or { } Core.SaleEvent = Core.data("ItemsForSaleEvent") or { } function Core.session() return Core.GUIS and Core.GUIS.session end function Core.prof() local jd = Core.session() return jd and jd.profile_data end function Core.cur(kd) local md = LocalPlayer:FindFirstChild("_stats") local od = md and ((kd == "gems" and md:FindFirstChild("gem_amount")) or (kd == "gold" and md:FindFirstChild("gold_amount")) or md:FindFirstChild("_resource" .. kd)) if od then return tonumber(od.Value) or 0 end local pd = Core.prof() if kd == "gems" then return tonumber(pd and pd.gem_amount) or 0 end if kd == "gold" then return tonumber(pd and pd.gold_amount) or 0 end return tonumber(pd and pd.resources and pd.resources [kd]) or 0 end App.where = (workspace:FindFirstChild("_MAP_CONFIG") and workspace._MAP_CONFIG:FindFirstChild("IsLobby") and workspace._MAP_CONFIG.IsLobby.Value) and "lobby" or "match" App.req = { back = { }, fails = { }, calls = { } } App.BUDGET = { upg = 30, place = 15 } function App.request(qd, sd, ...) local td = App.req local ud = Utils.now() if td.back [qd] and ud < td.back [qd] then return false, "waiting" end local vd = td.calls [qd] or { } td.calls [qd] = vd while vd [1] and ud - vd [1] > 10 do table.remove(vd, 1) end if # vd >= (App.BUDGET [qd] or 8) then td.back [qd] = ud + 3 return false, "slowing down" end table.insert(vd, ud) if not (ReplicatedStorage:FindFirstChild("endpoints") and ReplicatedStorage.endpoints.client_to_server:FindFirstChild(sd)) then return false, "no remote " .. sd end local wd = table.pack(...) local xd, yd, zd, Ad = false, false, nil, nil task.spawn(function() yd, zd, Ad = pcall(function() return Core.EP.invoke_server(sd, table.unpack(wd, 1, wd.n)) end) xd = true end) local Bd = Utils.now() while not xd and Utils.now() - Bd < 15 do task.wait(0.05) end if not xd then td.fails [qd] = (td.fails [qd] or 0) + 1 td.back [qd] = Utils.now() + 30 App.log(1, "NET", sd .. " timeout (no answer in 15s)") return false, "timeout" end if not yd then td.fails [qd] = (td.fails [qd] or 0) + 1 td.back [qd] = Utils.now() + math.min(60, 2 ^ td.fails [qd]) App.log(1, "NET", sd .. " error: " .. tostring(zd)) return false, tostring(zd) end td.fails [qd] = 0 return true, zd, Ad end function App.backoff(Cd, Dd) App.req.back [Cd] = Utils.now() + Dd end function App.listenGame() if App.heard then return end local Ed = ReplicatedStorage:FindFirstChild("endpoints") and ReplicatedStorage.endpoints:FindFirstChild("server_to_client") local Fd = Ed and Ed:FindFirstChild("show_message") if not Fd then return end App.heard = true table.insert(App.conns, Fd.OnClientEvent:Connect(function(Gd, Hd) if tostring(Hd) == "Error" then App.gameError = { msg = tostring(Gd), at = Utils.now() } end end)) for Id, Jd in ipairs({ "hatch_refresh_remaining_time_changed", "hatch_banner_changed" }) do local Kd = Ed:FindFirstChild(Jd) if Kd then table.insert(App.conns, Kd.OnClientEvent:Connect(function(...) pcall(FarmHub.snipeHeard, Jd, ...) end)) end end end function App.gameSaid(Ld) local Md = App.gameError return Md and Md.at >= Ld and Md.msg or nil end function Spending.add(Nd, Od, Pd, Qd) table.insert(Brain.spend, 1, { at = os.time(), what = Nd, cost = Od, cur = Pd, why = Qd }) while # Brain.spend > 60 do table.remove(Brain.spend) end App.log(1, "SPEND", string.format("%s for %s %s โ€” %s", Nd, Utils.fmt(Od), Pd, Qd)) App.saveSoon() end function Spending.text() if # Brain.spend == 0 then return "nothing spent yet" end local Rd = { } for Sd = 1, math.min(15, # Brain.spend) do local Td = Brain.spend [Sd] table.insert(Rd, string.format("%s %s โ€” %s %s (%s)", os.date("%b %d %H:%M", Td.at), Td.what, Utils.fmt(Td.cost), Td.cur, Td.why or "")) end return table.concat(Rd, "\n") end Units._rows, Units._mult = { }, { } Units.KEEP = { "upgrade", "spawn_cap", "primary_attack", "primary_attack_no_upgrades", "active_attack", "active_attack_stats", "farm_amount", "passives" } function Units.field(Ud, Vd) local Wd = Core.Units [Ud] if type(Wd) == "table" and Wd [Vd] ~= nil then return Wd [Vd] end local Xd = Brain.unitData [Ud] if Xd then return Xd [Vd] end local Yd, Zd = pcall(Reports.hiveStat, Ud) if Yd and Zd and Zd [Vd] ~= nil then return Zd [Vd] end local ae = Core.FILL_UNITS [Ud] return ae and ae [Vd] end function Units.hidden(be) local ce = Core.Units [be] if not (type(ce) == "table" and ce.upgrade == nil and (tonumber(ce.cost) or 0) > 0) then return false end if (Brain.unitData [be] and Brain.unitData [be].upgrade) or Core.FILL_UNITS [be] then return false end local de, ee = pcall(Reports.hiveStat, be) return not (de and ee and ee.upgrade) end function Units.markHidden() local fe = 0 for ge, he in pairs(Core.Units) do if type(he) == "table" and he.upgrade == nil and (tonumber(he.cost) or 0) > 0 then Brain.hidden [ge] = true fe = fe + 1 end end return fe end function Units.capture() local ie = { } for je in pairs(Brain.hidden) do local ke = Core.Units [je] if type(ke) == "table" and type(ke.upgrade) == "table" then local le = { } for me, ne in ipairs(Units.KEEP) do if ke [ne] ~= nil then le [ne] = Utils.plain(ke [ne]) end end if not Brain.unitData [je] then table.insert(ie, Units.name(je)) end Brain.unitData [je] = le Units._rows [je] = nil end end if # ie > 0 then App.log(1, "UNITS", "learned the hidden upgrade stats of " .. table.concat(ie, ", ") .. " (the game only sends them in a match)") App.saveSoon() end end Units.SUMMONER = { elyssia = { rate = 1, reach = 4, atk = "elyssia_evolved|live" }, elyssia_evolved = { rate = 1, reach = 4, atk = "elyssia_evolved|live" } } Units.BLESS = { by = { giselle_evolved = true }, dmg = 0.25, range = 0.10, cap = 8 } Units.ENTRY = { dokuro_evolved = true } function Units.summonShare(oe) if Units.SUMMONER [oe] or (Units.DRONES and Units.DRONES [oe]) then return 0 end local pe = Brain.str and type(Brain.str.summon) == "table" and Brain.str.summon [oe] return type(pe) == "table" and Utils.clamp(tonumber(pe.s) or 0, 0, 1) or 0 end function Units.rows(qe) if Units._rows [qe] then return Units._rows [qe] end local re = Core.Units [qe] if type(re) ~= "table" then return nil end local se = Units.SUMMONER [qe] and Units.SUMMONER [qe].rate or 1 local te = { cost = re.cost or 0, damage = re.damage or 0, cd = (re.attack_cooldown or 0) * se, range = re.range or 0, farm = Units.field(qe, "farm_amount") or 0, atk = Units.field(qe, "primary_attack_no_upgrades") or Units.field(qe, "primary_attack"), active = Units.field(qe, "active_attack") } local ue = Units.field(qe, "active_attack_stats") te.activeCd = type(ue) == "table" and tonumber(ue.attack_cooldown) or nil local ve = { [0] = table.clone(te) } local we = Units.field(qe, "upgrade") or { } for xe, ye in ipairs(we) do te = table.clone(te) te.cost = ye.cost or 0 if ye.damage then te.damage = ye.damage end if ye.attack_cooldown then te.cd = ye.attack_cooldown * se end if ye.range then te.range = ye.range end if ye.farm_amount then te.farm = ye.farm_amount end if ye.primary_attack then te.atk = ye.primary_attack end if ye.active_attack then te.active = ye.active_attack end if type(ye.active_attack_stats) == "table" and tonumber(ye.active_attack_stats.attack_cooldown) then te.activeCd = tonumber(ye.active_attack_stats.attack_cooldown) end ve [xe] = te end ve.max = # we local ze = Units.SUMMONER [qe] and Units.SUMMONER [qe].atk if ze then for Ae = 0, ve.max do ve [Ae].atk = ze end end Units._rows [qe] = ve return ve end function Units.isFarm(Be) return Core.USC and Core.USC.unit_static_is_farm(Be) or false end Units._atk = { } Units.CC_COOLDOWN = { slow = 3, stun = 8, freeze = 10, haki_stun = 15, knockback = 30, walk_back = 10, walk_back_time = 10 } Units._fill = { } function Units.fillAlias(Ce) local De, Ee = tostring(Ce):match("^(.-):([^:]+)$") if not De or De:find("|", 1, true) then return nil end local Fe for Ge, He in pairs(Core.FILL_ATTACKS) do if Ge:match(":([^:]+)$") == Ee then if Ge:sub(1, # De + 1) == De .. "|" then return He end if Ge:sub(1, # De + 1) == De .. "_" and Ge:find("|", # De + 2, true) then Fe = Fe or He end end end return Fe end function Units.fillAttack(Ie) if Units._fill [Ie] ~= nil then return Units._fill [Ie] end local Je = Core.FILL_ATTACKS [Ie] or Units.fillAlias(Ie) local Ke = false if Je then local Le = Je.k == "c" and { type = "circle", radius = Je.r } or Je.k == "n" and { type = "cone", angle = Je.r } or Je.k == "f" and { type = "full_aoe" } or Je.k == "l" and { type = "straight" } or { } local Me = { } if Je.slow then table.insert(Me, { name = "slow", time = Je.t, params = { slow_percent = Je.slow } }) end if Je.stun then table.insert(Me, { name = "stun", time = Je.stun, params = { } }) end if Je.freeze then table.insert(Me, { name = "freeze", time = Je.freeze, params = { } }) end if Je.knock then table.insert(Me, { name = "knockback", time = Je.knock, params = { } }) end if Je.amp then table.insert(Me, { name = "amplify", time = 1, params = { amplification = Je.amp, persistent = Je.keep } }) end if Je.dot then table.insert(Me, { name = Je.dot, time = Je.ticks, params = { damage_rate = Je.rate } }) end local Ne = Je.h or 1 local Oe = { } for Pe = 1, Ne do Oe [Pe] = { aoe_type = Le, hit = { damage = 1 / Ne, buffs = Pe == 1 and Me or nil } } end Ke = { aoe = Oe } end Units._fill [Ie] = Ke return Ke end function Units.attackInfo(Qe, Re) local Se = tostring(Qe) .. ":" .. tostring(Re and math.floor(Re) or "") if Units._atk [Se] then return Units._atk [Se] end local Te = { n = 1, dot = 0, slow = 0, stun = 0, amp = 0, ampPersist = false, hits = 0, bleed = false, slowPct = 0, slowTime = 0 } local Ue = Qe and (Core.Attacks [Qe] or Units.fillAttack(Qe)) if type(Ue) ~= "table" then Te.hits = 1 Units._atk [Se] = Te return Te end local Ve = 0 local function We(Xe, Ye) if type(Xe) ~= "table" then return end local Ze = type(Xe.aoe_type) == "table" and Xe.aoe_type or { } local _e = type(Xe.hit) == "table" and Xe.hit or { } local af = tonumber(_e.damage) or 1 if _e.break_shields then Te.breakShields = true end local bf = 1 if Ze.type == "target_aoe_circ" or Ze.type == "circle" or Ze.type == "circ" then bf = 1 + (tonumber(Ze.radius) or 4) / 5 elseif Ze.type == "cone" then bf = 1 + (tonumber(Ze.angle) or 45) / 60 elseif Ze.type == "full_aoe" or Ze.type == "full" then bf = 3 if not Xe.hit_allies then Te.full = true end elseif Ze.type == "straight" or Ze.type == "line" then bf = 1 + (tonumber(Ze.length) or tonumber(Re) or 10) / 15 end if not Xe.hit_allies then Ve = Ve + af * Ye * math.min(bf, 4) Te.hits = Te.hits + Ye end for cf, df in pairs(_e.buffs or { }) do if type(df) == "table" and type(df.name) == "string" then local ef = type(df.params) == "table" and df.params or { } local ff = tonumber(df.time) or 0 if Xe.hit_allies then if df.name:find("^damage_buff__") and type(ef.damage_types) == "table" then local gf = { } for hf, jf in pairs(ef.damage_types) do gf [hf] = type(jf) == "table" and tonumber(jf.rate) or 0 end Te.ally = { types = gf, time = ff } elseif df.name == "attack_cooldown_buff" then Te.allyCd = { reduce = tonumber(ef.attack_cooldown_reduce) or 0, time = ff } elseif df.name == "range_buff" then Te.allyRange = { add = tonumber(ef.range_add) or 0, time = ff } end else if tonumber(ef.damage_rate) then Te.dot = Te.dot + tonumber(ef.damage_rate) * math.max(1, ff) end if df.name:find("bleed") or df.name:find("wither") or df.name:find("decay") then Te.bleed = true end if df.name == "slow" then Te.slow = math.max(Te.slow, (tonumber(ef.slow_percent) or 0.3) * ff) Te.slowPct, Te.slowTime = math.max(Te.slowPct, tonumber(ef.slow_percent) or 0.3), math.max(Te.slowTime, ff) elseif df.name == "stun" or df.name == "freeze" or df.name == "haki_stun" then Te.stun = math.max(Te.stun, ff) elseif df.name == "knockback" or df.name == "walk_back" or df.name == "walk_back_time" then Te.stun = math.max(Te.stun, ff * 0.5) elseif tonumber(ef.amplification) then Te.amp = math.max(Te.amp, tonumber(ef.amplification)) if ef.persistent then Te.ampPersist = true end end if Units.CC_COOLDOWN [df.name] then Te.ccCd = math.max(Te.ccCd or 0, Units.CC_COOLDOWN [df.name]) end end end end end for kf, lf in pairs(type(Ue.aoe) == "table" and Ue.aoe or { }) do We(lf, 1) end for mf, of in pairs(type(Ue.aoe_dot) == "table" and Ue.aoe_dot or { }) do if type(of) == "table" and type(of.aoe) == "table" then We(of.aoe, (tonumber(of.time) or 1) / math.max(0.1, tonumber(of.interval) or 1)) if type(of.first_aoe) == "table" then We(of.first_aoe, 0) end end end for pf, qf in pairs(type(Ue.hit) == "table" and Ue.hit or { }) do if type(qf) == "table" and qf.damage then Te.hits = Te.hits + 1 end end Te.globalCd = Ue.global_cooldown and true or false for rf, sf in pairs(type(Ue.global_hit) == "table" and Ue.global_hit or { }) do if type(sf) == "table" then Te.wholeMap = true We({ hit = sf, hit_allies = sf.hit_allies, aoe_type = { type = "full_aoe" } }, 0) end end for tf, uf in pairs(type(Ue.buff) == "table" and Ue.buff or { }) do for vf, wf in pairs(type(uf) == "table" and uf or { }) do local xf = type(wf) == "table" and wf.buff_config local yf = type(xf) == "table" and type(xf.params) == "table" and xf.params or { } if tonumber(yf.damage_add) and not yf.persistent then Te.selfDmg = { add = tonumber(yf.damage_add), time = tonumber(xf.time) or 0 } end end end if type(Ue.on_base_hit) == "table" and Ue.on_base_hit.prevent_hit then Te.baseSafe = tonumber(Ue.on_base_hit.time) or 0 end Te.n = math.max(0.3, Ve > 0 and Ve or (Te.ally and 0.3 or 1)) Te.dot = math.min(1.5, Te.dot) Te.hits = math.max(1, Te.hits) Units._atk [Se] = Te return Te end function Units.aoe(zf, Af) local Bf = Units.attackInfo(zf, Af) return Bf.n * (1 + Bf.dot) end function Units.teamEffect(Cf, Df, Ef, Ff) local Gf = Units.attackInfo(Cf, Df) local Hf = math.max(Ef or 1, Gf.ccCd or 0, 0.5) local If = math.min(1, Gf.n / 4) local Jf = Ff and 0 or math.min(1, (Gf.slow + Gf.stun) / Hf) * If local Kf = Gf.amp * (Gf.ampPersist and 1 or math.min(1, 5 / Hf)) * If return Jf * 0.5 + Kf end Units._pas = { } function Units.passive(Lf) if Units._pas [Lf] then return Units._pas [Lf] end local Mf = { } local Nf = Core.Units [Lf] for Of, Pf in pairs(type(Nf) == "table" and type(Nf.passives) == "table" and Nf.passives or { }) do if type(Pf) == "table" then local Qf = tostring(Pf.description or ""):gsub("<[^>]+>", "") if Pf.name == "Pact of Frailty" then Mf.mark = Mf.mark or { amp = 0, max = 1, dot = 0 } Mf.mark.amp = (tonumber(Qf:match("(%d+)%%")) or 0) / 100 Mf.mark.max = tonumber(Qf:match("up to (%d+)")) or 1 elseif Pf.name == "Pact of Relevation" or Pf.name == "Pact of Revelation" then Mf.mark = Mf.mark or { amp = 0, max = 1, dot = 0 } local Rf, Sf = Qf:match("(%d+)%%.-every ([%d%.]+)") Mf.mark.dot = (tonumber(Rf) or 0) / 100 / math.max(0.1, tonumber(Sf) or 1) end end end Units._pas [Lf] = Mf return Mf end Units.DRONES = { metal_knight_evolved = { every = 20, max = 3, from = 6, share = 1, hits = 3, early = { dmg = 0.25, cd = 5.5, life = 30 }, late = { dmg = 0.5, cd = 4, life = 60 } }, elyssia_evolved = { every = 5, max = 3, from = 0, share = 1, hits = 1, early = { dmg = 0.25, cd = 8, life = 60 }, late = { dmg = 0.25, cd = 8, life = 60 } } } function Units.critMul(Tf) local Uf = Core.Units [Tf] return 1 + (tonumber(type(Uf) == "table" and Uf.physical_crit_chance) or 0) * 0.5 end Units._bleeds = { } function Units.bleeds(Vf) local Wf = Units._bleeds [Vf] if Wf ~= nil then return Wf or nil end local Xf = Units.rows(Vf) local Yf = false if Xf then for Zf = 0, Xf.max do local ag = Xf [Zf] if ag and ag.atk then local cg, eg = pcall(Units.attackInfo, ag.atk, ag.range) if cg and eg and eg.bleed then Yf = Zf break end end end end Units._bleeds [Vf] = Yf return Yf or nil end function Units.bestBleeder(fg) local gg = Core.session() if not gg then return nil end local hg = { } for ig, jg in ipairs(fg or { }) do hg [jg.unit_id] = true end local kg, lg for mg, ng in pairs(gg.profile_data.collection.owned_units) do local og = Core.Units [ng.unit_id] if og and not hg [ng.unit_id] and Units.rows(ng.unit_id) and not Units.hidden(ng.unit_id) and Units.bleeds(ng.unit_id) then local pg, qg = pcall(Core.USC.calculate_level, ng.unit_id, tonumber(ng.xp) or 0) if pg and (tonumber(qg) or 0) >= 50 then local rg = gg.collection:get_unit_by_uuid(mg) or ng local sg, tg = pcall(Units.score, rg) if sg and (not lg or tg > lg) then kg, lg = rg, tg end end end end return kg end function Units.allyBuff(ug) local vg = Core.Units [ug] if type(vg) ~= "table" or not vg.active_attack then return nil end local wg = Units.attackInfo(vg.active_attack) if not wg.ally then return nil end local xg = tonumber(vg.active_attack_stats and vg.active_attack_stats.attack_cooldown) or 60 return { types = wg.ally.types, time = wg.ally.time, cd = xg, uptime = math.min(1, (wg.ally.time or 0) / math.max(1, xg)), attack = vg.active_attack } end function Units.buffCopies(yg) local zg = math.max(1, math.ceil((tonumber(yg.cd) or 60) / math.max(1, tonumber(yg.time) or 30) - 0.001)) local Ag = Brain.stack [yg.attack] if Ag and Ag.stacks then local Bg = 0 for Cg, Dg in pairs(yg.types or { }) do Bg = math.max(Bg, tonumber(Dg) or 0) end if Bg > 0 and yg.uptime > 0 then zg = math.max(zg, math.ceil((tonumber(Ag.cap) or 1) / (Bg * yg.uptime) - 0.001)) end end return zg end function Units.idol(Eg) local Fg = Core.Units [Eg] if type(Fg) ~= "table" or not (Fg._NO_ATTACKS_UNIT and Fg._show_as_buff_unit) then return false end for Gg, Hg in pairs(Fg.passives or { }) do local Ig = type(Hg) == "table" and tostring(Hg.description or ""):lower() or "" if Ig:find("upgrade") and Ig:find("cost") then return true end end return false end function Units.mult(Jg, Kg) local Lg = tostring(Jg.uuid) .. Kg local Mg = Units._mult [Lg] if Mg and Utils.now() - Mg.t < 60 then return Mg.v end local Ng, Og = pcall(Core.USC.calculate_static_stat_multiplier, Jg, Jg.xp, Kg, Core.session()) Og = Ng and tonumber(Og) or 1 Units._mult [Lg] = { v = Og, t = Utils.now() } return Og end function Units.types(Pg) local Qg = Core.Units [Pg] or { } local Rg = { [Qg._base_damage_type or "physical"] = true } for Sg in pairs(Qg._secondary_damage_types or { }) do Rg [Sg] = true end return Rg, Qg._base_damage_type or "physical" end function Units.elem(Tg, Ug) if not Ug then return 1 end local Vg, Wg = Units.types(Tg) local Xg = 1 for Yg in pairs(Vg) do if Ug.weak [Yg] then Xg = math.max(Xg, Ug.weak [Yg]) end end if Ug.strong [Wg] then Xg = Xg * 0.5 end if Ug.res and Ug.res [Wg] then Xg = Xg * Ug.res [Wg] end return Xg end function Units.relic(Zg) local _g = Core.session() if not (_g and Zg._equipped_relic) then return 1 end local ah, bh = pcall(function() return _g.inventory:get_item_by_uuid(Zg._equipped_relic) end) local dh = ah and bh and bh._unique_item_data and bh._unique_item_data._unique_relic_data if not (dh and dh._core_damage_type_buffs) then return 1 end local eh = Units.types(Zg.unit_id) local gh = 1 for hh, ih in pairs(dh._core_damage_type_buffs) do if type(ih) == "table" and eh [ih.damage_type] and tonumber(ih.mul) then gh = gh + ih.mul end end return gh end function Units.relicName(jh) local kh = Core.session() if not (kh and jh._equipped_relic) then return nil end local lh, mh = pcall(function() return kh.inventory:get_item_by_uuid(jh._equipped_relic) end) local nh = lh and mh and Core.Items [mh.item_id] return nh and nh.name or (lh and mh and mh.item_id) or nil end function Units.nudge(oh) local ph = Brain.learn [oh] if not (ph and ph.n and ph.n >= 3) then local qh = Reports.hiveUnit(oh) return qh and Utils.clamp(math.sqrt(qh.eff), 0.8, 1.35) or 1 end return Utils.clamp(math.sqrt(ph.eff or 1), 0.8, 1.35) end function Units.trait(rh) for th, uh in pairs(rh.traits or { }) do if type(uh) == "table" then return uh.trait, uh.tier end end return nil end function Units.cap(vh) local wh = tonumber(Units.field(vh.unit_id, "spawn_cap")) or 1 if Units.trait(vh) == "unique" then wh = 1 end local xh = tonumber(Brain.caps [vh.uuid]) if xh then wh = math.min(wh, xh) end return math.max(1, wh) end Units.GALE = { rukia_bankai_evolved = { from = 1, tick = 1 / 3, duty = 1 / 3, targets = 3, freezeEvery = 13, freeze = 2, chill = 3, immune = 10 } } Units._galeOf = { } function Units.galeOf(yh) local zh = Units.GALE [yh] if not zh then return nil end local Ah = Brain.toggles and Brain.toggles [yh] if not (Ah and (Ah.nf or 0) >= 5 and (Ah.nd or 0) >= 5 and (Ah.fill or 0) > 0 and (Ah.drain or 0) > 0) then return zh end local Bh = Ah.fill / (Ah.fill + Ah.drain) local Ch = Units._galeOf [yh] if not (Ch and math.abs(Ch.duty - Bh) < 0.01) then Ch = table.clone(zh) Ch.duty = Bh Units._galeOf [yh] = Ch end return Ch end function Units.toggleOf(Dh) if Units.GALE [Dh] then return { from = Units.GALE [Dh].from, known = true } end local Eh = Brain.toggles and Brain.toggles [Dh] if Eh then return Eh end local Fh, Gh = pcall(Reports.hiveToggle, Dh) return Fh and Gh or nil end function Units.galeDps(Hh, Ih) local Jh = Units.galeOf(Hh) if not Jh or (Ih or 0) < Jh.from then return 0 end return Jh.duty * Jh.targets * (Jh.tick + 1 / Jh.freezeEvery) end function Units.dps(Kh, Lh, Mh, Nh) local Oh = Units.rows(Kh.unit_id) local Ph = Oh and Oh [math.min(Lh, Oh.max)] if not Ph or (Ph.cd or 0) <= 0 then return 0 end local Qh = Ph.damage * Units.mult(Kh, "damage") local Rh = Nh and 0 or Qh * Units.galeDps(Kh.unit_id, Lh) return (Qh / (Ph.cd * math.max(0.1, Units.mult(Kh, "attack_cooldown"))) * Units.aoe(Ph.atk, Ph.range) * Units.critMul(Kh.unit_id) + Rh) * Units.elem(Kh.unit_id, Mh) * Units.relic(Kh) * Units.nudge(Kh.unit_id) end Units._skinMul = { } function Units.skinMul(Sh, Th) local Uh = Th and Sh.skin and Sh.skin._skin_id local Vh = Uh and (Core.data("Skins") or { }) [Uh] if not (Vh and Vh._skin_set == Th) then return 1 end local Wh = tonumber(Vh._damage_boost) or 0 if Wh <= 0 then return 1 end local Xh = tostring(Sh.uuid or Uh) .. ":" .. tostring(Th) .. ":" .. tostring(App.where) local Yh = Units._skinMul [Xh] if Yh and Utils.now() - Yh.t < 60 then return Yh.v end local Zh = 1 + Wh local _h, ai = pcall(table.clone, Sh) if _h and type(ai) == "table" then ai.skin = nil local bi = Core.session() local ci, di = pcall(Core.USC.calculate_static_stat_multiplier, Sh, Sh.xp, "damage", bi) local ei, fi = pcall(Core.USC.calculate_static_stat_multiplier, ai, Sh.xp, "damage", bi) di, fi = ci and tonumber(di), ei and tonumber(fi) if di and fi and fi > 0 and di / fi > 1 + Wh * 0.5 then Zh = 1 end end Units._skinMul [Xh] = { v = Zh, t = Utils.now() } return Zh end function Units.range(gi, hi) local ii = Units.rows(gi.unit_id) local ji = ii and ii [math.min(hi, ii.max)] return (ji and ji.range or 10) * Units.mult(gi, "range") end function Units.farm(ki, li) local mi = Units.rows(ki.unit_id) local ni = mi and mi [math.min(li, mi.max)] return (ni and ni.farm or 0) * Units.mult(ki, "farm_amount") end function Units.score(oi, qi) local ri = Units.rows(oi.unit_id) if not ri then return 0 end local si = ri.max return Units.dps(oi, si, qi) * math.sqrt(math.max(1, Units.range(oi, si)) / 20) * math.sqrt(Units.cap(oi)) end function Units.name(ti) local ui = Core.Units [ti] return ui and ui.name or tostring(ti) end function Units.isEvolvedForm(vi) if not Units._evolvedForms then Units._evolvedForms = { } for wi, xi in pairs(Core.Units) do if type(xi) == "table" and type(xi.evolve) == "table" and xi.evolve.evolve_unit then Units._evolvedForms [xi.evolve.evolve_unit] = true end end end return Units._evolvedForms [vi] == true end function Units.equipped() local yi = Core.session() local zi = { } if not yi then return zi end for Ai, Bi in pairs(yi.profile_data.collection.equipped_units or { }) do local Ci = yi.collection:get_unit_by_uuid(Bi) if Ci and Core.Units [Ci.unit_id] then table.insert(zi, { slot = tonumber(Ai) or 0, uuid = Bi, u = Ci, id = Ci.unit_id, data = Core.Units [Ci.unit_id] }) end end table.sort(zi, function(Di, Ei) return Di.slot < Ei.slot end) return zi end function Units.owns(Fi) local Gi = Core.session() if not Gi then return false end for Hi, Ii in pairs(Gi.profile_data.collection.owned_units or { }) do if Ii.unit_id == Fi or (Core.Units [Ii.unit_id] and Core.Units [Ii.unit_id].evolved and Core.Units [Ii.unit_id].evolved.from == Fi) then return true end end return false end Modes.PRIOR_MIN = { story = 4.5, challenge = 5, daily = 10, event = 28, raid = 6, infinite = 30, dungeon = 8, artifact = 12 } Modes.PRIOR_WIN = { story = 0.9, hard = 0.8, new = 0.65, challenge = 0.75, daily = 0.7, event = 0.75, raid = 0.6, infinite = 1, dungeon = 0.85, artifact = 0.5 } Modes.OVERHEAD = 0.7 Modes.CHAIN_MIN = 0.6 Modes._levelMod = { } function Modes.levelMod(Ji) if Ji == nil then return nil end if Modes._levelMod [Ji] ~= nil then return Modes._levelMod [Ji] or nil end Modes._levelMod [Ji] = false local Ki = Core.Levels [Ji] local Li = type(Ki) == "table" and Core.Waves [Ki.waves or Ji] local Mi = type(Li) == "table" and Li.initial_unit_configs if type(Mi) ~= "table" then return nil end local Ni, Oi, Pi = { }, { }, 0 for Qi, Ri in pairs(Mi) do local Si = type(Ri) == "table" and type(Ri.override_stats) == "table" and (Ri.override_stats [1] or Ri.override_stats ["1"]) or nil if type(Si) == "table" then local Ti = tonumber(Si.health) or 20 Pi = Pi + Ti table.insert(Ni, { hp = Ti, st = Si }) for Ui in pairs(Si) do local Vi = tostring(Ui):match("^_dmg_amp_(.+)$") or tostring(Ui):match("^_dmg_res_(.+)$") if Vi then Oi [Vi] = true end end end end if not next(Oi) or Pi <= 0 then return nil end local Wi = { weak = { }, strong = { }, res = { } } for Xi in pairs(Oi) do local Yi = 0 for Zi, _i in ipairs(Ni) do local aj = (1 + (tonumber(_i.st ["_dmg_amp_" .. Xi]) or 0)) * 100 / (100 + math.max(0, tonumber(_i.st ["_dmg_res_" .. Xi]) or 0)) Yi = Yi + _i.hp / math.max(0.05, aj) end local bj = Pi / math.max(1, Yi) if bj > 1.02 then Wi.weak [Xi] = bj elseif bj < 0.98 then Wi.res [Xi] = bj end end if not (next(Wi.weak) or next(Wi.res)) then return nil end Modes._levelMod [Ji] = Wi return Wi end function Modes.dailyMod(cj, dj, ej) cj = tonumber(cj) local fj = cj and Random.new(cj) or nil local gj = { weak = { }, strong = { }, res = { } } for hj, ij in pairs(dj or { }) do gj.weak [ij] = fj and 1 + fj:NextNumber(0.5, 0.7) or 1.5 end for jj, kj in pairs(ej or { }) do if fj then gj.res [kj] = 100 / (100 + fj:NextNumber(100, 150)) else gj.strong [kj] = true end end return gj end function Modes.rec(lj) local mj = Brain.modes [lj] if not mj then mj = { runs = 0, wins = 0, sec = 0, gems = 0, coins = 0, streak = 0 } Brain.modes [lj] = mj end return mj end function Modes.itemValue(nj, oj) if Brain.goalItems [nj] then local pj = oj or 1 local qj = math.min(pj, tonumber(Brain.goalNeed [nj]) or pj) return Brain.goalItems [nj] * qj + (Core.ITEM_VALUE [nj] or Core.ITEM_DEFAULT) * math.max(0, pj - qj) end if Config.team and Strategy.itemWorth then local rj, sj = pcall(Strategy.itemWorth, nj) if rj and sj then return sj * (oj or 1) end end local tj = Core.GS.ITEM_CAPS and Core.GS.ITEM_CAPS [nj] local uj = Core.session() local vj = 0 pcall(function() vj = uj.inventory:get_number_of_owned_item(nj) end) if tj and vj >= tj then return 0 end return (Core.ITEM_VALUE [nj] or Core.ITEM_DEFAULT) * (oj or 1) end function Modes.rewardValue(wj) local xj = 0 for yj, zj in pairs((wj and wj._rewards) or { }) do if type(zj) == "table" then xj = xj + (tonumber(zj.gem_reward) or 0) + (tonumber(zj.gold_reward) or 0) * Core.GOLD_VALUE local Aj = Utils.list(zj.item) local Bj = 1 if zj._item_multichoice and # Aj > 0 then Bj = math.min(1, (tonumber(zj._item_multichoice.min) or # Aj) / # Aj) end for Cj, Dj in ipairs(Aj) do local Ej = tonumber(Dj.amount) or ((tonumber(Dj.min_amount) or 1) + (tonumber(Dj.max_amount) or tonumber(Dj.min_amount) or 1)) / 2 local Fj = (tonumber(Dj.chance) or 1) * (1 - (tonumber(Dj._zero_chance) or 0)) xj = xj + Modes.itemValue(Dj.item_id, Ej) * Fj * Bj end end end return xj end function Modes.done(Gj, Hj, Ij) if Brain.cleared [Hj .. ":" .. tostring(Ij or "Normal")] then return true end local Jj = Gj.completed_story_levels and Gj.completed_story_levels [Hj] if not Jj then return false end if Ij and Jj.difficulty_completed_data then local Kj = Jj.difficulty_completed_data [Ij] return Kj and (Kj.times_completed or 0) > 0 or false end return (Jj.times_completed or 0) > 0 end function Modes.open(Lj) local Mj, Nj = Core.session(), Core.Levels [Lj] if not (Mj and Nj) then return false end local Oj, Pj = pcall(Core.PSC.check_if_unlocked_level, Mj, Nj) return Oj and Pj and true or false end function Modes.artifactLeft(Qj) local Rj, Sj, Tj = Core.Levels [Qj], Brain.live.artifact or { }, Core.prof() local Uj = Tj and Tj.artifact_data if not (type(Uj) == "table" and Uj._interval == Sj.interval) and type(Sj.ad) == "table" then Uj = Sj.ad end local Vj = type(Uj) == "table" and Sj.interval ~= nil and Uj._interval == Sj.interval local Wj = Vj and type(Uj._completed) == "table" and Uj._completed [Qj] local Xj = type(Wj) == "table" and type(Wj.difficulty_completed_data) == "table" and Wj.difficulty_completed_data.Hard local Yj = type(Xj) == "table" and tonumber(Xj.times_completed) or 0 local Zj = Vj and type(Uj._clear_rewards) == "table" and type(Uj._clear_rewards [Qj]) == "table" and Uj._clear_rewards [Qj].Hard or { } local _j, ak = 0, 0 for bk, ck in pairs(Rj and Rj.clear_rewards and Rj.clear_rewards.Hard or { }) do local dk = tonumber(ck.clears) or 0 if dk > ak then ak = dk end if dk > Yj and not Zj [ck._id] then local ek = ck.reward or { } _j = _j + (tonumber(ek.gem_reward) or 0) for fk, gk in ipairs(Utils.list(ek.item)) do if type(gk) == "table" and gk.item_id then _j = _j + Modes.itemValue(gk.item_id, tonumber(gk.amount) or 1) end end end end return Yj, _j, ak, Zj end function Modes.diffOpen(hk, ik) local jk, kk = Core.session(), Core.Levels [hk] if not (jk and kk) then return false end local lk, mk = pcall(Core.PSC.check_if_unlocked_difficulty, jk, kk, ik) return lk and mk and true or false end function Modes.levelName(nk) local pk = Core.Levels [nk] if not pk then return tostring(nk) end local qk = pk.world and Core.Worlds [pk.world] return (qk and qk.name and (qk.name .. " ") or "") .. (pk.name or nk) end function Modes.isInf(rk) return tostring(rk):sub(1, 4) == "inf:" end function Modes.infReach(sk) local uk = Modes.rec(sk) if (tonumber(uk.waveN) or 0) > 0 then return uk.waveSum / uk.waveN, true end local vk = Brain.simRes [sk] if vk and tonumber(vk.wave) then return vk.wave, false end return 25, false end function Modes.infDaily(wk, xk, yk) local zk = wk.daily_infinite_reward_claims and wk.daily_infinite_reward_claims [xk] local Ak = tonumber(Core.GS.DAILY_INFINITE_REWARD_PERIOD) or 82800 local Bk = (type(zk) == "table" and os.time() - (tonumber(zk.highest_claim_time) or 0) < Ak) and (tonumber(zk.highest_claimed_wave) or 0) or 0 local Ck, Dk = 0, 0 for Ek, Fk in ipairs(Utils.list(Core.GS.DAILY_INFINITE_REWARDS)) do local Gk = tonumber(Fk.wave) or 0 if Gk > Bk and Gk <= (yk or 0) then Ck, Dk = Ck + (tonumber(Fk.gem_reward) or 0), math.max(Dk, Gk) end end return Ck, Dk, Bk end function Modes.infTier(Hk) local Ik = 0 for Jk, Kk in ipairs(Utils.list(Core.GS.DAILY_INFINITE_REWARDS)) do if (tonumber(Kk.wave) or 0) <= Hk then Ik = math.max(Ik, tonumber(Kk.wave) or 0) end end return Ik end function Modes.coinName() return (tostring(Core.GS.CURRENT_EVENT_RESOURCE or "event coins"):gsub("(%l)(%u)", "%1 %2")) end function Modes.eventNow() if Modes._event ~= nil then return Modes._event or nil end Modes._event = false for Lk in pairs(Core.GS.ACTIVE_EVENT_CHALLENGES or { }) do local Mk = Core.Levels [Lk] local Nk, Ok = Lk == Hunt.ID, type(Mk) == "table" and Mk._randomequippedmode ~= nil Modes._event = { id = Lk, name = type(Mk) == "table" and Mk.name or tostring(Lk), hunt = Nk, dealt = Ok, about = Nk and "50 waves with cards and weather, for Summer Pearls. It never spends them" or Ok and "AprilCoins every wave, for the April banner" or string.format("The game's current event, for %s", Modes.coinName()) } break end return Modes._event or nil end function Modes.onlyOn() return Config.onlyRaids or Config.onlyStory or Config.onlyChallenges or (Config.onlyCoins and Modes.eventNow() ~= nil) or Config.onlyDungeons or Config.onlyLegend or Config.onlyInfinite or Config.onlyPortals or Config.onlyArtifact end function Modes.allowed(Pk) if Config.dailyLoop and Pk.kind == "daily" then return true end if not Modes.onlyOn() then return true end if Brain.nextMode and Brain.nextMode == Pk.key then return true end if Pk.kind == "daily" and Config.team then return true end if Pk.kind == "raid" then return Config.onlyRaids end if Pk.legend then return Config.onlyLegend end if Pk.kind == "story" then return Config.onlyStory and (Pk.picked or (Pk.first and Pk.diff == "Normal")) end if Pk.kind == "daily" or Pk.kind == "challenge" then return Config.onlyChallenges end if Pk.kind == "event" then return Config.onlyCoins end if Pk.kind == "dungeon" then return Config.onlyDungeons end if Pk.kind == "infinite" then return Config.onlyInfinite end if Pk.kind == "artifact" then return Config.onlyArtifact end if Pk.kind == "portal" then if Pk.picked or Config.onlyPortals then return true end for Qk, Rk in pairs(Modes.PORTALS) do if Rk.level == Pk.level then return Config.onlyInfinite and (Config.pickInfinite == "" or Config.pickInfinite == "inf:" .. Rk.from) end end end return false end function Modes.onlyText() local Sk = { } local Tk = Modes.eventNow() for Uk, Vk in ipairs({ { "onlyRaids", "raids" }, { "onlyStory", "story" }, { "onlyLegend", "legend stages" }, { "onlyInfinite", "Infinite" }, { "onlyPortals", "portals" }, { "onlyArtifact", "the Artifact Stage" }, { "onlyChallenges", "challenges" }, { "onlyCoins", Tk and (Tk.hunt and "the Summer Hunt" or Tk.name) }, { "onlyDungeons", "dungeons" } }) do if Config [Vk [1]] and Vk [2] then table.insert(Sk, Vk [2]) end end return table.concat(Sk, ", ") end Modes.skipped = { } function Modes.chName(Wk) local Xk = Core.CAR.challenges and Core.CAR.challenges [Wk] return Xk and Xk.name or tostring(Wk):gsub("_", " ") end function Modes.chOk(Yk) local Zk = Yk.kind == "daily" and Brain.live.daily and Brain.live.daily.challenge or Yk.challenge if Zk and table.find(Config.chSkip or { }, Zk) then return false, Modes.chName(Zk) .. " is on your skip list" end if Config.chSmart then local _k = Modes.sim(Yk) local al = Brain.simTeam and Brain.simTeam [Yk.key] local bl = tonumber(Config.chMinP) or 0.5 if _k and _k.p and _k.p < bl and not (al and al.better and al.won) then return false, string.format("the simulator gives your team %d%%, under the %d%% you set", math.floor(_k.p * 100 + 0.5), math.floor(bl * 100 + 0.5)) end end return true end function Modes.infIds() local cl = { } for dl, el in pairs(Core.Levels) do if type(el) == "table" and el.infinite and type(el._MATCHMAKING) == "table" and el._MATCHMAKING.gamemode == "infinite" and not el._randomequippedmode then table.insert(cl, dl) end end table.sort(cl) return cl end function Modes.dropsGoal(fl) local gl = fl.level and Core.Levels [fl.level] if not gl then return false end for hl, il in ipairs(Adventure.levelItems(gl)) do if Brain.goalItems [il.item] then return true end end return false end Hunt.ID = "summer2_event" Hunt.WEATHERS = 9 Hunt.MULT_GUESS = 3.1 Hunt.HARD_GEMS, Hunt.STARFALL_GEMS = 30, 150 Hunt.STRENGTH_GEMS = 300 function Hunt.is(jl) return jl == Hunt.ID or (jl ~= nil and Brain.live.event ~= nil and Brain.live.event.board == Hunt.ID and Brain.live.event.level == jl) end function Hunt.weatherNow() if App.where == "match" then local kl = Core.svc("WeatherServiceClient") if kl and kl.current_weather then return kl.current_weather end end return Brain.live.event and Brain.live.event.weather end function Hunt.fx(ll) local ml = { dmg = 1, cd = 1, range = 1, cost = 1, hp = 1, speed = 1, regen = 0, crit = 0, kinds = { }, shield = 0, fly = 0, ice = 0 } if ll == "rain" then ml.speed = 1.2 elseif ll == "windy" then ml.fly, ml.speed = 0.2, 1.25 elseif ll == "snow" then ml.ice = 0.5 elseif ll == "downpour" then ml.range, ml.dmg = 0.7, 0.85 elseif ll == "mana" then ml.kinds.physical, ml.kinds.magic = 100 / 250 - 1, 0.25 elseif ll == "fire" then ml.speed, ml.noCC = 1.5, true elseif ll == "underwater" then ml.speed = 1.25 elseif ll == "sandstorm" then ml.kinds.magic, ml.kinds.physical, ml.shroud = 100 / 175 - 1, 0.25, 0.2 elseif ll == "meteor_shower" then ml.starfall = Hunt.starfall() end return ml end function Hunt.starfall() local nl = type(Brain.hunt) == "table" and Brain.hunt.starfall if type(nl) ~= "table" or (tonumber(nl.runs) or 0) < 1 or not tonumber(nl.perWave) then return nil end return nl end function Match.noteStar(ol, pl) Match.stars = Match.stars or setmetatable({ }, { __mode = "k" }) if Match.stars [ol] then return end Match.stars [ol] = true local ql = Match.wave() Match.starRun = Match.starRun or { n = 0, waves = { }, hpK = { } } local rl = Match.starRun rl.n = rl.n + 1 rl.waves [ql] = (rl.waves [ql] or 0) + 1 local sl = Match.info and Match.info.cfgById and Match.info.cfgById.star_golem_1 local tl, ul = pcall(Simulator.enemy, Match.info, sl, ql) local vl = pl and tonumber(pl.MaxHealth) or 0 if sl and tl and ul and (ul.hp or 0) > 0 and vl > 0 then table.insert(rl.hpK, vl / ul.hp) end end function Hunt.starfallRecord() local wl = Match.starRun if not (wl and wl.n > 0) then return end local xl = math.max(1, Match.wave()) local yl = wl.n / xl local zl if # wl.hpK > 0 then local Al = table.clone(wl.hpK) table.sort(Al) zl = Al [math.floor(# Al / 2) + 1] end local Bl = type(Brain.hunt.starfall) == "table" and Brain.hunt.starfall or { } local Cl = (tonumber(Bl.runs) or 0) + 1 local Dl = 1 / math.min(Cl, 5) Brain.hunt.starfall = { runs = Cl, perWave = Bl.perWave and (Bl.perWave + (yl - Bl.perWave) * Dl) or yl, hpK = zl and (Bl.hpK and (Bl.hpK + (zl - Bl.hpK) * Dl) or zl) or Bl.hpK, at = os.time() } local El, Fl for Gl in pairs(wl.waves) do El = math.min(El or Gl, Gl) Fl = math.max(Fl or Gl, Gl) end App.log(1, "STARFALL", string.format("%d Star Golems over %d waves (about %.2f a wave, from wave %s to %s), their HP ร—%.2f the simulator's for a Star Golem ยท the simulator plays Starfall with them from now on", wl.n, xl, yl, tostring(El), tostring(Fl), zl or 0)) return { n = wl.n, waves = xl, hpK = zl and math.floor(zl * 100 + 0.5) / 100 or nil } end function Hunt.perWave(Hl) local Il = Core.Levels [Hunt.ID] local Jl = Il and Il.infinite_resource_rewards and Il.infinite_resource_rewards.SummerPearls for Kl, Ll in ipairs(Jl or { }) do if Hl <= (tonumber(Ll.wave_up_to) or 0) then return tonumber(Ll.reward) or 0 end end return 0 end function Hunt.pearlsFrom(Ml, Nl) local Ol = 0 for Pl = Ml, Nl do Ol = Ol + Hunt.perWave(Pl) end return Ol end function Hunt.board() local Ql = Core.core("Summer2MapBoardCore") local Rl = Core.prof() if not (Ql and Rl) then return nil end local Sl = Ql.get_board(LocalPlayer.UserId) if not Sl then return nil end local Tl = type(Rl._summer2_hunt_wins) == "table" and Rl._summer2_hunt_wins or { } local Ul, Vl, Wl = Tl.maps or { }, Tl.weathers or { }, Rl._summer2_map_board_claimed_lines or { } local Xl, Yl, Zl = 0, 0, 0 for am, bm in ipairs(Sl) do if Ul [bm] then Xl = Xl + 1 end end for cm in pairs(Ql.get_completed_lines(Sl, Ul)) do Yl = Yl + 1 end for dm in pairs(Vl) do Zl = Zl + 1 end return { core = Ql, board = Sl, maps = Ul, weathers = Vl, claimed = Wl, squares = Xl, lines = Yl, weathersWon = Zl, total = # Ql.LINES, claimable = Ql.get_claimable_lines(Sl, Ul, Wl) } end function Hunt.lineGems() return 2000 * Config.pearlValue + 10 * (Core.ITEM_VALUE.star_remnant_limited or 25) end function Hunt.firstValue(em, fm) local hm = Hunt.board() if not hm then return 0, { } end local im, jm = 0, { } local km = em and table.find(hm.board, em) if km and not hm.maps [em] then local lm = 0 for mm, nm in ipairs(hm.core.LINES) do if table.find(nm.cells, km) then lm = lm + 1 end end im = im + lm * Hunt.lineGems() / hm.core.SIZE table.insert(jm, "a new bingo square") end if fm and fm ~= "clear" and not hm.weathers [fm] then im = im + (1000 + 5000 * Config.pearlValue + 50 * (Core.ITEM_VALUE.star_remnant_limited or 25)) / Hunt.WEATHERS table.insert(jm, "a new weather for the quest") end if not next(hm.maps) then im = im + 500 + 4500 * Config.pearlValue + 200 table.insert(jm, "the first Explosion Mage") end return im, jm end function Hunt.row(om) local pm = Brain.live.event local qm = Brain.simRes ["ev:" .. tostring(pm.level)] local rm = qm and (qm.won and 50 or tonumber(qm.wave)) or 50 local sm = om.runs > 0 and om.coins / om.runs or Hunt.pearlsFrom(1, rm) * Hunt.MULT_GUESS local tm, um = Hunt.firstValue(pm.map, pm.weather) local vm = Core.data("Weather") or { } local wm = { key = "ev:" .. tostring(pm.level), kind = "event", level = pm.level, diff = "Hard", value = sm * Config.pearlValue, coins = sm, p0 = 0.5, rec = om, label = "Summer Hunt" } local xm = 1 if tm > 0 then local ym, zm = pcall(Modes.winP, wm) xm = ym and Utils.clamp(tonumber(zm) or 0.5, 0, 1) or 0.5 wm.value = wm.value + tm * xm end wm.why = string.format("โ‰ˆ%d Summer Pearls a run%s%s", math.floor(sm), pm.weather and vm [pm.weather] and (" ยท " .. vm [pm.weather].name) or "", # um > 0 and (" ยท a win adds " .. table.concat(um, ", ") .. string.format(" (win โ‰ˆ%d%%)", math.floor(xm * 100 + 0.5))) or "") return wm end function Hunt.floor() return tonumber(Brain.hunt.pmin) or 1 end function Hunt.claim() local Am = Hunt.board() if not (Am and # Am.claimable > 0) then return end local Bm = App.request("bingo", "claim_summer2_map_board_lines") if Bm then App.log(1, "LOBBY", string.format("Summer Bingo: claimed %d line%s, each 2,000 Summer Pearls and 10 Limited Star Remnants", # Am.claimable, # Am.claimable == 1 and "" or "s")) end end function Hunt.write(Cm, Dm) local Em = Core.FOLDER .. "/" .. Cm local Fm, Gm = pcall(readfile, Em) pcall(writefile, Em, (Fm and type(Gm) == "string" and Gm or "") .. Dm .. "\n") end function Hunt.plain(Hm, Im) Im = Im or 0 if type(Hm) == "table" then if Im > 5 then return "..." end local Jm = { } for Km, Lm in pairs(Hm) do Jm [tostring(Km)] = Hunt.plain(Lm, Im + 1) end return Jm end if type(Hm) == "number" or type(Hm) == "string" or type(Hm) == "boolean" then return Hm end return tostring(Hm) end function Hunt.shopLook() local Mm, Nm = App.request("bsd", "request_current_bsd_shop") if Mm and type(Nm) == "table" then local Om, Pm = pcall(HttpService.JSONEncode, HttpService, Nm) if Om then pcall(writefile, Core.FOLDER .. "/hunt_shop.txt", os.date("%m-%d %H:%M ") .. Pm) end end local Qm, Rm = App.request("live_e", "get_event_challenges") if Qm and type(Rm) == "table" and type(Rm [Hunt.ID]) == "table" then local Sm = { } for Tm, Um in pairs(Rm [Hunt.ID]) do if Tm ~= "current_level_data" then Sm [Tm] = Um end end local Vm, Wm = pcall(HttpService.JSONEncode, HttpService, Sm) if Vm then pcall(writefile, Core.FOLDER .. "/hunt_board.txt", os.date("%m-%d %H:%M ") .. Wm) end end end function Hunt.charmApplies(Xm) if not Xm then return false end if Xm.kind == "event" and Hunt.is(Xm.level) then return true end return Xm.kind == "portal" and tostring(Xm.level):find("skypiea", 1, true) ~= nil end function Hunt.charms() local Ym = Core.prof() local Zm = { } for _m, an in pairs((Ym and Ym.inventory and Ym.inventory.unique_items) or { }) do local bn = type(an) == "table" and Core.Items [an.item_id] if type(bn) == "table" and bn.item_group == "Charms" then local cn = { uuid = an.uuid or _m, id = an.item_id, tier = tonumber(bn.tier) or 0, rolls = { } } local dn = type(an._unique_item_data) == "table" and type(an._unique_item_data._unique_charm_data) == "table" and an._unique_item_data._unique_charm_data._d for en, fn in pairs(type(dn) == "table" and dn or { }) do if type(fn) == "table" and fn._t then cn.rolls [tostring(fn._t)] = (cn.rolls [tostring(fn._t)] or 0) + (tonumber(fn._d) or 0) end end table.insert(Zm, cn) end end return Zm end function Hunt.charmOn() local gn = Core.prof() local hn = gn and gn.equipped_charms if type(hn) ~= "table" then return nil end for jn, kn in pairs(hn) do return type(kn) == "table" and (kn.uuid or kn [1]) or kn end return nil end function Hunt.charmScore(ln, mn) local nn = (mn and tonumber(mn.p)) or 0.5 local pn, qn, rn = ln.rolls.unit_damage or 0, ln.rolls.reward_mul or 0, 0 for sn, tn in pairs(ln.rolls) do if sn ~= "unit_damage" and sn ~= "reward_mul" then rn = rn + tn end end if nn >= 0.92 then return qn * 3 + pn + rn * 0.5 end return pn * 3 + qn + rn * 0.5 end function Hunt.charmWords(un) local vn = { } for wn, xn in pairs(un.rolls) do table.insert(vn, string.format("%s +%d%%", wn == "unit_damage" and "damage" or (wn == "reward_mul" and "rewards" or wn), math.floor(xn * 100 + 0.5))) end table.sort(vn) return string.format("Tier %d: %s", un.tier, # vn > 0 and table.concat(vn, ", ") or "no bonus read") end function Hunt.charmDmg(yn) if type(yn) ~= "table" or not (Hunt.is(yn.id) or yn.portal_group == "summer2" or tostring(yn.id):find("skypiea_portal", 1, true)) then return 0 end local zn, An = Hunt.charmOn(), 0 for Bn, Cn in ipairs(Hunt.charms()) do local Dn = Cn.rolls.unit_damage or 0 if zn and Cn.uuid == zn then return Dn end if Config.smartCharm and Dn > An then An = Dn end end return zn and 0 or An end function Hunt.charmWant(En) if not Config.smartCharm or not Hunt.charmApplies(En) then return nil end Brain.charm = type(Brain.charm) == "table" and Brain.charm or { } if os.time() - (tonumber(Brain.charm.failAt) or 0) < 1800 then return nil end local Fn, Gn for Hn, In in ipairs(Hunt.charms()) do local Jn = Hunt.charmScore(In, En) if not Gn or Jn > Gn then Fn, Gn = In, Jn end end if not Fn then return nil end local Kn = Hunt.charmOn() or (App.where == "match" and Brain.charm.uuid) or nil if Kn == Fn.uuid then return nil end return Fn, Kn end function Hunt.charmTick(Ln) local Mn, Nn = Hunt.charmWant(Ln) if not Mn then return end Nn = Hunt.charmOn() if Nn then App.request("charm", "request_unequip_charm", Nn) task.wait(1) end App.request("charm", "use_unique_item", Mn.uuid) local On = Utils.now() while Utils.now() - On < 4 and Hunt.charmOn() ~= Mn.uuid do task.wait(0.5) end if Hunt.charmOn() == Mn.uuid then Brain.charm.uuid, Brain.charm.at, Brain.charm.failAt = Mn.uuid, os.time(), nil App.saveSoon() App.log(1, "CHARM", string.format("wearing the Summer Charm (%s) for %s (win %d%%: %s first)", Hunt.charmWords(Mn), tostring(Ln.label), math.floor((tonumber(Ln.p) or 0) * 100 + 0.5), (tonumber(Ln.p) or 0) >= 0.92 and "rewards" or "damage")) else Brain.charm.failAt = os.time() App.saveSoon() App.log(1, "CHARM", string.format("the game didn't put on the Summer Charm (%s): trying again in 30 min", Hunt.charmWords(Mn))) end end function Hunt.weatherStrength(Pn) local Qn = Hunt.fx(Pn) local Rn = Qn.dmg / Qn.cd / Qn.hp / Qn.speed * (1 + (Qn.range - 1) / 2) for Sn, Tn in pairs(Qn.kinds) do Rn = Rn * (1 + Match.rogueShare(Sn) * Tn) end if Qn.ice > 0 then local Un = Match.rogueShare("fire") Rn = Rn * (Un + (1 - Un) / (1 - Qn.ice + Qn.ice / Simulator.modFactor("Ice@snow"))) end if Qn.fly > 0 then Rn = Rn * (1 - Qn.fly * 0.5) end if Qn.noCC then Rn = Rn * 0.95 end if Qn.shroud then Rn = Rn * 0.9 end return Rn end function Hunt.cardGems(Vn, Wn) local Xn = 0 local Yn = Hunt.pearlsFrom(Wn + 1, 50) for Zn, ao in pairs(type(Vn.effects) == "table" and Vn.effects or { }) do if type(ao) == "table" then local bo = tonumber(ao.amount) or 0 if ao.type == "candy_multiplier" then Xn = Xn + math.abs(bo) * Yn * Config.pearlValue elseif ao.type == "add_reward" then local co, eo = pcall(Modes.rewardValue, ao.reward) if co then Xn = Xn + (tonumber(eo) or 0) end elseif ao.type == "golem_chance" then Xn = Xn + bo * Hunt.STARFALL_GEMS elseif ao.type == "secret_drop" then Xn = Xn + bo * 100 end end end if Vn.option_type == "Bad" then Xn = Xn + Hunt.HARD_GEMS end if Vn.weather then local fo = Vn.weather ~= Hunt.weatherNow() if fo and Vn.weather == "meteor_shower" then Xn = Xn + Hunt.STARFALL_GEMS end local go = Hunt.board() if fo and go and Vn.weather ~= "clear" and not go.weathers [Vn.weather] then Xn = Xn + (1000 + 5000 * Config.pearlValue + 50 * (Core.ITEM_VALUE.star_remnant_limited or 25)) / Hunt.WEATHERS end end return Xn end function Hunt.text() local ho = Brain.live.event if not (ho and Hunt.is(ho.level)) then return "The Summer Hunt isn't on the board right now." end local io, jo = Core.data("Weather") or { }, Core.data("Maps") or { } local ko = { } local function mo(no) return no and jo [no] and jo [no].name or no or "?" end table.insert(ko, string.format("This hour: %s, %s.", mo(ho.map), ho.weather and io [ho.weather] and io [ho.weather].name or "weather unknown")) if type(ho.forecast) == "table" then table.insert(ko, string.format("Next hour: %s, %s.", ho.forecast.map and mo(ho.forecast.map) or (ho.forecast.map_hint or "map hidden"), ho.forecast.weather and io [ho.forecast.weather] and io [ho.forecast.weather].name or (ho.forecast.weather_hint or "weather hidden"))) end local oo = Modes.rec("ev:" .. tostring(ho.level)) if oo.runs > 0 then table.insert(ko, string.format("%d runs so far, %d won, โ‰ˆ%d Summer Pearls a run.", oo.runs, oo.wins, math.floor(oo.coins / oo.runs))) else table.insert(ko, string.format("Not played yet. A full run should pay about %d Summer Pearls.", math.floor(Hunt.pearlsFrom(1, 50) * Hunt.MULT_GUESS))) end local po = Hunt.board() if po then table.insert(ko, string.format("Bingo: %d of %d squares, %d of %d lines. Weathers won: %d of %d.", po.squares, # po.board, po.lines, po.total, po.weathersWon, Hunt.WEATHERS)) end local qo = table.find(Hunt.CARD_PICKS, Config.huntCards) and Config.huntCards or "Smart" table.insert(ko, "Cards: " .. (({ ["Secret Luck"] = "the most secret luck, then the most pay. Secret luck only comes on the harder cards, so one is skipped when it would leave your team too weak to win.", ["Gems"] = "the one that pays the most in gems and pearls, skipping any that would leave your team too weak to win.", ["Easiest Win"] = "the one that makes your team strongest, whatever it pays.", ["Smart"] = "what each pays weighed against how much weaker it leaves your team." }) [qo])) table.insert(ko, string.format("You hold %s Summer Pearls. The farm never trades gems for them, and spends them only on Sunny Stars, only with Open Sunny Stars on.", Utils.fmt(tonumber(Core.cur("SummerPearls")) or 0))) return table.concat(ko, "\n") end Hunt.SKIN_SET, Hunt.STAR = "summer2", "capsule_summer2" Hunt.SKIN_PRICE = { Rare = 10, Epic = 40, Legendary = 75, Mythic = 300 } Hunt.SKIN_DROP = { Rare = 0.02, Epic = 0.05, Legendary = 0.25, Mythic = 0.4 } Hunt.STAR_COST = 150 function Hunt.skinSet(ro) local so = type(ro) == "table" and ro.id return (so == Hunt.ID or so == "summer2_portal_final" or (type(ro) == "table" and ro.portal_group == "summer2")) and Hunt.SKIN_SET or nil end function Hunt.skinFits(uo, vo) local wo = Core.Units [uo] local xo = wo and wo.evolved and wo.evolved.from or uo return vo.for_unit == uo or vo.for_unit == xo or (vo.for_units ~= nil and (vo.for_units [uo] or vo.for_units [xo]) and true) or false end function Hunt.team() local yo = Brain.simTeam and Brain.simTeam ["ev:" .. tostring(Hunt.ID)] local zo = Core.session() local Ao = { } for Bo, Co in ipairs(yo and yo.uuids or Brain.savedTeam or { }) do local Do = zo.collection:get_unit_by_uuid(Co) if Do then table.insert(Ao, Do) end end return Ao end function Hunt.skinPlan() local Eo, Fo = Core.session(), Core.prof() local Go = Core.data("Skins") or { } local Ho = (Fo.inventory and Fo.inventory.unique_items) or Fo.unique_items or { } local Io, Jo = { }, { kinds = { }, sell = { }, wear = { }, pearls = 0, missing = { } } for Ko, Lo in pairs(Ho) do local Mo = type(Lo) == "table" and Core.Items [Lo.item_id] if Mo and Mo.item_group == "Skins" and Mo._skin_set == Hunt.SKIN_SET and Mo.usage and Go [Mo.usage.skin_id] then local No = Io [Lo.item_id] if not No then No = { id = Lo.item_id, name = Mo.name, rarity = Mo.rarity, skinId = Mo.usage.skin_id, skin = Go [Mo.usage.skin_id], uuids = { } } Io [Lo.item_id] = No table.insert(Jo.kinds, No) end table.insert(No.uuids, Lo.uuid or Ko) end end table.sort(Jo.kinds, function(Oo, Po) return Oo.name < Po.name end) local Qo = Hunt.team() local Ro = Eo.profile_data.collection.owned_units for So, To in ipairs(Jo.kinds) do for Uo, Vo in pairs(Ro) do if Hunt.skinFits(Vo.unit_id, To.skin) then To.fits = true break end end local Wo = To.rarity == "Legendary" or To.rarity == "Mythic" To.keep = (To.fits or Wo) and 1 or 0 for Xo = To.keep + 1, # To.uuids do table.insert(Jo.sell, { uuid = To.uuids [Xo], name = To.name, pearls = Hunt.SKIN_PRICE [To.rarity] or 0 }) Jo.pearls = Jo.pearls + (Hunt.SKIN_PRICE [To.rarity] or 0) end end for Yo, Zo in ipairs(Qo) do local _o = Zo.skin and Zo.skin._skin_id and Go [Zo.skin._skin_id] if not (_o and _o._skin_set == Hunt.SKIN_SET) then for ap, bp in pairs(Go) do if type(bp) == "table" and bp._skin_set == Hunt.SKIN_SET and Hunt.skinFits(Zo.unit_id, bp) then local cp = ap .. "_skin" if Io [cp] then table.insert(Jo.wear, { uuid = Io [cp].uuids [1], unit = Zo.uuid, unitName = Units.name(Zo.unit_id), name = Io [cp].name }) else table.insert(Jo.missing, { item = cp, unitName = Units.name(Zo.unit_id), name = bp.name, boost = tonumber(bp._damage_boost) or 0, drop = Hunt.SKIN_DROP [bp._skin_rarity] or 0 }) end end end end end return Jo end function Hunt.starPlan(dp) dp = dp or Hunt.skinPlan() local ep = Core.Items [Hunt.STAR] or { } local fp = tonumber(ep._mythic_unit_pity) or 400 local gp = (Core.prof().capsule_pity_data or { }) [Hunt.STAR] or { } local ip = tonumber(gp.opens_since_mythic_unit) or 0 local jp = { opened = tonumber(gp.total_opens) or 0, left = math.max(1, fp - ip), units = { } } local kp = tonumber(Core.cur("SummerPearls")) or 0 jp.can = math.max(0, math.floor((kp - (tonumber(Config.pearlFloor) or 0)) / Hunt.STAR_COST)) local lp = { } for mp, np in pairs(Core.session().profile_data.collection.owned_units) do lp [np.unit_id] = true local op = Core.Units [np.unit_id] if op and op.evolved and op.evolved.from then lp [op.evolved.from] = true end end for pp, qp in pairs(ep.usage and ep.usage.possible_rewards or { }) do local rp = qp.unit or qp.item if rp and Core.Units [rp] and Core.Units [rp].rarity == "Mythic" and not lp [rp] then table.insert(jp.units, Units.name(rp)) end end table.sort(jp.units) local tp = { } if # dp.missing > 0 then local up = dp.missing [1] table.insert(tp, string.format("%s to go on %s", up.name, up.unitName)) end if # jp.units > 0 then table.insert(tp, string.format("a Mythic unit you don't have (%s)", table.concat(jp.units, ", "))) end jp.why = table.concat(tp, " and ") jp.open = # tp > 0 and math.min(jp.can, # jp.units > 0 and jp.left or 100) or 0 return jp end function Hunt.skinText() local vp, wp = pcall(Hunt.skinPlan) if not vp then return "Reading your skins: " .. tostring(wp) end local yp = { } local zp = { } for Ap, Bp in ipairs(wp.kinds) do table.insert(zp, string.format("%s%s (+%d%% damage%s)", Bp.name, # Bp.uuids > 1 and (" ร—" .. # Bp.uuids) or "", (tonumber(Bp.skin._damage_boost) or 0) * 100, Bp.fits and "" or ", no unit of yours fits")) end table.insert(yp, "A skin counts only in the Hunt, on the unit wearing it. You hold: " .. (# zp > 0 and table.concat(zp, ", ") or "none") .. ".") if # wp.wear > 0 then local Cp = { } for Dp, Ep in ipairs(wp.wear) do table.insert(Cp, Ep.name .. " on " .. Ep.unitName) end table.insert(yp, "Would wear: " .. table.concat(Cp, ", ") .. ".") end for Fp, Gp in ipairs(wp.missing) do table.insert(yp, string.format("Your Hunt team could use %s on %s: +%d%% damage, +%d%% Secret Portal drop.", Gp.name, Gp.unitName, Gp.boost * 100, Gp.drop * 100)) end if # wp.sell > 0 then local Hp = { } for Ip, Jp in ipairs(wp.sell) do Hp [Jp.name] = (Hp [Jp.name] or 0) + 1 end local Kp = { } for Lp, Mp in pairs(Hp) do table.insert(Kp, Mp .. "ร— " .. Lp) end table.sort(Kp) table.insert(yp, string.format("Would sell %s for %s pearls. It can't be undone.", table.concat(Kp, ", "), Utils.fmt(wp.pearls))) else table.insert(yp, "Nothing to sell.") end local Np, Op = pcall(Hunt.starPlan, wp) if Np then table.insert(yp, string.format("Sunny Stars: %d opened. A sure Mythic unit in %d more (%s pearls).", Op.opened, Op.left, Utils.fmt(Op.left * Hunt.STAR_COST))) if Op.open > 0 then table.insert(yp, string.format("Would open %d (%s pearls) for %s, keeping %s pearls.", Op.open, Utils.fmt(Op.open * Hunt.STAR_COST), Op.why, Utils.fmt(Config.pearlFloor))) elseif Op.why ~= "" then table.insert(yp, string.format("Wants %s, but that would go under the %s pearls you keep.", Op.why, Utils.fmt(Config.pearlFloor))) else table.insert(yp, "No need for more stars.") end end if FarmHub.pickNote.skins then table.insert(yp, FarmHub.pickNote.skins) end return table.concat(yp, "\n") end function Hunt.skinTick() if not (Config.skinSmart or Config.starOpen) or not (Brain.live.event and Hunt.is(Brain.live.event.level)) then return end if (App.req.back.skins or 0) > Utils.now() then return end local Pp = Core.session() local function Qp(Rp, Sp, Tp) local Up = App.gameSaid(Sp) FarmHub.pickNote.skins = string.format("%s: the game said no (%s). Trying again in 30 min.", Rp, tostring(Up or Tp or "no answer")) App.log(1, "LOBBY", FarmHub.pickNote.skins) App.backoff("skins", 1800) end local Vp = Hunt.skinPlan() if Config.skinSmart then local Wp = Vp.wear [1] if Wp and (App.req.back.skinWear or 0) <= Utils.now() then local Xp = Strategy.slotOf(Wp.unit) if Xp then Brain.str.reequip = { uuid = Wp.unit, slot = Xp } App.saveNow() App.request("uneq", "unequip_unit", Wp.unit) local Yp = Utils.now() while Utils.now() - Yp < 4 and Strategy.slotOf(Wp.unit) do task.wait(0.25) end end local Zp = Utils.now() local _p, aq = App.request("skinWear", "use_unique_item", Wp.uuid, { unit_uuid = Wp.unit }) local function bq() local cq = Pp.collection:get_unit_by_uuid(Wp.unit) return cq and cq.skin ~= nil end while Utils.now() - Zp < 5 and not bq() do task.wait(0.25) end if Xp then Strategy.reequip() end if bq() then App.log(1, "LOBBY", string.format("Summer skins: %s on %s", Wp.name, Wp.unitName)) FarmHub.pickNote.skins = nil Vp = Hunt.skinPlan() else local dq = App.gameSaid(Zp) FarmHub.pickNote.skins = string.format("Wearing %s: the game said no (%s). Trying again in 30 min.", Wp.name, tostring(dq or aq or "no answer")) App.log(1, "LOBBY", FarmHub.pickNote.skins) App.backoff("skinWear", 1800) end end if # Vp.sell > 0 then local eq, fq = { }, 0 for gq = 1, math.min(50, # Vp.sell) do table.insert(eq, Vp.sell [gq].uuid) fq = fq + Vp.sell [gq].pearls end local hq = tonumber(Core.cur("SummerPearls")) or 0 local iq = Utils.now() local jq, kq = App.request("skinSell", "delete_unique_items", eq) while Utils.now() - iq < 5 and (tonumber(Core.cur("SummerPearls")) or 0) <= hq do task.wait(0.25) end local lq = (tonumber(Core.cur("SummerPearls")) or 0) - hq if lq <= 0 then return Qp("Selling skins", iq, kq) end Spending.add(string.format("sold %d spare Summer skins", # eq), # eq, "skins", string.format("Smart Skins: +%s pearls", Utils.fmt(lq))) FarmHub.pickNote.skins = nil end end if Config.starOpen then local mq = Pp.inventory local function nq() local oq, pq = pcall(mq.get_number_of_owned_item, mq, Hunt.STAR) return oq and tonumber(pq) or 0 end local qq = Utils.now() for rq = 1, 8 do if Utils.now() - qq > 15 or not App.alive or App.where ~= "lobby" then break end local sq = nq() local tq = Hunt.starPlan(Hunt.skinPlan()) local uq = Utils.now() if sq > 0 then local vq, wq = App.request("starOpen", "use_item", Hunt.STAR, { use10 = sq >= 10, use_all = false }) while Utils.now() - uq < 6 and nq() >= sq do task.wait(0.25) end if nq() >= sq then return Qp("Opening a Sunny Star", uq, wq) end App.log(1, "LOBBY", string.format("Sunny Stars: opened %d ยท %d opened in all", sq - nq(), (tonumber(((Core.prof().capsule_pity_data or { }) [Hunt.STAR] or { }).total_opens) or 0))) FarmHub.pickNote.skins = nil elseif tq.open > 0 then local xq = tq.open >= 10 and 10 or 1 local yq = tonumber(Core.cur("SummerPearls")) or 0 local zq, Aq = App.request("starBuy", "buy_item_generic", Hunt.STAR, "event", "event_shop", tostring(xq)) while Utils.now() - uq < 5 and nq() <= 0 do task.wait(0.25) end if nq() <= 0 then return Qp("Buying a Sunny Star", uq, Aq) end Spending.add(string.format("%d Sunny Stars", nq()), yq - (tonumber(Core.cur("SummerPearls")) or 0), "Summer Pearls", "Open Sunny Stars: for " .. tq.why) FarmHub.pickNote.skins = nil else break end end end end function Modes.list() local Bq = Core.prof() if not Bq then return { } end local Cq = Bq.level_data local Dq = { } local Eq = os.time() local function Fq(Gq) Gq.rec = Modes.rec(Gq.key) local Hq = (Gq.rec.restUntil or 0) > Eq and Gq.kind ~= "portal" if Hq and Gq.kind == "daily" and Config.dailyLoop and (Gq.rec.restWhy == "losses" or (Gq.rec.restWhy == nil and math.abs(Gq.rec.restUntil - (tonumber(Gq.rec.last) or 0) - 6 * 3600) <= 5)) then Hq = false end if Hq or not Modes.allowed(Gq) then return end if Gq.kind == "daily" and not Config.dailyMatch and not Config.dailyLoop then local Iq, Jq = pcall(Modes.needsHelp, Gq) if Iq and Jq then Modes.skipped [Gq.key] = Gq.label .. ": the simulator says your team can't win it alone, and Matchmake The Daily is off" return end end Gq.mod = Gq.mod or Modes.levelMod(Gq.level) if (Gq.kind == "daily" or Gq.kind == "challenge") and not (Gq.kind == "daily" and Config.dailyLoop) then local Kq, Lq = Modes.chOk(Gq) Modes.skipped [Gq.key] = not Kq and (Gq.label .. ": " .. Lq) or nil if not Kq then return end end table.insert(Dq, Gq) end local Mq = Brain.live or { } if (Config.dailyChallenge or Config.onlyChallenges or Config.dailyLoop) and Mq.daily and Mq.daily.uuid and Mq.daily.uuid ~= Bq.last_completed_daily_challenge_uuid and Mq.daily.uuid ~= Mq.daily.doneUuid and ((Mq.daily.lost or 0) < 2 or Config.dailyLoop) then Fq({ key = "dc", kind = "daily", level = "__DAILY_CHALLENGE", diff = "Hard", value = Mq.daily.value or 300, label = "Daily Challenge", why = string.format("daily reward โ‰ˆ%s gems of gems + items", Utils.fmt(Mq.daily.value or 0)), mod = Mq.daily.mod }) end if (Config.challenge or Config.onlyChallenges) and Mq.normal and Mq.normal.uuid and Mq.normal.uuid ~= Bq.last_completed_challenge_uuid and Mq.normal.uuid ~= Mq.normal.doneUuid and (Mq.normal.expires or 0) > Eq + 360 and (Mq.normal.lost or 0) < 2 then Fq({ key = "ch", kind = "challenge", level = Mq.normal.level, diff = "Hard", value = Mq.normal.value or 100, label = "Challenge ยท " .. Modes.levelName(Mq.normal.level), why = "new challenge every 30 min", challenge = Mq.normal.challenge }) end local Nq = Utils.list(Core.WLO.WORLD_ORDER) local Oq = Config.pickStory or "" local function Pq(Qq, Rq, Sq, Tq) if not Modes.done(Cq, Qq, Rq) then return { key = "st:" .. Qq .. ":" .. Rq, kind = "story", first = true, level = Qq, diff = Rq, rank = Sq, picked = Tq, value = (Core.GS.GEM_REWARD_STORY_FIRST or 60) + Config.unlockValue, p0 = Sq >= 16 and Modes.PRIOR_WIN.new or (Rq == "Hard" and Modes.PRIOR_WIN.hard or Modes.PRIOR_WIN.story), label = Modes.levelName(Qq) .. " (" .. Rq .. ")", why = (Tq and "your pick ยท " or "") .. "first clear: " .. (Core.GS.GEM_REWARD_STORY_FIRST or 60) .. " gems + unlocks the next act" } end return { key = "st:" .. Qq .. ":" .. Rq, kind = "story", level = Qq, diff = Rq, rank = Sq, picked = Tq, value = (Core.GS.GEM_REWARD_STORY or 20), p0 = Rq == "Hard" and Modes.PRIOR_WIN.hard or Modes.PRIOR_WIN.story, label = Modes.levelName(Qq) .. " (" .. (Tq and Rq or "farm") .. ")", why = (Tq and "your pick ยท " or "") .. (Core.GS.GEM_REWARD_STORY or 20) .. " gems a clear" } end local function Uq(Vq) for Wq, Xq in ipairs(Nq) do local Yq = Core.Worlds [Xq] if Yq and not Yq.legend_stage and not Yq.raid_world then for Zq, _q in ipairs(Utils.list(Yq.levels)) do if Modes.open(_q.id) and Vq(_q.id, Wq) then return end end end end end if not (Config.firstClears or Config.farm or Config.onlyStory) then elseif Oq == "progress" then for ar, br in ipairs({ "Normal", "Hard" }) do local cr Uq(function(dr, er) if Modes.diffOpen(dr, br) and not Modes.done(Cq, dr, br) then cr = Pq(dr, br, er, true) return true end end) if cr then Fq(cr) break end end elseif Oq ~= "" then local fr, gr = Oq:match("^st:(.+):(%a+)$") if fr and Modes.open(fr) and Modes.diffOpen(fr, gr) then Fq(Pq(fr, gr, 1, true)) end else Uq(function(hr, ir) for jr, kr in ipairs({ "Normal", "Hard" }) do if Modes.diffOpen(hr, kr) and not Modes.done(Cq, hr, kr) and (Config.firstClears or Config.onlyStory) then Fq(Pq(hr, kr, ir, false)) break end end if Config.farm and ir <= 3 and Modes.done(Cq, hr, "Normal") then Fq(Pq(hr, "Normal", ir, false)) end end) end local lr = Config.pickRaid or "" if Config.raids or Config.onlyRaids then for mr, nr in ipairs(Utils.list(Core.WLO.RAID_WORLD_ORDER)) do local pr = Core.Worlds [nr] for qr, rr in ipairs(Utils.list(pr and pr.levels)) do local sr = Core.Levels [rr.id] if sr and Modes.open(rr.id) and (lr == "" or lr == "rd:" .. rr.id) then local ur = tonumber(sr._raid_daily_tries) local vr = (Bq.raid_tries and Bq.raid_tries.levels and tonumber(Bq.raid_tries.levels [rr.id])) or 0 if not ur or vr < ur then local wr, xr = 0, nil for yr, zr in ipairs(Adventure.levelItems(sr)) do wr = wr + Modes.itemValue(zr.item, zr.per) if Brain.goalItems [zr.item] then xr = string.format("โ‰ˆ%.0f %s a run toward your goal", zr.per, (Core.Items [zr.item] or { }).name or zr.item) end end local Ar = not Modes.done(Cq, rr.id) local Br = tonumber(sr._override_gem_reward) or (Core.GS.GEM_REWARD_RAID or 75) Fq({ key = "rd:" .. rr.id, kind = "raid", level = rr.id, diff = "Hard", first = Ar, picked = lr ~= "", value = Br + wr + (Ar and Config.unlockValue or 0), p0 = Modes.PRIOR_WIN.raid, label = Modes.levelName(rr.id), why = (lr ~= "" and "your pick ยท " or "") .. (xr and (xr .. " ยท ") or "") .. string.format("raid: %d gems + items%s", Br, ur and string.format(" ยท %d/%d tries left", ur - vr, ur) or "") }) end end end end end if (Config.artifact or Config.onlyArtifact) and Brain.live.artifact then for Cr, Dr in ipairs(Brain.live.artifact.list or { }) do local Er = Core.Levels [Dr.level] if Er and Modes.open(Dr.level) then local Fr, Gr, Hr = Modes.artifactLeft(Dr.level) if Fr < Hr then local Ir = 0 for Jr, Kr in ipairs(Adventure.levelItems(Er)) do Ir = Ir + Modes.itemValue(Kr.item, Kr.per) end local Lr = tonumber(Er._override_gem_reward) or 30 Fq({ key = "ar:" .. Dr.level, kind = "artifact", level = Dr.level, world = Dr.world, diff = "Hard", value = Lr + Ir + Gr / math.max(1, Hr - Fr), label = (Core.Worlds [Dr.world] or { }).name or Modes.levelName(Dr.level), why = string.format("Artifact Stage, alone: %d/%d clears this rotation ยท %d gems a clear + %s gems of milestones to come", Fr, Hr, Lr, Utils.fmt(Gr)) }) end end end end local Mr = Config.pickLegend or "" local Nr = Mr ~= "progress" and Mr ~= "" and Mr or nil if Mr == "progress" then for Or, Pr in ipairs(Utils.list(Core.WLO.LEGEND_WORLD_ORDER)) do local Qr = Core.Worlds [Pr] for Rr, Sr in ipairs(Utils.list(Qr and Qr.levels)) do if not Nr and Core.Levels [Sr.id] and Modes.open(Sr.id) and not Modes.done(Cq, Sr.id, "Hard") then Nr = "st:" .. Sr.id .. ":Hard" end end end end if (Config.goals and next(Brain.goalItems)) or Config.onlyLegend or Mr ~= "" then for Tr, Ur in ipairs(Utils.list(Core.WLO.LEGEND_WORLD_ORDER)) do local Vr = Core.Worlds [Ur] for Wr, Xr in ipairs(Utils.list(Vr and Vr.levels)) do local Yr = Core.Levels [Xr.id] local Zr = "st:" .. Xr.id .. ":Hard" if Yr and Modes.open(Xr.id) and (Mr == "" or Mr == "progress" or Mr == Zr) then local as = Config.onlyLegend or (Mr ~= "" and Mr ~= "progress") local bs, cs, ds = 0, nil, 0 for es, fs in ipairs(Adventure.levelItems(Yr)) do if Brain.goalItems [fs.item] then bs = bs + Modes.itemValue(fs.item, fs.per) cs = string.format("โ‰ˆ%.1f %s a run toward your goal", fs.per, (Core.Items [fs.item] or { }).name or fs.item) elseif as then bs = bs + Modes.itemValue(fs.item, fs.per) ds = ds + 1 end end if bs > 0 or Nr == Zr then local gs = not Modes.done(Cq, Xr.id, "Hard") Fq({ key = Zr, kind = "story", legend = true, picked = Nr == Zr, level = Xr.id, diff = "Hard", first = gs, value = bs + (gs and ((Core.GS.GEM_REWARD_STORY_FIRST or 60) + Config.unlockValue) or 0), p0 = Modes.PRIOR_WIN.hard, label = Modes.levelName(Xr.id), why = (Nr == Zr and (Mr == "progress" and "the next legend stage you haven't beaten ยท " or "your pick ยท ") or "") .. (cs or string.format("legend stage: %d kinds of drops", ds)) }) end end end end end local hs = Config.pickInfinite or "" if Config.infinite or Config.onlyInfinite then for js, ks in ipairs(Modes.infIds()) do if Modes.open(ks) and (hs == "" or hs == "inf:" .. ks) then local ls = "inf:" .. ks local ms, ns = Modes.infReach(ls) local ps, qs, rs = Modes.infDaily(Bq, ks, ms) local ss, ts, us = pcall(Modes.portalBonus, ks) local vs, ws, xs = pcall(Modes.missionValue, ks) ts, ws = ss and ts or 0, vs and ws or 0 local ys = (ts > 0 and us and (" ยท " .. us) or "") .. (ws > 0 and xs and (" ยท " .. xs) or "") Fq({ key = ls, kind = "infinite", level = ks, diff = "Hard", value = ps + ts + ws, p0 = 1, reach = ms, picked = hs ~= "", portalMap = ts > 0 or nil, extra = ys, label = Modes.levelName(ks), why = (hs ~= "" and "your pick ยท " or "") .. string.format("until the base falls (โ‰ˆwave %d%s) ยท %s", math.floor(ms + 0.5), ns and "" or ", not played yet", ps > 0 and string.format("daily gems: %d up to wave %d", ps, qs) or (rs > 0 and "today's gems claimed" or "no daily gems in reach")) .. ys }) end end end local zs, As = pcall(Modes.portalRows) for Bs, Cs in ipairs(zs and As or { }) do Fq(Cs) end if Config.dungeon or Config.onlyDungeons then local Ds = Dungeon.data() if Ds and (Ds._started or Config.dungeonStart or Config.onlyDungeons) then local Es, Fs = pcall(Dungeon.pick, Ds) Fs = Es and Fs or nil local Gs, Hs, Is = Dungeon.res(Ds) local Js, Ks = pcall(Dungeon.payValue) Fq({ key = "dg", kind = "dungeon", level = "dungeon", diff = "Hard", value = (Js and Ks) or Config.dungeonValue, p0 = Fs and Fs.p or Modes.PRIOR_WIN.dungeon, started = Ds._started or nil, label = Ds._started and string.format("Dungeon room %d/%d", Fs and Fs.d or (Dungeon.depth(Ds) or 1), Dungeon.ROOMS) or "Dungeon (new run)", why = Ds._started and string.format("%s ยท %d lives ยท %d coins ยท chests %d + %d rare", Fs and Dungeon.roomName(Fs) or "between rooms", Dungeon.lives(Ds), Gs, Hs, Is) or "a new run: 20 rooms with chests, shops and shrines" }) end end if (Config.event or Config.onlyCoins or Brain.nextMode) and Mq.event and Hunt.is(Mq.event.level) then Fq(Hunt.row(Modes.rec("ev:" .. tostring(Mq.event.level)))) elseif (Config.event or Config.onlyCoins) and Mq.event and Mq.event.level then local Ls = Modes.rec("ev:" .. Mq.event.level) local Ms = Ls.runs > 0 and (Ls.coins / Ls.runs) or 400 local Ns = Adventure.pullGoal() local Os = Ns and Ns.needCoins > 0 and math.max(Config.aprilValue, Config.goalCoinValue) or Config.aprilValue Fq({ key = "ev:" .. Mq.event.level, kind = "event", level = Mq.event.level, diff = "Hard", value = Ms * Os, coins = Ms, p0 = Modes.PRIOR_WIN.event, label = Mq.event.name or "Event", why = string.format("โ‰ˆ%d %s a run (ร—%.2f gems each%s)", math.floor(Ms), Modes.coinName(), Os, Ns and Ns.needCoins > 0 and (", for " .. Units.name(Ns.to)) or "") }) end return Dq end function Modes.teamSig() if Modes._sigAt and Utils.now() - Modes._sigAt < 20 then return Modes._sig end local Ps = { } for Qs, Rs in ipairs(Units.equipped()) do table.insert(Ps, Rs.u) end Modes._sig, Modes._sigAt = Simulator.sig(Ps), Utils.now() return Modes._sig end function Modes.sim(Ss) local Ts = Config.sim and Brain.simRes [Ss.key] if Ts and Ts.p and os.time() - (Ts.at or 0) < 6 * 3600 and (Ts.sig == Modes.teamSig() or Ts.sig == Modes.ownSig()) then return Ts end return nil end function Modes.ownSig() local Us = (type(Brain.savedTeam) == "table" and # Brain.savedTeam > 0 and Brain.savedTeam) or (Brain.train.on and Brain.train.on.base) if type(Us) ~= "table" or # Us == 0 then return nil end if Modes._ownAt and Utils.now() - Modes._ownAt < 20 then return Modes._own end local Vs = Core.session() local Ws = { } for Xs, Ys in ipairs(Us) do local Zs = Vs and Vs.collection:get_unit_by_uuid(Ys) if Zs then table.insert(Ws, Zs) end end Modes._own, Modes._ownAt = # Ws > 0 and Simulator.sig(Ws) or nil, Utils.now() return Modes._own end function Modes.nextAct(_s) Modes._next = Modes._next or { } local bt = Modes._next [_s] if bt == nil then local ct, dt = pcall(Core.PSC.get_next_story_level, _s) bt = ct and dt or false Modes._next [_s] = bt end return bt or nil end function Modes.needsHelp(et) local ft, gt = pcall(Modes.sim, et) if ft and type(gt) == "table" and tonumber(gt.p) ~= nil then return tonumber(gt.p) < 0.5 end local ht, jt = pcall(Modes.rec, et.key) return ht and type(jt) == "table" and (tonumber(jt.streak) or 0) >= 2 or false end Modes.MATCHED_DAILY_P = 0.7 function Modes.winP(kt) if kt.kind == "daily" and Config.dailyMatch and not Config.dailyLoop then local lt, mt = pcall(Modes.needsHelp, kt) if lt and mt then return Modes.MATCHED_DAILY_P end end local nt = kt.rec local ot = kt.p0 or Modes.PRIOR_WIN [kt.kind] or 0.7 local pt = Modes.sim(kt) if pt and not (pt.trust and not pt.trust.ok) then local qt = pt.calibrated or nt.runs > 0 local rt = (pt.trust and pt.trust.ok) and 6 or (qt and 3 or 1.5) local tt = (qt or pt.hopeless) and 0 or 2 return (nt.wins + ot * tt + pt.p * rt) / (nt.runs + tt + rt) end return (nt.wins + ot * 2) / (nt.runs + 2) end function Modes.minutes(ut) local vt = ut.rec local wt = tonumber(vt.timed) or 0 local xt = ut.kind == "infinite" and Brain.simRes [ut.key] local yt = (wt > 0 and vt.sec > 0) and (vt.sec / wt / 60) or (xt and tonumber(xt.minutes)) or Modes.PRIOR_MIN [ut.kind] or 6 return yt + Modes.OVERHEAD end function Modes.score(zt) local At, Bt = ((zt.kind == "event" and zt.rec.runs > 0) or zt.kind == "infinite") and 1 or (zt.kind == "dungeon" and zt.p0) or Modes.winP(zt), Modes.minutes(zt) local Ct, Dt, Et = pcall(Strategy.modeBonus, zt) if Ct and (Dt or 0) > 0 then zt.teamBonus = Dt if not tostring(zt.why):find("for your team", 1, true) then zt.why = (zt.why or "") .. " ยท " .. Et end end local Ft = At * (zt.value + (zt.teamBonus or 0)) / Bt zt.baseScore = Ft local Gt, Ht, It = pcall(Training.valueFor, zt) if Gt and Ht > 0 then local Jt = At * (zt.value + Ht) / (Bt * It) if Jt > Ft then Ft, zt.train = Jt, Ht end end if zt.rec.runs < 2 then Ft = Ft * (1 + Config.exploreBonus) end return Ft end function Modes.trialRow() local Kt = Brain.trial if type(Kt) ~= "table" or type(Kt.list) ~= "table" then return nil end local Lt = Kt.list [tonumber(Kt.i) or 1] if not Lt then return nil end return { key = Kt.key, kind = Kt.kind or "story", level = Kt.level, diff = Kt.diff, label = Kt.label, rec = Modes.rec(Kt.key), value = 0, p0 = 1, p = 1, score = 0, why = string.format("%s %d of %d: %s", Kt.study and "study run" or "trial", tonumber(Kt.i) or 1, # Kt.list, Units.name(Lt)) } end function Modes.trialTeam() local Mt = Brain.trial local Nt = Mt.list [tonumber(Mt.i) or 1] if Brain.train.on then Training.home() end local Ot = Core.session() local Pt, Qt for Rt, St in pairs(Ot.profile_data.collection.owned_units) do if St.unit_id == Nt then local Tt = Ot.collection:get_unit_by_uuid(St.uuid) or St local Ut, Vt = pcall(Units.score, Tt) Vt = Ut and Vt or 0 if not Qt or Vt > Qt then Pt, Qt = Tt, Vt end end end local Wt = Mt.study and Brain.study and Brain.study.id == Nt and Brain.study or nil if Wt and Ot.collection:get_unit_by_uuid(Wt.uuid) then Pt = Ot.collection:get_unit_by_uuid(Wt.uuid) end if not Pt then App.log(1, "TRIAL", Units.name(Nt) .. ": no copy of it any more, skipped") Mt.i = (tonumber(Mt.i) or 1) + 1 return false end local Xt = table.clone(Brain.savedTeam or Training.team()) local Yt = table.find(Xt, Pt.uuid) ~= nil if not Yt then local Zt, au for bu, cu in ipairs(Xt) do local du = Ot.collection:get_unit_by_uuid(cu) if du and not Units.isFarm(du.unit_id) and not Units.allyBuff(du.unit_id) then local eu, fu = pcall(Units.score, du) fu = eu and fu or 0 if not au or fu < au then Zt, au = bu, fu end end end if Zt then Xt [Zt] = Pt.uuid else table.insert(Xt, Pt.uuid) end end for gu, hu in ipairs(Wt and Wt.summoners or { }) do if not table.find(Xt, hu) and Ot.collection:get_unit_by_uuid(hu) then local iu, ju for ku, lu in ipairs(Xt) do local mu = Ot.collection:get_unit_by_uuid(lu) if mu and lu ~= Pt.uuid and not table.find(Wt.summoners, lu) and not Units.isFarm(mu.unit_id) and not Units.allyBuff(mu.unit_id) then local nu, ou = pcall(Units.score, mu) ou = nu and ou or 0 if not ju or ou < ju then iu, ju = ku, ou end end end if iu then Xt [iu] = hu elseif # Xt < 6 then table.insert(Xt, hu) end end end if not Training.same(Training.team(), Xt) then FarmHub.equipTeam(Xt, (Wt and "study run for " or "trial run for ") .. Units.name(Nt)) end return true end function Match.trialDone(pu, qu) local ru = Brain.trial local su = Match.trialId if type(ru) ~= "table" or not su then return end local tu, uu = Match.damageShares() uu = uu or 0 local vu = tu and (tu [su] or 0) * uu or 0 local wu = Units.rows(su) local xu, yu, zu = 0, 0, 0 for Au, Bu in pairs(Match.ownDealt or { }) do if Bu.id == su then xu = xu + 1 yu = math.max(yu, Bu.lvl or 0) zu = zu + (tonumber(wu and wu [0] and wu [0].cost) or 0) for Cu = 1, Bu.lvl or 0 do zu = zu + (tonumber(wu and wu [Cu] and wu [Cu].cost) or 0) end end end local Du = { } for Eu, Fu in pairs(tu or { }) do if Eu ~= su and Fu >= 0.005 then table.insert(Du, { n = Eu == "helpers" and Eu or Units.name(Eu), v = Fu }) end end table.sort(Du, function(Gu, Hu) return Gu.v > Hu.v end) local Iu = { } for Ju, Ku in ipairs(Du) do table.insert(Iu, string.format("%s %d%%", (Ku.n:gsub(" %(.+%)", "")), math.floor(Ku.v * 100 + 0.5))) end local Lu = Match.trialSim local Mu = Lu and string.format("the sim expected %s alone: %s at wave %d, %s damage, upgrade %s", (Units.name(su):gsub(" %(.+%)", "")), Lu.won and "wins" or "falls", Lu.wave, Utils.fmt(Lu.dealt or 0), tostring(Lu.lvl or "?")) or "no sim reading" local Nu = string.format("%s ยท %s at wave %d of %d in %.1f min ยท dealt %s, %d%% of %s ยท %d copies, top upgrade %d of %d, about ยฅ%s ยท ability cast %d times ยท stood by: %s ยท %s", Units.name(su), pu and "won" or "lost", Match.wave(), Match.total or 0, (qu or 0) / 60, Utils.fmt(vu), math.floor((tu and tu [su] or 0) * 100 + 0.5), Utils.fmt(uu), xu, yu, wu and wu.max or 0, Utils.fmt(zu), Match.casts and Match.casts [su] or 0, # Iu > 0 and table.concat(Iu, ", ") or "nobody", Mu) App.log(1, "TRIAL", Nu) local Ou = "AA_SmartFarm/trials.txt" local Pu, Qu = pcall(readfile, Ou) pcall(writefile, Ou, (Pu and type(Qu) == "string" and Qu or "") .. os.date("%H:%M ") .. Nu .. "\n") do local Ru = Match.calRun or { } local Su = { } if (Ru.potSum or 0) > 0 then table.insert(Su, string.format("real/listed damage %.2f", (Ru.dealtSum or 0) / Ru.potSum)) end local Tu = "AA_SmartFarm/trial_trace.txt" local Uu, Vu = pcall(readfile, Tu) pcall(writefile, Tu, (Uu and type(Vu) == "string" and Vu or "") .. os.date("%H:%M ") .. Units.name(su) .. " " .. table.concat(Su, " ") .. "\n" .. table.concat(Match.trialTrace or { }, "\n") .. "\n\n") end ru.results = type(ru.results) == "table" and ru.results or { } table.insert(ru.results, { id = su, won = pu and true or false, wave = Match.wave(), sec = qu, dealt = vu, share = tu and tu [su] or 0, sum = uu, copies = xu, lvl = yu, yen = zu, casts = Match.casts and Match.casts [su] or 0, sim = Lu }) if ru.study and Brain.study and Brain.study.id == su then local Wu, Xu = pcall(Reports.studyRecord, su, ru.results [# ru.results]) if Wu and Xu then Brain.study.records = type(Brain.study.records) == "table" and Brain.study.records or { } table.insert(Brain.study.records, Xu.run) local Yu, Zu = pcall(Reports.studyFit, Xu) if Yu and Zu then table.insert(Brain.studyQ, Zu) while # Brain.studyQ > 10 do table.remove(Brain.studyQ, 1) end task.spawn(function() pcall(Reports.sendStudy, 3) end) local _u, av = pcall(readfile, Core.FOLDER .. "/studies.txt") pcall(writefile, Core.FOLDER .. "/studies.txt", (_u and type(av) == "string" and av or "") .. os.date("%m-%d %H:%M ") .. Zu .. "\n") end App.log(1, "STUDY", Reports.studyLine(Xu)) else App.log(1, "ERROR", "study record: " .. tostring(Xu)) end local bv = type(Brain.studied [su]) == "table" and Brain.studied [su] or { } Brain.studied [su] = { runs = (tonumber(bv.runs) or 0) + 1, trials = bv.trials, at = os.time() } elseif su then local cv = type(Brain.studied [su]) == "table" and Brain.studied [su] or { } Brain.studied [su] = { runs = cv.runs, trials = (tonumber(cv.trials) or 0) + 1, at = os.time() } end ru.i = (tonumber(ru.i) or 1) + 1 if not ru.list [ru.i] then App.log(1, "TRIAL", "all trial runs done: your own team goes back in") Brain.trial = nil if ru.study and Brain.study then Brain.study.done = os.time() App.log(1, "STUDY", Units.name(Brain.study.id) .. ": study done, " .. # (Brain.study.records or { }) .. " runs") if Brain.study.smart then local dv, fv, gv = pcall(FarmHub.studyStart) if dv then App.log(1, "STUDY", "Smart Study, next: " .. tostring(gv)) else App.log(1, "ERROR", "Smart Study: " .. tostring(fv)) end elseif Brain.study.holdAfter then Brain.holdLobby = { why = "the study is done", at = os.time() } end end end App.saveNow() end Reports.STUDY_WORDS = { "summon", "spawn", "minion", "takedown", "kill", "dead", "revive", "raise", "buff", "ally", "allies", "aura", "slow", "stun", "freeze", "burn", "bleed", "poison", "wither", "range", "cooldown", "spa", "crit", "yen", "money", "shield", "heal", "stack", "boss", "flying", "hill", "mark", "every", "chance" } function Reports.r3(hv) hv = tonumber(hv) return hv and math.floor(hv * 1000 + 0.5) / 1000 or nil end function Reports.atkBrief(iv) if type(iv) ~= "table" then return nil end local jv = { n = Reports.r3(iv.n), hits = iv.hits } if (iv.slow or 0) > 0 then jv.slow = Reports.r3(iv.slowPct) jv.slowTime = Reports.r3(iv.slowTime) end if (iv.stun or 0) > 0 then jv.stun = Reports.r3(iv.stun) end if (iv.dot or 0) > 0 then jv.dot = Reports.r3(iv.dot) end if iv.bleed then jv.bleed = true end if (iv.amp or 0) > 0 then jv.amp = Reports.r3(iv.amp) end if iv.full then jv.full = true end if iv.wholeMap then jv.wholeMap = true end if iv.breakShields then jv.breakShields = true end if iv.ally then jv.allyBuff = iv.ally.types jv.allyTime = iv.ally.time end if iv.allyCd then jv.allyCd = iv.allyCd end if iv.allyRange then jv.allyRange = iv.allyRange end if iv.selfDmg then jv.selfDmg = iv.selfDmg end if iv.baseSafe then jv.baseSafe = iv.baseSafe end return jv end function Reports.readPassives(kv) local lv = Units.field(kv, "passives") local mv = { } for nv, ov in pairs(type(lv) == "table" and lv or { }) do if type(ov) == "table" then local pv = tostring(ov.description or ""):gsub("<[^>]+>", ""):gsub("%s+", " ") local qv = pv:lower() local rv = { name = tostring(ov.name or nv), text = pv:sub(1, 400) } local sv = { } for tv in qv:gmatch("([%d%.]+)%%") do if tonumber(tv) then table.insert(sv, tonumber(tv)) end end if # sv > 0 then rv.pct = sv end rv.every = tonumber(qv:match("every (%d+) attacks?")) or tonumber(qv:match("every (%d+) hits?")) or tonumber(qv:match("every (%d+) takedowns?")) rv.sec = tonumber(qv:match("([%d%.]+) ?sec")) rv.upTo = tonumber(qv:match("up to (%d+)")) local uv = { } for vv, wv in ipairs(Reports.STUDY_WORDS) do if qv:find(wv, 1, true) then table.insert(uv, wv) end end if # uv > 0 then rv.tags = uv end table.insert(mv, rv) end end table.sort(mv, function(xv, yv) return xv.name < yv.name end) return mv end function Reports.readAbility(zv) local Av = Units.rows(zv) local Bv, Cv = Units.field(zv, "active_attack"), nil for Dv = 0, Av and Av.max or - 1 do if Av [Dv] and Av [Dv].active then Bv, Cv = Bv or Av [Dv].active, Cv or Dv end end if not Bv then return nil end local Ev = Core.Attacks [Bv] or Units.fillAttack(Bv) local Fv, Gv = pcall(Units.attackInfo, Bv) local Hv = type(Ev) == "table" and type(Ev.active_description) == "string" and Ev.active_description:gsub("<[^>]+>", ""):sub(1, 300) or nil local Iv = Units.field(zv, "active_attack_stats") return { id = Bv, name = type(Ev) == "table" and type(Ev.name) == "string" and Ev.name or nil, text = Hv, from = Cv, cd = type(Iv) == "table" and tonumber(Iv.attack_cooldown) or (Av and Cv and Av [Cv] and Av [Cv].activeCd) or nil, does = Fv and Reports.atkBrief(Gv) or nil } end function Reports.readAttacks(Jv) local Kv = Units.rows(Jv) local Lv, Mv = { }, nil for Nv = 0, Kv and Kv.max or - 1 do local Ov = Kv [Nv] if Ov and Ov.atk and Ov.atk ~= Mv then Mv = Ov.atk local Pv, Qv = pcall(Units.attackInfo, Ov.atk, Ov.range) table.insert(Lv, { from = Nv, id = Ov.atk, does = Pv and Reports.atkBrief(Qv) or nil }) if # Lv >= 6 then break end end end return Lv end function Reports.studyProfile(Rv) local Sv = Core.Units [Rv] or { } local Tv = Units.rows(Rv) local Uv = { id = Rv, name = Units.name(Rv), rarity = Sv.rarity, cost = tonumber(Sv.cost), type = Sv._base_damage_type, cap = tonumber(Units.field(Rv, "spawn_cap")), crit = tonumber(Sv.physical_crit_chance), farm = Units.isFarm(Rv) or nil, hill = Sv.hill_unit or nil, hidden = Brain.hidden [Rv] or nil, known = Tv ~= nil and (Tv.max or 0) > 0, ups = Tv and Tv.max or 0 } if Uv.known then local Vv, Wv = Tv [0], Tv [Tv.max] local Xv = 0 for Yv = 0, Tv.max do Xv = Xv + (tonumber(Tv [Yv].cost) or 0) end Uv.first = { dmg = Vv.damage, cd = Reports.r3(Vv.cd), range = Vv.range, cost = Vv.cost } Uv.last = { dmg = Wv.damage, cd = Reports.r3(Wv.cd), range = Wv.range, farm = (tonumber(Wv.farm) or 0) > 0 and Wv.farm or nil } Uv.totalCost = Xv end Uv.passives = Reports.readPassives(Rv) Uv.ability = Reports.readAbility(Rv) Uv.attacks = Reports.readAttacks(Rv) return Uv end function FarmHub.studyRole(Zv, _v) local aw = { kinds = { } } local bw = "" for cw, dw in ipairs(_v.passives or { }) do bw = bw .. " " .. tostring(dw.text):lower() end if _v.ability and _v.ability.text then bw = bw .. " " .. _v.ability.text:lower() end local function ew(fw) table.insert(aw.kinds, fw) end if Units.isFarm(Zv) then ew("farm") end if Units.BLESS.by [Zv] or (bw:find("minion", 1, true) and bw:find("summon", 1, true)) then ew("summon buffer") aw.team, aw.summoners = true, true elseif Units.SUMMONER [Zv] or (Units.DRONES and Units.DRONES [Zv]) or bw:find("summon", 1, true) or bw:find("spawn", 1, true) then ew("summoner") end if Units.allyBuff(Zv) or Units.idol(Zv) or (_v.ability and _v.ability.does and _v.ability.does.allyBuff) then ew("buffer") aw.team = true end if Units.ENTRY [Zv] or bw:find("dead", 1, true) or bw:find("raise", 1, true) or bw:find("revive", 1, true) then ew("needs kills") aw.entry = true end if not _v.known then ew("not read yet") aw.team, aw.first = true, true end if # aw.kinds == 0 then ew("attacker") end return aw end function FarmHub.studySummoners(gw) local hw = Core.session() local iw = { } for jw, kw in pairs(hw.profile_data.collection.owned_units) do local lw = kw.unit_id if lw ~= gw and (Units.SUMMONER [lw] or (Units.DRONES and Units.DRONES [lw]) or (Units.summonShare and Units.summonShare(lw) >= 0.3)) then local mw = hw.collection:get_unit_by_uuid(jw) or kw local nw, ow = pcall(Units.score, mw) ow = nw and ow or 0 if not iw [lw] or ow > iw [lw].sc then iw [lw] = { uuid = jw, sc = ow } end end end local pw = { } for qw, rw in pairs(iw) do table.insert(pw, { id = qw, uuid = rw.uuid, sc = rw.sc }) end table.sort(pw, function(sw, tw) return sw.sc > tw.sc end) local uw = { } for vw = 1, math.min(2, # pw) do table.insert(uw, pw [vw].uuid) end return uw end function FarmHub.studyMap() local ww, xw for yw, zw in pairs(Brain.modes or { }) do local Aw = tostring(yw):match("^st:(.+):Normal$") if Aw and (tonumber(zw.wins) or 0) > 0 and (not xw or zw.wins > xw) then ww, xw = Aw, zw.wins end end ww = ww or "namek_level_1" return "st:" .. ww .. ":Normal", ww, "Normal", Modes.levelName(ww) .. " (Normal)" end function FarmHub.studyPlanText(Bw, Cw, Dw) local Ew = { } local Fw = { } for Gw, Hw in ipairs(Cw.passives or { }) do table.insert(Fw, Hw.name) end table.insert(Ew, string.format("%s: read as %s.", Units.name(Bw), table.concat(Dw.kinds, ", "))) if Dw.first then table.insert(Ew, "The game hides this unit's upgrades, ability and passives until it plays, and no match has sent them yet: the first run reads them, with your whole team fighting.") else table.insert(Ew, (# Fw > 0 and ("Passives: " .. table.concat(Fw, ", ") .. ".") or (Cw.hidden and "Passives: the game sends them only in a match, so the first run reads them." or "Passives: none.")) .. " " .. (Cw.ability and string.format("Ability: %s%s.", tostring(Cw.ability.name or Cw.ability.id), Cw.ability.cd and string.format(", every %ds", math.floor(Cw.ability.cd + 0.5)) or "") or "No ability.")) end local Iw = Dw.team and "with your whole team fighting" or "alone with the yen, the rest of your team standing by" if Dw.summoners then local Jw = { } local Kw = Core.session() for Lw, Mw in ipairs(FarmHub.studySummoners(Bw)) do local Nw = Kw and Kw.collection:get_unit_by_uuid(Mw) if Nw then table.insert(Jw, Units.name(Nw.unit_id)) end end Iw = Iw .. (# Jw > 0 and (", with your summoners " .. table.concat(Jw, " and ")) or ", but you have no summoner to buff") end if Dw.entry then Iw = Iw .. ", at the road's entrance (it needs kills)" end local Ow, Pw, Qw, Rw = FarmHub.studyMap() table.insert(Ew, string.format("Test: %s, on %s.", Iw, Rw)) return table.concat(Ew, "\n") end function FarmHub.studySeed() if Brain.studiedSeeded then return end Brain.studiedSeeded = true local Sw, Tw = pcall(readfile, Core.FOLDER .. "/trials.txt") if not (Sw and type(Tw) == "string") then return end local Uw = { } for Vw, Ww in pairs(Core.Units) do if type(Ww) == "table" then Uw [Units.name(Vw)] = Uw [Units.name(Vw)] or Vw end end local Xw = 0 for Yw in Tw:gmatch("[^\n]+") do local Zw = Yw:match("^%d%d:%d%d (.-) ยท ") local _w = Zw and Uw [Zw] if _w then local ax = type(Brain.studied [_w]) == "table" and Brain.studied [_w] or { } Brain.studied [_w] = { runs = ax.runs, trials = (tonumber(ax.trials) or 0) + 1, at = ax.at or 0 } Xw = Xw + 1 end end if Xw > 0 then App.log(1, "STUDY", string.format("%d trial runs from before count as trained (trials.txt)", Xw)) end App.saveSoon() end function FarmHub.studyNext() FarmHub.studySeed() local bx = Core.session() local cx = { } for dx, ex in pairs(bx and bx.profile_data.collection.owned_units or { }) do local gx = ex.unit_id if Core.Units [gx] and not Brain.studied [gx] then local hx = bx.collection:get_unit_by_uuid(dx) or ex local ix, jx = pcall(Units.score, hx) jx = ix and tonumber(jx) or 0 if not cx [gx] or jx > cx [gx].sc then cx [gx] = { u = hx, sc = jx } end end end local kx for lx, mx in pairs(cx) do if not kx or mx.sc > kx.sc then kx = mx end end return kx and kx.u or nil end function FarmHub.studyStart() local nx = Core.session() local ox = Config.studyUnit == "smart" local px if ox then px = FarmHub.studyNext() else px = nx and Config.studyUnit ~= "" and nx.collection:get_unit_by_uuid(Config.studyUnit) end if not px then if ox and Brain.study then Brain.study.smart = nil end return false, ox and "every unit you own has been studied" or "pick a unit to study first" end if Brain.trial and not Brain.trial.study then return false, "trial runs are going: start the study after them" end local qx = px.unit_id local rx = Reports.studyProfile(qx) local sx = FarmHub.studyRole(qx, rx) local tx, ux, vx, wx = FarmHub.studyMap() local xx = math.clamp(math.floor(tonumber(Config.studyRuns) or 2), 1, 5) local yx = { } for zx = 1, xx do yx [zx] = qx end Brain.study = { uuid = px.uuid, id = qx, n = xx, role = sx, summoners = sx.summoners and FarmHub.studySummoners(qx) or nil, key = tx, label = wx, at = os.time(), records = { }, smart = ox or nil } Brain.trial = { key = tx, kind = "story", level = ux, diff = vx, label = wx, list = yx, i = 1, study = true } FarmHub.studyCache = nil App.log(1, "STUDY", string.format("%s: %d run%s ยท %s", Units.name(qx), xx, xx == 1 and "" or "s", FarmHub.studyPlanText(qx, rx, sx):gsub("\n", " "))) App.saveNow() return true, string.format("%s: %d run%s on %s%s", Units.name(qx), xx, xx == 1 and "" or "s", wx, Config.master and (Brain.holdLobby and " once you press Start Farming Again" or "") or " once the Auto Farm is on") end function FarmHub.studyStop() local Ax = (Brain.trial and Brain.trial.study) or (Brain.study and not Brain.study.done) if Brain.trial and Brain.trial.study then Brain.trial = nil end if Brain.study and not Brain.study.done then Brain.study.done, Brain.study.stopped = os.time(), true App.log(1, "STUDY", Units.name(Brain.study.id) .. ": study stopped (you pressed Stop Study)") end FarmHub.studyCache = nil App.saveNow() return Ax end function FarmHub.studyText() local Bx = Brain.study local Cx = Bx and not Bx.done and Brain.trial and Brain.trial.study if not Cx then local Dx = "" if Bx and Bx.done then local Ex = Bx.records and Bx.records [# Bx.records] Dx = string.format("Last study: %s, %d run%s%s.%s\n", Units.name(Bx.id), # (Bx.records or { }), # (Bx.records or { }) == 1 and "" or "s", Bx.stopped and " (stopped)" or "", Ex and string.format(" Last run: %s at wave %d, dealt %s (%d%%), ability cast %d times.", Ex.won and "won" or "lost", Ex.wave or 0, Utils.fmt(Ex.dealt or 0), math.floor((Ex.share or 0) * 100 + 0.5), Ex.casts or 0) or "") end local Fx = 0 for Gx in pairs(Brain.studied) do Fx = Fx + 1 end if Fx > 0 then Dx = Dx .. string.format("%d kind%s of unit studied so far (โœ“ in the list).\n", Fx, Fx == 1 and "" or "s") end if Config.studyUnit == "" then return Dx .. "Pick a unit, or Smart. The farm reads its passives and ability, plays real matches with it, and saves what it learned locally." end local Hx = Core.session() local Ix if Config.studyUnit == "smart" then Ix = FarmHub.studyNext() if not Ix then return Dx .. "Smart Study: every unit you own has been studied." end Dx = Dx .. "Smart Study goes down your units from the strongest not studied yet. Next up:\n" else Ix = Hx and Hx.collection:get_unit_by_uuid(Config.studyUnit) end if not Ix then return Dx .. "That unit is gone: pick another." end local Jx = FarmHub.studyCache if not (Jx and Jx.uuid == Ix.uuid and Utils.now() - Jx.at < 30) then local Lx, Mx = pcall(function() local Kx = Reports.studyProfile(Ix.unit_id) return FarmHub.studyPlanText(Ix.unit_id, Kx, FarmHub.studyRole(Ix.unit_id, Kx)) end) Jx = { uuid = Ix.uuid, at = Utils.now(), text = Lx and Mx or ("Couldn't read it: " .. tostring(Mx)) } FarmHub.studyCache = Jx end return Dx .. Jx.text .. "\nPress Start Study to begin." end local Nx = tonumber(Brain.trial.i) or 1 local Ox = (tonumber(Brain.studySent) or 0) return string.format("Studying %s: run %d of %d on %s, %s.%s%s", Units.name(Bx.id), math.min(Nx, Bx.n), Bx.n, tostring(Bx.label), Bx.role and Bx.role.team and "with your team fighting" or "alone with the yen", Bx.smart and " Smart Study: then the next strongest unit not studied yet." or "", Config.master and (Brain.holdLobby and " Waiting: press Start Farming Again." or "") or " Waiting: switch the Auto Farm on.") end function Reports.studyRecord(Px, Qx) Qx = type(Qx) == "table" and Qx or { } local Rx = Reports.studyProfile(Px) local Sx, Tx = pcall(Match.summonSplit) local Ux = Sx and type(Tx) == "table" and Tx [Px] local Vx = 0 for Wx, Xx in ipairs(Match.mine or { }) do local Yx = Xx._stats local Zx = Yx:FindFirstChild("takedown_count") if Yx.id.Value == Px and Zx then Vx = Vx + (tonumber(Zx.Value) or 0) end end local _x = Brain.toggles [Px] local ay = Match.calRun or { } local cy = { } for dy, ey in ipairs(Match.team or { }) do if ey.id ~= Px then table.insert(cy, ey.id) end end local fy, gy = pcall(Match.damageShares) local hy = { } if fy and type(gy) == "table" then for iy, jy in pairs(gy) do if (tonumber(jy) or 0) >= 0.005 then hy [iy] = Reports.r3(jy) end end end local ky = 0 for ly in pairs(Match.helperDealt or { }) do ky = ky + 1 end local my = { map = Match.ld.map, level = Match.ld.id, mode = Match.key, how = (Brain.study and Brain.study.role and Brain.study.role.team) and "team" or "solo", won = Qx.won, wave = Qx.wave, total = Match.total, sec = math.floor(tonumber(Qx.sec) or 0), dealt = math.floor(tonumber(Qx.dealt) or 0), share = Reports.r3(Qx.share), copies = Qx.copies, top = Qx.lvl, yen = Qx.yen, casts = Qx.casts, summons = Ux and Reports.r3(Ux.share) or nil, takedowns = Vx, realVsListed = (ay.potSum or 0) > 0 and Reports.r3((ay.dealtSum or 0) / ay.potSum) or nil, toggle = type(_x) == "table" and { from = _x.from, fill = Reports.r3(_x.fill), drain = Reports.r3(_x.drain) } or nil, sim = type(Qx.sim) == "table" and { won = Qx.sim.won, wave = Qx.sim.wave, dealt = math.floor(tonumber(Qx.sim.dealt) or 0) } or nil, mates = cy, teamShares = next(hy) and hy or nil, teamDealt = math.floor(tonumber(Qx.sum) or 0), helpers = ky } return { v = VERSION, id = Brain.anonId, at = os.time(), unit = Rx, role = Brain.study and Brain.study.role and Brain.study.role.kinds or nil, run = my } end function Reports.studyFit(ny) local oy = Utils.json(ny) if # oy <= 3900 then return oy end for py, qy in ipairs({ 200, 100 }) do for ry, sy in ipairs(ny.unit.passives or { }) do sy.text = tostring(sy.text):sub(1, qy) end oy = Utils.json(ny) if # oy <= 3900 then return oy end end for ty, uy in ipairs(ny.unit.attacks or { }) do uy.does = nil end if ny.unit.ability then ny.unit.ability.text = ny.unit.ability.text and ny.unit.ability.text:sub(1, 120) end ny.run.mates = nil oy = Utils.json(ny) if # oy <= 3900 then return oy end ny.unit.attacks = nil return Utils.json(ny):sub(1, 3900) end function Reports.sendStudy(vy) if not (Config.shareStats and Core.WEBHOOK_URL ~= "") then return end for wy = 1, math.max(1, tonumber(vy) or 1) do if # Brain.studyQ == 0 then return end local xy = Utils.now() while Reports.sending and Utils.now() - xy < 8 do task.wait(0.25) end if Reports.sending then return end Reports.sending = true local yy, zy = pcall(Reports.postEmbed, string.format("๐ŸŽŒ AA ยท study ยท %s ยท v%s", tostring(Brain.anonId), VERSION), "```json\n" .. Brain.studyQ [1] .. "\n```") Reports.sending = nil if not (yy and zy) then return end table.remove(Brain.studyQ, 1) Brain.studySent = (tonumber(Brain.studySent) or 0) + 1 pcall(App.saveNow) if # Brain.studyQ > 0 then task.wait(1.5) end end end function Reports.studyLine(Ay) local By, Cy = Ay.unit or { }, Ay.run or { } local Dy = { } for Ey, Fy in ipairs(By.passives or { }) do table.insert(Dy, Fy.name .. (Fy.every and (" (every " .. Fy.every .. ")") or "") .. (Fy.pct and (" " .. table.concat(Fy.pct, "/") .. "%") or "")) end local Gy = By.ability and string.format("%s%s", tostring(By.ability.name or By.ability.id), By.ability.cd and string.format(" every %ds", math.floor(By.ability.cd + 0.5)) or "") or "none" return string.format("%s ยท %s at wave %s in %.1f min, %s ยท dealt %s (%d%%)%s ยท %d takedowns ยท ability %s, cast %d ยท passives: %s%s ยท read %d upgrades, %d attacks", tostring(By.name), Cy.won and "won" or "lost", tostring(Cy.wave), (Cy.sec or 0) / 60, Cy.how == "team" and "team fighting" or "alone with the yen", Utils.fmt(Cy.dealt or 0), math.floor((Cy.share or 0) * 100 + 0.5), Cy.summons and string.format(", %d%% of it from summons", math.floor(Cy.summons * 100 + 0.5)) or "", Cy.takedowns or 0, Gy, Cy.casts or 0, # Dy > 0 and table.concat(Dy, ", ") or "none", Cy.toggle and string.format(" ยท switch ability from upgrade %s", tostring(Cy.toggle.from)) or "", By.ups or 0, # (By.attacks or { })) end function Modes.pick() local Hy = Modes.trialRow() if Hy then Modes.lastScored = { Hy } return Hy end local Iy = Modes.list() for Jy, Ky in ipairs(Iy) do Ky.score = Modes.score(Ky) Ky.p = Modes.winP(Ky) local Ly = Modes.sim(Ky) if Ly and not tostring(Ky.why):find("sim:") then local My = Ly.trust and not Ly.trust.ok Ky.why = (Ky.why or "") .. " ยท sim: " .. (My and string.format(Ly.trust.ratio < 0.1 and "not trusted here (off ร—%.2f vs real runs)" or "not trusted here (off ร—%.1f vs real runs)", Ly.trust.ratio) or tostring(Ly.playing or Ly.margin)) end if Ky.train then Ky.why = (Ky.why or "") .. string.format(" ยท takedown training โ‰ˆ%d gems of progress", math.floor(Ky.train + 0.5)) end end table.sort(Iy, function(Ny, Oy) return Ny.score > Oy.score end) for Py, Qy in ipairs(Iy) do if Qy.kind == "dungeon" and Qy.started then if Py > 1 and Iy [1].kind ~= "daily" then table.remove(Iy, Py) table.insert(Iy, 1, Qy) Qy.why = "finishing the run ยท " .. (Qy.why or "") end break end end if Config.autoEvolve and Config.evolveUnit ~= "" then local Ry = Iy [1] and Iy [1].kind == "daily" and Iy [1] or nil local Sy, Ty = { }, { } for Uy, Vy in ipairs(Iy) do if Vy ~= Ry then if Modes.dropsGoal(Vy) or Vy.train then Vy.why = "for your focus unit ยท " .. (Vy.why or "") table.insert(Sy, Vy) else table.insert(Ty, Vy) end end end if # Sy > 0 then Iy = { } if Ry then table.insert(Iy, Ry) end for Wy, Xy in ipairs(Sy) do table.insert(Iy, Xy) end for Yy, Zy in ipairs(Ty) do table.insert(Iy, Zy) end end end for _y, az in ipairs(Iy) do local bz, cz = pcall(Modes.sim, az) local dz = Config.portalsAny or (bz and cz and (tonumber(cz.p) or 0) >= 0.25) or ((az.rec and tonumber(az.rec.wins)) or 0) > 0 if az.kind == "portal" and dz then local ez = Iy [1] if _y > 1 and ez.kind ~= "daily" and not (ez.kind == "dungeon" and ez.started) then table.remove(Iy, _y) table.insert(Iy, 1, az) az.why = "your portal first ยท " .. (az.why or "") end break end end for fz, gz in ipairs(Iy) do if gz.kind == "artifact" then local hz = Iy [1] if fz > 1 and (gz.p or 0) >= 0.25 and hz.kind ~= "daily" and hz.kind ~= "portal" and not (hz.kind == "dungeon" and hz.started) and not tostring(hz.why):find("for your focus unit", 1, true) then table.remove(Iy, fz) table.insert(Iy, 1, gz) gz.why = "this rotation's clears ยท " .. (gz.why or "") end break end end local iz = Iy [1] if iz and iz.kind == "story" and iz.first then local jz, kz = iz, (iz.baseScore or iz.score) * Modes.CHAIN_MIN for lz = 1, 6 do local mz for nz, oz in ipairs(Iy) do if oz.kind == "story" and oz.first and oz.diff == jz.diff and (oz.baseScore or oz.score) >= kz and Modes.nextAct(oz.level) == jz.level then mz = oz break end end if not mz then break end jz = mz end if jz ~= iz then table.remove(Iy, table.find(Iy, jz)) table.insert(Iy, 1, jz) jz.why = "the first of these acts in a row ยท " .. (jz.why or "") end end local pz = Brain.testTeam if pz and (pz.left or 0) > 0 then for qz, rz in ipairs(Iy) do if rz.key == pz.key then if qz > 1 and Iy [1].kind ~= "daily" and (pz.first or not (Iy [1].kind == "dungeon" and Iy [1].started)) then table.remove(Iy, qz) table.insert(Iy, 1, rz) end rz.why = string.format("team test, run %d of %d: %s ยท ", (tonumber(pz.n) or 0) - pz.left + 1, tonumber(pz.n) or 0, tostring(pz.label)) .. (rz.why or "") break end end end if Brain.nextMode then for sz, tz in ipairs(Iy) do if tz.key == Brain.nextMode then table.remove(Iy, sz) table.insert(Iy, 1, tz) tz.why = "asked for once ยท " .. (tz.why or "") break end end end if Config.dailyLoop then for uz, vz in ipairs(Iy) do if vz.kind == "daily" then table.remove(Iy, uz) table.insert(Iy, 1, vz) vz.why = "you asked to keep playing it ยท " .. (vz.why or "") break end end end Modes.lastScored = Iy return Iy [1] end function Modes.fromLevelData(wz) if not wz then return nil end local xz = wz.id if xz == "__DAILY_CHALLENGE" then return "dc", "daily" end if wz._dungeon then return "dg", "dungeon" end if wz._challenge then return "ch", "challenge" end local yz = Core.Levels [xz] and Core.Levels [xz].world if yz and Core.Worlds [yz] and Core.Worlds [yz]._artifact_stage then return "ar:" .. xz, "artifact" end if Core.Levels [xz] and Core.Levels [xz]._portal_only_level then local zz = tonumber(wz.portal_depth) or tonumber(wz._portal_depth) if zz and zz > 0 then return "pt:" .. xz .. ":t" .. math.floor(zz), "portal" end local Az = Brain.pending and Brain.pending.kind == "portal" and type(Brain.pending.key) == "string" and Brain.pending.key or "" local Bz = "pt:" .. xz .. ":t" if Az:sub(1, # Bz) == Bz and os.time() - (tonumber(Brain.pending.at) or 0) < 1200 then return Az, "portal" end return "pt:" .. xz, "portal" end if wz._gamemode == "infinite" or wz.infinite then return "inf:" .. xz, "infinite" end if wz._gamemode == "raid" and Brain.live.event and Brain.live.event.level == xz then return "ev:" .. xz, "event" end if wz._gamemode == "raid" or (Core.Levels [xz] and Core.Levels [xz].is_raid) then return "rd:" .. xz, "raid" end if wz._gamemode == "story" then return "st:" .. xz .. ":" .. tostring(wz._difficulty or "Normal"), "story" end return tostring(wz._gamemode) .. ":" .. tostring(xz), tostring(wz._gamemode) end function Adventure.levelItems(Cz, Dz) local Ez = { } if type(Cz) ~= "table" then return Ez end Dz = Dz or Cz.id local Fz = Cz.rewards if type(Fz) == "table" and Fz.item then Fz = { Fz } end local Gz = false for Hz, Iz in pairs(type(Fz) == "table" and Fz or { }) do if type(Iz) == "table" then local Jz = Utils.list(Iz.item) local Kz = 1 if Iz._item_multichoice and # Jz > 0 then Kz = math.min(1, (tonumber(Iz._item_multichoice.min) or # Jz) / # Jz) end for Lz, Mz in ipairs(Jz) do if type(Mz) == "table" and Mz.item_id then local Nz = tonumber(Mz.amount) or ((tonumber(Mz.min_amount) or 1) + (tonumber(Mz.max_amount) or tonumber(Mz.min_amount) or 1)) / 2 table.insert(Ez, { item = Mz.item_id, per = Nz * (tonumber(Mz.chance) or 1) * Kz }) Gz = true end end end end if not Gz then for Oz, Pz in pairs(type(Cz._show_rewards) == "table" and Cz._show_rewards or { }) do if type(Pz) == "table" and Pz.type == "item" and Pz.params and Pz.params.item_id then table.insert(Ez, { item = Pz.params.item_id, per = 1 }) end end end local Qz, Rz = pcall(Reports.hiveDrops, Dz) if Qz and Rz then local Sz = { } for Tz, Uz in ipairs(Ez) do Sz [Uz.item] = Tz end for Vz, Wz in pairs(Rz) do if Sz [Vz] then Ez [Sz [Vz]].per = Wz else table.insert(Ez, { item = Vz, per = Wz }) end end end return Ez end function Adventure.sources() if Adventure._src then return Adventure._src end local Xz = { } for Yz, Zz in pairs(Core.Levels) do for _z, aA in ipairs(Adventure.levelItems(Zz, Yz)) do Xz [aA.item] = Xz [aA.item] or { } table.insert(Xz [aA.item], { level = Yz, per = aA.per }) end end for bA, cA in pairs(Xz) do table.sort(cA, function(dA, eA) return dA.per > eA.per end) end Adventure._src = Xz return Xz end function Adventure.have(fA) local gA = Core.session() local hA = 0 pcall(function() hA = gA.inventory:get_number_of_owned_item(fA) end) return hA end function Adventure.killsShort(iA, jA) if not jA then return nil end local kA = tonumber(jA._takedown_requirement) if kA and (tonumber(iA.total_takedowns) or 0) < kA then return { what = "takedowns", have = tonumber(iA.total_takedowns) or 0, need = kA } end local lA = tonumber(jA._kill_requirement) if lA and (tonumber(iA.total_kills) or 0) < lA then return { what = "kills", have = tonumber(iA.total_kills) or 0, need = lA } end return nil end Adventure.TIER_RANK = { SS = 6, S = 5, A = 4, B = 3, C = 2, D = 1 } function Adventure.tiers() if Adventure._tiers then return Adventure._tiers end local mA = { } for nA, oA in pairs(Core.Units) do if type(oA) == "table" and type(oA.name) == "string" then local pA = oA.name:lower() if not mA [pA] or (oA.rarity and not Core.Units [mA [pA]].rarity) then mA [pA] = nA end end end local qA = { } local function rA(sA, tA) return not tA or (Adventure.TIER_RANK [sA] or 0) > (Adventure.TIER_RANK [tA] or 0) end local function uA(vA, wA, xA, yA) qA [vA] = qA [vA] or { base = { } } if rA(xA, qA [vA] [wA]) then qA [vA] [wA] = xA end if yA and rA(xA, qA [vA].base [wA]) then qA [vA].base [wA] = xA end end for zA, AA in ipairs({ { Core.TIERS, true }, { Core.TIERS_USER, true }, { Core.TIERS_EXTRA, false } }) do for BA, CA in pairs(AA [1]) do for DA, EA in pairs(CA) do for FA in EA:gmatch("[^|]+") do local GA = Core.TIER_ALIAS [FA] or mA [FA:lower()] if GA and Core.Units [GA] then uA(GA, BA, DA, AA [2]) end end end end end Adventure._tiers = qA return qA end function Adventure.tier(HA, IA) if not Config.community then return nil, nil, 0 end local JA = Adventure.tiers() [HA] if IA and JA then JA = JA.base end if not JA then return nil, nil, 0 end local KA, LA = Adventure.TIER_RANK [JA.story or ""] or 0, Adventure.TIER_RANK [JA.overall or ""] or 0 local MA = KA >= LA and JA.story or JA.overall if not MA then return nil, nil, 0 end return MA, JA, math.max(KA, LA) end function Adventure.tierText(NA) local OA, PA = Adventure.tier(NA) if not OA then return nil end local QA = { } if PA.story then table.insert(QA, "Story " .. PA.story) end if PA.overall then table.insert(QA, "overall " .. PA.overall) end if PA.infinite then table.insert(QA, "Infinite " .. PA.infinite) end if PA.castle then table.insert(QA, "Infinity Castle " .. PA.castle) end return "tier " .. table.concat(QA, ", ") end function Adventure.tierBest(RA, SA) local TA = Core.Units [RA] local UA = TA and TA.evolve and TA.evolve.evolve_unit local VA, WA, XA = Adventure.tier(RA, SA) local YA, ZA, _A = Adventure.tier(UA, SA) if UA and _A > XA then return UA, _A end return RA, XA end function Adventure.role(aB) if not Adventure._roles then Adventure._roles = { } for bB, cB in pairs(Core.KEEP_ROLES) do for dB, eB in ipairs(cB) do Adventure._roles [eB] = bB end end end return Adventure._roles [aB] end function Adventure.fodderOrder(fB) local gB, hB, iB = 0, { }, fB while iB and Core.Units [iB] and not hB [iB] do hB [iB] = true if Adventure.role(iB) then return nil end local jB, kB, lB = Adventure.tier(iB) gB = math.max(gB, lB) local mB = Core.Units [iB] iB = mB.evolve and mB.evolve.evolve_unit end if gB >= Adventure.TIER_RANK.A then return nil end local nB, oB = Adventure.tier(fB) if (oB and oB.overall == "D") or gB == Adventure.TIER_RANK.D then return 1 end return 2 + gB end function Adventure.strongest(pB) if Adventure._strong and Adventure._strongN == pB and Utils.now() - Adventure._strongAt < 60 then return Adventure._strong end local qB = Core.session() local rB = { } for sB, tB in pairs(qB.profile_data.collection.owned_units) do local uB, vB = pcall(Units.score, qB.collection:get_unit_by_uuid(sB) or tB) if uB and tonumber(vB) then table.insert(rB, { uuid = sB, sc = vB }) end end table.sort(rB, function(wB, xB) return wB.sc > xB.sc end) local yB = { } for zB = 1, math.min(pB, # rB) do yB [rB [zB].uuid] = true end Adventure._strong, Adventure._strongN, Adventure._strongAt = yB, pB, Utils.now() return yB end function Adventure.communityText() if not Config.community then return "Off (Settings โ†’ Community tips)." end local AB = Core.prof() if not AB then return "โ€ฆ" end local BB = { } local function CB(DB, EB, FB) local GB = Core.Banners [EB] local HB = { } for IB, JB in pairs(GB and GB.rateup_units or { }) do if type(JB) == "table" and type(JB.possible_units_set) == "table" then for KB in pairs(JB.possible_units_set) do table.insert(HB, KB) end end end local LB = { } for MB, NB in ipairs(HB) do local OB, PB = Adventure.tierBest(NB) table.insert(LB, { r = PB, text = string.format("%s %s%s", Units.name(OB), (Adventure.tier(OB)) or "unrated", Units.owns(NB) and " (you have it)" or "") }) end table.sort(LB, function(QB, RB) return QB.r > RB.r end) local SB = { } for TB, UB in ipairs(LB) do table.insert(SB, UB.text) end if # SB > 0 then table.insert(BB, string.format("%s (%s): %s", DB, FB, table.concat(SB, " ยท "))) end end CB("April banner", "April2025", "150 AprilCoins a pull") CB("Sakamoto banner", "SakamotoEvent", "10 Assassin Tokens a pull") CB("Gem banner", "EventClover", "50 gems a pull") local VB = { } for WB, XB in ipairs(Units.equipped()) do if not Units.isFarm(XB.id) and not Units.allyBuff(XB.id) then local YB, ZB = Units.types(XB.id) if ZB then VB [ZB] = true end end end local _B, aC = { }, { } for bC, cC in pairs(AB.collection.owned_units) do local dC, eC = Adventure.tierBest(cC.unit_id) if eC >= 4 and not aC [dC] then aC [dC] = true local fC = "" local gC = Units.allyBuff(dC) if gC then local hC = false for iC in pairs(gC.types or { }) do if VB [iC] then hC = true end end if not hC then for jC in pairs(gC.types or { }) do fC = string.format(" (buffs %s units: your damage dealers aren't)", jC) end end end if dC ~= cC.unit_id then fC = fC .. " (evolve " .. Units.name(cC.unit_id) .. ")" end table.insert(_B, { r = eC, text = Units.name(dC) .. " " .. ((Adventure.tier(dC)) or "") .. fC }) end end table.sort(_B, function(kC, lC) return kC.r > lC.r end) local mC = { } for nC, oC in ipairs(_B) do if nC <= 8 then table.insert(mC, oC.text) end end if # mC > 0 then table.insert(BB, "Yours rated A or better: " .. table.concat(mC, " ยท ")) end table.insert(BB, "The guides rank evolved, fully upgraded units and ignore traits (a strong trait can move a unit a whole tier). " .. "Their advice: save gems and commit to one banner.") return table.concat(BB, "\n") end function Adventure.pullGoal() local pC = Config.pullGoal if type(pC) ~= "string" or pC == "" or type(Core.Units [pC]) ~= "table" then return nil end local qC = Core.prof() if not qC then return nil end local rC = Core.Units [pC] local sC = rC.evolve and rC.evolve.evolve_unit or pC local tC = { id = pC, to = sC, coins = Core.cur("AprilCoins"), itemCost = 0, needCoins = 0 } for uC, vC in pairs(rC.evolve and rC.evolve.normal and rC.evolve.normal.item_requirement or { }) do local wC = Core.SaleEvent [vC.item_id] if wC and type(wC.resource_cost) == "table" and wC.resource_cost.id == "AprilCoins" then tC.item, tC.itemCost = vC.item_id, tonumber(wC.resource_cost.amount) or 0 end end tC.haveItem = tC.item ~= nil and Adventure.have(tC.item) > 0 local xC, yC = FarmHub.hatched [pC] or false, false for zC, AC in pairs(qC.collection.owned_units) do if AC.unit_id == pC then xC = true end if AC.unit_id == sC then yC = true end end if yC then tC.step = "done" elseif not xC then tC.step, tC.needCoins = "pull", 1 elseif tC.item and not tC.haveItem then tC.step, tC.needCoins = "item", math.max(0, tC.itemCost - tC.coins) else tC.step = "evolve" end return tC end function Adventure.goalText() local BC = Adventure.pullGoal() if not BC then return "No main goal set (Settings: pull goal)." end local CC = Core.prof() local DC = string.format("%s%s ยท ", Units.name(BC.to), (Adventure.tier(BC.to)) and (" (tier " .. Adventure.tier(BC.to) .. ")") or "") if BC.step == "pull" then if not Core.Banners.April2025 then return DC .. string.format("the April banner isn't in the game right now, so there's nothing to pull %s from. This waits until it comes back.", Units.name(BC.id)) end local EC = math.max(Config.aprilFloor, BC.haveItem and 0 or BC.itemCost) local FC = CC and CC.pity_data and CC.pity_data.April2025 local GC = FC and tonumber(FC.opens_since_mythic) return DC .. string.format("step 1 of 3: pull %s from the April banner. You have %s AprilCoins: %d ten-pulls above the %s kept for its evolve item. " .. "Each Mythic from this banner is one of 3 limited units (1 in 3 is %s), and one is guaranteed every 200 pulls%s. " .. "The farm plays Fool's Hunt for AprilCoins (worth %.2f gems each to the ranking while this goal needs them)%s.", Units.name(BC.id), Utils.fmt(BC.coins), math.max(0, math.floor((BC.coins - EC) / 1500)), Utils.fmt(EC), Units.name(BC.id), GC and string.format(" (%d pulls since the last Mythic)", GC) or "", math.max(Config.aprilValue, Config.goalCoinValue), Config.summonApril and " and pulls 10 at a time" or "; April pulls are off (Units โ†’ Summon)") elseif BC.step == "item" then return DC .. string.format("step 2 of 3: you have %s! Its evolve item costs %s AprilCoins in the event shop; you have %s. The farm buys it when the coins are in.", Units.name(BC.id), Utils.fmt(BC.itemCost), Utils.fmt(BC.coins)) elseif BC.step == "evolve" then return DC .. "step 3 of 3: its takedowns come from training (it joins easy runs), then smart evolve evolves it." end return DC .. "done, it's yours!" end function FarmHub.eventShop() local HC = Adventure.pullGoal() if not (Config.summonApril and HC and HC.step == "item" and HC.coins >= HC.itemCost and HC.itemCost > 0) then return end if App.req.back.eshop and Utils.now() < App.req.back.eshop then return end local IC, JC = App.request("eshop", "buy_item_generic", HC.item, "event", "event_shop", "1") local KC = Utils.now() while Utils.now() - KC < 4 and Adventure.have(HC.item) < 1 do task.wait(0.25) end local LC = (Core.Items [HC.item] or { }).name or HC.item if Adventure.have(HC.item) > 0 then Spending.add(LC, HC.itemCost, "AprilCoins", "evolve item for " .. Units.name(HC.to)) if App.notify then App.notify("Main goal", "Bought " .. LC .. " for " .. Units.name(HC.id), "success") end pcall(Adventure.refresh) else App.log(1, "SHOP", "event shop refused " .. tostring(HC.item) .. " (" .. tostring(IC and JC or "no answer") .. ")") App.backoff("eshop", 900) end end function FarmHub.codes() if not Config.codes then return App.status("Codes", "off (Settings)") end local MC = Core.prof() if not MC then return end local NC = MC.redeemed_codes or { } local OC, QC = { }, { } for RC, SC in ipairs(Core.CODES) do local TC = tonumber(Brain.codes [SC]) if NC [SC] then if TC and TC > 0 then table.insert(QC, SC) Brain.codes [SC] = - 1 end elseif os.time() - (TC or 0) > 86400 and (TC or 0) >= 0 then Brain.codes [SC] = os.time() local UC = App.request("code", "redeem_code", SC) if UC then table.insert(OC, SC) end task.wait(1) end end if # QC > 0 then App.log(1, "CODES", "the game confirms " .. table.concat(QC, ", ")) end if # OC > 0 then App.log(1, "CODES", "sent " .. table.concat(OC, ", ") .. " (the game confirms a code after the next teleport; one it refuses is tried again tomorrow)") end App.saveSoon() local VC = 0 for WC, XC in ipairs(Core.CODES) do if not NC [XC] then VC = VC + 1 end end App.status("Codes", # OC > 0 and ("sent " .. table.concat(OC, ", ")) or (VC == 0 and "every known code is redeemed โœ“" or (VC .. " codes not taken yet, trying again tomorrow"))) end function Adventure.copies(YC, ZC, _C) local aD = Core.session() local bD = { } for cD, dD in pairs(aD and aD.profile_data.collection.owned_units or { }) do if dD.unit_id == YC and dD.uuid ~= _C and not dD._locked and (not ZC or dD.shiny) then table.insert(bD, dD) end end return bD end function Adventure.bestSource(eD) if not (Modes.lastScored and Modes.lastScored [1]) then pcall(Modes.pick) end local fD = { } for gD, hD in ipairs(Modes.lastScored or { }) do if hD.level then fD [hD.level] = hD end end local iD, jD for kD, lD in ipairs(eD or { }) do local mD = fD [lD.level] local nD, oD = pcall(function() return mD and Modes.minutes(mD) or 10 end) local pD = lD.per * (mD and (mD.p or 0.5) or 0.5) / math.max(1, nD and oD or 10) if not jD or pD > jD then iD, jD = lD, pD end end return iD end function Adventure.planItem(qD, rD) local sD = Adventure.have(qD) if sD >= rD then return { item = qD, done = true } end local tD = Adventure.sources() [qD] if tD and tD [1] then return { item = qD, need = rD, have = sD, farm = qD, farmNeed = rD - sD, levels = tD } end local uD = Core.SaleEvent [qD] if uD and type(uD.resource_cost) == "table" then return { item = qD, need = rD, have = sD, eventShop = true, cost = tonumber(uD.resource_cost.amount) or 0, currency = uD.resource_cost.id, farmNeed = 0 } end for vD, wD in pairs(Brain.raidShop) do if wD.id == qD and wD.costItem and (wD.stock == "inf" or (tonumber(wD.stock) or 0) > 0) then local xD = Adventure.sources() [wD.costItem] local yD = wD.cost * (rD - sD) local zD = Adventure.have(wD.costItem) return { item = qD, need = rD, have = sD, shopKey = vD, costItem = wD.costItem, cost = wD.cost, farm = wD.costItem, farmNeed = math.max(0, yD - zD), levels = xD or { } } end end for AD, BD in pairs(Modes.PORTALS) do if BD.item == qD then return { item = qD, need = rD, have = sD, farm = qD, farmNeed = rD - sD, portal = AD, levels = { { level = BD.level, per = Modes.portalPer(AD) } } } end end return { item = qD, need = rD, have = sD, unknown = true } end Modes.PORTALS = { portal_item__shibuya = { level = "shibuya_portal", item = "shibuya_wheel", from = "shibuya_infinite", per = 10, drops = 1 } } function Modes.portalPer(CD) local DD = Brain.portalPay [CD] if DD and (tonumber(DD.n) or 0) > 0 then return tonumber(DD.per), true end local ED = Modes.PORTALS [CD] local FD, GD = pcall(Reports.hiveDrops, ED.level) if FD and GD and GD [ED.item] then return GD [ED.item], true end return ED.per, false end function Modes.portalDrops(HD) local JD = Brain.portalPay ["drop:" .. HD] if JD and (tonumber(JD.n) or 0) > 0 then return tonumber(JD.per), true end local KD = Modes.PORTALS [HD] local LD, MD = pcall(Reports.hiveDrops, KD.from) if LD and MD and MD [HD] then return MD [HD], true end return KD.drops, false end function Modes.portalsHeld(ND) local OD = Core.prof() local PD = (OD.inventory and OD.inventory.unique_items) or OD.unique_items or { } local QD = { } for RD, SD in pairs(PD) do if type(SD) == "table" and SD.item_id == ND then table.insert(QD, SD.uuid or RD) end end table.sort(QD) return QD end Modes._spawns = { } function Modes.levelSpawns(TD, UD) local VD = tostring(TD) local WD = Modes._spawns [VD] if WD and os.time() - WD.at < 3600 then return WD.n end local XD = 0 pcall(function() local YD = Core.Levels [TD] local ZD = Simulator.info(Simulator.slim and Simulator.slim(YD) or YD, "count:" .. VD) local _D = false for aE, bE in pairs(ZD.configs or { }) do if UD [aE] or (type(bE) == "table" and UD [bE.unit_id or ""]) then _D = true break end end if not _D then return end local cE = ZD.infinite and math.floor(Modes.infReach("inf:" .. VD) + 0.5) or ZD.total local function dE(eE) local fE = ZD.configs [eE] return UD [eE] or (type(fE) == "table" and UD [fE.unit_id or ""]) end for gE = 1, math.min(cE or 30, 200) do if ZD.infinite then local hE, iE = pcall(Core.LU.Infinite.infinite_amt_scale, gE, ZD.ld) local jE, kE = 0, 0 for lE, mE in pairs(ZD.configs) do if type(mE) == "table" and mE.spawn_tier_normal then local nE, oE = pcall(Core.LU.Infinite.infinite_get_normal_unit_chance, mE, gE) oE = nE and tonumber(oE) or 0 jE = jE + oE if dE(lE) then kE = kE + oE end end end if jE > 0 then XD = XD + (hE and tonumber(iE) or 10) * kE / jE end else local pE = ZD.list [gE] for qE, rE in ipairs(Utils.list(pE and pE.wave)) do if dE(rE.unit) then XD = XD + (tonumber(rE.amount) or 1) end end end end XD = math.floor(XD + 0.5) end) Modes._spawns [VD] = { n = XD, at = os.time() } return XD end function Modes.missionValue(sE) local tE = Core.session() local uE, vE = 0, nil for wE, xE in pairs(tE and tE.quest_handler.quest_profile_data.quests or { }) do local yE = xE.quest_info local zE = yE and yE.quest_class if zE and zE.class == "kill_unit_multi" and type(zE.unit_ids) == "table" then local AE = tonumber(zE.amount) or 0 local BE = AE - (tonumber(xE.quest_progress and xE.quest_progress.amount) or 0) if BE > 0 then local CE = Modes.levelSpawns(sE, zE.unit_ids) if CE > 0 then local DE = tonumber(yE.quest_reward and yE.quest_reward.gem_reward) or 0 for EE, FE in pairs(yE.quest_reward and yE.quest_reward.item or { }) do if type(FE) == "table" and FE.item_id then DE = DE + Modes.itemValue(FE.item_id, tonumber(FE.max_amount or FE.amount or FE.min_amount) or 1) end end uE = uE + DE * math.min(1, CE / BE) vE = string.format("%s: %s/%s, โ‰ˆ%s a run", tostring(yE.quest_name), Utils.fmt(AE - BE), Utils.fmt(AE), Utils.fmt(math.min(CE, BE))) end end end end return uE, vE end function Modes.portalKinds() if Modes._kinds then return Modes._kinds end local GE = { } for HE, IE in pairs(Core.Items) do if type(IE) == "table" and type(IE.usage) == "table" and IE.usage.type == "open_portal" then local JE = { } if IE.usage.level_id then table.insert(JE, IE.usage.level_id) end if type(IE._unique_portal_levels) == "table" then for KE in pairs(IE._unique_portal_levels) do table.insert(JE, KE) end end table.sort(JE) local LE = { } local function ME(NE, OE) if type(NE) ~= "table" or OE > 4 then return end if type(NE.item_id) == "string" then LE [NE.item_id] = true end for PE, QE in pairs(NE) do ME(QE, OE + 1) end end for RE, SE in ipairs(JE) do ME((Core.Levels [SE] or { }).rewards, 0) end if Modes.PORTALS [HE] then LE [Modes.PORTALS [HE].item] = true end table.insert(GE, { pid = HE, name = IE.name or HE, rarity = IE.rarity, levels = JE, drops = LE }) end end table.sort(GE, function(TE, UE) if TE.name ~= UE.name then return TE.name < UE.name end return TE.pid < UE.pid end) Modes._kinds = GE return GE end function Modes.portalLevel(VE) local WE = Core.prof() local XE = (WE.inventory and WE.inventory.unique_items) or WE.unique_items or { } for YE, ZE in pairs(XE) do if type(ZE) == "table" and (ZE.uuid or YE) == VE then local _E = type(ZE._unique_item_data) == "table" and ZE._unique_item_data._unique_portal_data return type(_E) == "table" and _E.level_id or nil end end end function Modes.portalsHeldData(aF) local bF = Core.prof() local cF = (bF.inventory and bF.inventory.unique_items) or bF.unique_items or { } local dF = { } for eF, fF in pairs(cF) do if type(fF) == "table" and fF.item_id == aF then local gF = type(fF._unique_item_data) == "table" and fF._unique_item_data._unique_portal_data or { } local hF = type(gF._ctl) == "table" and gF._ctl or { } local iF = { } for jF, kF in pairs(type(hF._levelmodifiers) == "table" and hF._levelmodifiers or { }) do if type(kF) == "table" and kF.id then table.insert(iF, tostring(kF.id)) end end table.insert(dF, { uuid = fF.uuid or eF, pid = aF, level = gF.level_id, tier = tonumber(gF.portal_depth) or 0, diff = tonumber(gF.difficulty_scale) or 1, reward = tonumber(gF._portal_reward_scale) or 1, lanes = tonumber(gF._lanes), host = tonumber(gF._host_star_remnants) or 0, map = gF._override_map, weak = hF._weak_against or gF._weak_against, strong = hF._strong_against or gF._strong_against, mods = iF }) end end table.sort(dF, function(lF, mF) return lF.uuid < mF.uuid end) return dF end function Modes.portalMod(nF) local oF, pF = { weak = { }, strong = { }, res = { } }, false for qF, rF in pairs(type(nF.weak) == "table" and nF.weak or { }) do local sF = type(rF) == "table" and rF.damage_type or (type(rF) == "string" and rF) or nil if sF and sF ~= "" then pF = true oF.weak [sF] = Modes.weakAmp(type(rF) == "table" and rF.amplification) end end for tF, uF in pairs(type(nF.strong) == "table" and nF.strong or { }) do local vF = type(uF) == "table" and uF.damage_type or (type(uF) == "string" and uF) or nil local wF = type(uF) == "table" and tonumber(uF.amplification) if vF and vF ~= "" then pF = true if wF and wF < 1 then oF.res [vF] = wF else oF.strong [vF] = true end end end return pF and oF or nil end function Modes.weakAmp(xF) xF = tonumber(xF) if not xF then return 1.5 end return xF < 1 and 1 + xF or xF end function Modes.portalFit(yF, zF) local AF = Modes.portalMod(yF) if not AF then return 1 end local BF, CF = 0, 0 for DF, EF in ipairs(zF or { }) do if not Units.isFarm(EF.id) and not Units.allyBuff(EF.id) then local FF, GF = pcall(Units.score, EF.u) GF = FF and tonumber(GF) or 0 BF = BF + GF CF = CF + GF * Units.elem(EF.id, AF) end end return BF > 0 and CF / BF or 1 end function Modes.portalRows() local HF = { } local IF = Config.pickPortal or "smart" if IF == "off" or Config.portals == false then return HF end local JF = FarmHub.badPortal or { } local KF = Brain.testTeam local function LF(MF) for NF, OF in ipairs(Modes.portalKinds()) do local PF = MF or IF == OF.pid or (KF and (KF.left or 0) > 0 and KF.pid == OF.pid) if PF or IF == "smart" then local QF = Modes.portalsHeld(OF.pid) local RF = { } for SF in pairs(OF.drops) do if (Brain.goalNeed [SF] or 0) > 0 then table.insert(RF, SF) end end table.sort(RF) if # QF > 0 and (PF or # RF > 0) then local TF = Units.equipped() local UF, VF = { }, { } for WF, XF in ipairs(Modes.portalsHeldData(OF.pid)) do VF [XF.tier] = (VF [XF.tier] or 0) + 1 XF.fit = Modes.portalFit(XF, TF) local YF = UF [XF.tier] local ZF = not YF or (JF [XF.uuid] or 0) < (JF [YF.uuid] or 0) or ((JF [XF.uuid] or 0) == (JF [YF.uuid] or 0) and XF.fit > YF.fit) if ZF then UF [XF.tier] = XF end end for _F, aG in pairs(UF) do local bG = aG.level or Modes.portalLevel(aG.uuid) or OF.levels [1] if bG and Core.Levels [bG] and Modes.open(bG) then local cG = tonumber(Core.Levels [bG]._override_gem_reward) or 0 local dG, eG = cG, { } local fG = Modes.PORTALS [OF.pid] for gG, hG in ipairs(RF) do local iG, jG = 1, true if fG and fG.item == hG then iG, jG = Modes.portalPer(OF.pid) end dG = dG + Modes.itemValue(hG, iG) table.insert(eG, string.format("โ‰ˆ%s %s a run%s", Utils.fmt(iG), (Core.Items [hG] or { }).name or hG, jG and "" or " (a guess until one is played)")) end local kG, lG = pcall(Reports.hiveDrops, bG) for mG, nG in pairs(kG and type(lG) == "table" and lG or { }) do if not table.find(RF, mG) then dG = dG + Modes.itemValue(mG, tonumber(nG) or 0) end end dG = dG * aG.reward + aG.host * Modes.itemValue("star_remnant_limited", 1) local oG = { key = _F > 0 and ("pt:" .. bG .. ":t" .. _F) or ("pt:" .. bG), kind = "portal", level = bG, diff = "Hard", portal = aG.uuid, first = false, picked = PF or nil, value = dG, p0 = Modes.PRIOR_WIN.hard, label = Modes.levelName(bG) .. (_F > 0 and string.format(" (Tier %d)", _F) or ""), tier = _F, mod = Modes.portalMod(aG), why = string.format("%s%s: %s%s ยท you hold %d%s", MF and "none left of your pick, so your other portals ยท " or (PF and "your pick ยท " or ""), OF.name, # eG > 0 and table.concat(eG, ", ") or (cG .. " gems a run"), _F > 0 and string.format(" ยท Tier %d%s, enemies ร—%.1f, rewards ร—%.1f", _F, aG.lanes and string.format(" (%d lane%s)", aG.lanes, aG.lanes == 1 and "" or "s") or "", aG.diff, aG.reward) or "", VF [_F], aG.host > 0 and string.format(" ยท the host gets %d Limited Star Remnant%s", aG.host, aG.host == 1 and "" or "s") or "") } if aG.diff ~= 1 or aG.map or aG.lanes then local pG = table.clone(Core.Levels [bG]) pG._lobby_difficulty_scale = aG.diff if aG.map then pG.map = aG.map end if aG.lanes then pG._portal_lanes = aG.lanes end oG.ldOverride = pG end if # aG.mods > 0 then oG.why = oG.why .. " ยท modifiers: " .. table.concat(aG.mods, ", ") end table.insert(HF, oG) end end end end end end LF(false) if # HF == 0 and ((IF ~= "smart" and # Modes.portalsHeld(IF) == 0) or Config.onlyPortals) then LF(true) end return HF end function Modes.portalBonus(qG) if Config.pickPortal == "off" or Config.portals == false then return 0, nil end for rG, sG in pairs(Modes.PORTALS) do if sG.from == qG and (Brain.goalNeed [sG.item] or 0) > 0 then local tG, uG = Modes.portalDrops(rG) local vG = Modes.portalPer(rG) return tG * Modes.itemValue(sG.item, vG), string.format("โ‰ˆ%s %s a run%s, for %s", Utils.fmt(tG), (Core.Items [rG] or { }).name or rG, uG and "" or " (a guess)", (Core.Items [sG.item] or { }).name or sG.item) end end return 0, nil end function Adventure.goals() local wG = Core.session() if not wG then return { } end local xG = Units.equipped() local yG = math.huge for zG, AG in ipairs(xG) do if not Units.isFarm(AG.id) and not Units.allyBuff(AG.id) then local BG, CG = pcall(Units.score, AG.u) if BG then yG = math.min(yG, CG) end end end local EG = { } for FG, GG in ipairs(xG) do EG [GG.uuid] = true end local HG = Config.autoEvolve and Config.evolveUnit ~= "" and Config.evolveUnit or nil local IG = { } for JG, KG in pairs(wG.profile_data.collection.owned_units) do local LG = Core.Units [KG.unit_id] if LG and LG.evolve and LG.evolve.evolve_unit and not LG.evolve.hide_from_ui and Core.Units [LG.evolve.evolve_unit] then local MG, NG = pcall(Units.score, wG.collection:get_unit_by_uuid(KG.uuid) or KG) NG = (MG and NG or 0) + (EG [KG.uuid] and 0.5 or 0) + (KG.uuid == HG and 1e30 or 0) if not IG [KG.unit_id] or NG > IG [KG.unit_id].sc then IG [KG.unit_id] = { u0 = KG, sc = NG } end end end local OG = { } local PG = { } for QG, RG in pairs(wG.profile_data.collection.owned_units) do PG [RG.unit_id] = true end for SG, TG in pairs(IG) do local UG = TG.u0 local VG = Core.Units [UG.unit_id] if not PG [VG.evolve.evolve_unit] or UG.uuid == HG then local WG = UG.shiny and VG.evolve.shiny or VG.evolve.normal local XG, YG, ZG = true, { }, { } for aH, bH in pairs(WG and WG.unit_requirement or { }) do local cH = (tonumber(bH.amount) or 1) - (bH.unit_id == UG.unit_id and 1 or 0) if cH > 0 then local dH = # Adventure.copies(bH.unit_id, bH.shiny, UG.uuid) if dH < cH then XG = false table.insert(ZG, { copies = bH.unit_id, have = dH, need = cH }) end table.insert(YG, { id = bH.unit_id, amount = cH, shiny = bH.shiny and true or nil }) end end local eH = { } for fH, gH in pairs(WG and WG.item_requirement or { }) do table.insert(eH, { id = gH.item_id, amount = tonumber(gH.amount) or 1 }) end local hH = UG.uuid == HG or nil if WG and (XG or hH) then local iH = wG.collection:get_unit_by_uuid(UG.uuid) or UG local jH = { unit_id = VG.evolve.evolve_unit, uuid = "evo:" .. tostring(iH.uuid), xp = iH.xp, traits = iH.traits, trait_stats = iH.trait_stats, further_modifiers = iH.further_modifiers } local kH, lH = pcall(Units.score, iH) local mH, nH = pcall(Units.score, jH) lH, nH = kH and lH or 0, mH and nH or 0 local oH, pH = pcall(Core.USC.calculate_xp, VG.evolve.evolve_unit, 100) local qH, rH = pcall(Units.score, { unit_id = VG.evolve.evolve_unit, uuid = "evo100:" .. tostring(iH.uuid), xp = math.max(tonumber(iH.xp) or 0, oH and tonumber(pH) or 0), traits = iH.traits, trait_stats = iH.trait_stats, further_modifiers = iH.further_modifiers }) rH = qH and tonumber(rH) or nH local sH = Units.hidden(UG.unit_id) or Units.hidden(VG.evolve.evolve_unit) local tH, uH, vH, wH = { }, 0, false, true local xH = Adventure.killsShort(iH, WG) if xH then wH = false table.insert(tH, { takedown = true, what = xH.what, have = xH.have, need = xH.need }) end for yH, zH in ipairs(ZG) do wH = false table.insert(tH, zH) end for AH, BH in pairs(WG.item_requirement or { }) do local CH = Adventure.planItem(BH.item_id, BH.amount or 1) if not CH.done then wH = false table.insert(tH, CH) if CH.unknown then vH = true elseif CH.farmNeed > 0 then CH.best = Adventure.bestSource(CH.levels) uH = uH + CH.farmNeed / math.max(0.1, CH.best and CH.best.per or 1) end end end local DH, EH, FH = Adventure.tier(VG.evolve.evolve_unit) if nH > lH or sH or hH then table.insert(OG, { uuid = UG.uuid, from = UG.unit_id, to = VG.evolve.evolve_unit, scoreFrom = lH, scoreTo = nH, scoreFull = rH, gain = sH and 0 or nH - lH, pinned = hH, intoTeam = hH or (not sH and nH > yG) or (sH and FH >= Adventure.TIER_RANK.S), ready = wH, unknown = vH, hidden = sH or nil, rating = Adventure.tierText(VG.evolve.evolve_unit), tierRank = FH, plans = tH, runs = uH, items = eH, eats = YG }) end end end end table.sort(OG, function(GH, HH) if (GH.pinned or false) ~= (HH.pinned or false) then return GH.pinned or false end local IH, JH = (GH.hidden and not GH.intoTeam) or false, (HH.hidden and not HH.intoTeam) or false if IH ~= JH then return not IH end if GH.ready ~= HH.ready then return GH.ready end if GH.unknown ~= HH.unknown then return not GH.unknown end if GH.intoTeam ~= HH.intoTeam then return GH.intoTeam end return GH.gain / math.max(1, GH.runs) > HH.gain / math.max(1, HH.runs) end) return OG end function Adventure.text(KH) local LH = { } for MH, NH in ipairs(KH.plans) do local OH = (Core.Items [NH.item] or { }).name or NH.item if NH.takedown then table.insert(LH, string.format("%s %s/%s (it earns them on your team)", NH.what, Utils.fmt(NH.have), Utils.fmt(NH.need))) elseif NH.copies then table.insert(LH, string.format("spare copies of %s %d/%d (unlocked, from summons)", Units.name(NH.copies), NH.have, NH.need)) elseif NH.unknown then table.insert(LH, OH .. ": source unknown (event/portal)") elseif NH.portal then local PH = Modes.PORTALS [NH.portal] table.insert(LH, string.format("%s %d/%d from %s (you hold %d; %s drops them)", OH, NH.have, NH.need, (Core.Items [NH.portal] or { }).name or NH.portal, # Modes.portalsHeld(NH.portal), Modes.levelName(PH.from))) elseif NH.eventShop then table.insert(LH, string.format("%s from the event shop: %s/%s %s", OH, Utils.fmt(Core.cur(NH.currency)), Utils.fmt(NH.cost), tostring(NH.currency))) elseif NH.shopKey then table.insert(LH, string.format("%s from the raid shop: %d/%d %s", OH, Adventure.have(NH.costItem), NH.cost * (NH.need - NH.have), (Core.Items [NH.costItem] or { }).name or NH.costItem)) else table.insert(LH, string.format("%s %d/%d", OH, NH.have, NH.need)) end end local QH = "" local RH for SH, TH in ipairs(KH.plans) do if TH.levels and TH.levels [1] then RH = TH break end end local UH = RH and (RH.best or RH.levels [1]) if UH then QH = string.format(" ยท farm %s (โ‰ˆ%.0f a run, โ‰ˆ%d runs)", Modes.levelName(UH.level), UH.per, math.ceil(KH.runs)) end local VH = KH.hidden and "strength unknown (the game shows its upgrades only in a match)" or string.format("score %s โ†’ %s", Utils.fmt(KH.scoreFrom), Utils.fmt(KH.scoreTo)) return string.format("%s โ†’ %s ยท %s%s%s ยท %s%s", Units.name(KH.from), Units.name(KH.to), VH, KH.intoTeam and " (joins your team)" or "", KH.rating and (" ยท " .. KH.rating) or "", KH.ready and "ready to evolve" or table.concat(LH, "; "), KH.ready and "" or QH) end function Adventure.refresh() local WH = Adventure.goals() local XH = table.clone(WH) table.sort(XH, function(YH, ZH) local _H, aI = tonumber(YH.tierRank) or 0, tonumber(ZH.tierRank) or 0 if _H ~= aI then return _H > aI end local bI, cI = tonumber(YH.scoreFull) or 0, tonumber(ZH.scoreFull) or 0 if bI ~= cI then return bI > cI end return tostring(YH.uuid) < tostring(ZH.uuid) end) Brain.goalsTop = { } for dI, eI in ipairs(XH) do if dI <= 10 or eI.pinned then table.insert(Brain.goalsTop, { uuid = eI.uuid, from = eI.from, to = eI.to, ready = eI.ready, intoTeam = eI.intoTeam, unknown = eI.unknown, hidden = eI.hidden, pinned = eI.pinned, text = Adventure.text(eI) }) end end Brain.goals = { } table.clear(Brain.goalItems) table.clear(Brain.goalNeed) local fI = 0 local gI = WH [1] and WH [1].pinned for hI, iI in ipairs(WH) do table.insert(Brain.goals, { uuid = iI.uuid, from = iI.from, to = iI.to, ready = iI.ready, intoTeam = iI.intoTeam, unknown = iI.unknown, hidden = iI.hidden, tierRank = iI.tierRank, text = Adventure.text(iI), gain = iI.gain, runs = iI.runs, plans = iI.plans, items = iI.items, eats = iI.eats, scoreFrom = iI.scoreFrom, scoreTo = iI.scoreTo, pinned = iI.pinned }) if not iI.ready and (iI.pinned or (not gI and Config.goals and not iI.unknown and (not iI.hidden or iI.intoTeam) and (iI.intoTeam or fI == 0) and fI < 2)) then fI = fI + 1 for jI, kI in ipairs(iI.plans) do if kI.farm and kI.farmNeed > 0 then local lI = Config.goalValue * (iI.intoTeam and 1 or 0.4) / (fI == 1 and 1 or 2) / math.max(1, kI.farmNeed) Brain.goalItems [kI.farm] = math.max(Brain.goalItems [kI.farm] or 0, lI) Brain.goalNeed [kI.farm] = math.max(Brain.goalNeed [kI.farm] or 0, kI.farmNeed) end end end if # Brain.goals >= 12 then break end end App.saveSoon() return WH end function Adventure.buyShopGoals() if not Config.raidShopGoals then return end for mI, nI in ipairs(Brain.goals) do for oI, pI in ipairs(nI.plans or { }) do if pI.shopKey and pI.costItem and Adventure.have(pI.item) < pI.need and Adventure.have(pI.costItem) >= pI.cost then local qI = Adventure.have(pI.costItem) local rI, sI = App.request("rshop", "buy_raidshop_shop_item", pI.shopKey, "1") local tI = Utils.now() while Utils.now() - tI < 3 and Adventure.have(pI.costItem) >= qI do task.wait(0.2) end if rI and Adventure.have(pI.costItem) < qI then Spending.add((Core.Items [pI.item] or { }).name or pI.item, pI.cost, (Core.Items [pI.costItem] or { }).name or pI.costItem, "for " .. Units.name(nI.to)) if App.notify then App.notify("Goals", string.format("Bought %s for %s (Units โ†’ Goals)", (Core.Items [pI.item] or { }).name or pI.item, Units.name(nI.from)), "success") end return true else App.log(1, "SHOP", "raid shop refused " .. tostring(pI.shopKey) .. " (" .. tostring(sI) .. ")") App.backoff("rshop", 900) end return end end end end function Adventure.lbText() local uI = Core.session() if not uI then return "โ€ฆ" end local vI = Brain.lb local wI = Adventure.have("divine_wish") local xI = { } for yI, zI in ipairs(Units.equipped()) do xI [zI.uuid] = true end local AI, BI = 0, 0 for CI, DI in pairs(uI.profile_data.collection.owned_units) do local EI = Core.Units [DI.unit_id] if EI and (EI.rarity == "Mythic" or EI.rarity == "Secret") and not EI._event_unit and not xI [CI] then local FI, GI, HI = pcall(Core.USC.is_unit_good_for_fusion, { uuid = "x" }, DI) HI = (FI and GI) and (tonumber(HI) or 1) or 0 if DI._locked then BI = BI + HI else AI = AI + HI end end end local II, JI for KI, LI in ipairs(Units.equipped()) do local MI = LI.u.further_modifiers and LI.u.further_modifiers.limit_break and LI.u.further_modifiers.limit_break.current_limit_break or 0 if MI < 1 and not Units.isFarm(LI.id) then local NI, OI = pcall(Units.score, LI.u) if NI and (not JI or OI > JI) then II, JI = LI, OI end end end local PI = vI and tonumber(vI.requirement) or 12 return string.format("Best target: %s (your strongest unit; level cap 100 โ†’ 110 โ‰ˆ +20%% damage after levelling, and +5%% damage for your whole team). " .. "Needs %d fuel points: you have %d in unlocked Mythic/Secret units (+%d in locked ones) and %d Divine Wish. " .. "It destroys the fuel units, so the farm never does it โ€” the Unit Fusion NPC does it when you choose.", II and Units.name(II.id) or "โ€”", PI, AI, BI, wI) end Strategy.LOG = "POWER" function Strategy.traitName(QI, RI) if not QI then return "no trait" end local SI = Core.traitInfo(QI) local TI = SI and SI.name or tostring(QI) return TI .. ((RI and tostring(RI) ~= "1") and (" " .. tostring(RI)) or "") end function Strategy.role(VI) if Units.isFarm(VI) then return "farm" elseif Units.allyBuff(VI) then return "buffer" end return "carry" end Strategy._salt = 0 function Strategy.whatIf(WI, XI) Strategy._salt = Strategy._salt + 1 local YI = { } for ZI, _I in pairs(WI) do YI [ZI] = _I end YI.uuid = "what:" .. Strategy._salt .. ":" .. tostring(WI.uuid) for aJ, bJ in pairs(XI or { }) do YI [aJ] = bJ end return YI end function Strategy.base(cJ) return Strategy.score(Strategy.whatIf(cJ, { })) end function Strategy.score(dJ) local eJ, fJ = pcall(Units.score, dJ) return (eJ and tonumber(fJ)) or 0 end function Strategy.level(gJ) local hJ, iJ = pcall(Core.USC.calculate_level, gJ.unit_id, tonumber(gJ.xp) or 0) return (hJ and tonumber(iJ)) or 1 end function Strategy.cap(jJ) local kJ, lJ = pcall(Core.USC.get_max_level, jJ) return (kJ and tonumber(lJ)) or 100 end function Strategy.xpFor(mJ, nJ) local oJ, pJ = pcall(Core.USC.calculate_xp, mJ, nJ) return (oJ and tonumber(pJ)) or 0 end function Strategy.lb(qJ) local rJ = qJ.further_modifiers return (rJ and rJ.limit_break and tonumber(rJ.limit_break.current_limit_break)) or 0 end function Strategy.share(sJ) local tJ = Brain.str.share [sJ] if tJ and (Brain.str.shareN or 0) >= 3 then return math.max(0.05, tJ) end local uJ, vJ = 0, 0 for wJ, xJ in ipairs(Units.equipped()) do if not Units.isFarm(xJ.id) then local yJ = Strategy.score(xJ.u) uJ = uJ + yJ if xJ.id == sJ then vJ = yJ end end end return math.max(0.05, uJ > 0 and vJ / uJ or 0.1) end function Strategy.learn(zJ, AJ) Brain.str.shareN = (Brain.str.shareN or 0) + 1 local BJ = math.max(0.1, 1 / Brain.str.shareN) for CJ, DJ in ipairs(AJ or { }) do if DJ.id and not Units.isFarm(DJ.id) then local EJ = zJ [DJ.id] or 0 local FJ = Brain.str.share [DJ.id] Brain.str.share [DJ.id] = FJ and (FJ + (EJ - FJ) * BJ) or EJ end end App.saveSoon() end function Strategy.core(GJ) local HJ = Core.session() if not HJ then return Brain.str.core or { } end local IJ = { } if type(Brain.savedTeam) == "table" and # Brain.savedTeam > 0 then for JJ, KJ in ipairs(Brain.savedTeam) do local LJ = HJ.collection:get_unit_by_uuid(KJ) if LJ then table.insert(IJ, { uuid = KJ, id = LJ.unit_id }) end end end if # IJ == 0 then for MJ, NJ in ipairs(Units.equipped()) do table.insert(IJ, { uuid = NJ.uuid, id = NJ.id }) end end local OJ = { } for PJ, QJ in ipairs(IJ) do table.insert(OJ, QJ.uuid) end table.sort(OJ) local RJ = table.concat(OJ, ",") if not GJ and # (Brain.str.core or { }) >= 6 and os.time() - (tonumber(Brain.str.coreAt) or 0) < (Config.teamCoreDays or 7) * 86400 then if Brain.str.teamSig == RJ then return Brain.str.core end if Brain.str.pendingSig ~= RJ then Brain.str.pendingSig, Brain.str.pendingAt = RJ, os.time() return Brain.str.core end if os.time() - (tonumber(Brain.str.pendingAt) or 0) < 600 then return Brain.str.core end end Brain.str.pendingSig, Brain.str.pendingAt = nil, nil local SJ, TJ = { }, { } for UJ, VJ in ipairs(IJ) do table.insert(SJ, VJ.uuid) TJ [VJ.id] = true end for WJ, XJ in ipairs({ "evolveUnit", "fuseUnit", "focusDone" }) do local YJ = Config [XJ] local ZJ = YJ ~= "" and HJ.collection:get_unit_by_uuid(YJ) if ZJ and not table.find(SJ, YJ) then table.insert(SJ, YJ) TJ [ZJ.unit_id] = true end end local _J = { } for aK, bK in pairs(Brain.simTeam or { }) do if type(bK) == "table" and os.time() - (tonumber(bK.at) or 0) < 7 * 86400 then for cK, dK in ipairs(bK.names or { }) do _J [dK] = (_J [dK] or 0) + 1 end end end local eK = { } for fK, gK in pairs(HJ.profile_data.collection.owned_units) do local hK = Core.Units [gK.unit_id] if hK and (hK.rarity == "Mythic" or hK.rarity == "Secret") and not TJ [gK.unit_id] and Units.rows(gK.unit_id) and not Units.hidden(gK.unit_id) and not Units.isFarm(gK.unit_id) and not Units.allyBuff(gK.unit_id) then local iK = HJ.collection:get_unit_by_uuid(fK) or gK table.insert(eK, { uuid = fK, id = gK.unit_id, k = (_J [Units.name(gK.unit_id)] or 0) * 1e12 + Strategy.score(iK) }) end end table.sort(eK, function(jK, kK) if jK.k ~= kK.k then return jK.k > kK.k end return jK.uuid < kK.uuid end) for lK, mK in ipairs(eK) do if # SJ >= 8 then break end if not TJ [mK.id] then table.insert(SJ, mK.uuid) TJ [mK.id] = true end end Brain.str.core, Brain.str.coreAt, Brain.str.teamSig = SJ, os.time(), RJ local nK = { } for oK, pK in ipairs(SJ) do local qK = HJ.collection:get_unit_by_uuid(pK) if qK then table.insert(nK, Units.name(qK.unit_id)) end end App.log(1, Strategy.LOG, "core units: " .. table.concat(nK, ", ")) App.saveSoon() return SJ end function Strategy.gainXp(rK, sK) local tK, uK = tonumber(rK.xp) or 0, Strategy.xpFor(rK.unit_id, Strategy.cap(rK)) local vK = math.max(0, uK - tK) if vK <= 0 or (sK or 0) <= 0 then return 0, 0 end local wK = math.min(sK, vK) local xK = Strategy.base(rK) if xK <= 0 then return 0, wK end return Strategy.share(rK.unit_id) * (Strategy.score(Strategy.whatIf(rK, { xp = tK + wK })) / xK - 1) * 100, wK end function Strategy.gainFuse(yK) if Strategy.lb(yK) >= 1 then return 0 end local zK = Strategy.base(yK) if zK <= 0 then return 0 end local AK = Strategy.whatIf(yK, { xp = math.max(tonumber(yK.xp) or 0, Strategy.xpFor(yK.unit_id, 110)), further_modifiers = { limit_break = { current_limit_break = 1 } } }) return Strategy.share(yK.unit_id) * (Strategy.score(AK) / zK - 1) * 100 + 5 end Strategy.MAX_RELICS = 6 Strategy.RELIC_MOVE_GAIN = 0.5 Strategy.RELIC_SETTLED_GAIN = 2 function Strategy.relicBar() if Brain.str.relicTeam == nil or Brain.str.relicTeam == Modes.teamSig() then return Strategy.RELIC_SETTLED_GAIN end return Strategy.RELIC_MOVE_GAIN end function Strategy.relicWorth(BK, CK) local DK = CK._unique_item_data and CK._unique_item_data._unique_relic_data if not DK then return 0 end local EK = Core.Items [CK.item_id] or { } local FK, GK = Units.types(BK.unit_id) if EK.main_damage_type and EK.main_damage_type ~= "both" and EK.main_damage_type ~= GK then return 0 end local HK, IK = 0, 0 for JK, KK in pairs(DK._core_damage_type_buffs or { }) do if FK [KK.damage_type] then HK = HK + (tonumber(KK.mul) or 0) + (tonumber(KK.add_upg) or 0) * 0.01 + (tonumber(KK.pen) or 0) * 0.001 IK = IK + (tonumber(KK.crit) or 0) end end HK = HK + IK * 0.5 local LK = Units.rows(BK.unit_id) local MK = LK and LK [LK.max] local NK = (MK and tonumber(MK.cd) or 0) * Units.mult(BK, "attack_cooldown") for OK, PK in pairs(DK._unique_relic_effects or { }) do local QK = type(PK.params) == "table" and PK.params or { } if PK.id == "hourglass_relic" then local RK = math.min(1, (tonumber(QK.extra_damage) or 0) * NK / math.max(1, tonumber(QK.time_interval) or 40)) local SK = Units.DRONES and Units.DRONES [BK.unit_id] if SK and SK.late and NK > 0 then local TK = 1 / NK RK = RK * TK / (TK + (tonumber(SK.max) or 0) * (tonumber(SK.late.dmg) or 0) * (tonumber(SK.hits) or 1) / math.max(0.1, tonumber(SK.late.cd) or 4)) end HK = HK + RK elseif PK.id == "yuno_clover" then HK = HK + (tonumber(QK.dmg_buff) or 0) * 0.25 elseif PK.id == "nail" then HK = HK + (tonumber(QK.dmg_buff) or 0) * IK elseif PK.id == "asta_clover" then if FK.magic then HK = 0 else HK = HK + (tonumber(QK.dmg_buff) or 0) * 0.3 end elseif PK.id == "noelle_crown" then if FK.water_damage then HK = HK + 0.05 end end end return HK end function Strategy.relicPlan() local UK = Core.session() if not UK then return nil end local VK = UK.inventory and UK.inventory.inventory_profile_data or { } local WK = { } for XK, YK in ipairs(VK.unique_items or { }) do if type(YK) == "table" and YK.uuid and (Core.Items [YK.item_id] or { }).item_group == "Relics" then table.insert(WK, YK) end end local ZK = { } for _K, aL in ipairs(Units.equipped()) do local bL = aL.data and aL.data.rarity if not Units.isFarm(aL.id) and (bL == "Legendary" or bL == "Mythic" or bL == "Secret" or bL == "Exclusive") then local cL = Strategy.share(aL.id) if not Brain.str.share [aL.id] then cL = math.min(cL, 0.15) end local dL = { } for eL, fL in ipairs(WK) do local gL = Strategy.relicWorth(aL.u, fL) * cL * 100 if gL > 0.01 then table.insert(dL, { it = fL, w = gL }) end end table.sort(dL, function(hL, iL) if hL.w ~= iL.w then return hL.w > iL.w end return hL.it.uuid < iL.it.uuid end) local jL, kL = { }, { } for lL, mL in ipairs(dL) do local nL = kL [mL.it.item_id] or 0 if # jL < 6 and nL < ((Core.Items [mL.it.item_id] or { }).unique_equip and 1 or 2) then kL [mL.it.item_id] = nL + 1 table.insert(jL, mL) end end if aL.u._equipped_relic then local oL = false for pL, qL in ipairs(jL) do if qL.it.uuid == aL.u._equipped_relic then oL = true end end local rL = not oL and UK.inventory:get_item_by_uuid(aL.u._equipped_relic) if rL then table.insert(jL, { it = rL, w = Strategy.relicWorth(aL.u, rL) * cL * 100 }) end end table.insert(ZK, { e = aL, share = cL, opts = jL }) end end local sL, tL = { }, - 1 local uL, vL, wL = { }, { }, { } local function xL(yL, zL) if yL > # ZK then if zL > tL then tL = zL sL = table.clone(uL) end return end local AL = ZK [yL] uL [AL.e.uuid] = nil xL(yL + 1, zL) for BL, CL in ipairs(AL.opts) do local DL = (Core.Items [CL.it.item_id] or { }).unique_equip if not vL [CL.it.uuid] and not (DL and wL [CL.it.item_id]) then vL [CL.it.uuid] = true if DL then wL [CL.it.item_id] = true end uL [AL.e.uuid] = CL.it.uuid xL(yL + 1, zL + CL.w + (CL.it.uuid == AL.e.u._equipped_relic and 0.01 or 0)) uL [AL.e.uuid] = nil vL [CL.it.uuid] = nil if DL then wL [CL.it.item_id] = nil end end end end xL(1, 0) local EL, FL = 0, { } for GL, HL in ipairs(ZK) do local IL = HL.e.u._equipped_relic and UK.inventory:get_item_by_uuid(HL.e.u._equipped_relic) local JL = IL and Strategy.relicWorth(HL.e.u, IL) * HL.share * 100 or 0 EL = EL + JL local KL = sL [HL.e.uuid] and UK.inventory:get_item_by_uuid(sL [HL.e.uuid]) local LL = KL and Strategy.relicWorth(HL.e.u, KL) * HL.share * 100 or 0 table.insert(FL, string.format("%s: %s (%+.1f%%) โ†’ %s (%+.1f%%)", Units.name(HL.e.id), IL and (Core.Items [IL.item_id] or { }).name or "none", JL, KL and (Core.Items [KL.item_id] or { }).name or "none", LL)) end return sL, tL - EL, FL, ZK end function Strategy.relicsApply(ML) if not (Config.team and Config.teamRelics) or Config.strDry then return false end local NL = Modes.teamSig() if not ML and Brain.str.relicSig == NL and os.time() - (tonumber(Brain.str.relicAt) or 0) < 600 then return false end if App.req.back.relicMove and Utils.now() < App.req.back.relicMove then return false end Brain.str.relicSig, Brain.str.relicAt = NL, os.time() local OL, PL, QL, RL = Strategy.relicPlan() if not OL then return false end Brain.str.relicNote = string.format("relics now on the best spread for this team (a change would add %.1f%%)", math.max(0, PL)) if PL < Strategy.relicBar() then Brain.str.relicTeam = NL return false end local SL = Core.session() local TL = SL.profile_data.collection.owned_units local function UL(VL) for WL, XL in pairs(TL) do if XL._equipped_relic == VL then return WL, XL end end end local function YL(ZL) return App.gameSaid(ZL) or "no answer" end local function _L(aM) local bM, cM = UL(aM) if not bM then return true end local dM = Utils.now() App.request("relicMove", "request_unequip_relic", aM) while Utils.now() - dM < 4 and cM._equipped_relic == aM do task.wait(0.25) end task.wait(1.2) if cM._equipped_relic == aM then App.log(1, "POWER", string.format("%s's %s wouldn't come off (%s): relics stay as they are for now", Units.name(cM.unit_id), tostring(Units.relicName(cM)), YL(dM))) App.backoff("relicMove", 300) return false end return true end App.log(1, "POWER", string.format("moving relics for +%.1f%% team damage: %s", PL, table.concat(QL, " ยท "))) for eM, fM in ipairs(RL) do local gM = fM.e.u._equipped_relic if gM and gM ~= OL [fM.e.uuid] and not _L(gM) then return false end end for hM, iM in pairs(OL) do local jM = UL(iM) if jM and jM ~= hM and not _L(iM) then return false end end table.sort(RL, function(kM, lM) return kM.share > lM.share end) local mM = 0 for nM, oM in ipairs(RL) do local pM = OL [oM.e.uuid] if pM and oM.e.u._equipped_relic ~= pM then local qM, rM = 0, nil local sM = { } for tM, uM in ipairs(Units.equipped()) do sM [uM.uuid] = true end for vM, wM in pairs(TL) do if wM._equipped_relic then qM = qM + 1 if not sM [vM] and (not rM or not table.find(Brain.str.core or { }, vM)) then rM = wM._equipped_relic end end end if qM >= Strategy.MAX_RELICS and rM and not _L(rM) then return false end local xM = Utils.now() App.request("relicMove", "use_unique_item", pM, { unit_uuid = oM.e.uuid }) while Utils.now() - xM < 4 and oM.e.u._equipped_relic ~= pM do task.wait(0.25) end task.wait(1.2) if oM.e.u._equipped_relic ~= pM then App.log(1, "POWER", string.format("the game didn't put %s on %s (%s): trying again in 5 min", (Core.Items [(SL.inventory:get_item_by_uuid(pM) or { }).item_id] or { }).name or "that relic", Units.name(oM.e.id), YL(xM))) App.backoff("relicMove", 300) Brain.str.relicAt = 0 return false end mM = mM + 1 end end Modes._sigAt = nil Brain.str.relicSig = Modes.teamSig() Brain.str.relicTeam = Brain.str.relicSig Brain.str.relicNote = string.format("relics moved for +%.1f%% team damage", PL) Strategy.note(string.format("moved %d relics for +%.1f%% team damage", mM, PL)) return true end function Strategy.foodHeld() local yM = Core.session() local zM = { } local AM = yM and yM.inventory and yM.inventory.inventory_profile_data or { } for BM, CM in pairs(AM.normal_items or { }) do local DM = Core.Items [BM] if type(DM) == "table" and type(DM.usage) == "table" and DM.usage.type == "xp_feed" and (tonumber(CM) or 0) > 0 then table.insert(zM, { id = BM, n = tonumber(CM), xp = tonumber(DM.usage.xp_amount) or 0 }) end end table.sort(zM, function(EM, FM) return EM.xp > FM.xp end) return zM end function Strategy.xpRoom() local GM, HM = Core.session(), 0 for IM, JM in ipairs(GM and Strategy.core() or { }) do local KM = GM.collection:get_unit_by_uuid(JM) if KM then HM = HM + math.max(0, Strategy.xpFor(KM.unit_id, Strategy.cap(KM)) - (tonumber(KM.xp) or 0)) end end return HM end function Strategy.plan() local LM = Core.session() if not LM then return { } end local MM = { } local NM = Strategy.core() local OM = Strategy.foodHeld() local PM = 0 for QM, RM in ipairs(OM) do PM = PM + RM.n * RM.xp end local SM = Config.autoFuse and Config.fuseUnit ~= "" and Config.fuseUnit or nil local TM = (Brain.fuseInfo and tonumber(Brain.fuseInfo.requirement)) or (Brain.lb and tonumber(Brain.lb.requirement)) or 12 local UM = Adventure.have("divine_wish") for VM, WM in ipairs(NM) do local XM = LM.collection:get_unit_by_uuid(WM) if XM then local YM, ZM = XM.unit_id, Units.name(XM.unit_id) local _M = Strategy.role(YM) if Config.teamXp and PM > 0 then local aN, bN = Strategy.gainXp(XM, PM) local cN, dN = Strategy.gainXp(XM, 500000) local eN = dN > 0 and cN / dN * bN or 0 if bN > 0 and eN > 0 then table.insert(MM, { kind = "feed", uuid = WM, name = ZM, gain = eN, add = bN, text = string.format("feed %s %s XP: level %d โ†’ %d (+%.1f%% team damage)", ZM, Utils.fmt(bN), Strategy.level(XM), Strategy.level({ unit_id = YM, xp = (tonumber(XM.xp) or 0) + bN }), eN) }) end end local fN = Brain.str.fuseNo and tonumber(Brain.str.fuseNo [WM]) if Config.teamFuse and Strategy.lb(XM) == 0 and _M ~= "farm" and WM ~= SM and not (fN and os.time() - fN < 86400) then local gN = Strategy.gainFuse(XM) local hN = Strategy.xpFor(YM, Strategy.cap(XM)) <= (tonumber(XM.xp) or 0) local iN, jN, kN = pcall(FarmHub.fuseFuel, XM, TM) kN = iN and kN or 0 local lN = (not hN and "not at its level cap yet") or (UM < 1 and "no Divine Wish (the Daily Challenge pays one)") or (kN < TM and string.format("spare units it may use are worth %d of %d points", math.floor(kN), math.floor(TM))) or nil table.insert(MM, { kind = "fuse", uuid = WM, name = ZM, gain = gN, blocked = lN, text = string.format("limit break %s (+%.1f%% team damage once it reaches level 110)", ZM, gN) }) end end end table.sort(MM, function(mN, nN) if mN.gain ~= nN.gain then return mN.gain > nN.gain end return mN.text < nN.text end) for oN in pairs(Units._mult) do if type(oN) == "string" and oN:sub(1, 5) == "what:" then Units._mult [oN] = nil end end Brain.str.acts, Brain.str.actsAt = { }, os.time() for pN = 1, math.min(12, # MM) do local qN = MM [pN] table.insert(Brain.str.acts, { kind = qN.kind, uuid = qN.uuid, name = qN.name, gain = qN.gain, text = qN.text, blocked = qN.blocked }) end return MM end function Strategy.itemWorth(rN) if not (Brain.str.acts and os.time() - (tonumber(Brain.str.actsAt) or 0) < 900) then return nil end local sN = { } for tN, uN in ipairs(Brain.str.acts) do if uN.gain and (not sN [uN.kind] or uN.gain > sN [uN.kind]) then sN [uN.kind] = uN.gain end end local vN = Config.strengthValue or 300 if rN == "divine_wish" then return sN.fuse and math.max(Core.ITEM_VALUE.divine_wish or 400, sN.fuse * vN) or nil end return nil end function Strategy.xpRate() local wN = Core.session() if not wN then return 0 end local xN = 0 for yN, zN in ipairs(Strategy.core()) do local AN = wN.collection:get_unit_by_uuid(zN) if AN then local BN, CN = Strategy.gainXp(AN, 500000) if CN > 0 then xN = math.max(xN, BN / CN) end end end local DN = xN * (Config.strengthValue or 300) if (tonumber(Core.cur("gold")) or 0) > (Config.xpGoldFloor or 100000) then DN = math.min(DN, (Core.GOLD_VALUE or 0.02) / 14) end return DN end function Strategy.xpGuess(EN) local FN = EN.level and Core.Levels [EN.level] if not FN then return 0 end local GN = math.min(9, tonumber(FN.story_world_rank) or tonumber(EN.rank) or 9) local HN = tonumber(FN.story_level_rank) or 6 local IN = (EN.kind == "raid" and 1.5 or 1) * (GN > 5 and 2 or 1) local JN = 2.1564 ^ (GN - 1) * (40 + 10 * HN) / IN if EN.kind == "infinite" then JN = 3 * 30 * 2.1564 ^ (GN - 1) / IN end local KN = GN >= 9 and 0.3 or (GN >= 7 and 0.5 or 1) return JN * KN * 6 end function Strategy.modeBonus(LN) if not (Config.team and Config.teamXp) or LN.kind == "dungeon" or LN.kind == "event" then return 0 end local MN = Strategy._xpRate and os.time() - (Strategy._xpRateAt or 0) < 300 and Strategy._xpRate or nil if not MN then MN = Strategy.xpRate() Strategy._xpRate, Strategy._xpRateAt = MN, os.time() end if MN <= 0 then return 0 end local NN = Brain.modes and Brain.modes [LN.key] local ON = tonumber(NN and NN.xp) or Strategy.xpGuess(LN) local PN = ON * MN if PN < 1 then return 0 end return PN, string.format("โ‰ˆ%s XP for your team (โ‰ˆ%d gems)", Utils.fmt(ON), math.floor(PN + 0.5)) end function Strategy.xpSettle() local QN = Brain.lastRun if not (QN and type(QN.xp0) == "table" and QN.key) then return end local RN = Core.session() if not RN then return end local SN, TN = 0, 0 for UN, VN in pairs(QN.xp0) do local WN = RN.collection:get_unit_by_uuid(UN) if WN then SN = SN + math.max(0, (tonumber(WN.xp) or 0) - (tonumber(VN) or 0)) TN = TN + 1 end end Brain.lastRun = nil if TN == 0 then return end local XN = Modes.rec(QN.key) XN.xp = XN.xp and (XN.xp + (SN - XN.xp) * 0.3) or SN if SN > 0 then App.log(2, Strategy.LOG, string.format("%s paid your units %s XP", QN.key, Utils.fmt(SN))) end App.saveSoon() end function Strategy.note(YN) App.log(1, Strategy.LOG, YN) table.insert(Brain.str.log, 1, { at = os.time(), msg = YN }) while # Brain.str.log > 20 do table.remove(Brain.str.log) end App.saveSoon() end function Strategy.run(ZN) local _N = Core.session() local aO = _N and _N.collection:get_unit_by_uuid(ZN.uuid) if not aO then return end if ZN.kind == "feed" then local bO = Units.trait(aO) local cO = bO == "neuroplasticity" and 1.5 or 1 local dO = math.max(0, Strategy.xpFor(aO.unit_id, Strategy.cap(aO)) - (tonumber(aO.xp) or 0)) local eO, fO, gO = { }, 0, 0 for hO, iO in ipairs(Strategy.foodHeld()) do local jO = iO.xp * cO local kO = math.min(iO.n, math.floor((dO - fO) / math.max(1, jO))) if kO > 0 then eO [iO.id] = kO fO = fO + kO * jO gO = gO + kO end end if gO == 0 then Brain.str.feedNote = ZN.name .. ": no food small enough for the XP it has left" return end local lO, mO = tonumber(aO.xp) or 0, Strategy.level(aO) local nO, oO = App.request("feed", "level_up_feed", ZN.uuid, eO) local pO = Utils.now() while Utils.now() - pO < 4 and (tonumber((_N.collection:get_unit_by_uuid(ZN.uuid) or aO).xp) or 0) <= lO do task.wait(0.25) end local qO = _N.collection:get_unit_by_uuid(ZN.uuid) or aO local rO = (tonumber(qO.xp) or 0) - lO if rO > 0 then Spending.add(string.format("%d XP food on %s", gO, ZN.name), gO, "items", string.format("Units โ†’ Upgrades: +%s XP, level %d โ†’ %d", Utils.fmt(rO), mO, Strategy.level(qO))) Strategy.note(string.format("fed %s %d items: +%s XP, level %d โ†’ %d%s", ZN.name, gO, Utils.fmt(rO), mO, Strategy.level(qO), math.abs(rO - fO) > fO * 0.1 and string.format(" (planned %s)", Utils.fmt(fO)) or "")) else Strategy.note(string.format("the game didn't take the food for %s (%s): trying again in 10 min", ZN.name, tostring(nO and oO or "no answer"))) App.backoff("feed", 600) end elseif ZN.kind == "fuse" then FarmHub.fuseInto(ZN.uuid, false) end end function Strategy.slotOf(sO) local tO = Core.session() for uO, vO in pairs(tO and tO.profile_data.collection.equipped_units or { }) do if vO == sO then return tonumber(uO) or uO end end return nil end function Strategy.reequip() local wO = Brain.str.reequip if not wO then return true end local xO = Core.session() local yO = xO and xO.collection:get_unit_by_uuid(wO.uuid) if not yO then Brain.str.reequip = nil return true end for zO = 1, 3 do if Strategy.slotOf(wO.uuid) then break end App.request("eq", "equip_unit", wO.uuid) local AO = Utils.now() while Utils.now() - AO < 3 and not Strategy.slotOf(wO.uuid) do task.wait(0.25) end end Modes._sigAt = nil if Strategy.slotOf(wO.uuid) then Brain.str.reequip = nil App.saveSoon() return true end App.log(1, "TEAM", string.format("%s didn't go back into slot %s: trying again", Units.name(yO.unit_id), tostring(wO.slot))) return false end Strategy.REMOTE = { feed = "feed", fuse = "fuse" } Strategy.WORK_MAX = 300 function Strategy.nextAct(BO) local CO = os.time() for DO, EO in ipairs(BO or { }) do local FO = App.req.back [Strategy.REMOTE [EO.kind] or ""] if not EO.blocked and not (FO and Utils.now() < FO) and (tonumber(Brain.str.noWork [EO.kind]) or 0) <= CO and (tonumber(Brain.str.noWork [EO.kind .. ":" .. tostring(EO.uuid)]) or 0) <= CO then return EO end end end function Strategy.workSig() local GO = 0 for HO, IO in ipairs(Strategy.foodHeld()) do GO = GO + IO.n end return table.concat({ GO, Adventure.have("divine_wish") }, ",") end Strategy.FEED_STOP_GAIN = 0.5 function Strategy.focusReady() if not (Config.autoEvolve and Config.evolveUnit ~= "") then return nil end pcall(Adventure.refresh) for JO, KO in ipairs(Brain.goals or { }) do if KO.pinned and KO.uuid == Config.evolveUnit and KO.ready and (FarmHub.evoBlocked [KO.uuid] or 0) < os.time() then local LO = { } for MO, NO in ipairs(Units.equipped()) do LO [NO.uuid] = true end if not FarmHub.evoEats(KO, LO) then return KO end end end end function Strategy.hasWork(OO) if not OO then local PO = Strategy.focusReady() if PO then return "evolve " .. Units.name(PO.from) .. ", your focus", "evolve" end if FarmHub.snipeWanted() then return "pull " .. Units.name(Config.snipeUnit) .. " while it's featured (Banner Snipe)", "snipe" end if FarmHub.snipeDue() then return "look at the banners for " .. Units.name(Config.snipeUnit) .. ": they've turned over (Banner Snipe)", "snipe" end if Brain.ownSwap then return "put " .. Units.name(Brain.ownSwap.into) .. " on your team", "ownSwap" end end if not Config.team or Config.strDry then return nil end Strategy.acts = Strategy.plan() FarmHub.last.teamPlan = Utils.now() local QO = Strategy.acts if not OO then QO = { } for RO, SO in ipairs(Strategy.acts) do if not (SO.kind == "feed" and (tonumber(SO.gain) or 0) < Strategy.FEED_STOP_GAIN) then table.insert(QO, SO) end end end local TO = Strategy.nextAct(QO) if TO then return TO.text, TO.kind end if Config.teamRelics and (tonumber(Brain.str.noWork.relic) or 0) <= os.time() and (not OO or not (Config.autoTeam and Config.play)) then local UO, VO = Strategy.relicPlan() if VO and VO >= (OO and Strategy.relicBar() or Strategy.RELIC_SETTLED_GAIN) then return string.format("move relics for +%.1f%% team damage", VO), "relic" end end if not OO and Config.teamXp and Config.teamBuyXp and Config.goldShop and os.time() - (tonumber(Brain.str.shopAt) or 0) > 21600 and Strategy.xpRoom() > 0 and (tonumber(Core.cur("gold")) or 0) > (Config.xpGoldFloor or 100000) then return "look for XP food in the gold shop", "shop" end return nil end function Strategy.finishWork() local WO = Utils.now() local XO = 0 while App.alive and Config.master and not Brain.holdLobby and Utils.now() - WO < Strategy.WORK_MAX do local YO, ZO = Strategy.hasWork(true) if not YO then break end App.status("Mode", "Units โ†’ Upgrades first: " .. tostring(YO)) if ZO == "relic" then local _O, aP = pcall(Strategy.relicsApply, true) if not (_O and aP) then Brain.str.noWork.relic = os.time() + 1800 end else Strategy.tick() end XO = XO + 1 task.wait(4) end if XO > 0 then App.log(1, Strategy.LOG, string.format("Units โ†’ Upgrades finished for now: %d actions in %ds", XO, math.floor(Utils.now() - WO))) Modes._sigAt, Modes.lastScored = nil, nil App.saveSoon() end end function Strategy.tick() if not Config.team then Brain.str.next = "Off." return end if Brain.str.reequip then Strategy.reequip() end local bP if FarmHub.every("teamPlan", 60) or not Brain.str.acts then bP = Strategy.plan() else bP = Strategy.acts end Strategy.acts = bP or Strategy.acts or { } local cP = Strategy.nextAct(Strategy.acts) if not cP then local dP = Strategy.acts [1] Brain.str.next = dP and (dP.text .. " โ€” waiting: " .. tostring(dP.blocked)) or "Nothing to do: every core unit is as strong as what you have allows." return end if Config.strDry then Brain.str.next = "Would " .. cP.text .. ". Dry Run is on: nothing is spent." if Brain.str.dryLast ~= cP.text then Brain.str.dryLast = cP.text App.log(1, Strategy.LOG, "dry run: would " .. cP.text) end return end Brain.str.next = cP.text local eP = Strategy.workSig() local fP, gP = pcall(Strategy.run, cP) if not fP then App.log(1, "ERROR", "team: " .. tostring(gP)) end local hP = cP.kind == "fuse" and os.time() - (tonumber(Config.fuseArmAt) or 0) < 45 if Strategy.workSig() == eP and not hP then Brain.str.noWork [cP.kind .. ":" .. tostring(cP.uuid)] = os.time() + 1800 end local iP = App.req.back [Strategy.REMOTE [cP.kind] or ""] if iP and iP > Utils.now() then Brain.str.noWork [cP.kind] = os.time() + math.ceil(iP - Utils.now()) end FarmHub.last.teamPlan = nil end function Strategy.rows() local jP = Core.session() local kP = { } if not jP then return kP end for lP, mP in ipairs(Brain.str.core or { }) do local nP = jP.collection:get_unit_by_uuid(mP) if nP then local oP, pP = Units.trait(nP) local qP = { string.format("level %d/%d", Strategy.level(nP), Strategy.cap(nP)), Strategy.traitName(oP, pP) } if nP.trait_stats then table.insert(qP, (FarmHub.statRanks(nP):gsub(" ยท ", " / "))) end table.insert(qP, Units.relicName(nP) and ("relic: " .. Units.relicName(nP)) or "no relic") if Strategy.lb(nP) >= 1 then table.insert(qP, "limit broken โœ“") end local rP for sP, tP in ipairs(Brain.str.acts or { }) do if tP.uuid == mP then rP = tP break end end if rP then table.insert(qP, "next: " .. (rP.text:gsub("^%l", string.upper)) .. (rP.blocked and (" (waiting: " .. rP.blocked .. ")") or "")) end table.insert(kP, { name = Units.name(nP.unit_id), text = table.concat(qP, " ยท ") }) end end return kP end function Strategy.text() if not Config.team then return "Off. Switch it on and the farm makes your core units stronger with what you already have." end local uP = { Brain.str.next or "Working it out in the lobbyโ€ฆ" } if Brain.str.fuseNote then table.insert(uP, "Limit break: " .. Brain.str.fuseNote) end if Brain.str.relicNote then table.insert(uP, "Relics: " .. Brain.str.relicNote) end for vP = 1, math.min(4, # (Brain.str.log or { })) do table.insert(uP, os.date("%H:%M", Brain.str.log [vP].at) .. " " .. Brain.str.log [vP].msg) end return table.concat(uP, "\n") end FarmHub.last = { } FarmHub.hatched = { } function FarmHub.evolve(wP, xP) local yP = Core.session() local zP = yP.collection:get_unit_by_uuid(wP.uuid) if not zP then return false, "unit not found" end local AP, BP = 0, { } for CP, DP in pairs(yP.profile_data.collection.owned_units) do if DP.unit_id == wP.to then AP = AP + 1 BP [CP] = true end end local FP, GP = App.request("evolve", "evolve_unit", wP.uuid) task.wait(2) local HP = yP.collection:get_unit_by_uuid(wP.uuid) local IP = 0 for JP, KP in pairs(yP.profile_data.collection.owned_units) do if KP.unit_id == wP.to then IP = IP + 1 if not BP [JP] then if Config.fuseXpUnit == wP.uuid then Config.fuseXpUnit, Config.fuseXpId = JP, wP.to App.saveSoon() end if Config.fuseUnit == wP.uuid then Config.fuseUnit = JP App.saveSoon() end end end end if IP > AP then local LP = { } for MP, NP in ipairs(wP.items or { }) do table.insert(LP, string.format("%s ร—%d", (Core.Items [NP.id] or { }).name or NP.id, NP.amount)) end for OP, PP in ipairs(wP.eats or { }) do table.insert(LP, string.format("%dร— %s", PP.amount, Units.name(PP.id))) end local QP = string.format("%s โ†’ %s (score %s โ†’ %s)", Units.name(wP.from), Units.name(wP.to), Utils.fmt(wP.scoreFrom or 0), Utils.fmt(wP.scoreTo or 0)) Spending.add("evolved " .. QP, 1, "evolution", (xP or "your choice") .. (# LP > 0 and (" ยท used " .. table.concat(LP, ", ")) or "")) App.log(1, "EVOLVE", QP .. " โ€” " .. (xP or "your choice") .. (# LP > 0 and (" ยท used " .. table.concat(LP, ", ")) or "")) Modes._sigAt = nil pcall(Adventure.refresh) return true, Units.name(wP.to) .. " is yours!" end App.log(1, "EVOLVE", "evolve refused for " .. Units.name(wP.from) .. " (" .. tostring(GP) .. "), unit now " .. tostring(HP and HP.unit_id)) return false, "the game said no (" .. tostring(GP) .. ")" end FarmHub.evoBlocked = { } function FarmHub.evoEats(RP, SP) local TP = Core.session() local UP, VP = pcall(Units.score, TP.collection:get_unit_by_uuid(RP.uuid)) for WP, XP in ipairs(RP.eats or { }) do for YP, ZP in ipairs(Adventure.copies(XP.id, XP.shiny, RP.uuid)) do local _P = TP.collection:get_unit_by_uuid(ZP.uuid) or ZP local aQ, bQ = pcall(Units.score, _P) if SP [ZP.uuid] or Units.trait(_P) or (UP and aQ and bQ >= VP) then return string.format("would use up a %s you may want (on your team, a trait, or stronger) โ€” lock it in the game to keep it", Units.name(XP.id)) end end end end FarmHub.pickNote = { } function FarmHub.pickDone(cQ, dQ, eQ, fQ) Config [cQ] = false pcall(function() GUI.UI [cQ].Set(false, true) end) FarmHub.pickNote [cQ] = eQ App.log(1, "LOBBY", dQ .. ": " .. eQ) if App.notify then App.notify(dQ, eQ, fQ or "success") end App.saveSoon() end function FarmHub.focusNextUp() if Config.focusNext == "" or GUI.focusNow() ~= "" then return end local gQ = Config.focusNext local hQ = Core.session() local iQ, jQ for kQ, lQ in pairs(hQ.profile_data.collection.owned_units) do if lQ.unit_id == gQ and (not iQ or (tonumber(lQ.xp) or 0) > jQ) then iQ, jQ = kQ, tonumber(lQ.xp) or 0 end end if not iQ then Config.focusNext = "" App.log(1, "LOBBY", "Focus: next up was " .. Units.name(gQ) .. ", and you don't have one any more") return App.saveSoon() end Config.focusDone = Config.evolveUnit App.log(1, "LOBBY", "Focus: next up is " .. Units.name(gQ)) FarmHub.setFocus(iQ) GUI.focusSig = nil if App.notify then App.notify("Focus", "now focusing on " .. Units.name(gQ), "success") end end function FarmHub.autoEvolve() if not Config.autoEvolve or Config.evolveUnit == "" then return end local mQ = Core.session() if not (mQ and mQ.collection:get_unit_by_uuid(Config.evolveUnit)) then return FarmHub.pickDone("autoEvolve", "Focus", "that unit is gone", "warning") end local nQ for oQ, pQ in ipairs(Brain.goals or { }) do if pQ.pinned and pQ.uuid == Config.evolveUnit then nQ = pQ break end end if not nQ then local qQ = mQ.collection:get_unit_by_uuid(Config.evolveUnit) local rQ = Core.Units [qQ.unit_id] if not (rQ and rQ.evolve and rQ.evolve.evolve_unit) then FarmHub.pickDone("autoEvolve", "Focus", Units.name(qQ.unit_id) .. " has no evolution left", "warning") end return end FarmHub.pickNote.autoEvolve = nil if not nQ.ready then return end local sQ = { } for tQ, uQ in ipairs(Units.equipped()) do sQ [uQ.uuid] = true end local vQ = FarmHub.evoEats(nQ, sQ) if vQ then FarmHub.pickNote.autoEvolve = vQ return end if (FarmHub.evoBlocked [nQ.uuid] or 0) >= os.time() then return end local wQ, xQ = FarmHub.evolve(nQ, "your focus (Units โ†’ Goals)") if wQ then FarmHub.pickDone("autoEvolve", "Focus", xQ) else FarmHub.evoBlocked [nQ.uuid] = os.time() + 3600 FarmHub.pickNote.autoEvolve = xQ end end function FarmHub.evolveText() if Config.evolveUnit == "" then return "Pick a unit. The farm goes after the items and takedowns it needs, and evolves it once it's ready (evolving can't be undone)." end local yQ = FarmHub.pickNote.autoEvolve and ("\n" .. FarmHub.pickNote.autoEvolve) or "" if not Config.autoEvolve then return "Switch it on to start." .. yQ end for zQ, AQ in ipairs(Brain.goals or { }) do if AQ.pinned and AQ.uuid == Config.evolveUnit then return AQ.text .. ((Modes.onlyOn() and not AQ.ready) and ("\nOnly " .. Modes.onlyText() .. " is on (Farm โ†’ Stages), so it can only farm there.") or "") .. yQ end end return "Works out what it needs in the lobbyโ€ฆ" .. yQ end function FarmHub.fuseFuel(BQ, CQ) local DQ = Core.session() local EQ = { [BQ.uuid] = true, [Config.evolveUnit] = true, [Config.fuseXpUnit] = true } local FQ = { } for GQ, HQ in ipairs(Units.equipped()) do EQ [HQ.uuid] = true end for IQ, JQ in ipairs(Brain.savedTeam or { }) do EQ [JQ] = true end for KQ, LQ in ipairs(Brain.train and Brain.train.on and Brain.train.on.base or { }) do EQ [LQ] = true end for MQ, NQ in ipairs(Brain.goals or { }) do EQ [NQ.uuid] = true for OQ, PQ in ipairs(NQ.eats or { }) do FQ [PQ.id] = true end end for QQ, RQ in ipairs(Brain.str and Brain.str.core or { }) do EQ [RQ] = true end local SQ = Adventure.strongest(10) local TQ, UQ = { }, 0 for VQ, WQ in pairs(DQ.profile_data.collection.owned_units) do local XQ = DQ.collection:get_unit_by_uuid(VQ) or WQ local YQ, ZQ, _Q = pcall(Core.USC.is_unit_good_for_fusion, BQ, XQ) if YQ and ZQ and not (WQ._locked or XQ._locked) and not EQ [VQ] and not FQ [XQ.unit_id] and not (WQ._equipped_relic or XQ._equipped_relic) and not (WQ.shiny or XQ.shiny) and not Units.trait(XQ) and not (WQ.further_modifiers and WQ.further_modifiers.limit_break) then local aR = Adventure.fodderOrder(XQ.unit_id) if aR and not SQ [VQ] then local bR, cR = pcall(Units.score, XQ) table.insert(TQ, { uuid = VQ, id = XQ.unit_id, pts = tonumber(_Q) or 1, sc = bR and cR or 0, order = aR }) UQ = UQ + (tonumber(_Q) or 1) end end end table.sort(TQ, function(dR, eR) if dR.order ~= eR.order then return dR.order < eR.order end if dR.sc ~= eR.sc then return dR.sc < eR.sc end return dR.uuid < eR.uuid end) local fR, gR = { }, 0 for hR, iR in ipairs(TQ) do if gR >= CQ then break end table.insert(fR, iR) gR = gR + iR.pts end return fR, gR, UQ end function FarmHub.fodderRows(jR) local kR = Core.session() if not (kR and jR) then return { } end local lR, mR, nR, oR = { }, { }, { }, { } for pR, qR in ipairs(Units.equipped()) do lR [qR.uuid] = true end for rR, sR in ipairs(Brain.str and Brain.str.core or { }) do mR [sR] = true end for tR, uR in ipairs(Brain.goals or { }) do nR [uR.uuid] = true for vR, wR in ipairs(uR.eats or { }) do oR [wR.id] = true end end local xR = { } local yR, zR = pcall(FarmHub.fuseFuel, jR, math.huge) for AR, BR in ipairs(yR and zR or { }) do xR [BR.uuid] = true end local CR = { } for DR, ER in pairs(kR.profile_data.collection.owned_units) do if DR ~= jR.uuid and not lR [DR] and not ER._locked then local FR = kR.collection:get_unit_by_uuid(DR) or ER local GR, HR, IR = pcall(Core.USC.is_unit_good_for_fusion, jR, FR) if GR and HR and not FR._locked then local JR = Core.Units [FR.unit_id] or { } local KR = { } local LR, MR = Units.trait(FR) if LR then table.insert(KR, "trait " .. tostring((Core.traitInfo(LR) or { }).name or LR) .. (MR and MR ~= "1" and (" " .. MR) or "")) end if FR._equipped_relic or ER._equipped_relic then table.insert(KR, "relic " .. tostring(Units.relicName(FR) or "")) end if FR.shiny or ER.shiny then table.insert(KR, "shiny") end if FR.further_modifiers and FR.further_modifiers.limit_break then table.insert(KR, "limit broken") end if mR [DR] then table.insert(KR, "a core unit") end if nR [DR] or oR [FR.unit_id] then table.insert(KR, "part of a goal") end local NR, OR = pcall(Units.score, FR) table.insert(CR, { uuid = DR, id = FR.unit_id, name = Units.name(FR.unit_id), rarity = JR.rarity or "?", lvl = Strategy.level(FR), pts = tonumber(IR) or 1, evolved = Units.isEvolvedForm(FR.unit_id), flags = KR, safe = xR [DR] or false, sc = NR and OR or 0 }) end end end table.sort(CR, function(PR, QR) if PR.safe ~= QR.safe then return PR.safe end if PR.sc ~= QR.sc then return PR.sc < QR.sc end return PR.uuid < QR.uuid end) return CR end function FarmHub.fodderPicked(RR) local SR = Core.session() local TR, UR, VR = { }, 0, 0 local WR = { } for XR, YR in ipairs(Units.equipped()) do WR [YR.uuid] = true end for ZR, _R in ipairs(Config.fuseFodder or { }) do local aS = SR and SR.collection:get_unit_by_uuid(_R) local bS, cS, dS = false, false, 0 if aS and RR and _R ~= RR.uuid and not aS._locked and not WR [_R] then bS, cS, dS = pcall(Core.USC.is_unit_good_for_fusion, RR, aS) end if bS and cS then table.insert(TR, { uuid = _R, id = aS.unit_id, pts = tonumber(dS) or 1 }) UR = UR + (tonumber(dS) or 1) else VR = VR + 1 end end return TR, UR, VR end function FarmHub.fuseText() if Config.fuseUnit == "" then return "Pick a unit. Fusing limit breaks it (level cap 100 โ†’ 110), and destroys the units it uses." end local eS = Core.session() local fS = eS and eS.collection:get_unit_by_uuid(Config.fuseUnit) if not fS then return "That unit is gone." end local gS = (Brain.fuseInfo and Brain.fuseInfo.uuid == fS.uuid) and tonumber(Brain.fuseInfo.requirement) or 12 local hS if # (Config.fuseFodder or { }) > 0 then local iS, jS, kS = FarmHub.fodderPicked(fS) local lS = { } for mS, nS in ipairs(iS) do table.insert(lS, Units.name(nS.id)) end hS = string.format("Your picks: %d units worth %d of %d points%s.", # iS, jS, gS, kS > 0 and string.format(" (%d of them can't be used any more)", kS) or "") if jS >= gS then hS = hS .. "\nIt would destroy: " .. table.concat(lS, ", ") .. "." end else local oS, pS, qS = FarmHub.fuseFuel(fS, gS) local rS = { } for sS, tS in ipairs(oS) do table.insert(rS, Units.name(tS.id)) end hS = string.format("No picks yet, so the farm chooses: units it may use are worth %d of %d points.", qS, gS) if pS >= gS then hS = hS .. "\nIt would destroy: " .. table.concat(rS, ", ") .. "." else hS = hS .. "\nIt only takes units that are unlocked, off your team, not part of a goal, with no trait, relic or shine, and that the guides don't rate S or better. Tick units below to choose yourself." end end return hS .. (FarmHub.pickNote.autoFuse and ("\n" .. FarmHub.pickNote.autoFuse) or "") end function FarmHub.autoFuse() if not Config.autoFuse or Config.fuseUnit == "" then return end FarmHub.fuseInto(Config.fuseUnit, true) end FarmHub.FUSE_RANK = { Rare = 1, Epic = 2, Legendary = 3 } function FarmHub.xpFodder(uS) local vS = Core.session() local wS = FarmHub.FUSE_RANK [Config.fuseXpRarity] or 2 local xS = { [uS.uuid] = true, [Config.evolveUnit] = true, [Config.fuseUnit] = true } for yS, zS in ipairs(Units.equipped()) do xS [zS.uuid] = true end for AS, BS in ipairs(Brain.savedTeam or { }) do xS [BS] = true end for CS, DS in ipairs(Brain.train and Brain.train.on and Brain.train.on.base or { }) do xS [DS] = true end local ES, FS = { }, 0 for HS, IS in pairs(vS.profile_data.collection.owned_units) do local JS = Core.Units [IS.unit_id] local KS = JS and FarmHub.FUSE_RANK [JS.rarity] if KS and KS <= wS and not xS [HS] then local LS = vS.collection:get_unit_by_uuid(HS) or IS if not (IS._locked or LS._locked) and not (IS._equipped_relic or LS._equipped_relic) then local MS, NS = pcall(Core.USC.calculate_xp_gain_from_fuse, uS, { LS }, vS) NS = MS and tonumber(NS) or 0 if NS > 0 then table.insert(ES, { uuid = HS, id = IS.unit_id, xp = NS, rarity = JS.rarity }) FS = FS + NS end end end end table.sort(ES, function(OS, PS) if OS.xp ~= PS.xp then return OS.xp < PS.xp end return OS.uuid < PS.uuid end) local QS = math.max(0, Strategy.xpFor(uS.unit_id, Strategy.cap(uS)) - (tonumber(uS.xp) or 0)) local RS, TS = { }, 0 for US, VS in ipairs(ES) do if TS >= QS then break end table.insert(RS, VS) TS = TS + VS.xp end return RS, TS, QS, FS end function FarmHub.fuseXpTarget(WS) local XS = WS and WS.collection:get_unit_by_uuid(Config.fuseXpUnit) if XS then if Config.fuseXpId ~= XS.unit_id then Config.fuseXpId = XS.unit_id App.saveSoon() end return XS end local YS = WS and Core.Units [Config.fuseXpId] local ZS = YS and YS.evolve and YS.evolve.evolve_unit local _S, aT for bT, cT in pairs(ZS and WS.profile_data.collection.owned_units or { }) do if cT.unit_id == ZS and (not _S or (tonumber(cT.xp) or 0) > aT) then _S, aT = bT, tonumber(cT.xp) or 0 end end if not _S then return nil end App.log(1, "LOBBY", string.format("Fuse For XP: %s evolved, it now feeds %s", Units.name(Config.fuseXpId), Units.name(ZS))) Config.fuseXpUnit, Config.fuseXpId = _S, ZS App.saveSoon() return WS.collection:get_unit_by_uuid(_S) end function FarmHub.fuseXpText() if Config.fuseXpUnit == "" then return "Pick a unit. It gets the XP of the spare units fused into it, and those units are destroyed." end local dT = Core.session() local eT = FarmHub.fuseXpTarget(dT) if not eT then return "That unit is gone, so nothing is fused. Pick another: your switch stays as you set it." end local fT, gT, hT = FarmHub.xpFodder(eT) local iT = { } for jT, kT in ipairs(fT) do iT [kT.rarity] = (iT [kT.rarity] or 0) + 1 end local lT = { } for mT, nT in ipairs({ "Rare", "Epic", "Legendary" }) do if iT [nT] then table.insert(lT, iT [nT] .. " " .. nT) end end local oT, pT = Strategy.level(eT), Strategy.cap(eT) local qT if hT <= 0 then qT = string.format("%s: level %d/%d, at its level cap.", Units.name(eT.unit_id), oT, pT) elseif # fT == 0 then qT = string.format("%s: level %d/%d. No spare units to fuse yet.", Units.name(eT.unit_id), oT, pT) else qT = string.format("%s: level %d/%d. It would destroy %s, for %s XP (level %d).", Units.name(eT.unit_id), oT, pT, table.concat(lT, ", "), Utils.fmt(gT), Strategy.level({ unit_id = eT.unit_id, xp = (tonumber(eT.xp) or 0) + math.min(gT, hT) })) end qT = qT .. "\nEvery unit up to " .. tostring(Config.fuseXpRarity) .. " that isn't locked, shiny and traited ones too, except your team and picks and units wearing a relic. Lock a unit in the game to keep it out." return qT .. (FarmHub.pickNote.autoFuseXp and ("\n" .. FarmHub.pickNote.autoFuseXp) or "") end function FarmHub.autoFuseXp() if not Config.autoFuseXp or Config.fuseXpUnit == "" then return end local rT = Core.session() local sT = FarmHub.fuseXpTarget(rT) if not sT then return end local tT = Units.name(sT.unit_id) local uT, vT, wT = FarmHub.xpFodder(sT) if wT <= 0 then FarmHub.pickNote.autoFuseXp = nil return end if # uT == 0 then FarmHub.pickNote.autoFuseXp = "Waiting: no spare units to fuse." return end if os.time() - (tonumber(Config.fuseXpOnAt) or 0) < 30 then FarmHub.pickNote.autoFuseXp = "Fusing in a moment: switch it off to cancel." return end FarmHub.pickNote.autoFuseXp = nil if App.req.back.fuseXp and Utils.now() < App.req.back.fuseXp then return end FarmHub.fuseBatch = FarmHub.fuseBatch or 50 local xT, yT, zT = 0, 0, { } while App.alive and xT < 10 do if xT > 0 then local AT, BT, CT = FarmHub.xpFodder(rT.collection:get_unit_by_uuid(Config.fuseXpUnit) or sT) uT, wT = { }, CT for ET, FT in ipairs(AT) do if not zT [FT.uuid] then table.insert(uT, FT) end end if wT <= 0 or # uT == 0 then break end end local GT = { } for HT = 1, math.min(FarmHub.fuseBatch, # uT) do table.insert(GT, uT [HT].uuid) end local IT = tonumber((rT.collection:get_unit_by_uuid(Config.fuseXpUnit) or sT).xp) or 0 local JT = Utils.now() local KT, LT = App.request("fuseXp", "feed_units", Config.fuseXpUnit, GT) if not KT and (LT == "slowing down" or LT == "waiting") then if LT == "waiting" or yT >= 3 then break end yT = yT + 1 task.wait(3.5) continue end while Utils.now() - JT < 5 and (tonumber((rT.collection:get_unit_by_uuid(Config.fuseXpUnit) or sT).xp) or 0) <= IT do task.wait(0.1) end local NT = tonumber((rT.collection:get_unit_by_uuid(Config.fuseXpUnit) or sT).xp) or 0 if NT > IT then xT = xT + 1 for OT, PT in ipairs(GT) do zT [PT] = true end Spending.add(string.format("%d units fused into %s", # GT, tT), # GT, "units", string.format("Fuse For XP: your pick (Units โ†’ Fusion): +%s XP", Utils.fmt(NT - IT))) App.log(1, "LOBBY", string.format("Fuse For XP: %d units into %s, +%s XP, level %d", # GT, tT, Utils.fmt(NT - IT), Strategy.level(rT.collection:get_unit_by_uuid(Config.fuseXpUnit) or sT))) elseif # GT > 20 then FarmHub.fuseBatch = 20 App.log(1, "LOBBY", string.format("Fuse For XP: the game didn't take %d at once (%s): 20 a batch", # GT, tostring(App.gameSaid(JT) or (KT and LT) or "no answer"))) else local QT = App.gameSaid(JT) FarmHub.pickNote.autoFuseXp = "The game said no (" .. tostring(QT or (KT and LT) or "no answer") .. "): trying again in 30 min." App.log(1, "LOBBY", "Fuse For XP refused: " .. tostring(QT or (KT and LT) or "no answer")) App.backoff("fuseXp", 1800) break end end end function FarmHub.fuseInto(RT, TT) local UT = Core.session() local VT = UT and UT.collection:get_unit_by_uuid(RT) local function WT(XT, YT) if TT then FarmHub.pickDone("autoFuse", "Auto Fuse", XT, YT) else Brain.str.fuseNote = XT App.log(1, Strategy.LOG, "limit break: " .. XT) end end local function ZT(_T) if TT then FarmHub.pickNote.autoFuse = _T else Brain.str.fuseNote = _T end end if not VT then return WT("that unit is gone", "warning") end local aU = Units.name(VT.unit_id) local function bU(cU) return cU.further_modifiers and cU.further_modifiers.limit_break and tonumber(cU.further_modifiers.limit_break.current_limit_break) or 0 end if bU(VT) >= (tonumber(Core.USC.get_max_limit_break(VT)) or 1) then return WT(aU .. " is already limit broken") end local dU, eU = App.request("fuseInfo", "request_fusion_information", RT) if not (dU and type(eU) == "table") then return end Brain.fuseInfo = { requirement = eU.requirement, divine_wish = eU.divine_wish, allowed = eU.allowed, uuid = RT } if not eU.allowed then if not TT then Brain.str.fuseNo = Brain.str.fuseNo or { } Brain.str.fuseNo [RT] = os.time() FarmHub.last.teamPlan = nil end return WT("the game says " .. aU .. " can't be limit broken", "warning") end ZT(nil) if Core.USC.calculate_xp(VT.unit_id, Core.USC.get_max_level(VT)) > (tonumber(VT.xp) or 0) then return ZT("Waiting: " .. aU .. " has to reach its level cap first.") end local fU = tonumber(eU.divine_wish) or 1 if Adventure.have("divine_wish") < fU then return ZT("Waiting: it needs a Divine Wish.") end local gU = tonumber(eU.requirement) or 12 local hU = TT and # (Config.fuseFodder or { }) > 0 local iU, jU if hU then local kU kU, jU = FarmHub.fodderPicked(VT) if jU < gU then return ZT(string.format("Waiting: the units you ticked are worth %d of %d points. Tick more on the Units โ†’ Fusion.", math.floor(jU), math.floor(gU))) end iU, jU = { }, 0 for lU, mU in ipairs(kU) do if jU >= gU then break end table.insert(iU, mU) jU = jU + mU.pts end else iU, jU = FarmHub.fuseFuel(VT, gU) if jU < gU then return ZT(string.format("Waiting: spare units it may use are worth %d of %d points.", math.floor(jU), math.floor(gU))) end end local nU, oU = { }, { } for pU, qU in ipairs(iU) do table.insert(nU, qU.uuid) table.insert(oU, Units.name(qU.id)) end local rU = RT .. "|" .. table.concat(nU, ",") if Config.fuseArmSig ~= rU then Config.fuseArmSig, Config.fuseArmAt = rU, os.time() App.saveSoon() App.log(1, TT and "LOBBY" or Strategy.LOG, string.format("%s: %s will use %s (switch it off to cancel)", TT and "Auto Fuse" or "Limit break", aU, table.concat(oU, ", "))) end if os.time() - Config.fuseArmAt < 30 then return ZT("Fusing " .. aU .. " in a moment: switch it off to cancel. It uses " .. table.concat(oU, ", ") .. ".") end local sU = bU(VT) local tU, uU = App.request("fuse", "fusion_units", RT, nU) local vU = Utils.now() while Utils.now() - vU < 5 and bU(UT.collection:get_unit_by_uuid(RT) or VT) <= sU do task.wait(0.25) end Config.fuseArmSig, Config.fuseArmAt = "", 0 if bU(UT.collection:get_unit_by_uuid(RT) or VT) > sU then Spending.add("limit broke " .. aU, # nU, "units", (TT and ("Auto Limit Break: your pick (Units โ†’ Fusion)" .. (hU and ", your fodder" or "")) or "Units โ†’ Upgrades") .. " ยท used " .. table.concat(oU, ", ") .. " and a Divine Wish") if hU then local wU = { } for xU, yU in ipairs(Config.fuseFodder or { }) do if not table.find(nU, yU) then table.insert(wU, yU) end end Config.fuseFodder = wU App.saveSoon() end WT(aU .. " is limit broken!") Modes._sigAt = nil else WT("the game said no to limit breaking " .. aU .. " (" .. tostring(tU and uU or "no answer") .. ")", "error") end end FarmHub.GRADES = { "C-", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "S-", "S", "S+", "SS", "SSS" } FarmHub.STATS = { { id = "potency_stat", name = "Attack" }, { id = "speed_stat", name = "Speed" }, { id = "range_stat", name = "Range" } } function FarmHub.statRank(zU, AU) local BU = Core.core("TraitServiceCore").calculate_stat_rank(zU.unit_id, zU.trait_stats, AU, zU.trait_stats [AU]) return BU, table.find(FarmHub.GRADES, BU) or 0 end function FarmHub.statRanks(CU) local DU = { } for EU, FU in ipairs(FarmHub.STATS) do table.insert(DU, FU.name .. " " .. (FarmHub.statRank(CU, FU.id))) end return table.concat(DU, " ยท ") end FarmHub.BANNER_NAME = { EventClover = "Newest Units", Legacy = "Legacy Units", April2025 = "April (limited)", SakamotoEvent = "Assassin (limited)", Summer2026 = "Summer (Sunny Stars)" } function FarmHub.bannerCost(GU) local HU = Core.Banners [GU] if not HU then return nil end if HU.item_cost then local IU = HU.item_cost.item_id return { cost = tonumber(HU.item_cost.amount) or 10, have = function() return Adventure.have(IU) end, name = (Core.Items [IU] or { }).name or IU, floor = "tokenFloor" } end local JU = HU.resource_cost if JU and JU.id == "SummerPearls" then return { cost = tonumber(JU.amount) or 150, have = function() return Core.cur("SummerPearls") end, name = "Summer Pearls", floor = "pearlFloor" } end if JU and JU.id then return { cost = tonumber(JU.amount) or 50, have = function() return Core.cur(JU.id) end, name = JU.id == "GemsLegacy" and "Legacy Gems" or JU.id, floor = JU.id == "AprilCoins" and "aprilFloor" or "tokenFloor" } end return { cost = tonumber(HU.gem_cost) or 50, have = function() return Core.cur("gems") end, name = "gems", floor = "gemFloor" } end function FarmHub.quietFx(KU) local MU = Config.skipSummonFx and not KU local NU = Core.svc("SummonFX") if NU and type(NU.open_star_new) == "function" then if rawget(NU, "__aaOrig") == nil then rawset(NU, "__aaOrig", NU.open_star_new) end NU.open_star_new = MU and function(OU, PU, QU, RU, SU) if type(SU) == "function" then task.defer(SU) end end or rawget(NU, "__aaOrig") end local TU = Core.GUIS and Core.GUIS.drop_obtained_ui if type(TU) == "table" then for UU, VU in ipairs({ "show_message", "show_message_custom" }) do rawset(TU, VU, MU and function() end or nil) end end end function FarmHub.summonText() local WU = Config.summonBanner ~= "" and FarmHub.bannerCost(Config.summonBanner) if not WU then return "Pick a banner. It stops at the line below, or when your unit storage is full." end return string.format("You have %s %s. 10 summons cost %s, and it keeps %s.%s", Utils.fmt(WU.have()), WU.name, Utils.fmt(WU.cost * 10), Utils.fmt(Config [WU.floor]), Status.Summon and ("\n" .. tostring(Status.Summon)) or "") end function FarmHub.smartEvolve() if not Config.smartEvolve then return App.status("Evolve", "smart evolve is off (Units โ†’ Goals)") end local XU = Core.session() if not XU then return end local YU = { } for ZU, _U in ipairs(Brain.goals or { }) do table.insert(YU, _U) end table.sort(YU, function(aV, bV) return (aV.gain or 0) > (bV.gain or 0) end) local cV = { } for dV, eV in ipairs(Units.equipped()) do cV [eV.uuid] = true end local fV, gV, hV = { }, 0, { } for iV, jV in ipairs(YU) do if jV.pinned then for kV, lV in ipairs(jV.items or { }) do fV [lV.id] = (fV [lV.id] or 0) + lV.amount end end end for mV, nV in ipairs(YU) do local oV, pV if nV.ready and not nV.pinned and gV < 2 and (FarmHub.evoBlocked [nV.uuid] or 0) < os.time() then if nV.hidden and (nV.tierRank or 0) < Adventure.TIER_RANK.S then oV = "its strength is unknown (the game hides its upgrades until it's in a match), so this one is your call" end for qV, rV in ipairs(oV and { } or nV.items or { }) do if Adventure.have(rV.id) - rV.amount < (fV [rV.id] or 0) then oV = string.format("keeps %s for a bigger evolution", (Core.Items [rV.id] or { }).name or rV.id) break end end oV = oV or FarmHub.evoEats(nV, cV) if not oV then local sV, tV = FarmHub.evolve(nV, "smart evolve: ready, stronger at full upgrade, uses only spare items" .. (# (nV.eats or { }) > 0 and " and spare copies" or "")) if sV then gV, pV = gV + 1, true table.insert(hV, "evolved " .. Units.name(nV.from) .. " โ†’ " .. Units.name(nV.to)) else FarmHub.evoBlocked [nV.uuid] = os.time() + 3600 oV = tV end end if oV then table.insert(hV, Units.name(nV.from) .. ": " .. oV) end end if not pV then for uV, vV in ipairs(nV.items or { }) do fV [vV.id] = (fV [vV.id] or 0) + vV.amount end end end App.status("Evolve", # hV > 0 and table.concat(hV, " ยท ") or "nothing ready to evolve") end Training.BONUS = 1.5 Training.SLOW = 1.4 function Training.boost(wV, xV, yV, zV) if xV < 2 or yV >= math.min(Config.dangerReach, (zV or 1) * 0.9) then return end local AV = yV < math.min(Config.safeReach, (zV or 1) * 0.75) local BV = 0 for CV, DV in ipairs(wV) do if not DV.farm and not DV.train and DV.cost > 0 then BV = math.max(BV, DV.gain / DV.cost) end end for EV, FV in ipairs(wV) do if FV.train == "first" or (FV.train == "more" and AV) then FV.gain = math.max(FV.gain, BV * FV.cost * 1.05) end end end Training.HOLD_REACH = 0.3 Training.ZONE = 0.25 function Training.hold(GV, HV, IV) if HV < 2 or GV >= Training.HOLD_REACH + (IV and 0.1 or 0) then return false end return true, string.format("holding your other units back so enemies reach the trainee (they die at %d%% of the road)", math.floor(GV * 100 + 0.5)) end function Training.same(JV, KV) if # JV ~= # KV then return false end local LV = { } for MV, NV in ipairs(JV) do LV [NV] = true end for OV, PV in ipairs(KV) do if not LV [PV] then return false end end return true end function Training.team() local QV = { } for RV, SV in ipairs(Units.equipped()) do table.insert(QV, SV.uuid) end return QV end function Training.target() local TV = Core.session() if not TV then return nil end local UV, VV local WV = Config.autoEvolve and Config.evolveUnit ~= "" and Config.evolveUnit or nil for XV, YV in ipairs(Brain.goals or { }) do local ZV, _V = nil, 0 for aW, bW in ipairs(YV.plans or { }) do if bW.takedown then ZV = bW else _V = _V + 1 end end local cW = ZV and YV.intoTeam and (YV.pinned or not YV.unknown) and (not WV or YV.uuid == WV) and TV.collection:get_unit_by_uuid(YV.uuid) if cW then local dW = tonumber(ZV.what == "kills" and cW.total_kills or cW.total_takedowns) or 0 if dW < ZV.need then local eW = (_V == 0 and 1000 or 0) + (tonumber(YV.tierRank) or 0) * 10 - XV * 0.01 if not UV or eW > VV then UV, VV = { uuid = YV.uuid, id = YV.from, to = YV.to, what = ZV.what, have = dW, need = ZV.need }, eW end end end end return UV end function Training.realHave(fW) local gW = Core.session() local hW = gW and gW.collection:get_unit_by_uuid(fW.uuid) local iW = hW and tonumber(fW.what == "kills" and hW.total_kills or hW.total_takedowns) or 0 return math.max(iW, fW.have + (fW.got or 0)) end function Training.now() if Training._at and Utils.now() - Training._at < 10 then return Training._t, Training._base end local jW = (Brain.train.on and Brain.train.on.base) or Training.team() local kW = table.clone(jW) table.sort(kW) Training._t, Training._base, Training._at = Training.target(), table.concat(kW, ","), Utils.now() return Training._t, Training._base end function Training.valueFor(lW) if not (Config.trainTakedowns and Config.goals) then return 0, 1 end local mW = Brain.trainSim [lW.key] if not (mW and mW.ok and os.time() - (mW.at or 0) < 6 * 3600) then return 0, 1 end local nW, oW = Training.now() local pW = { } for qW, rW in ipairs(Brain.savedTeam or { }) do pW [qW] = rW end table.sort(pW) if not (nW and mW.uuid == nW.uuid and (mW.base == oW or mW.base == table.concat(pW, ","))) or not Training.eligible(lW) then return 0, 1 end local sW = Training.perRun(lW.key, nW.id) or 0 local tW = math.max(1, nW.need - nW.have) return Config.goalValue * math.min(sW, tW) / tW, mW.slow or 1 end function Training.scan() local uW = Config.trainTakedowns and Config.sim and Training.now() if not uW then return end local vW = (Brain.train.on and Brain.train.on.base) or Training.team() for wW, xW in ipairs(vW) do if xW == uW.uuid then return end end local yW, zW = 0, 0 for AW, BW in ipairs(Modes.lastScored or { }) do if yW >= 3 or zW >= 2 then break end if Training.eligible(BW) then local CW = Brain.trainSim [BW.key] local DW = CW and CW.uuid == uW.uuid and os.time() - (CW.at or 0) < 6 * 3600 local EW = Training.plan(BW, uW, vW) if not DW then zW = zW + 1 end if EW.ok then yW = yW + 1 end end end local FW = Training.bestInfinite() if FW then Training.plan(FW, uW, vW) end end function Training.bestInfinite() local GW, HW for IW, JW in ipairs(Modes.lastScored or { }) do if JW.kind == "infinite" and Training.eligible(JW) then local KW, LW = Brain.train.real [JW.key], JW.rec or Modes.rec(JW.key) local MW = (tonumber(LW.timed) or 0) > 0 and LW.sec / LW.timed / 60 or 0 local NW = (KW and KW.n > 0 and MW > 0) and (KW.sum / KW.n) / MW or 0 if NW <= 0 then NW = Reports.hiveTrainRate(JW.key) or 0 end if NW > (HW or 0) then GW, HW = JW, NW end end end return GW end function Training.ratio() local OW = Brain.train.cal if not (OW and (OW.n or 0) > 0 and (OW.sim or 0) > 0) then return 1 end return Utils.clamp(OW.real / OW.sim, 0.05, 2) end function Training.perRun(PW, QW) local RW = Brain.train.real [PW] if RW and RW.n > 0 then return RW.sum / RW.n, true end local SW = Reports.hiveTrain(PW, QW) if SW then return SW, true end local TW, UW = 0, 0 local VW = Modes.isInf(PW) for WW, XW in pairs(Brain.train.real) do if Modes.isInf(WW) == VW then TW, UW = TW + XW.sum, UW + XW.n end end if UW > 0 then return TW / UW, true end local YW = Brain.trainSim [PW] return YW and YW.tk and YW.tk * Training.ratio() or nil, false end function Training.eligible(ZW) if ZW.kind == "daily" or ZW.kind == "challenge" then return false, "a limited reward, played with your own team" end if ZW.kind == "dungeon" then return false, "a lost dungeon room costs a life: played with your own team" end if Core.Levels [ZW.level] and Core.Levels [ZW.level]._randomequippedmode then return false, "this mode deals you a random team" end if (Brain.train.bad [ZW.key] or 0) > os.time() then return false, "a training run lost there, so not before " .. os.date("%H:%M", Brain.train.bad [ZW.key]) end local _W = ZW.rec or Modes.rec(ZW.key) if _W.runs >= 3 and _W.wins / _W.runs < 0.8 then return false, "your farm wins it less than 80% of the time" end local aX = Brain.simRes [ZW.key] if aX and aX.trust and not aX.trust.ok then return false, "the simulator doesn't match real runs there yet" end return true end function Training.plan(bX, cX, dX) local eX = Core.session() local fX = table.clone(dX) table.sort(fX) local gX = table.concat(fX, ",") local hX = (tonumber((Brain.simCal [bX.key] or { }).n) or 0) > 0 local iX = hX and 1.3 or 2 local jX = gX .. "+" .. cX.uuid .. (hX and "#c" or "#n") .. "#r4" local kX = Brain.trainSim [bX.key] if kX and kX.sig == jX and not kX.pending and os.time() - (kX.at or 0) < 6 * 3600 then return kX end local lX = { sig = jX, base = gX, uuid = cX.uuid, at = os.time(), ok = false, margin = iX, pending = true } Brain.trainSim [bX.key] = lX local mX, nX = Simulator.levelFor(bX) if bX.ldOverride then mX = bX.ldOverride end if not mX then lX.why, lX.pending = "the simulator can't play this mode", nil return lX end local oX = Simulator.info(mX, bX.key, nX, bX.mod) local pX = { } for qX, rX in ipairs(dX) do local sX = eX.collection:get_unit_by_uuid(rX) if sX then table.insert(pX, sX) end end local tX = eX.collection:get_unit_by_uuid(cX.uuid) if not tX or # pX == 0 then lX.why, lX.pending = "the unit wasn't found", nil return lX end local uX = Simulator.run(oX, Simulator.teamFrom(pX, oX.map), { hpMul = 1 }) local vX = oX.infinite and Simulator.unbound(mX) local wX = vX and Modes.infTier(Simulator.run(oX, Simulator.teamFrom(pX, oX.map), { hpMul = iX }).wave) or nil local xX = { } for yX, zX in ipairs(pX) do if not Units.isFarm(zX.unit_id) then local AX, BX = pcall(Units.score, zX, bX.mod) table.insert(xX, { i = yX, sc = AX and BX or 0 }) end end table.sort(xX, function(CX, DX) return CX.sc < DX.sc end) local EX for FX = 1, math.min(3, # xX) do local GX = table.clone(pX) GX [xX [FX].i] = tX local HX = Simulator.teamFrom(GX, oX.map) local IX = Simulator.run(oX, HX, { hpMul = iX, train = cX.uuid }) if (vX and Modes.infTier(IX.wave) >= wX) or (not vX and IX.won) then local JX = Simulator.run(oX, HX, { hpMul = 1, train = cX.uuid }) local KX = (JX.tk or 0) / math.max(0.5, JX.t / 60) if (JX.won or vX) and (not EX or KX > math.max(EX.rate * 1.5, EX.rate + 1)) then EX = { out = pX [xX [FX].i].unit_id, r = JX, rate = KX, trial = GX } end end end lX.ownMin = uX.t / 60 if not EX then lX.why = vX and string.format("every swap loses a daily milestone (wave %d) against %s enemies in the simulator", wX, hX and "30% tougher" or "twice as tough") or hX and "no swap still wins with 30% tougher enemies in the simulator" or "no swap still wins against twice as tough enemies in the simulator (no real run there yet)" else lX.out, lX.tk, lX.minutes = EX.out, EX.r.tk or 0, EX.r.t / 60 lX.uuids = { } for LX, MX in ipairs(EX.trial) do table.insert(lX.uuids, MX.uuid) end lX.slow = (uX.won and not vX) and lX.minutes / math.max(0.1, lX.ownMin) or 1 if lX.tk <= 0 and next(Brain.train.real) == nil then lX.why = "the simulator says it would hit nothing there" elseif lX.slow > Training.SLOW then lX.why = string.format("the clear would be %d%% slower", math.floor((lX.slow - 1) * 100 + 0.5)) else lX.ok = true end end lX.pending = nil App.log(1, "TRAIN", string.format("%s: %s%s โ†’ %s", bX.label, Units.name(cX.id), lX.out and (" in for " .. Units.name(lX.out)) or "", lX.ok and string.format("trains here (โ‰ˆ%d %s a run, %.1f min vs %.1f with your team)", math.floor((Training.perRun(bX.key, cX.id) or 0) + 0.5), cX.what, lX.minutes, lX.ownMin) or tostring(lX.why))) App.saveSoon() return lX end function Training.home() local NX = Brain.train.on if not NX then return end Brain.train.on = nil App.saveSoon() if not Training.same(Training.team(), NX.uuids or { }) then App.log(1, "TRAIN", "your team was changed outside the farm, so it stays as it is") if not Config.autoTeam then Brain.savedTeam = nil end return end if Brain.savedTeam and Training.same(Brain.savedTeam, NX.base) then FarmHub.restoreTeam() else FarmHub.equipTeam(NX.base, "back from takedown training") end App.log(1, "TRAIN", Units.name(NX.out or "?") .. " is back in your team") end function Training.apply(OX) local PX = Brain.testTeam if PX and (PX.left or 0) > 0 and OX and PX.key == OX.key then Training.home() return App.status("Training", "paused for a team test") end local QX = Config.trainTakedowns and Training.target() or nil if not QX then Training.home() return App.status("Training", Config.trainTakedowns and "no evolution is waiting on takedowns" or "off (Units โ†’ Goals)") end local RX = Units.name(QX.id) local SX = string.format("%s %s/%s", QX.what, Utils.fmt(QX.have), Utils.fmt(QX.need)) local TX = Brain.train.on local UX = TX and TX.base or Training.team() for VX, WX in ipairs(UX) do if WX == QX.uuid then return App.status("Training", string.format("%s is on your team (%s)", RX, SX)) end end local XX, YX = Training.eligible(OX) local ZX if XX then App.status("Training", string.format("checking %s in %s with the simulatorโ€ฆ", RX, OX.label)) ZX = Training.plan(OX, QX, UX) XX, YX = ZX.ok, ZX.why end if not XX then Training.home() return App.status("Training", string.format("%s ยท %s ยท not in %s: %s", RX, SX, OX.label, tostring(YX))) end if not (TX and Training.same(Training.team(), ZX.uuids)) then FarmHub.equipTeam(ZX.uuids, string.format("takedown training: %s in for %s (%s)", RX, Units.name(ZX.out), OX.label)) local _X = Utils.now() while Utils.now() - _X < 4 and not Training.same(Training.team(), ZX.uuids) do task.wait(0.25) end if not Training.same(Training.team(), ZX.uuids) then if Brain.savedTeam and Training.same(Brain.savedTeam, UX) then FarmHub.restoreTeam() else FarmHub.equipTeam(UX, "takedown training swap didn't work") end Brain.train.bad [OX.key] = os.time() + 3600 App.log(1, "TRAIN", "the game didn't take the training team for " .. OX.label .. ": your own team is back") return App.status("Training", RX .. ": the swap didn't work, trying again in an hour") end Brain.train.on = { uuid = QX.uuid, id = QX.id, to = QX.to, what = QX.what, have = QX.have, need = QX.need, got = 0, base = UX, uuids = ZX.uuids, out = ZX.out, key = OX.key, at = os.time() } App.log(1, "TRAIN", string.format("%s in for %s for %s (%s)", RX, Units.name(ZX.out), OX.label, SX)) end Brain.train.on.key = OX.key local aY, bY = Training.perRun(OX.key, QX.id) App.status("Training", string.format("%s in for %s in %s ยท %s ยท โ‰ˆ%d a run%s", RX, Units.name(ZX.out), OX.label, SX, math.floor((aY or 0) + 0.5), bY and "" or " (simulated)")) end function Training.text() if not Config.trainTakedowns then return "Off. A unit whose evolution needs takedowns only earns them while you keep it on your team." end local cY = Training.target() if not cY then return "No evolution that would join your team is waiting on takedowns." end local dY, eY = 0, 0 for fY, gY in pairs(Brain.train.real) do dY = dY + gY.sum eY = eY + gY.n end local hY = eY > 0 and dY / eY or nil local iY = { } for jY, kY in ipairs(Brain.train.log) do if # iY < 5 then table.insert(iY, string.format("+%d (%s, %s)", kY.got, GUI.keyName and GUI.keyName(kY.key) or kY.key, kY.won and "won" or "lost")) end end return string.format("%s needs %s %s for %s and has %s.%s%s\n" .. "It joins your team only where the simulator says you still win with 30%% tougher enemies (twice as tough where the farm has no real run yet), in place of your weakest unit that passes that test (a stronger one of your 3 weakest only if it trains 1.5ร— faster). " .. "Never in the challenges, where your farm wins under 80%% of runs, or where clearing gets more than 40%% slower. " .. "Its progress counts in the mode ranking (Goal Value, spread over the takedowns it still needs). " .. "Your own team goes back after every run, and a lost run stops training in that mode for 6 hours.", Units.name(cY.id), Utils.fmt(cY.need), cY.what, Units.name(cY.to), Utils.fmt(cY.have), hY and string.format(" It gets about %d a run, so โ‰ˆ%d more runs.", math.floor(hY + 0.5), math.ceil((cY.need - cY.have) / math.max(1, hY))) or "", # iY > 0 and (" Last runs: " .. table.concat(iY, " ยท ") .. ".") or "") end function FarmHub.every(lY, mY) local nY = Utils.now() if FarmHub.last [lY] and nY - FarmHub.last [lY] < mY then return false end FarmHub.last [lY] = nY return true end function FarmHub.refreshLive() local oY = Brain.live local pY, qY = App.request("live_n", "get_normal_challenge") if pY and type(qY) == "table" and qY.current_uuid then local rY = (oY.normal and oY.normal.uuid == qY.current_uuid) and oY.normal.lost or 0 local sY = (oY.normal and oY.normal.uuid == qY.current_uuid) and oY.normal.doneUuid or nil local tY = Core.GS.CHALLENGE_DURATION or 1800 local uY = (math.floor(os.time() / tY) + 1) * tY oY.normal = { uuid = qY.current_uuid, level = qY.current_level_id, challenge = qY.current_challenge, value = Modes.rewardValue(qY.current_reward), expires = uY, lost = rY, doneUuid = sY } end local vY, wY = App.request("live_d", "get_daily_challenge") if vY and type(wY) == "table" and wY.current_uuid then local xY = type(wY.current_level) == "table" and wY.current_level or { } local yY = Modes.dailyMod(xY._challenge_uuid or wY.current_uuid, xY._daily_challenge_weak_against or wY.weak_against, xY._daily_challenge_strong_against or wY.strong_against) local zY = (oY.daily and oY.daily.uuid == wY.current_uuid) and oY.daily.lost or 0 local AY = (oY.daily and oY.daily.uuid == wY.current_uuid) and oY.daily.doneUuid or nil local BY = type(wY.current_level) == "table" and Simulator.slim(wY.current_level) or nil if BY then BY._challenge = wY.current_challenge end oY.daily = { uuid = wY.current_uuid, value = Modes.rewardValue(wY.current_reward), challenge = wY.current_challenge, mod = yY, lost = zY, doneUuid = AY, ld = BY } end local CY, DY = App.request("live_e", "get_event_challenges") if CY and type(DY) == "table" then oY.event = nil for EY, FY in pairs(DY) do local GY = type(FY) == "table" and FY.current_level_data if type(GY) == "string" then GY = Core.Levels [GY] end if GY then local HY = Simulator.slim(GY) HY._difficulty = HY._difficulty or "Hard" oY.event = { level = GY.id or EY, name = GY.name, pad = Core.GS.ACTIVE_EVENT_CHALLENGES and Core.GS.ACTIVE_EVENT_CHALLENGES [EY], board = EY, map = GY.map, weather = FY.current_weather, ld = HY, forecast = type(FY.forecast) == "table" and { map = FY.forecast.map, weather = FY.forecast.weather, map_hint = FY.forecast.map_hint, weather_hint = FY.forecast.weather_hint } or nil } end end end local IY, JY, KY = App.request("live_a", "get_current_artifacts") if IY and type(JY) == "table" and type(JY.world_ids) == "table" then local LY = { } for MY, NY in ipairs(JY.world_ids) do local OY = Core.Worlds [NY] local PY = OY and type(OY.levels) == "table" and (OY.levels ["1"] or OY.levels [1]) if PY and Core.Levels [PY.id] then table.insert(LY, { level = PY.id, world = NY }) end end oY.artifact = { list = LY, interval = JY.interval, ends = tonumber(JY.ends_at), ad = type(KY) == "table" and KY or (oY.artifact and oY.artifact.interval == JY.interval and oY.artifact.ad) or nil } end oY.at = os.time() App.saveSoon() end function FarmHub.rewards() if not Config.rewards then return App.status("Rewards", "off") end local QY = Core.session() local RY = Core.prof() if not (QY and RY) then return end local SY = { } if (os.time() - (tonumber(RY.last_daily_reward_claimed_time) or 0)) >= (Core.GS.DAILY_REWARD_WAIT or 82800) then local TY, UY = App.request("daily", "claim_daily_reward") if TY and UY then table.insert(SY, "daily login") end end for VY, WY in pairs(QY.quest_handler.quest_profile_data.quests or { }) do local XY, YY = pcall(Core.QCC.is_quest_complete, QY, WY) if XY and YY and not Brain.claims [VY] then local ZY, _Y = App.request("quest", "redeem_quest", VY) if ZY and _Y then Brain.claims [VY] = os.time() table.insert(SY, "quest " .. tostring(WY.quest_info and (WY.quest_info.name or WY.quest_info.id) or "")) else Brain.claims [VY] = os.time() - 259200 + 3600 end task.wait(0.3) end end if FarmHub.every("bp", 600) then App.request("bp", "claim_battlepass_rewards") end if FarmHub.every("bingo", 120) then pcall(Hunt.claim) end if FarmHub.every("clr", 300) then for aZ, bZ in pairs(RY.level_data.completed_story_levels or { }) do local cZ = Core.Levels [aZ] if cZ and cZ.clear_rewards and bZ.difficulty_completed_data then for dZ, eZ in pairs(cZ.clear_rewards) do local fZ = bZ.difficulty_completed_data [dZ] and bZ.difficulty_completed_data [dZ].times_completed or 0 for gZ, hZ in pairs(eZ) do local iZ = RY.clear_rewards [aZ] and RY.clear_rewards [aZ] [dZ] and RY.clear_rewards [aZ] [dZ] [hZ._id] local jZ = aZ .. dZ .. tostring(hZ._id) if not iZ and fZ >= (hZ.clears or 1e9) and not Brain.claims [jZ] then local kZ, lZ = App.request("clr", "claim_first_clear_rewards", aZ, dZ, hZ._id) Brain.claims [jZ] = os.time() if kZ and lZ then table.insert(SY, "milestone " .. Modes.levelName(aZ)) end end end end end end end if FarmHub.every("clrA", 300) then for mZ, nZ in ipairs(Brain.live.artifact and Brain.live.artifact.list or { }) do local oZ = Core.Levels [nZ.level] local pZ, qZ, rZ, sZ = Modes.artifactLeft(nZ.level) for tZ, uZ in pairs(oZ and oZ.clear_rewards and oZ.clear_rewards.Hard or { }) do local vZ = "ar" .. nZ.level .. tostring(Brain.live.artifact.interval) .. tostring(uZ._id) if not sZ [uZ._id] and pZ >= (tonumber(uZ.clears) or 1e9) and not Brain.claims [vZ] then local wZ, xZ = App.request("clr", "claim_first_clear_rewards", nZ.level, "Hard", uZ._id) Brain.claims [vZ] = os.time() if wZ and xZ then table.insert(SY, "Artifact Stage " .. tostring(uZ._reward_text or uZ._id)) end end end end end for yZ, zZ in pairs(Brain.claims) do if os.time() - zZ > 259200 then Brain.claims [yZ] = nil end end if # SY > 0 then App.log(1, "REWARD", "claimed " .. table.concat(SY, ", ")) end App.status("Rewards", # SY > 0 and ("claimed " .. table.concat(SY, ", ")) or "nothing waiting") end function FarmHub.goldShop() if not Config.goldShop then return App.status("Gold Shop", "off") end Brain.str.shopAt = os.time() local AZ = Core.prof() local BZ = Core.session() if not (AZ and BZ) then return end local CZ, DZ = App.request("gshop", "request_current_gold_shop") if not (CZ and type(DZ) == "table") then return App.status("Gold Shop", "shop not answering") end local EZ = 0 local FZ = (type(AZ.gold_shop_data) == "table" and type(AZ.gold_shop_data.bought_items) == "table") and AZ.gold_shop_data.bought_items or { } local function GZ(HZ, IZ) if IZ.stock == "inf" then return math.huge end return math.max(0, (tonumber(IZ.stock) or 0) - (tonumber(FZ [HZ]) or 0)) end for JZ, KZ in pairs(DZ) do if type(KZ) == "table" and tostring(KZ.id or JZ):find("^StarFruit") and GZ(JZ, KZ) > 0 then local LZ = Core.GS.ITEM_CAPS and Core.GS.ITEM_CAPS [KZ.id] or 1e9 local MZ = 0 pcall(function() MZ = BZ.inventory:get_number_of_owned_item(KZ.id) end) local NZ = tonumber(KZ.gold_cost) or 1e12 local OZ = GZ(JZ, KZ) while OZ > 0 and MZ < LZ and (tonumber(AZ.gold_amount) or 0) - NZ >= Config.goldFloor and EZ < 10 do local PZ = tonumber(AZ.gold_amount) or 0 local QZ = Utils.now() local RZ, SZ = App.request("gbuy", "buy_gold_shop_item", JZ, "1") local TZ = Utils.now() while Utils.now() - TZ < 3 and (tonumber(AZ.gold_amount) or 0) >= PZ do task.wait(0.2) end if not RZ or (tonumber(AZ.gold_amount) or 0) >= PZ then App.log(1, "SHOP", "gold shop refused " .. tostring(JZ) .. " (" .. tostring(App.gameSaid(QZ) or SZ) .. ")") App.backoff("gbuy", 600) break end EZ = EZ + 1 OZ = OZ - 1 MZ = MZ + 1 Spending.add((Core.Items [KZ.id] and Core.Items [KZ.id].name or KZ.id), NZ, "gold", string.format("evolve material, %d/%d held ยท gold floor %s", MZ, LZ, Utils.fmt(Config.goldFloor))) end end end local UZ, VZ = 0, 0 if Config.team and Config.teamXp and Config.teamBuyXp then local WZ, XZ pcall(function() WZ = Core.core("ProgressionServiceCore") XZ = Core.data("WorldLevelOrder") end) local function YZ(ZZ) local _Z = Core.LU and type(Core.LU.xp_items_order) == "table" and Core.LU.xp_items_order [ZZ] local a_ = _Z and XZ and type(XZ.WORLD_ORDER) == "table" and XZ.WORLD_ORDER [_Z] if not (a_ and WZ and WZ.check_if_unlocked_story_world) then return true end local b_, c_ = pcall(WZ.check_if_unlocked_story_world, Core.session(), a_) return b_ and c_ == true end local d_, e_ = { }, 0 for f_, g_ in pairs(DZ) do local h_ = type(g_) == "table" and Core.Items [g_.id or f_] if h_ and type(h_.usage) == "table" and h_.usage.type == "xp_feed" and GZ(f_, g_) > 0 and tonumber(g_.gold_cost) and (tonumber(h_.usage.xp_amount) or 0) / math.max(1, tonumber(g_.gold_cost)) >= 3 then if YZ(g_.id or f_) then table.insert(d_, { key = f_, id = g_.id or f_, it = g_, xp = tonumber(h_.usage.xp_amount) or 0, cost = tonumber(g_.gold_cost) }) else e_ = e_ + 1 end end end table.sort(d_, function(i_, j_) return i_.xp / i_.cost > j_.xp / j_.cost end) local k_ = Strategy.xpRoom() for l_, m_ in ipairs(Strategy.foodHeld()) do k_ = k_ - m_.n * m_.xp end if Config.strDry then local n_, o_, p_ = { }, tonumber(AZ.gold_amount) or 0, 0 for q_, r_ in ipairs(d_) do local s_ = math.min(GZ(r_.key, r_.it), math.floor(math.max(0, o_ - Config.xpGoldFloor) / r_.cost), r_.xp > 0 and math.floor(k_ / r_.xp) or 0) if s_ > 0 then table.insert(n_, string.format("%d %s", s_, (Core.Items [r_.id] or { }).name or r_.id)) o_, k_, p_ = o_ - s_ * r_.cost, k_ - s_ * r_.xp, p_ + s_ end end local t_ = (p_ > 0 and string.format("dry run: would buy %s for %s gold", table.concat(n_, ", "), Utils.fmt((tonumber(AZ.gold_amount) or 0) - o_)) or "dry run: no XP food worth buying (your core units are full, or gold is at the line)") .. (e_ > 0 and string.format(" ยท %d kinds locked behind later worlds", e_) or "") if Brain.str.dryShop ~= t_ then Brain.str.dryShop = t_ App.log(1, Strategy.LOG, t_) end else local u_ local function v_() return tonumber(Core.cur("gold")) or tonumber(AZ.gold_amount) or 0 end for w_, x_ in ipairs(d_) do if u_ then break end local y_ = GZ(x_.key, x_.it) while y_ > 0 and k_ > 0 and x_.xp > 0 and v_() - x_.cost >= Config.xpGoldFloor and UZ < 80 do local z_ = (y_ >= 10 and v_() - x_.cost * 10 >= Config.xpGoldFloor and k_ >= x_.xp * 10) and 10 or 1 local A_, B_ = v_(), Adventure.have(x_.id) local C_ = Utils.now() local D_, E_ = App.request("xpbuy", "buy_gold_shop_item", x_.key, tostring(z_)) if not D_ and (E_ == "slowing down" or E_ == "waiting") then task.wait(3.5) continue end local F_ = Utils.now() while Utils.now() - F_ < 3 and v_() >= A_ and Adventure.have(x_.id) <= B_ do task.wait(0.2) end if not D_ or (v_() >= A_ and Adventure.have(x_.id) <= B_) then App.log(1, "SHOP", "gold shop refused " .. tostring(x_.key) .. " ร—" .. z_ .. " (" .. tostring(App.gameSaid(C_) or E_) .. "): XP food waits for the next pass") App.backoff("xpbuy", 600) u_ = true break end UZ, y_, k_, VZ = UZ + z_, y_ - z_, k_ - x_.xp * z_, VZ + x_.xp * z_ Spending.add((Core.Items [x_.id] or { }).name or x_.id, x_.cost * z_, "gold", string.format("XP food for your team: %s XP ยท gold kept %s", Utils.fmt(x_.xp * z_), Utils.fmt(Config.xpGoldFloor))) end end if UZ > 0 then App.log(1, Strategy.LOG, string.format("bought %d XP food (%s XP) in the gold shop ยท gold %s", UZ, Utils.fmt(VZ), Utils.fmt(AZ.gold_amount))) end end end App.status("Gold Shop", (EZ > 0 or UZ > 0) and string.format("bought %d Star Fruit(s), %d XP food", EZ, UZ) or string.format("nothing to buy (gold %s, floor %s)", Utils.fmt(AZ.gold_amount), Utils.fmt(Config.goldFloor))) end FarmHub.SNIPE_KEEP = { gems = "snipeGemFloor", SummerPearls = "snipePearlFloor", GemsLegacy = "snipeLegacyFloor" } FarmHub.SNIPE_ALSO = { griffith = { "griffith_reincarnation" } } function FarmHub.snipeForms(G_) local H_, I_ = { }, { } while G_ and Core.Units [G_] and not I_ [G_] do I_ [G_] = true table.insert(H_, G_) local J_ = Core.Units [G_].evolve G_ = type(J_) == "table" and J_.evolve_unit or nil end for K_, L_ in ipairs(FarmHub.SNIPE_ALSO [H_ [1]] or { }) do table.insert(H_, L_) end return H_ end function FarmHub.snipeHas(M_) M_ = M_ or Config.snipeUnit if not M_ or M_ == "" then return false end for N_, O_ in ipairs(FarmHub.snipeForms(M_)) do if FarmHub.hatched [O_] or Units.owns(O_) then return true end end return false end function FarmHub.snipeCost(P_) local Q_ = FarmHub.bannerCost(P_) local R_ = Core.Banners [P_] if not (Q_ and R_) then return nil end Q_.cur = (R_.item_cost and R_.item_cost.item_id) or (R_.resource_cost and R_.resource_cost.id) or "gems" Q_.keep = tonumber(Config [FarmHub.SNIPE_KEEP [Q_.cur] or Q_.floor]) or 0 Q_.label = FarmHub.BANNER_NAME [P_] or P_ return Q_ end function FarmHub.watchRoom() local S_ = Core.prof() local T_ = 0 for U_ in pairs(S_.collection.owned_units) do T_ = T_ + 1 end return T_ + 20 <= (tonumber(S_.collection.capacity) or math.huge) end function FarmHub.snipeSay(V_, W_) Brain.snipe.note, Brain.snipe.noteAt = V_, os.time() App.log(1, "SUMMON", "Banner Snipe: " .. V_) if App.notify then App.notify("Banner Snipe", V_, W_ or "success") end App.saveSoon() end function FarmHub.snipeRate() local X_ for Y_, Z_ in pairs(Core.Banners or { }) do if type(Z_) == "table" and Z_.active and not Z_._does_not_rotate and tonumber(Z_.HATCH_REFRESH_RATE) then X_ = math.min(X_ or math.huge, tonumber(Z_.HATCH_REFRESH_RATE)) end end return X_ or 14400 end function FarmHub.snipeTurnAfter(__) local a0 = FarmHub.snipeRate() local b0 = Brain.snipe.clock local c0 = (b0 and tonumber(b0.next) or 0) % a0 return math.floor((__ - c0) / a0 + 1) * a0 + c0 end function FarmHub.snipeStale() local d0 = Brain.snipe if not d0.lookAt then return true end return os.time() >= FarmHub.snipeTurnAfter(d0.lookAt) or (tonumber(d0.changedAt) or 0) > d0.lookAt end function FarmHub.snipeHeard(e0, ...) local f0 = Brain.snipe local g0 = table.pack(...) local function h0() local i0 = { } for j0 = 1, math.min(g0.n, 4) do local k0 = g0 [j0] if type(k0) == "table" then local l0 = { } for m0 in pairs(k0) do if # l0 < 8 then table.insert(l0, tostring(m0)) end end table.insert(i0, "{" .. table.concat(l0, ",") .. "}") else table.insert(i0, tostring(k0):sub(1, 40)) end end return table.concat(i0, ", ") end if e0 == "hatch_refresh_remaining_time_changed" then if not f0.clockSeen then f0.clockSeen = true App.log(1, "SUMMON", "Banner Snipe: the game's hourly countdown says " .. h0()) end return end f0.clock = { next = math.floor((os.time() + 30) / 60) * 60 + FarmHub.snipeRate(), at = os.time(), src = "changed" } local n0, o0 = { }, nil for q0 = 1, g0.n do if type(g0 [q0]) == "table" then for r0, s0 in pairs(g0 [q0]) do if type(s0) == "table" and Core.Banners [r0] then if not o0 then local t0 = { } for v0, w0 in pairs(s0) do if # t0 < 10 then table.insert(t0, tostring(v0) .. ":" .. type(w0)) end end o0 = tostring(r0) .. " {" .. table.concat(t0, ",") .. "}" end local x0 = type(s0.rateup_units) == "table" and s0.rateup_units or nil if x0 then local y0 = { } for z0, A0 in pairs(x0) do if type(A0) == "string" and Core.Units [A0] then table.insert(y0, A0) end end table.sort(y0) if # y0 > 0 then n0 [r0] = y0 end end end end end end if next(n0) then for B0, C0 in pairs(f0.feat or { }) do if not n0 [B0] then n0 [B0] = C0 end end f0.feat, f0.lookAt, f0.changedAt = n0, os.time(), os.time() local D0 = { } for E0, F0 in pairs(n0) do local G0 = { } for H0, I0 in ipairs(F0) do table.insert(G0, Units.name(I0)) f0.seen [I0] = { banner = E0, at = os.time() } end table.insert(D0, (FarmHub.BANNER_NAME [E0] or E0) .. ": " .. table.concat(G0, ", ")) end table.sort(D0) App.log(1, "SUMMON", "Banner Snipe: the banners changed: " .. table.concat(D0, " ยท ")) local J0 = Config.snipeOn and Config.snipeUnit ~= "" and not FarmHub.snipeHas() and FarmHub.snipeWhere(Config.snipeUnit) if J0 and f0.featured ~= J0 then f0.featured = J0 FarmHub.snipeSay(string.format("%s is featured on %s right now%s", Units.name(Config.snipeUnit), FarmHub.BANNER_NAME [J0] or J0, App.where == "match" and ": the lobby pulls after this match" or "")) end else f0.changedAt = os.time() App.log(1, "SUMMON", "Banner Snipe: the game says a banner changed (" .. h0() .. (o0 and ("; " .. o0) or "") .. ")" .. (App.where == "match" and ": the lobby looks after this match" or "")) end FarmHub.last.snipe = nil App.saveSoon() end function FarmHub.snipeLook() local K0 = Brain.snipe local L0, M0 = App.request("hatch", "get_hatch_setup_data") if not (L0 and type(M0) == "table") then return nil end local N0, O0 = { }, { } for P0, Q0 in pairs(M0) do if type(Q0) == "table" then for R0 in pairs(Q0) do O0 [tostring(R0)] = true end if type(Q0.rateup_units) == "table" and Core.Banners [P0] then local S0 = { } for T0, U0 in pairs(Q0.rateup_units) do if type(U0) == "string" and Core.Units [U0] then table.insert(S0, U0) end end table.sort(S0) N0 [P0] = S0 for V0, W0 in ipairs(S0) do K0.seen [W0] = { banner = P0, at = os.time() } end end end end if not K0.keysSeen then local X0 = { } for Y0 in pairs(O0) do table.insert(X0, Y0) end table.sort(X0) K0.keysSeen = true App.log(1, "SUMMON", "Banner Snipe: a banner from the game carries " .. table.concat(X0, ", ")) end K0.feat, K0.lookAt = N0, os.time() return N0 end function FarmHub.snipeWhere(Z0) local _0, a1 for b1, c1 in pairs(Brain.snipe.feat or { }) do local d1 = Core.Banners [b1] if d1 and d1.active and table.find(c1, Z0) then local e1 = FarmHub.snipeCost(b1) local f1 = not e1 and 9 or e1.cur == "GemsLegacy" and 1 or e1.cur == "gems" and 3 or 2 if not a1 or f1 < a1 then _0, a1 = b1, f1 end end end return _0 end function FarmHub.bannerSnipe() if not (Config.snipeOn and Config.snipeUnit ~= "") then return end local g1 = Brain.snipe local h1 = Config.snipeUnit local i1 = Units.name(h1) if g1.unit ~= h1 then g1.unit, g1.pulls, g1.got, g1.featured, g1.note, g1.saidAt = h1, 0, nil, nil, nil, nil end if Adventure.have("celestial_compass_u21") > 0 and not (g1.compass and g1.compass.unit == h1) then local j1, k1 = App.request("hatch", "get_celestial_compass_units", "celestial_compass_u21") if j1 and type(k1) == "table" then local l1, m1 = false, 0 for n1, o1 in pairs(k1) do m1 = m1 + 1 if o1 == h1 then l1 = true end end g1.compass = { unit = h1, at = os.time(), n = m1, on = l1 } App.log(1, "SUMMON", string.format("Celestial Compass: it can pick %d units, %s", m1, l1 and (i1 .. " among them") or ("not " .. i1))) if l1 then FarmHub.snipeSay("your Celestial Compass can pick " .. i1 .. ". It's still in your bag: that pick is yours to make") end end end if FarmHub.snipeHas(h1) then if not g1.got then g1.got = os.time() FarmHub.snipeSay("you have " .. i1 .. "!") end return end if not FarmHub.snipeLook() then return end local p1 = FarmHub.snipeWhere(h1) if not p1 then if g1.featured then FarmHub.snipeSay(string.format("%s isn't featured any more. It waits for the next turnover (%s)", i1, os.date("%H:%M", FarmHub.snipeTurnAfter(os.time()))), "warning") end g1.featured = nil return end local q1 = FarmHub.snipeCost(p1) if not q1 then return end if g1.featured ~= p1 then g1.featured = p1 FarmHub.snipeSay(string.format("%s is featured on %s right now", i1, q1.label)) end local r1 = { } for s1, t1 in ipairs({ "gems", "GemsLegacy", "SummerPearls" }) do if t1 ~= q1.cur then r1 [t1] = tonumber(Core.cur(t1)) or 0 end end local u1, v1 = 0, 0 while App.alive and Config.snipeOn and Config.snipeUnit == h1 and q1.have() - q1.cost * 10 >= q1.keep and FarmHub.watchRoom() and not FarmHub.snipeHas(h1) do local w1, x1 = q1.have(), Utils.now() local y1, z1 = App.request("snipe", "buy_from_banner", p1, "gems10") if not y1 and (z1 == "slowing down" or z1 == "waiting") and v1 < 3 then v1 = v1 + 1 task.wait(3.5) continue end local A1 = Utils.now() while Utils.now() - A1 < 4 and q1.have() >= w1 do task.wait(0.25) end for B1, C1 in pairs(r1) do if (tonumber(Core.cur(B1)) or 0) < C1 then Config.snipeOn = false pcall(function() GUI.UI.snipeOn.Set(false, true) end) App.saveSoon() return FarmHub.snipeSay(string.format("%s went down during a pull on %s, so Banner Snipe switched itself off", B1 == "gems" and "Gems" or B1, q1.label), "warning") end end if q1.have() >= w1 then local D1 = App.gameSaid(x1) or z1 if tostring(D1):lower():find("too fast") and v1 < 3 then v1 = v1 + 1 task.wait(6) continue end App.log(1, "SUMMON", "Banner Snipe: " .. q1.label .. " refused (" .. tostring(D1) .. ")") App.backoff("snipe", 600) break end v1 = 0 u1 = u1 + 10 g1.pulls = (tonumber(g1.pulls) or 0) + 10 Spending.add("10 summons on " .. q1.label, w1 - q1.have(), q1.name, "Banner Snipe: " .. i1 .. " is featured") task.wait(5) end local E1 = FarmHub.snipeTurnAfter(os.time()) if FarmHub.snipeHas(h1) then g1.got = os.time() FarmHub.snipeSay(string.format("you have %s! It dropped after %d pulls", i1, tonumber(g1.pulls) or 0)) elseif g1.saidAt ~= E1 and not FarmHub.watchRoom() then g1.saidAt = E1 FarmHub.snipeSay(i1 .. " is featured, but your unit storage is nearly full. Sell some units and it carries on", "warning") elseif g1.saidAt ~= E1 and q1.have() - q1.cost * 10 < q1.keep then g1.saidAt = E1 FarmHub.snipeSay(string.format("%s is featured, but %s is at your Keep line (%s, keeping %s) after %d pulls", i1, q1.name, Utils.fmt(q1.have()), Utils.fmt(q1.keep), tonumber(g1.pulls) or 0), "warning") end end function FarmHub.snipeWanted() if not (Config.snipeOn and Config.snipeUnit ~= "") or FarmHub.snipeHas() or FarmHub.snipeStale() then return false end if App.req.back.snipe and Utils.now() < App.req.back.snipe then return false end local F1 = FarmHub.snipeWhere(Config.snipeUnit) local G1 = F1 and FarmHub.snipeCost(F1) return G1 ~= nil and G1.have() - G1.cost * 10 >= G1.keep and FarmHub.watchRoom() end function FarmHub.snipeDue() if not (Config.snipeOn and Config.snipeUnit ~= "") or FarmHub.snipeHas() or not FarmHub.snipeStale() then return false end local H1 = Brain.snipe local I1 = FarmHub.snipeTurnAfter(os.time()) - FarmHub.snipeRate() if (tonumber(H1.askAt) or 0) >= math.max(I1, tonumber(H1.changedAt) or 0) or not FarmHub.watchRoom() then return false end local J1 = false for K1, L1 in pairs(Core.Banners or { }) do local M1 = type(L1) == "table" and L1.active and FarmHub.snipeCost(K1) if M1 and M1.have() - M1.cost * 10 >= M1.keep then J1 = true end end if not J1 then return false end H1.askAt = os.time() return true end function FarmHub.snipeText() local N1 = Brain.snipe local O1 = Config.snipeUnit local P1 = { } if O1 == "" then table.insert(P1, "Pick a unit below. It's pulled on whichever banner features it, until it drops or a Keep line below is reached.") elseif FarmHub.snipeHas(O1) then table.insert(P1, "You have " .. Units.name(O1) .. ".") elseif not Config.snipeOn then table.insert(P1, Units.name(O1) .. " is picked. Switch Banner Snipe on to pull for it.") else local Q1 = not FarmHub.snipeStale() and FarmHub.snipeWhere(O1) table.insert(P1, Q1 and (Units.name(O1) .. " is featured on " .. (FarmHub.BANNER_NAME [Q1] or Q1) .. " right now.") or ("Waiting for " .. Units.name(O1) .. " to be featured.")) end if N1.lookAt then local R1 = { } for S1, T1 in pairs(N1.feat or { }) do local U1 = { } for V1, W1 in ipairs(T1) do table.insert(U1, Units.name(W1)) end if # U1 > 0 then table.insert(R1, (FarmHub.BANNER_NAME [S1] or S1) .. ": " .. table.concat(U1, ", ")) end end table.sort(R1) table.insert(P1, "Featured at " .. os.date("%H:%M", N1.lookAt) .. " ยท " .. table.concat(R1, " ยท ")) end table.insert(P1, string.format("Banners turn over next at %s%s", os.date("%H:%M", FarmHub.snipeTurnAfter(os.time())), N1.clock and "" or " (expected)")) table.insert(P1, string.format("Gems %s ยท Pearls %s ยท Legacy Gems %s ยท pulled so far: %d", Utils.fmt(tonumber(Core.cur("gems")) or 0), Utils.fmt(tonumber(Core.cur("SummerPearls")) or 0), Utils.fmt(tonumber(Core.cur("GemsLegacy")) or 0), tonumber(N1.pulls) or 0)) if N1.note then table.insert(P1, os.date("%H:%M", N1.noteAt or os.time()) .. " " .. N1.note) end return table.concat(P1, "\n") end function FarmHub.summon() local X1 = Core.prof() if not X1 then return end local Y1 = { } local function Z1(_1, a2, b2, c2, d2, e2, f2, g2) local h2, i2 = 0, 0 while h2 < Config.summonsPerVisit and b2() - d2 * 10 >= c2 and not (g2 and g2()) do local j2 = b2() local k2 = Utils.now() local l2, m2 = App.request("summon", "buy_from_banner", _1, "gems10") if not l2 and (m2 == "slowing down" or m2 == "waiting") and i2 < 3 then i2 = i2 + 1 task.wait(3.5) continue end local n2 = Utils.now() while Utils.now() - n2 < 4 and b2() >= j2 do task.wait(0.1) end if not l2 or b2() >= j2 then local o2 = App.gameSaid(k2) or m2 if tostring(o2):lower():find("too fast") and i2 < 3 then i2 = i2 + 1 task.wait(2 * i2) continue end App.log(1, "SUMMON", _1 .. " refused (" .. tostring(o2) .. ")") App.backoff("summon", 600) break end i2 = 0 h2 = h2 + 10 Spending.add("10 summons on " .. _1, j2 - b2(), f2, e2) task.wait(0.3) end return h2 end if Config.summonApril and Core.Banners.April2025 then local p2 = Adventure.pullGoal() local q2, r2 = { }, { } local function s2(t2) if not table.find(r2, t2) then table.insert(q2, Units.name(t2)) table.insert(r2, t2) end end if p2 then if p2.step == "pull" then s2(p2.id) end else for u2, v2 in ipairs({ "osaka", "dokuro", "rikka", "chihiro" }) do local w2, x2 = Adventure.tierBest(v2) if not Units.owns(v2) and (not Config.community or x2 >= Adventure.TIER_RANK.S) then s2(v2) end end end for y2, z2 in ipairs(Config.aprilTargets or { }) do if Core.Units [z2] and not Units.owns(z2) and not FarmHub.hatched [z2] then s2(z2) end end local A2 = Config.aprilFloor if p2 and p2.item and not p2.haveItem then A2 = math.max(A2, p2.itemCost) end local B2 = function() return Core.cur("AprilCoins") end if # q2 == 0 then table.insert(Y1, p2 and ("April banner: " .. Units.name(p2.id) .. " is yours โœ“") or (Config.community and "April banner: you have every limited unit worth the coins โœ“" or "April banner: all limited units owned โœ“")) else local function C2(D2) return FarmHub.hatched [D2] or Units.owns(D2) end local G2 = function() for E2, F2 in ipairs(r2) do if not C2(F2) then return false end end return true end local H2 = Z1("April2025", "AprilCoins", B2, A2, 150, "for " .. table.concat(q2, ", "), "AprilCoins", G2) for I2, J2 in ipairs(r2) do if C2(J2) then App.log(1, "SUMMON", Units.name(J2) .. " dropped from the April banner!") if p2 and J2 == p2.id then if App.notify then App.notify("Main goal", Units.name(p2.id) .. " is yours! Next: its evolve item (" .. Utils.fmt(p2.itemCost) .. " AprilCoins)", "success") end pcall(Adventure.refresh) elseif App.notify then App.notify("April banner", Units.name(J2) .. " is yours!", "success") end end end table.insert(Y1, string.format("April banner: %s (%s coins, keeping %s)", H2 > 0 and (H2 .. " summons") or "saving", Utils.fmt(B2()), Utils.fmt(A2))) end end if Config.summonGems then local K2 = Units.equipped() local L2 = math.huge for M2, N2 in ipairs(K2) do if not Units.isFarm(N2.id) then L2 = math.min(L2, Units.score(N2.u)) end end local O2, P2 = App.request("hatch", "get_hatch_setup_data") local Q2, R2 = { }, 0 if O2 and type(P2) == "table" and P2.EventClover and P2.EventClover.rateup_units then for S2, T2 in pairs(P2.EventClover.rateup_units) do local U2, V2 = Adventure.tierBest(T2, true) if Core.Units [T2] and not Units.owns(T2) and Units.hidden(T2) and V2 >= Adventure.TIER_RANK.S then table.insert(Q2, Units.name(U2) .. " (tier " .. tostring((Adventure.tier(U2, true))) .. ")") elseif Core.Units [T2] and not Units.owns(T2) and Units.hidden(T2) then R2 = R2 + 1 elseif Core.Units [T2] and not Units.owns(T2) then local W2 = { unit_id = T2, uuid = "sim:" .. T2, xp = 0, traits = { }, trait_stats = { potency_stat = 1, speed_stat = 1, range_stat = 1 } } local X2, Y2 = pcall(Units.score, W2) if X2 and Y2 > L2 then table.insert(Q2, Units.name(T2)) end end end end if # Q2 == 0 then table.insert(Y1, "gem banner: nothing that beats your team" .. (R2 > 0 and string.format(" (%d featured units can't be judged: the game hides their upgrades)", R2) or "")) else local Z2 = function() return Core.cur("gems") end local _2 = Z1("EventClover", "gems", Z2, Config.gemFloor, 50, "would beat your weakest unit: " .. table.concat(Q2, ", "), "gems") table.insert(Y1, string.format("gem banner: %s", _2 > 0 and (_2 .. " summons") or "saving")) end end if Config.autoSummon and Config.summonBanner ~= "" and not (App.req.back.summon and Utils.now() < App.req.back.summon) then local a3 = FarmHub.bannerCost(Config.summonBanner) if a3 then local function b3() local c3 = 0 for d3 in pairs(X1.collection.owned_units) do c3 = c3 + 1 end return c3 + 20 > (tonumber(X1.collection.capacity) or math.huge) end local e3 = Z1(Config.summonBanner, a3.name, a3.have, Config [a3.floor], a3.cost, "Auto Summon: your pick (Units โ†’ Summon)", a3.name, b3) local f3 = FarmHub.BANNER_NAME [Config.summonBanner] or Config.summonBanner table.insert(Y1, e3 > 0 and string.format("%s: %d summons", f3, e3) or (b3() and (f3 .. ": your unit storage is full") or (f3 .. ": at your Keep At Least line"))) end end App.status("Summon", # Y1 > 0 and table.concat(Y1, " ยท ") or "off") end function FarmHub.buyStorage() local g3 = Brain.buyStorage if not g3 then return end Brain.buyStorage = nil App.saveNow() local h3 = Core.prof() local i3 = Core.GS.CAPACITY_INCREASES and Core.GS.CAPACITY_INCREASES.units local j3 = h3.inventory_capacity_increases and tonumber(h3.inventory_capacity_increases.unit_capacity_increase) or 0 local k3 = i3 and i3 [j3 + 1] if not k3 then return App.log(1, "SHOP", "unit storage: no more steps to buy") end local l3 = tonumber(k3.gold_cost) or math.huge if l3 > (tonumber(g3.maxGold) or 0) then return App.log(1, "SHOP", string.format("unit storage: the next step costs %s gold, more than the %s you agreed: not bought", Utils.fmt(l3), Utils.fmt(g3.maxGold))) end local m3, n3 = tonumber(Core.cur("gold")) or 0, h3.collection.capacity local o3 = Utils.now() local p3, q3 = App.request("storage", "try_purchase_unit_capacity") while Utils.now() - o3 < 5 and (tonumber(Core.cur("gold")) or 0) >= m3 do task.wait(0.25) end local s3 = m3 - (tonumber(Core.cur("gold")) or 0) if s3 > 0 then Spending.add("+" .. tostring(k3.increase) .. " unit storage", s3, "gold", "your go-ahead: the collection was full, which refuses every pad") App.log(1, "SHOP", string.format("bought +%s unit storage for %s gold (storage was %s) ยท gold %s", tostring(k3.increase), Utils.fmt(s3), tostring(n3), Utils.fmt(Core.cur("gold")))) Modes.rec("dc").restUntil, Modes.rec("dc").restWhy = nil, nil Modes.lastScored, FarmHub.last.pick = nil, nil else task.wait(0.5) App.log(1, "SHOP", string.format("the game didn't sell unit storage (%s): not trying again", tostring(App.gameSaid(o3) or (p3 and q3) or "no answer"))) end App.saveSoon() end function FarmHub.buyOnce() local t3 = Brain.buyOnce if not t3 then return end local u3 = (Core.Items [t3.item] or { }).name or t3.item Brain.buyOnce = nil App.saveNow() if Adventure.have(t3.item) >= 1 then return App.log(1, "SHOP", u3 .. ": you have one already, so nothing was bought") end local v3 = tonumber(Core.cur(t3.cur)) or 0 if v3 < (tonumber(t3.max) or 0) then return App.log(1, "SHOP", string.format("%s: %s short of its %s %s, so nothing was bought", u3, Utils.fmt(v3), Utils.fmt(t3.max), tostring(t3.cur))) end local w3 = Utils.now() local x3, y3 = App.request("eshop", "buy_item_generic", t3.item, t3.shop or "event", t3.gui or "event_shop", "1") while Utils.now() - w3 < 5 and Adventure.have(t3.item) < 1 do task.wait(0.25) end local z3 = v3 - (tonumber(Core.cur(t3.cur)) or 0) if Adventure.have(t3.item) >= 1 then Spending.add(u3, z3, tostring(t3.cur), tostring(t3.why or "your go-ahead")) pcall(Adventure.refresh) FarmHub.last.goals = nil if App.notify then App.notify("Shop", "Bought " .. u3, "success") end else task.wait(0.5) App.log(1, "SHOP", string.format("the shop didn't sell %s (%s): not trying again", u3, tostring(App.gameSaid(w3) or (x3 and y3) or "no answer"))) end end function FarmHub.pad(A3, B3) local function C3(D3) for E3, F3 in ipairs(D3:GetChildren()) do local G3 = F3:FindFirstChild("Owner") local H3 = F3:FindFirstChild("Active") if G3 and G3.Value == nil and not (H3 and H3.Value) then return F3 end end end if A3 == "story" or A3 == "infinite" then return C3(workspace._LOBBIES.Story) end if A3 == "raid" then return C3(workspace._RAID.Raid) end if A3 == "challenge" then return workspace._CHALLENGES.Challenges:GetChildren() [1] end if A3 == "daily" then return workspace._CHALLENGES.DailyChallenge:GetChildren() [1] end if A3 == "event" then local I3 = Brain.live.event and Brain.live.event.pad return I3 and workspace._EVENT_CHALLENGES.Lobbies:FindFirstChild(I3) or nil end end function FarmHub.launch(J3) if J3.kind == "dungeon" then return Dungeon.launch(J3) end local K3, L3 = pcall(Strategy.relicsApply) if not K3 then App.log(1, "ERROR", "relics: " .. tostring(L3)) end Brain.watch = nil for M3, N3 in pairs(Modes.PORTALS) do if (J3.kind == "portal" and J3.level == N3.level) or (J3.portalMap and J3.level == N3.from) then Brain.watch = { key = J3.key, portal = M3, item = N3.item, have = Adventure.have(N3.item), held = # Modes.portalsHeld(M3), used = J3.kind == "portal", at = os.time() } end end if J3.kind == "portal" or J3.kind == "artifact" then return FarmHub.launchPortal(J3) end local O3 = FarmHub.pad(J3.kind, J3) if not O3 then App.log(1, "MODE", "no free pad for " .. J3.label) App.backoff("launch", 20) return false end Brain.pending = { key = J3.key, kind = J3.kind, level = J3.level, diff = J3.diff, label = J3.label, at = os.time(), value = J3.value } if Brain.nextMode == J3.key then Brain.nextMode = nil end App.log(1, "MODE", string.format("launching %s on %s โ€” %s (โ‰ˆ%.0f gems/min)", J3.label, O3.Name, J3.why or "", J3.score or 0)) App.saveNow() App.status("Mode", "starting " .. J3.label) if FarmHub.matchOn(J3) then FarmHub.matchmake(J3) local P3, Q3 = pcall(Modes.needsHelp, J3) if App.alive and J3.kind == "daily" and P3 and Q3 then App.log(1, "MODE", "nobody joined the Daily Challenge, and the simulator says your team can't win it alone: not played alone, matchmaking again in 10 min") Brain.pending = nil local R3 = Modes.rec(J3.key) R3.restUntil, R3.restWhy = os.time() + 600, "alone" Modes.lastScored = nil App.backoff("launch", 5) return false end end local S3 = Utils.now() local T3, U3 = App.request("join", "request_join_lobby", O3.Name) if not (T3 and U3) then task.wait(0.5) local V3 = App.gameSaid(S3) App.log(1, "MODE", "join refused on " .. O3.Name .. " (" .. tostring(V3 or U3) .. ")" .. (V3 and (": " .. J3.label .. " rests for half an hour") or "")) Brain.pending = nil if V3 then Modes.rec(J3.key).restUntil, Modes.rec(J3.key).restWhy = os.time() + 1800, "refused" Modes.lastScored = nil end App.backoff("launch", V3 and 5 or 15) return false end if J3.kind == "story" or J3.kind == "raid" or J3.kind == "infinite" then local W3, X3 = App.request("lock", "request_lock_level", O3.Name, J3.level, false, J3.diff) if not (W3 and X3) then App.log(1, "MODE", "lock refused for " .. J3.label .. " (" .. tostring(X3) .. ")") App.request("leave", "request_leave_lobby", O3.Name) local Y3 = Modes.rec(J3.key) Y3.restUntil, Y3.restWhy = os.time() + 1800, "refused" Brain.pending = nil App.backoff("launch", 10) return false end end task.wait(0.5) App.request("start", "request_start_game", O3.Name) local Z3 = Utils.now() while App.alive and Utils.now() - Z3 < 45 do task.wait(1) end if App.alive then App.log(1, "MODE", "no teleport after 45s for " .. J3.label .. " โ€” leaving the pad") App.request("leave", "request_leave_lobby", O3.Name) local _3 = Modes.rec(J3.key) _3.restUntil, _3.restWhy = os.time() + 900, "no teleport" Brain.pending = nil App.backoff("launch", 10) end return false end function FarmHub.matchOn(a4) if Brain.trial or (Brain.testTeam and (Brain.testTeam.left or 0) > 0) then return false end if a4.kind == "daily" then return Config.dailyMatch == true end local b4 = (a4.kind == "raid" and Config.mmRaid) or (a4.kind == "infinite" and Config.mmInfinite) or (a4.kind == "story" and a4.legend and Config.mmLegend) or (a4.kind == "event" and Config.mmEvent) local c4 = b4 and Core.Levels [a4.level] return c4 ~= nil and c4 ~= false and type(c4._MATCHMAKING) == "table" end function FarmHub.matchmake(d4) local e4 = ReplicatedStorage.endpoints.server_to_client local f4, g4, h4 = false, false, { } if e4:FindFirstChild("matchmaking_started") then table.insert(h4, e4.matchmaking_started.OnClientEvent:Connect(function() f4 = true end)) end if e4:FindFirstChild("matchmaking_ended") then table.insert(h4, e4.matchmaking_ended.OnClientEvent:Connect(function() g4 = true end)) end for i4, j4 in ipairs(h4) do table.insert(App.conns, j4) end local function k4() for l4, m4 in ipairs(h4) do m4:Disconnect() end end local n4 = Utils.now() local o4 = d4.kind == "daily" and "__DAILY_CHALLENGE" or d4.level local p4 = tostring(d4.label or o4) local q4, r4 = App.request("match", "request_matchmaking", o4) while App.alive and Utils.now() - n4 < 5 and not f4 do task.wait(0.25) end if not f4 then App.log(1, "MODE", "matchmaking didn't start for " .. p4 .. " (" .. tostring(App.gameSaid(n4) or (q4 and r4) or "no answer") .. "): alone") k4() return false end local s4 = math.max(30, tonumber(Config.dailyMatchWait) or 120) App.log(1, "MODE", string.format("matchmaking for %s: up to %d s for other players, then alone", p4, s4)) App.status("Mode", "matchmaking: " .. p4) local t4 while App.alive and Utils.now() - n4 < s4 do if g4 and not t4 then t4 = Utils.now() end if t4 and Utils.now() - t4 > 30 then break end task.wait(1) end if not App.alive then return true end k4() App.request("match_leave", "leave_matchmaking") App.log(1, "MODE", t4 and ("matchmaking ended with no match: " .. p4 .. " alone") or string.format("nobody joined in %d s: %s alone", s4, p4)) task.wait(2) return false end function FarmHub.launchPortal(u4) Brain.pending = { key = u4.key, kind = u4.kind, level = u4.level, diff = u4.diff, label = u4.label, at = os.time(), value = u4.value } App.log(1, "MODE", string.format("launching %s %s โ€” %s (โ‰ˆ%.0f gems/min)", u4.label, u4.kind == "artifact" and "alone" or ("with a " .. ((Core.Items [(Brain.watch or { }).portal or ""] or { }).name or "portal")), u4.why or "", u4.score or 0)) App.saveNow() App.status("Mode", "opening " .. u4.label) local v4 = Utils.now() local w4 if u4.kind == "artifact" then _, w4 = App.request("portal", "open_portal_level_new", u4.level, { difficulty = "Hard" }) else _, w4 = App.request("portal", "use_portal", u4.portal) end local x4 = Core.svc("LobbyServiceClient") local function y4() local z4 = x4 and x4.current_lobby if z4 then return z4 end local A4 = workspace:FindFirstChild("_PORTALS") and workspace._PORTALS:FindFirstChild("Lobbies") for B4, C4 in ipairs(A4 and A4:GetChildren() or { }) do local D4 = C4:FindFirstChild("Owner") if D4 and D4.Value == LocalPlayer then return C4.Name end end end local E4 while App.alive and Utils.now() - v4 < 28 and not E4 do E4 = y4() if not E4 then if App.gameSaid(v4) then break end task.wait(0.5) end end if E4 and Utils.now() - v4 > 10 then App.log(1, "MODE", string.format("the portal opened late (after %.0f s): it plays", Utils.now() - v4)) end if not E4 then local F4 = App.gameSaid(v4) local G4 = F4 and F4:lower():find("once every", 1, true) FarmHub.badPortal = FarmHub.badPortal or { } if u4.portal and not G4 then FarmHub.badPortal [u4.portal] = os.time() + 3600 end local H4 = u4.kind == "artifact" and not G4 if H4 then Modes.rec(u4.key).restUntil, Modes.rec(u4.key).restWhy = os.time() + 1800, "refused" Modes.lastScored = nil end App.log(1, "MODE", string.format("the %s didn't open (%s): trying again %s", u4.kind == "artifact" and "Artifact Stage" or "portal", tostring(F4 or w4 or "no answer"), G4 and "once the game's 30 s are up" or (H4 and "in half an hour" or "with another portal"))) task.wait(2) local I4 = y4() if I4 then App.request("leave", "request_leave_lobby", I4) end Brain.pending, Brain.watch = nil, nil App.backoff("launch", math.max(5, 32 - (Utils.now() - v4))) return false end task.wait(0.5) pcall(function() local J4 = x4 and x4.more_lobby_params and x4.more_lobby_params [E4] local K4 = J4 and J4._level_data if type(K4) ~= "table" then return end local L4 = Core.Levels [u4.level] or { } local M4 = { } for N4, O4 in pairs(K4) do local P4 = L4 [N4] if type(O4) ~= "table" and type(O4) ~= "function" and O4 ~= P4 then table.insert(M4, tostring(N4) .. "=" .. tostring(O4)) elseif type(O4) == "table" and type(P4) ~= "table" then local Q4, R4 = pcall(HttpService.JSONEncode, HttpService, O4) table.insert(M4, tostring(N4) .. "=" .. (Q4 and R4:sub(1, 120) or "{โ€ฆ}")) end end table.sort(M4) local S4 = # M4 > 0 and table.concat(M4, " ยท ") or "none beyond the plain level" Brain.portalMods = S4 App.saveNow() App.log(1, "PORTAL", string.format("%s opened: %s", u4.label, S4:sub(1, 400))) local T4 = S4:find("weak") or S4:find("challenge") or S4:find("difficulty") or S4:find("resist") or S4:find("health") if T4 and Config.autoTeam then u4.ldOverride = K4 local U4, V4 = pcall(FarmHub.teamFor, u4, FarmHub.QUICK_SEARCH) if not U4 then App.log(1, "ERROR", "portal team: " .. tostring(V4)) end u4.ldOverride = nil end end) local W4 = Utils.now() local X4 = { } for Y4 = 1, 3 do local Z4, _4 = App.request("start", "request_start_game", E4) table.insert(X4, tostring(Z4 and _4)) local a5 = Utils.now() while App.alive and Utils.now() - a5 < 12 do task.wait(1) end if not App.alive then return false end E4 = (x4 and x4.current_lobby) or E4 end if App.alive then local b5 = App.gameSaid(W4) App.log(1, "MODE", string.format("no teleport after 3 Starts (%s%s) for %s โ€” leaving the portal lobby, %s", table.concat(X4, ", "), b5 and (", the game said: " .. b5) or "", u4.label, u4.kind == "artifact" and "trying again in 15 min" or "next try with another portal")) App.request("leave", "request_leave_lobby", E4) FarmHub.badPortal = FarmHub.badPortal or { } if u4.portal then FarmHub.badPortal [u4.portal] = os.time() + 3600 end if u4.kind == "artifact" then Modes.rec(u4.key).restUntil, Modes.rec(u4.key).restWhy = os.time() + 900, "no teleport" Modes.lastScored = nil end Brain.pending, Brain.watch = nil, nil App.backoff("launch", 10) end return false end function FarmHub.portalLine(c5) local d5 = Core.FOLDER .. "/portal_runs.txt" local e5, f5 = pcall(readfile, d5) pcall(writefile, d5, (e5 and f5 or "") .. c5 .. "\n") end function FarmHub.watchDone() local g5 = Brain.watch if not g5 then return end Brain.watch = nil if os.time() - (tonumber(g5.at) or 0) > 3 * 3600 then return end local h5 = Adventure.have(g5.item) - (tonumber(g5.have) or 0) local i5 = # Modes.portalsHeld(g5.portal) local function j5(k5, l5) local m5 = Brain.portalPay [k5] or { n = 0 } m5.n = (tonumber(m5.n) or 0) + 1 m5.per = m5.per and (m5.per + (l5 - m5.per) * math.max(0.25, 1 / m5.n)) or l5 Brain.portalPay [k5] = m5 end local n5, o5 = (Core.Items [g5.item] or { }).name or g5.item, (Core.Items [g5.portal] or { }).name or g5.portal if g5.used then FarmHub.portalLine(string.format("%s ยท paid +%d %s (now %d) ยท %d portals left", os.date("%m-%d %H:%M"), h5, n5, Adventure.have(g5.item), i5)) if g5.won == false then App.log(1, "PORTAL", string.format("%s: lost, so +%d %s (now %d) ยท %d portals left ยท not counted as what a portal pays", Modes.levelName(Modes.PORTALS [g5.portal].level), h5, n5, Adventure.have(g5.item), i5)) else j5(g5.portal, math.max(0, h5)) App.log(1, "PORTAL", string.format("%s: +%d %s (now %d) ยท %d portals left", Modes.levelName(Modes.PORTALS [g5.portal].level), h5, n5, Adventure.have(g5.item), i5)) end else local p5 = i5 - (tonumber(g5.held) or 0) j5("drop:" .. g5.portal, math.max(0, p5)) App.log(1, "PORTAL", string.format("%s dropped %d %s (now %d)%s", Modes.levelName(Modes.PORTALS [g5.portal].from), p5, o5, i5, h5 > 0 and string.format(" ยท +%d %s", h5, n5) or "")) end Modes.lastScored = nil App.saveSoon() end function FarmHub.simFresh(q5) local r5 = Brain.simRes [q5.key] return r5 and r5.sig == Modes.teamSig() and os.time() - (r5.at or 0) < 6 * 3600 or false end function FarmHub.relicClash(s5) local t5 = Core.session() local u5 = { } for v5, w5 in ipairs(Brain.savedTeam or { }) do u5 [w5] = true end local x5 = { } for y5, z5 in ipairs(s5) do local A5 = t5.collection:get_unit_by_uuid(z5) local B5 = A5 and A5._equipped_relic and t5.inventory:get_item_by_uuid(A5._equipped_relic) if B5 and B5.item_id and (Core.Items [B5.item_id] or { }).unique_equip then table.insert(x5, { uuid = z5, u = A5, kind = B5.item_id, relic = A5._equipped_relic, k = (u5 [z5] and 1e12 or 0) + Strategy.score(A5) }) end end table.sort(x5, function(C5, D5) if C5.k ~= D5.k then return C5.k > D5.k end return C5.uuid < D5.uuid end) local E5 = { } for F5, G5 in ipairs(x5) do if E5 [G5.kind] then local H5 = (Core.Items [G5.kind] or { }).name or G5.kind local I5, J5 = App.request("relicOff", "request_unequip_relic", G5.relic) local K5 = Utils.now() while Utils.now() - K5 < 3 and G5.u._equipped_relic == G5.relic do task.wait(0.25) end if G5.u._equipped_relic ~= G5.relic then Brain.relicOff [G5.uuid] = G5.relic App.log(1, "TEAM", string.format("%s took off its %s for this team: %s has one, and a team can hold one of each", Units.name(G5.u.unit_id), H5, Units.name(E5 [G5.kind]))) else App.log(1, "TEAM", string.format("%s's %s wouldn't come off (%s): the game won't take both on a team", Units.name(G5.u.unit_id), H5, tostring(J5))) end else E5 [G5.kind] = G5.u.unit_id end end end function FarmHub.relicBack() if Config.team and Config.teamRelics then table.clear(Brain.relicOff) return end local L5 = Core.session() for M5, N5 in pairs(Brain.relicOff) do local O5 = L5.collection:get_unit_by_uuid(M5) local P5 = L5.inventory:get_item_by_uuid(N5) local Q5 = P5 and P5._unique_item_data and P5._unique_item_data._unique_relic_data if O5 and Q5 and not O5._equipped_relic and not Q5._equipped_unit_uuid and not Strategy.slotOf(M5) then local R5 for S5, T5 in ipairs(Units.equipped()) do local U5 = T5.u._equipped_relic and L5.inventory:get_item_by_uuid(T5.u._equipped_relic) if U5 and U5.item_id == P5.item_id then R5 = T5 end end if R5 then Brain.str.reequip = { uuid = R5.uuid, slot = R5.slot } App.saveNow() App.request("uneq", "unequip_unit", R5.uuid) local V5 = Utils.now() while Utils.now() - V5 < 4 and Strategy.slotOf(R5.uuid) do task.wait(0.25) end end App.request("relicOff", "use_unique_item", N5, { unit_uuid = M5 }) local W5 = Utils.now() while Utils.now() - W5 < 3 and O5._equipped_relic ~= N5 do task.wait(0.25) end if R5 then Strategy.reequip() end App.log(1, "TEAM", string.format("%s has its %s back%s", Units.name(O5.unit_id), (Core.Items [P5.item_id] or { }).name or tostring(P5.item_id), O5._equipped_relic == N5 and "" or ": the game didn't take it")) end Brain.relicOff [M5] = nil end end function FarmHub.equipTeam(X5, Y5) local Z5 = { } for _5, a6 in ipairs(Units.equipped()) do Z5 [a6.uuid] = true end if not Brain.savedTeam then local b6 = { } for c6, d6 in ipairs(Units.equipped()) do table.insert(b6, d6.uuid) end Brain.savedTeam = b6 end local e6, f6 = pcall(FarmHub.relicClash, X5) if not e6 then App.log(1, "ERROR", "relic clash: " .. tostring(f6)) end local g6 = { } for h6, i6 in ipairs(X5) do g6 [i6] = true end local j6, k6 = false, { } for l6 = 1, 4 do for m6 in pairs(Z5) do if not g6 [m6] then local n6, o6 = App.request("uneq", "unequip_unit", m6) table.insert(k6, string.format("out %s: %s", Units.name((Core.session().collection:get_unit_by_uuid(m6) or { }).unit_id or "?"), tostring(o6))) task.wait(0.8) end end for p6, q6 in ipairs(X5) do if not Z5 [q6] then local r6 if l6 > 2 then local s6 = { } for t6, u6 in ipairs(Units.equipped()) do s6 [u6.slot] = true end for v6 = 1, 6 do if not s6 [v6] then r6 = v6 break end end end local w6, x6 if r6 then w6, x6 = App.request("eq", "equip_unit", q6, r6) else w6, x6 = App.request("eq", "equip_unit", q6) end table.insert(k6, string.format("in %s%s: %s %s", Units.name((Core.session().collection:get_unit_by_uuid(q6) or { }).unit_id or "?"), r6 and (" slot " .. r6) or "", tostring(w6), tostring(x6))) task.wait(0.8) end end local y6 = Utils.now() while Utils.now() - y6 < 3 and not Training.same(Training.team(), X5) do task.wait(0.25) end j6 = Training.same(Training.team(), X5) if j6 then break end Z5 = { } for z6, A6 in ipairs(Units.equipped()) do Z5 [A6.uuid] = true end end Modes._sigAt = nil if j6 then App.log(1, "TEAM", "equipped " .. # X5 .. " units โ€” " .. tostring(Y5)) else local B6 = { } for C6, D6 in ipairs(Units.equipped()) do table.insert(B6, Units.name(D6.id)) end App.log(1, "TEAM", "the team swap didn't take (" .. tostring(Y5) .. "): the game has " .. table.concat(B6, ", ") .. " ยท calls: " .. table.concat(k6, " ยท ")) if not FarmHub.refilling and # Units.equipped() < 6 then FarmHub.refilling = true local E6, F6 = { }, { } for G6, H6 in ipairs(Units.equipped()) do F6 [H6.uuid] = true end for I6, J6 in ipairs(Brain.savedTeam or { }) do if not F6 [J6] and Core.session().collection:get_unit_by_uuid(J6) then table.insert(E6, J6) end end local K6 = 0 for L6, M6 in ipairs(E6) do if # Units.equipped() >= 6 then break end App.request("eq", "equip_unit", M6) task.wait(0.8) local N6 = Utils.now() while Utils.now() - N6 < 3 and # Units.equipped() < math.min(6, K6 + 1 + (6 - # E6)) do task.wait(0.25) end for O6, P6 in ipairs(Units.equipped()) do if P6.uuid == M6 then K6 = K6 + 1 end end end local Q6 = { } for R6, S6 in ipairs(Units.equipped()) do table.insert(Q6, Units.name(S6.id)) end App.log(1, "TEAM", string.format("filled the empty slots with %d of your own units back: the team is %s", K6, table.concat(Q6, ", "))) Modes._sigAt = nil FarmHub.refilling = nil end end App.saveSoon() return j6 end function FarmHub.ownSwap() local T6 = Brain.ownSwap if not T6 then return end local U6 = App.request("hatch", "get_hatch_setup_data") if not U6 then return end if Brain.train.on then Training.home() end local V6 = Core.session() local W6 = table.clone(Brain.savedTeam or Training.team()) local X6, Y6 for Z6, _6 in ipairs(W6) do local a7 = V6.collection:get_unit_by_uuid(_6) if a7 and a7.unit_id == T6.out then X6 = Z6 end if a7 and a7.unit_id == T6.into then Y6 = true end end local b7, c7 for d7, e7 in pairs(V6.profile_data.collection.owned_units) do if e7.unit_id == T6.into and (not b7 or (tonumber(e7.xp) or 0) > c7) then b7, c7 = d7, tonumber(e7.xp) or 0 end end if Y6 or not X6 or not b7 then Brain.ownSwap = nil App.log(1, "TEAM", string.format("your own team: %s for %s wasn't needed (%s), nothing changed", Units.name(T6.into), Units.name(T6.out), Y6 and "it's already on" or not X6 and "that unit isn't on your team" or "you don't have that unit")) return App.saveSoon() end W6 [X6] = b7 local f7 = Brain.savedTeam Brain.savedTeam = nil if FarmHub.equipTeam(W6, string.format("your own team: %s in for %s", Units.name(T6.into), Units.name(T6.out))) then Brain.savedTeam, Brain.ownSwap = nil, nil Modes.lastScored = nil if App.notify then App.notify("Team", string.format("%s is on your team, in for %s", Units.name(T6.into), Units.name(T6.out)), "success") end else Brain.savedTeam = f7 or Brain.savedTeam end App.saveSoon() end function FarmHub.restoreTeam() if not Brain.savedTeam then return false end local g7 = { } for h7, i7 in ipairs(Brain.savedTeam) do if Core.session() and Core.session().collection:get_unit_by_uuid(i7) then table.insert(g7, i7) end end Brain.savedTeam = nil Brain.savedTeam = not FarmHub.equipTeam(g7, "your own team back") and g7 or nil if not Brain.savedTeam then local j7, k7 = pcall(FarmHub.relicBack) if not j7 then App.log(1, "ERROR", "relic back: " .. tostring(k7)) end end App.saveSoon() return true end FarmHub.QUICK_SEARCH = 7 function FarmHub.teamFor(l7, m7) if l7.key == "dg" then if Brain.savedTeam then FarmHub.restoreTeam() end return end local n7 = Core.session() local o7 = Brain.testTeam if o7 and (o7.left or 0) > 0 and o7.key == l7.key and type(o7.uuids) == "table" then local p7 = 0 for q7, r7 in ipairs(o7.uuids) do if n7.collection:get_unit_by_uuid(r7) then p7 = p7 + 1 end end if p7 == # o7.uuids then if Brain.train.on then Training.home() end if not Training.same(Training.team(), o7.uuids) then FarmHub.equipTeam(o7.uuids, "team test: " .. tostring(o7.label)) end return end end if Config.dailyLoop and l7.kind == "daily" and type(Config.dailyTeam) == "table" and # Config.dailyTeam > 0 then local s7, t7 = { }, { } for u7, v7 in ipairs(Brain.savedTeam or Training.team()) do local w7 = n7.collection:get_unit_by_uuid(v7) if w7 then t7 [w7.unit_id] = v7 end end for x7, y7 in ipairs(Config.dailyTeam) do local z7, A7 = t7 [y7], nil if not z7 then local B7, C7 = Units.isFarm(y7), Units.rows(y7) for D7, E7 in pairs(n7.profile_data.collection.owned_units) do if E7.unit_id == y7 then local F7 = n7.collection:get_unit_by_uuid(E7.uuid) or E7 local G7, H7 if B7 then G7, H7 = pcall(Units.farm, F7, C7 and C7.max or 0) else G7, H7 = pcall(Units.score, F7) end H7 = G7 and H7 or 0 if not A7 or H7 > A7 then z7, A7 = E7.uuid, H7 end end end end if z7 then table.insert(s7, z7) end end if # s7 == # Config.dailyTeam then if Brain.train.on then Training.home() end if not Training.same(Training.team(), s7) then FarmHub.equipTeam(s7, "your team for the Daily Challenge") end return end App.log(1, "TEAM", "your Daily Challenge team has a unit you no longer own: the search picks instead") end local I7 = Brain.savedTeam or Training.team() local J7 = { } for K7, L7 in ipairs(I7) do local M7 = n7.collection:get_unit_by_uuid(L7) if M7 then table.insert(J7, M7) end end if # J7 == 0 then return end local N7 = Simulator.sig(J7) local O7 = Brain.simTeam [l7.key] if not (O7 and O7.base == N7 and os.time() - (tonumber(O7.at) or 0) < 120) then App.status("Mode", "looking for the best team for " .. tostring(l7.label)) Simulator.memoOn() local P7, Q7 = pcall(Simulator.plan, l7, true, m7 or 20, J7) Simulator.memo = nil if not P7 then App.log(1, "ERROR", "team search: " .. tostring(Q7)) end end local R7 = Brain.simTeam [l7.key] local S7 = R7 and R7.base == N7 and os.time() - (tonumber(R7.at) or 0) < 120 local T7 = (S7 and R7.better and type(R7.uuids) == "table" and # R7.uuids > 0) and R7.uuids or I7 local U7 = Brain.train.on if U7 and not Training.same(U7.base or { }, T7) then Training.home() U7 = nil end if not U7 and not Training.same(Training.team(), T7) then if T7 == I7 and Brain.savedTeam then FarmHub.restoreTeam() else FarmHub.equipTeam(T7, (S7 and R7.why) and (R7.why .. ": " .. tostring(l7.label)) or ("the simulator's best team for " .. tostring(l7.label))) end end end function FarmHub.run() App.status("Mode", "in the lobby") local V7 = Utils.now() pcall(Units.markHidden) pcall(function() local W7 = ReplicatedStorage.endpoints.server_to_client:FindFirstChild("units_hatched_through_banner") if not W7 then return end table.insert(App.conns, W7.OnClientEvent:Connect(function(X7) for Y7, Z7 in pairs(type(X7) == "table" and X7 or { }) do local _7 = Core.session() and Core.session().collection:get_unit_by_uuid(Z7) if _7 and _7.unit_id then FarmHub.hatched [_7.unit_id] = true App.log(2, "SUMMON", "pulled " .. Units.name(_7.unit_id)) end end end)) end) App.listenGame() task.delay(6, function() local a8, b8 = pcall(Strategy.xpSettle) if not a8 then App.log(1, "ERROR", "xp settle: " .. tostring(b8)) end end) task.delay(8, function() local c8, d8 = pcall(FarmHub.watchDone) if not c8 then App.log(1, "ERROR", "portal count: " .. tostring(d8)) end end) while App.alive do if Config.master and not Brain.holdLobby then if Brain.str.reequip then pcall(Strategy.reequip) end if Brain.buyStorage then local e8, f8 = pcall(FarmHub.buyStorage) if not e8 then App.log(1, "ERROR", "unit storage: " .. tostring(f8)) end end if Brain.buyOnce then local g8, h8 = pcall(FarmHub.buyOnce) if not g8 then App.log(1, "ERROR", "shop item: " .. tostring(h8)) end end if Brain.ownSwap and FarmHub.every("ownSwap", 60) then local i8, j8 = pcall(FarmHub.ownSwap) if not i8 then App.log(1, "ERROR", "team change: " .. tostring(j8)) end end if Brain.train.on then local k8, l8 = pcall(Training.home) if not k8 then App.log(1, "ERROR", "training: " .. tostring(l8)) end end if FarmHub.every("live", 60) then pcall(FarmHub.refreshLive) end if FarmHub.every("huntShop", 3600) then pcall(Hunt.shopLook) end if FarmHub.every("hive", 60) then pcall(Reports.sendHive) end if FarmHub.every("hiveUnit", 60) then task.delay(20, function() pcall(Reports.sendUnit) end) end if # Brain.studyQ > 0 and FarmHub.every("hiveStudy", 15) then task.spawn(function() pcall(Reports.sendStudy, 3) end) end if Config.useHive and FarmHub.every("hiveGet", 6 * 3600) then task.spawn(function() local m8, n8 = pcall(Reports.hiveFetch) if not m8 then App.log(1, "ERROR", "local learning read: " .. tostring(n8)) end end) end if FarmHub.every("rewards", 45) then local o8, p8 = pcall(FarmHub.rewards) if not o8 then App.log(1, "ERROR", "rewards: " .. tostring(p8)) end end if FarmHub.every("gold", 600) then local q8, r8 = pcall(FarmHub.goldShop) if not q8 then App.log(1, "ERROR", "gold shop: " .. tostring(r8)) end end if Config.snipeOn and (FarmHub.every("snipe", 600) or (FarmHub.snipeStale() and FarmHub.every("snipeStale", 30))) then local s8, t8 = pcall(FarmHub.bannerSnipe) if not s8 then App.log(1, "ERROR", "Banner Snipe: " .. tostring(t8)) end end if FarmHub.every("summon", 30) then local u8, v8 = pcall(FarmHub.summon) if not u8 then App.log(1, "ERROR", "summon: " .. tostring(v8)) end local w8, x8 = pcall(FarmHub.eventShop) if not w8 then App.log(1, "ERROR", "event shop: " .. tostring(x8)) end end if FarmHub.every("codes", 21600) then local y8, z8 = pcall(FarmHub.codes) if not y8 then App.log(1, "ERROR", "codes: " .. tostring(z8)) end end if FarmHub.every("rshopCache", 1800) then local A8, B8 = App.request("rshopList", "request_current_raidshop_shop") if A8 and type(B8) == "table" then table.clear(Brain.raidShop) for C8, D8 in pairs(B8) do if type(D8) == "table" and D8.id and D8.item_cost then Brain.raidShop [C8] = { id = D8.id, costItem = D8.item_cost.item_id, cost = tonumber(D8.item_cost.amount) or 0, stock = D8.stock } end end end end if FarmHub.every("lbInfo", 1800) then local E8 = Units.equipped() [1] if E8 then local F8, G8 = App.request("lbInfo", "request_fusion_information", E8.uuid) if F8 and type(G8) == "table" then Brain.lb = { requirement = G8.requirement, allowed = G8.allowed } end end end if FarmHub.every("goals", 300) then local H8, I8 = pcall(Adventure.refresh) if not H8 then App.log(1, "ERROR", "goals: " .. tostring(I8)) else local J8, K8 = pcall(Adventure.buyShopGoals) if J8 and K8 then pcall(Adventure.refresh) end local L8, M8 = pcall(FarmHub.smartEvolve) if not L8 then App.log(1, "ERROR", "smart evolve: " .. tostring(M8)) end end Modes.lastScored = nil end if FarmHub.every("picks", 20) then for N8, O8 in ipairs({ FarmHub.focusNextUp, FarmHub.autoEvolve, FarmHub.autoFuse, FarmHub.autoFuseXp, Hunt.skinTick }) do local P8, Q8 = pcall(O8) if not P8 then App.log(1, "ERROR", "lobby picks: " .. tostring(Q8)) end end local R8, S8 = pcall(Strategy.tick) if not R8 then App.log(1, "ERROR", "team: " .. tostring(S8)) end end local T8, U8 = pcall(Strategy.finishWork) if not T8 then App.log(1, "ERROR", "team work: " .. tostring(U8)) end local V8 = Modes.lastScored and Modes.lastScored [1] if FarmHub.every("pick", 5) or not V8 then local W8, X8 = pcall(Modes.pick) if not W8 then App.log(1, "ERROR", "ranking: " .. tostring(X8)) X8 = nil end V8 = X8 end if Config.sim and Config.simTeams and (FarmHub.planNow or not (Config.autoTeam and Config.play)) and FarmHub.every("simTeam", 600) then FarmHub.planNow = nil task.spawn(function() for Y8, Z8 in ipairs(Modes.lastScored or { }) do if Z8.kind == "daily" or Z8.kind == "challenge" or Z8.first then local _8, a9 = pcall(Simulator.plan, Z8, true) if not _8 then App.log(1, "ERROR", "planner: " .. tostring(a9)) end break end end end) end if Config.trainTakedowns and Config.sim and Modes.lastScored and FarmHub.every("trainScan", 600) then FarmHub.scanning = Utils.now() task.spawn(function() local b9, c9 = pcall(Training.scan) if not b9 then App.log(1, "ERROR", "training planner: " .. tostring(c9)) end FarmHub.scanning = nil Modes.lastScored = nil end) V8 = nil end if FarmHub.scanning and Utils.now() - FarmHub.scanning < 30 then V8 = nil end if Config.play and Config.sim and V8 and not FarmHub.simFresh(V8) and V8.kind ~= "event" and V8.kind ~= "dungeon" then App.status("Mode", "simulating " .. V8.label .. " with your teamโ€ฆ") local d9, e9 = pcall(Simulator.plan, V8, false) if not d9 then App.log(1, "ERROR", "sim: " .. tostring(e9)) end if not (d9 and e9) then Brain.simRes [V8.key] = { none = true, sig = Modes.teamSig(), at = os.time() } end Modes.lastScored = nil V8 = nil end if Config.play and V8 and Utils.now() - V7 > 8 and not (App.req.back.launch and Utils.now() < App.req.back.launch) then local f9 = LocalPlayer:FindFirstChild("AlreadyInLobby") if Brain.trial then local g9, h9 = pcall(Modes.trialTeam) if not g9 then App.log(1, "ERROR", "trial team: " .. tostring(h9)) Brain.trial = nil end if g9 and h9 and not (f9 and f9.Value) then pcall(FarmHub.launch, V8) end else if Config.autoTeam or (Brain.testTeam and (Brain.testTeam.left or 0) > 0 and Brain.testTeam.key == V8.key) then pcall(FarmHub.teamFor, V8) end local i9, j9 = pcall(Training.apply, V8) if not i9 then App.log(1, "ERROR", "training: " .. tostring(j9)) pcall(Training.home) end local k9, l9 = pcall(Hunt.charmTick, V8) if not k9 then App.log(1, "ERROR", "charm: " .. tostring(l9)) end if not (f9 and f9.Value) then pcall(FarmHub.launch, V8) end end elseif not Config.play then App.status("Mode", "Auto Play is off") elseif not V8 then App.status("Mode", Modes.onlyOn() and string.format("only %s is on and none can be played right now: waiting", Modes.onlyText()) or "nothing to play (all modes off or resting)") end else App.status("Mode", Brain.holdLobby and ("held in the lobby: " .. tostring(Brain.holdLobby.why)) or "paused") if not Config.master or Brain.holdLobby then if # Brain.studyQ > 0 and FarmHub.every("hiveStudy", 15) then task.spawn(function() pcall(Reports.sendStudy, 3) end) end if FarmHub.every("summon", 30) then local m9, n9 = pcall(FarmHub.summon) if not m9 then App.log(1, "ERROR", "summon: " .. tostring(n9)) end end if Config.snipeOn and (FarmHub.every("snipe", 600) or (FarmHub.snipeStale() and FarmHub.every("snipeStale", 30))) then local o9, p9 = pcall(FarmHub.bannerSnipe) if not o9 then App.log(1, "ERROR", "Banner Snipe: " .. tostring(p9)) end end if FarmHub.every("picks", 20) then for q9, r9 in ipairs({ FarmHub.autoFuse, FarmHub.autoFuseXp, Hunt.skinTick }) do local s9, t9 = pcall(r9) if not s9 then App.log(1, "ERROR", "lobby picks: " .. tostring(t9)) end end end end end task.wait(2) end end function Economy.upgradeGain(u9, v9, w9, x9, y9, z9) local A9, B9 = v9 [w9], v9 [w9 + 1] local C9 = (A9.cd > 0 and A9.damage / A9.cd or 0) local D9 = (B9.cd > 0 and B9.damage / B9.cd or 0) * Units.aoe(B9.atk, B9.range) / math.max(0.3, Units.aoe(A9.atk, A9.range)) local E9 = C9 > 0 and u9 * (D9 / C9 - 1) or (z9 or 0) local F9 = v9 [w9 + 2] if F9 and w9 + 1 < v9.max and F9.cd > 0 and B9.cd > 0 and C9 > 0 then local G9 = u9 * ((F9.damage / F9.cd) / C9 - 1) local H9 = x9 + F9.cost * y9 if G9 / H9 > E9 / x9 then E9 = E9 + (G9 / H9 * x9 - E9) * 0.5 end end local I9 = (B9.range or 0) > (A9.range or 0) and (1 + ((B9.range - A9.range) / math.max(1, A9.range))) or 1 return math.max(0, E9) * I9 end function Economy.dpsFor(J9, K9) if K9 <= 0 then return 0 end local L9 = { } for M9, N9 in ipairs(J9) do if not N9.farm and N9.gain > 0 then table.insert(L9, N9) end end table.sort(L9, function(O9, P9) return O9.gain / O9.cost > P9.gain / P9.cost end) local Q9, R9 = 0, 0 for S9, T9 in ipairs(L9) do if K9 <= 0 then break end R9 = T9.gain / T9.cost local U9 = math.min(1, K9 / T9.cost) Q9 = Q9 + T9.gain * U9 K9 = K9 - T9.cost * U9 end return Q9 + math.max(0, K9) * R9 * 0.7 end function Economy.shieldDps(V9, W9, X9, Y9, Z9, _9, aab) if (Y9 or 0) <= 0 or (V9 or 0) <= 0 or (Z9 or 0) <= 0 then return V9 or 0 end W9, X9, _9 = math.max(0.3, W9 or 2), math.max(1, X9 or 1), _9 or 1 local bab = V9 * _9 * W9 / X9 return Z9 * X9 / ((Y9 / math.max(1, aab or 1) + math.max(1, Z9 / bab)) * W9) / _9 end function Economy.shieldGain(cab, dab, eab, fab, gab, hab, iab, jab) local kab, lab = Units.attackInfo(dab.atk, dab.range), Units.attackInfo(fab.atk, fab.range) local mab = Economy.shieldDps(cab, (tonumber(dab.cd) or 2) * (iab or 1), kab.n, gab, hab, jab, kab.hits) local nab = Economy.shieldDps(eab, (tonumber(fab.cd) or 2) * (iab or 1), lab.n, gab, hab, jab, lab.hits) local oab = math.max(0, ((tonumber(fab.range) or 0) - (tonumber(dab.range) or 0)) / math.max(1, tonumber(dab.range) or 1)) return math.max(0, nab - mab) + mab * oab end function Economy.shieldWork(pab, qab, rab, sab, tab, uab, vab, wab, xab) if (sab or 0) <= 0 then return 0 end local yab = 3 * sab * (tab or 1) / math.max(1, uab or 20) if (rab or 0) >= yab then return 0 end return math.max(1, pab or 1) * math.max(1, xab or 1) / math.max(0.3, qab or 2) * math.min(vab or 0, wab or 0) end function Economy.holdGain(zab, Aab, Bab, Cab) if not zab or (Aab or 0) <= 0 then return 0 end local Dab, Eab = tonumber(zab.stun) or 0, tonumber(zab.slowTime) or 0 if Dab <= 0 and Eab <= 0 then return 0 end local Fab, Gab = Dab > 0 and Dab or Eab, Dab > 0 and 1 or (tonumber(zab.slowPct) or 0.3) local Hab = Gab * Fab / (Fab + (tonumber(zab.ccCd) or 8)) local Iab = Hab / math.max(0.1, 1 - Hab) return Aab * Iab * Utils.clamp(Bab or 0.5, 0, 1) * math.min(1, (tonumber(zab.n) or 1) / 2) / (1 + (Cab or 0)) end function Economy.bleedGain(Jab, Kab, Lab, Mab) if (Jab or 0) <= 0 then return 0 end local Nab = Kab or 0 for Oab, Pab in ipairs(Lab or { }) do Nab = math.max(Nab, (tonumber(Pab.hp) or 0) * 0.5) end return Jab * Nab * (Mab and 0.5 or 1) end function Economy.bleedShare(Qab, Rab, Sab, Uab) if Qab <= 0 or not Rab then return 0 end if Uab then return Rab == 0 and Qab or Qab / (Rab + 1) end return Sab < Rab and Qab / (Rab - Sab) or 0 end function Economy.keepsUp(Vab, Wab, Xab, Yab, Zab) local _ab = Vab.coming if not (_ab and # _ab > 0 and Vab.waveSec) then return true end local abb = Vab.eff or 1 local bbb = Vab.money - Xab local cbb = (Vab.teamDps or 0) * (Vab.shieldK or 1) + (Yab or 0) * abb + Economy.dpsFor(Wab, math.max(0, bbb)) * abb local dbb = (Vab.farmNow or 0) + (Zab or 0) local ebb = (Vab.roadSec or 130) * Utils.clamp(Vab.cover or 0.6, 0.1, 1) local fbb = Vab.hpAlive or 0 for gbb, hbb in ipairs(_ab) do fbb = math.max(0, fbb + hbb.hp - cbb * Vab.waveSec) local ibb = cbb * ebb if fbb > ibb then return false, gbb, ibb / math.max(1, fbb) end if gbb < # _ab then cbb = cbb + Economy.dpsFor(Wab, (hbb.reward or 0) + dbb) * abb * (0.85 ^ gbb) end end return true end function Economy.decide(jbb, kbb) local lbb = Utils.clamp(kbb.cover or 0.6, 0.1, 1) local mbb = math.max(Config.clearSec, 0.7 * (kbb.roadSec or 50) * lbb) local nbb = (kbb.clearSec or 0) > mbb kbb.piling = kbb.piling and nbb local obb = kbb.attackers > 0 and kbb.pressure < math.min(Config.safeReach, lbb * 0.75) and not kbb.piling and not nbb local pbb = kbb.pressure >= math.min(Config.dangerReach, lbb * 0.9) or (kbb.piling and kbb.enemies >= 18) local qbb, rbb for sbb, tbb in ipairs(jbb) do if tbb.farm then if Config.farmUnits and tbb.gain > 0 then tbb.payback = tbb.cost / tbb.gain if kbb.left - 1 >= tbb.payback + Config.farmSpare and (not qbb or tbb.payback < qbb.payback) then qbb = tbb end end else tbb.ratio = tbb.gain / math.max(1, tbb.cost) if not rbb or tbb.ratio > rbb.ratio then rbb = tbb end end end if kbb.leak and kbb.attackers > 0 then local ubb for vbb, wbb in ipairs(jbb) do local xbb = not kbb.airLeak or (wbb.e and Match.cardAir(wbb.e.id)) if wbb.place and not wbb.farm and xbb and wbb.cost <= kbb.money and not (wbb.e and Units.allyBuff(wbb.e.id)) and (not ubb or wbb.gain > ubb.gain) then ubb = wbb end end if ubb then return ubb, string.format("an enemy got past all your units (%d%% of the road): a unit in its way", math.floor(kbb.leak * 100)) end end local ybb = kbb.future < kbb.wantLeft * 1.1 local zbb, Abb, Bbb = true, nil, nil if qbb then zbb, Abb, Bbb = Economy.keepsUp(kbb, jbb, qbb.cost, 0, qbb.gain) end local Cbb = kbb.coming and not Economy.keepsUp(kbb, jbb, 0, 0, 0) local Dbb = false if Match.random and qbb and not pbb and kbb.attackers >= 2 and qbb.payback <= kbb.left / 3 then local Ebb = 0 for Fbb, Gbb in ipairs(Match.mine or { }) do if Units.isFarm(Gbb._stats.id.Value) then Ebb = Ebb + 1 end end Dbb = not qbb.place or Ebb < 4 end if Cbb and kbb.attackers > 0 then pbb = true end local Hbb = math.max(1, kbb.incomeRate or 20) local Ibb = kbb.waveSec or 20 local function Jbb(Kbb, Lbb, Mbb) local Nbb = math.max(0, (Kbb.cost - kbb.money) / Hbb) local Obb = Kbb.gain * math.max(0, Mbb - Nbb) if Lbb then local Pbb = math.max(0, kbb.money - Kbb.cost) local Qbb = Nbb + math.max(0, (Lbb.cost - Pbb) / Hbb) Obb = Obb + Lbb.gain * math.max(0, Mbb - Qbb) end return Obb end local Rbb, Sbb if kbb.attackers == 0 and rbb then local Tbb = 4 * Ibb local Ubb, Vbb for Wbb, Xbb in ipairs(jbb) do if Xbb.place and not Xbb.farm then local Ybb = Jbb(Xbb, nil, Tbb) if not Vbb or Ybb > Vbb then Ubb, Vbb = Xbb, Ybb end end end Rbb, Sbb = Ubb or rbb, "opening: the unit that does the most damage over the first waves" elseif qbb and obb and ybb and not pbb and zbb then Rbb = qbb Sbb = string.format("farm pays back in %.1f waves (%d left) and the defense still keeps up with the next %d waves", qbb.payback, kbb.left, kbb.coming and # kbb.coming or 0) elseif Dbb then Rbb = qbb Sbb = string.format("farm pays back in %.1f waves (%d left) and nothing is close to the base", qbb.payback, kbb.left) elseif rbb then Rbb = rbb if Cbb then Sbb = string.format("the next waves bring more HP than your units can kill: damage now") elseif pbb then Sbb = string.format("enemies reach %d%% of the road (your units cover %d%%): damage now", math.floor(kbb.pressure * 100), math.floor(lbb * 100)) elseif qbb and not zbb then Sbb = string.format("defense first: after that farm buy, wave +%d would only be %d%% covered", Abb or 1, math.floor((Bbb or 0) * 100)) elseif qbb and nbb then Sbb = string.format("defense first: killing what's alive takes %ds (farm only under %ds)", math.min(999, math.floor(kbb.clearSec)), math.floor(mbb)) elseif qbb and kbb.piling then Sbb = string.format("defense first: %d enemies piling up", kbb.enemies) elseif qbb and not obb then Sbb = "defense first (enemies get too far to farm)" elseif qbb and not ybb then Sbb = "farm not needed: coming yen already covers every upgrade" else Sbb = "most damage per yen" end end if not Rbb then return nil end if Rbb.cost > kbb.money then local Zbb if not Rbb.farm then local _bb = ((kbb.pressure or 0) >= 0.95 and 0.5 or pbb and 2 or 6) * Ibb local acb for bcb, ccb in ipairs(jbb) do if not ccb.farm and ccb ~= Rbb and ccb.cost <= kbb.money then local dcb = Jbb(ccb, Rbb, _bb) local ecb = Jbb(Rbb, ccb, _bb) if dcb > ecb and (not acb or dcb > acb) then Zbb, acb = ccb, dcb end end end end if not Zbb then return nil, nil, string.format("saving ยฅ%s for: %s (โ‰ˆ%ds at ยฅ%d/s) โ€” %s", Utils.fmt(Rbb.cost), Rbb.what or "?", math.ceil((Rbb.cost - kbb.money) / Hbb), math.floor(Hbb), Sbb) end Rbb, Sbb = Zbb, Sbb .. " (worth buying while saving)" end return Rbb, Sbb end Simulator.DT = 1 Simulator.BINS = 20 Simulator.PRIOR = { waveSec = 20, walk = 0.0054, spawnGap = 1, baseHp = 100, baseRel = 0.3, start = 600, eff = 0.7, waveTimer = 60, skipDelay = 5, voteAt = 30, voteBase = 2, votePer = 1.9 } function Simulator.voteSec(fcb, gcb) local hcb = fcb.voteWave and tonumber(fcb.voteWave [tostring(gcb)]) if hcb then return hcb end if gcb <= 2 then return 5 elseif gcb <= 5 then return 10 end return fcb.voteAt end Simulator.LAND_CAP = 1.3 function Simulator.autoskip() local icb, jcb = pcall(function() return LocalPlayer._settings.autoskip_waves.Value end) if icb and type(jcb) == "boolean" then return jcb end return true end function Simulator.cal(kcb) local lcb = Brain.simCal [kcb] if not lcb then lcb = { } Brain.simCal [kcb] = lcb end return lcb end function Simulator.effFor(mcb) local ncb, ocb = 0, 0 for pcb, qcb in pairs(Brain.simCal) do local rcb, scb = tonumber(qcb.eff), tonumber(qcb.n) or 0 if rcb and scb > 0 then ncb = ncb + rcb * scb ocb = ocb + scb end end local tcb = ocb > 0 and ncb / ocb or Simulator.PRIOR.eff local ucb, vcb = tonumber(mcb and mcb.eff), tonumber(mcb and mcb.n) or 0 if not ucb then return tcb end return (ucb * vcb + tcb * 3) / (vcb + 3) end Simulator.LD_FIELDS = { "id", "map", "waves", "world", "story_world_rank", "story_level_rank", "_gamemode", "_OVERRIDE_GAMEMODE", "_difficulty", "_challenge", "infinite", "_use_max", "_override_infinite_bound_waves", "_override_infinite_boss_interval", "_override_multiplayer_diff_scale", "_no_multiplayer_scale", "_raid_multiplayer_scale", "portal_group", "_infinite_bound_level", "_lobby_difficulty_scale", "_location_name", "name", "boss", "_world_diff_scale_multiplier", "_portal_lanes" } function Simulator.unbound(wcb) return not tonumber(wcb._override_infinite_bound_waves) and not wcb._infinite_bound_level end function Simulator.boundEnd(xcb, ycb, zcb) if not (xcb and xcb._infinite_bound_level) or tonumber(xcb._override_infinite_bound_waves) then return nil end local Acb = zcb and Brain.simCal [zcb] if type(Acb) == "table" and (tonumber(Acb.lastWave) or 0) > 15 then return nil end return (ycb or 0) > 0 and math.min(ycb, 15) or 15 end function Simulator.slim(Bcb) local Ccb = { } for Dcb, Ecb in ipairs(Simulator.LD_FIELDS) do local Fcb = Bcb [Ecb] if type(Fcb) ~= "table" and type(Fcb) ~= "function" then Ccb [Ecb] = Fcb end end if type(Bcb._override_waves) == "table" then Ccb._override_waves = { infinite = Bcb._override_waves.infinite, template = Bcb._override_waves.template } end if type(Bcb._dungeon) == "table" then Ccb._dungeon = { depth = Bcb._dungeon.depth, choice_str = Bcb._dungeon.choice_str } end if type(Bcb._override_infinite_final_boss_wave) == "table" then Ccb._final = { wave = Bcb._override_infinite_final_boss_wave.wave, cfg = Bcb._override_infinite_final_boss_wave.final_boss_config_id } end if type(Bcb._override_infinite_boss_interval__skip_waves) == "table" then Ccb._skip = { } for Gcb in pairs(Bcb._override_infinite_boss_interval__skip_waves) do Ccb._skip [tostring(Gcb)] = true end end return Ccb end local function Hcb(Icb, ...) local Jcb, Kcb = pcall(Icb, ...) return Jcb and tonumber(Kcb) or nil end function Simulator.info(Lcb, Mcb, Ncb, Ocb) local Pcb = Brain.simCal [Mcb] or { } local Qcb = Lcb._OVERRIDE_GAMEMODE or Lcb._gamemode or (Lcb.infinite and "infinite") or "story" local Rcb = Lcb.id == "__DAILY_CHALLENGE" local Scb = (Brain.live.event and Lcb.id == Brain.live.event.level) and true or false local Tcb = { _is_daily_challenge = Rcb or nil, _is_event_challenge = Scb or nil, _use_max = Lcb._use_max, world = Lcb.world, id = Lcb.id, _override_multiplayer_diff_scale = Lcb._override_multiplayer_diff_scale, _no_multiplayer_scale = Lcb._no_multiplayer_scale, portal_group = Lcb.portal_group, _raid_multiplayer_scale = Lcb._raid_multiplayer_scale } local Ucb = tonumber(Lcb.story_world_rank) or # Utils.list(Core.WLO.WORLD_ORDER) local Vcb = (Qcb == "infinite") and "challenge" or Qcb local Wcb = Hcb(Core.LU.calculate_world_diff_scale, Ucb, Vcb, nil, Lcb.map, Tcb) or 1 local Xcb = Hcb(Core.LU.calculate_level_diff_scale, tonumber(Lcb.story_level_rank) or 6, (Qcb == "infinite" or Qcb == "raid") and Qcb or (Qcb == "challenge" and "challenge" or "story"), Tcb) or 1 local Ycb = Hcb(Core.LU.calculate_multiplayer_diff_scale, Qcb, 1, Tcb) or 1 local Zcb = Lcb._override_waves local _cb = Core.Waves [(Zcb and Zcb.template) or Lcb.waves or Lcb.id] local adb = _cb if _cb and _cb.template and Core.Waves [_cb.template] then adb = Core.Waves [_cb.template] end local bdb = (Zcb and Zcb.infinite) or Lcb.infinite or (_cb and _cb.infinite) or false local cdb = (_cb and _cb.initial_unit_configs) or (adb and adb.initial_unit_configs) or { } if bdb and Lcb._infinite_bound_level then local ddb = false for edb, fdb in pairs(cdb) do if type(fdb) == "table" and fdb.spawn_tier_normal then ddb = true end end if not ddb then bdb = false end end local gdb = Utils.list((_cb and (_cb.override_waves or _cb.waves)) or (adb and adb.waves)) local hdb = Ncb or Lcb._challenge local idb = Core.CAR.challenges and Core.CAR.challenges [hdb or ""] or nil local jdb, kdb = 0, 0 for ldb, mdb in pairs(idb and idb.enemy_buffs or { }) do if type(mdb) == "table" and mdb.name == "damage_reduction" then jdb = jdb + (tonumber(mdb.params and mdb.params.reduction) or 0) end if type(mdb) == "table" and mdb.name == "healing" then kdb = kdb + (tonumber(mdb.params and mdb.params.percent) or 0) end end local ndb = Lcb._difficulty or ((bdb and not Lcb._dungeon and not Lcb._infinite_bound_level) and "Hard") or "Normal" local odb = ((not bdb) or (not Lcb._dungeon and not Lcb._infinite_bound_level)) and (Core.GS.DIFFICULTY_HEALTH_SCALES and Core.GS.DIFFICULTY_HEALTH_SCALES [ndb]) or 1 local pdb = Wcb * Xcb * Ycb * odb * (idb and tonumber(idb.enemy_health_scale) or 1) * (tonumber(Lcb._lobby_difficulty_scale) or 1) * (tonumber(Lcb._world_diff_scale_multiplier) or 1) local qdb = Hcb(Core.LU.calculate_world_resource_scale, Ucb, Vcb) or 2.6 local rdb = (_cb and tonumber(_cb.wave_max_time)) or (adb and tonumber(adb.wave_max_time)) or Simulator.PRIOR.waveTimer local sdb, tdb = Lcb.map, (Brain.maps [Lcb.map] or { }).lanes local udb = tonumber(Lcb._portal_lanes) if udb and udb >= 1 and type(tdb) == "table" and # tdb > udb then sdb = tostring(Lcb.map) .. "~" .. udb local vdb = { } for wdb = 1, udb do vdb [wdb] = tdb [wdb] end tdb = # vdb >= 2 and vdb or nil end local zdb = { key = Mcb, ld = Lcb, map = sdb, infinite = bdb and true or false, list = gdb, configs = cdb, gm = Qcb, total = (type(Lcb._dungeon) == "table" and 15) or tonumber(Lcb._override_infinite_bound_waves) or Simulator.boundEnd(Lcb, # gdb, Mcb) or (((not bdb) or Lcb._infinite_bound_level) and # gdb > 0 and # gdb) or (Lcb._infinite_bound_level and 15) or (bdb and Simulator.INF_CAP) or 30, hpRaw = pdb, hpScale = pdb * (tonumber(Pcb.hpCal) or (function() local xdb, ydb = pcall(Reports.hiveHp, Mcb) return xdb and ydb or nil end)() or 1), speedScale = idb and tonumber(idb.enemy_speed_scale) or 1, costScale = idb and tonumber(idb.player_cost_scale) or 1, rangeScale = idb and tonumber(idb.player_range_scale) or 1, red = math.min(0.75, jdb), regen = kdb, shield = idb and tonumber(idb.enemy_additional_shield) or 0, flyChance = hdb == "flying_enemies" and 1 or 0, armorAll = hdb == "armored_enemies" or nil, bounty = qdb * (tonumber(Pcb.bountyCal) or 1), payBase = qdb, pay = qdb * (tonumber(Pcb.yenCal) or 1), waveSec = tonumber(Pcb.waveSec) or Simulator.PRIOR.waveSec, walk = Simulator.mapWalk(sdb) or Simulator.mapWalk(Lcb.map) or tonumber(Pcb.walk) or Simulator.PRIOR.walk, waveTimer = rdb, skipDelay = tonumber(Pcb.skipDelay) or Simulator.PRIOR.skipDelay, autoskip = Simulator.autoskip(), voteAt = tonumber(Pcb.voteAt) or rdb / 2, votePer = tonumber(Pcb.votePer) or Simulator.PRIOR.votePer, eff = Simulator.effFor(Pcb), voteWave = type(Pcb.voteWave) == "table" and Pcb.voteWave or nil, spawnGap = tonumber(Pcb.spawnGap) or Simulator.PRIOR.spawnGap, baseHp = tonumber(Pcb.baseHp) or (_cb and tonumber(_cb.base_health)) or (adb and tonumber(adb.base_health)) or Simulator.PRIOR.baseHp, baseRel = tonumber(Pcb.baseRel) or Simulator.PRIOR.baseRel, timerAt = type(Pcb.timerAt) == "table" and Pcb.timerAt or { }, start = tonumber(Pcb.start) or (Lcb._dungeon and 2500) or (_cb and tonumber(_cb.initial_resource)) or (adb and tonumber(adb.initial_resource)) or Simulator.PRIOR.start, mod = Ocb, cal = Pcb, skinSet = Hunt.skinSet(Lcb), laneList = tdb, bossEvery = tonumber(Lcb._override_infinite_boss_interval) or 10, final = Lcb._final, skip = Lcb._skip or { }, zone = type(Lcb._dungeon) == "table" and Config.killZone ~= false } zdb.cfgById = { } for Adb, Bdb in pairs(cdb) do if type(Bdb) == "table" and Bdb.unit_id then zdb.cfgById [Bdb.unit_id] = Adb local Cdb = Core.Units [Bdb.unit_id] if Simulator.flies(Cdb) then zdb.mapFly = true end local Ddb = type(Cdb) == "table" and type(Cdb.enemy_modifiers) == "table" and Cdb.enemy_modifiers or { } if Ddb.Armor or Ddb.Ice or zdb.armorAll then zdb.mapMods = true end end end if Hunt.is(Lcb.id) then Simulator.applyFx(zdb, Hunt.fx(Hunt.weatherNow())) end local Edb, Fdb = pcall(Hunt.charmDmg, Lcb) if Edb and (tonumber(Fdb) or 0) > 0 then if not zdb.fx then Simulator.applyFx(zdb, Hunt.fx("clear")) end zdb.fx.dmg = zdb.fx.dmg * (1 + Fdb) end return zdb end function Simulator.applyFx(Gdb, Hdb) if not Hdb then return Gdb end Gdb.fx = Hdb Gdb.rangeScale = Gdb.rangeScale * Hdb.range Gdb.costScale = Gdb.costScale * Hdb.cost Gdb.speedScale = Gdb.speedScale * Hdb.speed Gdb.regen = (Gdb.regen or 0) + Hdb.regen Gdb.shield = (Gdb.shield or 0) + (Hdb.shield or 0) Gdb.flyChance = (Gdb.flyChance or 0) + (Hdb.fly or 0) Gdb.noCC, Gdb.shroud = Hdb.noCC, Hdb.shroud if (Hdb.ice or 0) > 0 then Gdb.iceShare, Gdb.mapMods = Hdb.ice, true end Gdb.hpRaw, Gdb.hpScale = Gdb.hpRaw * Hdb.hp, Gdb.hpScale * Hdb.hp return Gdb end function Simulator.fxDps(Idb, Jdb) local Kdb = Idb.fx if not Kdb then return 1 end local Ldb, Mdb = Units.types(Jdb) local Ndb = Kdb.dmg / Kdb.cd * (1 + (Kdb.kinds [Mdb] or 0)) if Mdb == "physical" and Kdb.crit > 0 then Ndb = Ndb * (1 + Kdb.crit * 0.5) end return Ndb end function Simulator.waveYen(Odb, Pdb, Qdb) local Rdb if Odb.infinite then Rdb = (Core.LU.Infinite and Core.LU.Infinite.initial_wave_template and Core.LU.Infinite.initial_wave_template.wave_reward) or 1000 else Rdb = Odb.list [Pdb] and tonumber(Odb.list [Pdb].wave_reward) or 0 end return Rdb * (Qdb or Odb.pay or 1) end function Simulator.waveScale(Sdb, Tdb, Udb) if Sdb.infinite then return Hcb(Core.LU.Infinite.infinite_diff_scale, Tdb, Sdb.ld.id) or 1 end return Udb and tonumber(Udb.wave_diff_scale) or 1 end function Simulator.baseStats(Vdb, Wdb) local Xdb = Vdb.configs [Wdb] if type(Xdb) ~= "table" then return nil end local Ydb = Xdb.override_stats and (Xdb.override_stats [1] or Xdb.override_stats ["1"]) or { } return tonumber(Ydb.health) or 20, tonumber(Ydb.speed) or 1, tonumber(Ydb.resource_reward) or 30 end function Simulator.enemy(Zdb, _db, aeb, beb) local ceb, deb, eeb = Simulator.baseStats(Zdb, _db) if not ceb then return nil end ceb = ceb * Zdb.hpScale * Simulator.waveScale(Zdb, aeb, beb) local feb = Core.Units [Zdb.configs [_db].unit_id] or { } local geb = { hp = ceb, max = ceb, speed = deb * Zdb.speedScale, bounty = eeb * Zdb.bounty, boss = tostring(_db):find("boss") ~= nil, p = 0, sh = (tonumber(feb.shield) or 0) + (Zdb.shield or 0), fly = Simulator.flies(feb) } local heb = type(feb.enemy_modifiers) == "table" and feb.enemy_modifiers or { } geb.fire, geb.soar = heb.Fire and true or nil, heb.Soaring and true or nil geb.armor, geb.ice = (heb.Armor or Zdb.armorAll) and true or nil, heb.Ice and true or nil if not geb.ice and (Zdb.iceShare or 0) > 0 then if geb.boss then geb.iceAvg = Zdb.iceShare else Zdb.iceAcc = (Zdb.iceAcc or 0) + Zdb.iceShare if Zdb.iceAcc >= 1 then geb.ice, geb.iceKey, Zdb.iceAcc = true, "Ice@snow", Zdb.iceAcc - 1 end end end Simulator.enemyTraits(geb, feb, ceb) return geb end function Simulator.modRead(ieb) if type(ieb) ~= "table" or type(ieb.r) ~= "table" or # ieb.r < 8 then return nil end local jeb = # ieb.r if ieb._n == jeb and ieb._last == ieb.r [jeb] and ieb._f ~= nil then return ieb._f end local keb = table.clone(ieb.r) table.sort(keb) local leb, meb, neb = keb [math.floor(jeb / 2) + 1], keb [math.floor(jeb / 4) + 1], keb [math.min(jeb, math.floor(jeb * 3 / 4) + 1)] ieb._n, ieb._last = jeb, ieb.r [jeb] ieb._f = (meb >= leb * 0.7 and neb <= leb * 1.4) and leb or false ieb.f = ieb._f or nil return ieb._f end function Simulator.modFactor(oeb) local peb = Simulator.modRead(Brain.enemyMods [oeb]) if peb then return peb end local qeb = Reports.hive() local reb = qeb and type(qeb.enemyMods) == "table" and qeb.enemyMods [oeb] if type(reb) == "table" and tonumber(reb.f) and ((tonumber(reb.players) or 0) >= 2 or (tonumber(reb.pairs) or 0) >= 16) then return tonumber(reb.f) end return Simulator.MOD_GUESS [oeb] or 1 end Simulator.MOD_GUESS = { ["Ice@snow"] = 1 / 3 } function Simulator.modHit(seb, teb) local ueb = 1 if seb.armor and teb.full then ueb = ueb * Simulator.modFactor("Armor") end if seb.ice and not teb.fireDmg then ueb = ueb * Simulator.modFactor(seb.iceKey or "Ice") end if seb.iceAvg and not teb.fireDmg then ueb = ueb / (1 - seb.iceAvg + seb.iceAvg / Simulator.modFactor("Ice@snow")) end return ueb end function Simulator.flies(veb) return type(veb) == "table" and ((type(veb.enemy_modifiers) == "table" and veb.enemy_modifiers.Flying) or (type(veb.passives) == "table" and veb.passives.Flying)) and true or false end function Simulator.enemyTraits(web, xeb, yeb) for zeb, Aeb in pairs(type(xeb.buffs) == "table" and xeb.buffs or { }) do if type(Aeb) == "table" and Aeb.name == "healing" and type(Aeb.params) == "table" then web.regen = (web.regen or 0) + (tonumber(Aeb.params.percent) or 0) / math.max(0.1, tonumber(Aeb.params.healing_rate) or 1) end end if type(xeb.barrier) == "table" and tonumber(xeb.barrier.health_ratio) then local Beb = 0 for Ceb, Deb in pairs(type(xeb.barrier.buffs) == "table" and xeb.barrier.buffs or { }) do if type(Deb) == "table" and Deb.name == "damage_reduction" and type(Deb.params) == "table" then Beb = Beb + (tonumber(Deb.params.reduction) or 0) end end web.bar = yeb * tonumber(xeb.barrier.health_ratio) / math.max(0.1, 1 - Beb) end local Eeb = xeb.death_spawn if type(Eeb) == "table" and type(Eeb.units) == "table" then for Feb, Geb in pairs(Eeb.units) do if type(Geb) == "table" and tonumber(Geb.health_ratio) then web.split = { n = tonumber(Eeb.amount) or 1, ratio = tonumber(Geb.health_ratio), unit = Geb.unit } break end end end end function Simulator.wave(Heb, Ieb, Jeb) local Keb = { } Heb.iceAcc = (Ieb * 0.6180339887) % 1 if not Heb.infinite then local Leb = Heb.list [Ieb] if not Leb then return Keb, 0 end for Meb, Neb in ipairs(Utils.list(Leb.wave)) do for Oeb = 1, tonumber(Neb.amount) or 1 do local Peb = Simulator.enemy(Heb, Neb.unit, Ieb, Leb) if Peb then table.insert(Keb, Peb) end end end else local Qeb = Hcb(Core.LU.Infinite.infinite_amt_scale, Ieb, Heb.ld) or 10 local Reb, Seb = { }, 0 for Teb, Ueb in pairs(Heb.configs) do if type(Ueb) == "table" and Ueb.spawn_tier_normal then local Veb = Hcb(Core.LU.Infinite.infinite_get_normal_unit_chance, Ueb, Ieb) or 0 if Veb > 0 then table.insert(Reb, { k = Teb, c = Veb }) Seb = Seb + Veb end end end table.sort(Reb, function(Web, Xeb) return Web.k < Xeb.k end) local Yeb, Zeb = math.floor(Qeb + 0.5), 0 for _eb, afb in ipairs(Reb) do local bfb = (_eb == # Reb) and math.max(0, Yeb - Zeb) or math.floor(Yeb * afb.c / Seb + 0.5) for cfb = 1, bfb do local dfb = Simulator.enemy(Heb, afb.k, Ieb) if dfb then table.insert(Keb, dfb) end end Zeb = Zeb + bfb end local efb = Heb.ld._infinite_bound_level and Ieb == Heb.total if efb then local ffb, gfb = { }, 0 for hfb, ifb in pairs(Heb.configs) do if type(ifb) == "table" and tonumber(ifb.spawn_tier_boss) and (Hcb(Core.LU.Infinite.infinite_get_boss_unit_chance, ifb, Ieb) or 0) > 0 then local jfb = Simulator.baseStats(Heb, hfb) if jfb then table.insert(ffb, { k = hfb, hp = jfb, tier = tonumber(ifb.spawn_tier_boss) }) gfb = gfb + jfb end end end table.sort(ffb, function(kfb, lfb) return kfb.tier > lfb.tier end) local mfb = type(Heb.ld._dungeon) == "table" and (tonumber(Heb.ld._dungeon.depth) or 0) >= 20 local nfb = ffb [1] if nfb and not mfb then local ofb = gfb / # ffb for pfb, qfb in ipairs(ffb) do if math.abs(qfb.hp - ofb) < math.abs(nfb.hp - ofb) then nfb = qfb end end end if nfb then local rfb = Simulator.enemy(Heb, nfb.k, Ieb) if rfb then rfb.boss = true table.insert(Keb, rfb) end end elseif Ieb % Heb.bossEvery == 0 and not Heb.skip [tostring(Ieb)] then local sfb = math.max(1, math.min(6, math.floor(Ieb / Heb.bossEvery))) for tfb, ufb in pairs(Heb.configs) do if type(ufb) == "table" and tonumber(ufb.spawn_tier_boss) == sfb then local vfb = Simulator.enemy(Heb, tfb, Ieb) if vfb then vfb.boss = true table.insert(Keb, vfb) end break end end end if Heb.final and tonumber(Heb.final.wave) == Ieb and Heb.final.cfg then local wfb = Simulator.enemy(Heb, Heb.final.cfg, Ieb) if wfb then wfb.boss = true table.insert(Keb, wfb) end end end local xfb = Heb.fx and Heb.fx.starfall local yfb = xfb and Heb.cfgById and Heb.cfgById.star_golem_1 if yfb and (tonumber(xfb.perWave) or 0) > 0 then if Ieb <= 1 then Heb.starAcc = 0 end Heb.starAcc = (Heb.starAcc or 0) + xfb.perWave while Heb.starAcc >= 1 do local zfb = Simulator.enemy(Heb, yfb, Ieb) if zfb then local Afb = tonumber(xfb.hpK) or 1 zfb.hp, zfb.max = zfb.hp * Afb, zfb.max * Afb table.insert(Keb, zfb) end Heb.starAcc = Heb.starAcc - 1 end end local Bfb = tonumber(Heb.cal and Heb.cal.countCal) if Bfb and not Jeb and math.abs(Bfb - 1) > 0.05 and # Keb > 0 then local Cfb = math.max(1, math.floor(# Keb * Bfb + 0.5)) local Dfb = # Keb if Cfb > Dfb then for Efb = 1, Cfb - Dfb do local Ffb = Keb [((Efb - 1) % Dfb) + 1] table.insert(Keb, table.clone(Ffb)) end else for Gfb = 1, Dfb - Cfb do table.remove(Keb) end end end return Keb, Simulator.waveYen(Heb, Ieb) end Simulator._masks = { } Simulator._walk = { } function Simulator.mapWalk(Hfb) if not Hfb then return nil end if Simulator._walk [Hfb] ~= nil then return Simulator._walk [Hfb] or nil end local Ifb = Brain.maps [Hfb] and tonumber(Brain.maps [Hfb].walk) if not Ifb then local Jfb = { } for Kfb, Lfb in pairs(Brain.simCal) do local Mfb = type(Lfb) == "table" and tonumber(Lfb.walk) and (tostring(Kfb):match("^st:([^:]+)") or tostring(Kfb):match("^rd:([^:]+)") or tostring(Kfb):match("^inf:([^:]+)")) local Nfb = Mfb and Core.Levels [Mfb] if Nfb and Nfb.map == Hfb then table.insert(Jfb, tonumber(Lfb.walk)) end end if # Jfb > 0 then table.sort(Jfb) Ifb = Jfb [math.ceil(# Jfb / 2)] end end if not Ifb then local Ofb = Reports.hive() local Pfb = Ofb and type(Ofb.mapInfo) == "table" and Ofb.mapInfo [Hfb] if type(Pfb) == "table" and (tonumber(Pfb.runs) or 0) >= 3 then Ifb = tonumber(Pfb.walk) end end Simulator._walk [Hfb] = Ifb or false return Ifb end function Simulator.mask(Qfb, Rfb) local Sfb = math.max(5, math.min(60, math.floor((Rfb or 10) / 5 + 0.5) * 5)) local Tfb = tostring(Qfb) .. ":" .. Sfb if Simulator._masks [Tfb] then return Simulator._masks [Tfb] end local Ufb = Brain.maps [Qfb] or Brain.maps [tostring(Qfb):match("^(.-)~%d+$") or ""] local Vfb = Ufb and Ufb.bins and Ufb.bins [tostring(Sfb)] local Wfb = { } if type(Vfb) == "string" and # Vfb == Simulator.BINS then for Xfb = 1, Simulator.BINS do Wfb [Xfb] = Vfb:sub(Xfb, Xfb) == "1" end local Yfb = Ufb.binsL and Ufb.binsL [tostring(Sfb)] if type(Yfb) == "table" then Wfb.lanes = { } for Zfb, _fb in pairs(Yfb) do local agb = { } for bgb = 1, Simulator.BINS do agb [bgb] = _fb:sub(bgb, bgb) == "1" end Wfb.lanes [Zfb] = agb end end else local cgb = math.min(0.7, Sfb / 70) for dgb = 1, Simulator.BINS do local egb = (dgb - 0.5) / Simulator.BINS Wfb [dgb] = egb >= 0.1 and egb <= 0.1 + cgb end end Simulator._masks [Tfb] = Wfb return Wfb end function Simulator.laneList(fgb) local ggb, hgb = { }, { } for igb, jgb in ipairs(fgb or { }) do local kgb = tostring(jgb.lane) if jgb.lane ~= nil and not ggb [kgb] then ggb [kgb] = true table.insert(hgb, kgb) end end table.sort(hgb) return # hgb >= 2 and hgb or nil end function Simulator.spotMask(lgb, mgb, ngb) local ogb = math.floor(ngb + 0.5) local pgb = string.format("%d,%d,%d", mgb.X, mgb.Z, ogb) local qgb = lgb.cache [pgb] if qgb then return qgb end qgb = { } local rgb = ogb * ogb local sgb = Simulator.laneList(lgb.samples) ~= nil if sgb then qgb.lanes = { } end for tgb, ugb in ipairs(lgb.samples) do local vgb, wgb = ugb.pos.X - mgb.X, ugb.pos.Z - mgb.Z if vgb * vgb + wgb * wgb <= rgb then local xgb = math.min(Simulator.BINS, math.floor(ugb.prog * Simulator.BINS) + 1) qgb [xgb] = true if sgb then local ygb = tostring(ugb.lane) qgb.lanes [ygb] = qgb.lanes [ygb] or { } qgb.lanes [ygb] [xgb] = true end end end lgb.cache [pgb] = qgb return qgb end function Simulator.teamFrom(zgb, Agb) local Bgb = { } local Cgb = Brain.maps [Agb] or Brain.maps [tostring(Agb):match("^(.-)~%d+$") or ""] local Dgb = not (Cgb and Cgb.hill == false) for Egb, Fgb in ipairs(zgb) do local Ggb = Core.Units [Fgb.unit_id] local Hgb = Ggb and Units.rows(Fgb.unit_id) if Hgb then table.insert(Bgb, { u = Fgb, id = Fgb.unit_id, data = Ggb, rows = Hgb, cap = Units.cap(Fgb), farm = Units.isFarm(Fgb.unit_id), placeable = (not Ggb.hill_unit) or Dgb }) end end return Bgb end function Simulator.sig(Igb) local Jgb = { } for Kgb, Lgb in ipairs(Igb) do table.insert(Jgb, tostring(Lgb.unit_id)) end table.sort(Jgb) return table.concat(Jgb, ",") end Simulator.WALK_STUDS = 1.9 function Simulator.boxBins(Mgb, Ngb, Ogb) local Pgb = 2 * (Ngb or 0) * (tonumber(Mgb.walk) or Simulator.PRIOR.walk) / Simulator.WALK_STUDS return math.max(1, math.min(Ogb, math.floor(Pgb * Simulator.BINS + 0.5))) end Simulator._holds, Simulator._holdsFrom = { }, { } function Simulator.holds(Qgb) local Rgb = Simulator._holds [Qgb] if Rgb == nil then Rgb = false local Sgb, Tgb = pcall(Units.toggleOf, Qgb) if Sgb and Tgb then Rgb, Simulator._holdsFrom [Qgb] = true, tonumber(Tgb.from) or 1 end local Ugb = not Rgb and Units.rows(Qgb) for Vgb = 0, Ugb and Ugb.max or - 1 do local Wgb = Ugb [Vgb] local Xgb, Ygb = false, nil if Wgb then Xgb, Ygb = pcall(Units.attackInfo, Wgb.atk, Wgb.range) end if Xgb and Ygb and ((Ygb.stun or 0) > 0 or (Ygb.slowTime or 0) > 0) then Rgb, Simulator._holdsFrom [Qgb] = true, Vgb break end end Simulator._holds [Qgb] = Rgb end return Rgb, Simulator._holdsFrom [Qgb] end Simulator.GALE_SLOW = 0.3 function Simulator.memoOn() Simulator.memo, Simulator.memoAt = setmetatable({ }, { __mode = "k" }), os.clock() end function Simulator.run(Zgb, _gb, ahb) local bhb = Simulator.memo if bhb and os.clock() - (Simulator.memoAt or 0) > 180 then Simulator.memo, bhb = nil, nil end if not bhb then return Simulator.runRaw(Zgb, _gb, ahb) end for chb in pairs(ahb or { }) do if chb ~= "hpMul" then return Simulator.runRaw(Zgb, _gb, ahb) end end local dhb = { tostring(Zgb.zone), tostring(ahb and ahb.hpMul or 1) } for ehb, fhb in ipairs(_gb) do dhb [# dhb + 1] = tostring(fhb.u and fhb.u.uuid) .. ":" .. tostring(fhb.id) .. ":" .. tostring(fhb.u and fhb.u.xp) .. ":" .. tostring(fhb.u and fhb.u._equipped_relic) end local ghb = table.concat(dhb, ",") local hhb = bhb [Zgb] if not hhb then hhb = { } bhb [Zgb] = hhb end local ihb = hhb [ghb] if not ihb then ihb = Simulator.runRaw(Zgb, _gb, ahb) hhb [ghb] = ihb Simulator.memoMiss = (Simulator.memoMiss or 0) + 1 else Simulator.memoHit = (Simulator.memoHit or 0) + 1 end return ihb end function Simulator.runRaw(jhb, khb, lhb) lhb = lhb or { } local mhb = lhb.hpMul or 1 local nhb = Simulator.DT local ohb = 0 if lhb.layout and lhb.layout.laneList == nil then lhb.layout.laneList = Simulator.laneList(lhb.layout.samples) or false end local phb = not lhb.noLanes and ((lhb.layout and lhb.layout.laneList) or jhb.laneList) or nil local qhb, rhb = phb and # phb or 0, 0 local function shb(thb, uhb, vhb) local whb = qhb >= 2 and uhb.lane and thb.mask.lanes if whb then local xhb = whb [uhb.lane] return xhb ~= nil and xhb [vhb] == true end return thb.mask [vhb] and true or false end local yhb, zhb, Ahb, Bhb = jhb.start, 0, 0, 3 local Chb, Dhb, Ehb, Fhb, Ghb, Hhb, Ihb = { }, { }, { }, { }, { }, { }, { } local Jhb, Khb = { }, { } local Lhb = jhb.baseHp local Mhb = { } for Nhb = 1, jhb.total do Mhb [Nhb] = Simulator.waveYen(jhb, Nhb) end local Ohb = true local Phb = Simulator.wave(jhb, 1, true) local Qhb = { } local function Rhb(Shb, Thb, Uhb) local Vhb = Qhb [Shb] if not Vhb then Vhb = { } Qhb [Shb] = Vhb end local Whb = Uhb and - 1 - Thb or Thb local Xhb = Vhb [Whb] if not Xhb then Xhb = Units.dps(Shb.u, Thb, jhb.mod, Uhb) * (1 - jhb.red) * Simulator.fxDps(jhb, Shb.id) * Units.skinMul(Shb.u, jhb.skinSet) Vhb [Whb] = Xhb end return Xhb end local Yhb, Zhb = nil, 0 for _hb, aib in ipairs(khb) do local bib = aib.rows [0] if not aib.farm and bib then local cib = Rhb(aib, 0) * (tonumber(bib.cd) or 2) / math.max(1, Units.attackInfo(bib.atk, bib.range).n) if cib > Zhb then Yhb, Zhb = aib.id, cib end end end local dib = 0 for eib, fib in ipairs(Phb) do dib = dib + (fib.sh or 0) end dib = # Phb > 0 and dib / # Phb or 0 local gib = jhb.eff or Simulator.PRIOR.eff local hib = { } local function iib(jib) if jib > jhb.total then return nil end if not hib [jib] then local kib = 0 for lib, mib in ipairs((Simulator.wave(jhb, jib))) do kib = kib + mib.hp + (mib.bar or 0) end hib [jib] = kib * mhb end return hib [jib] end local nib if lhb.train then nib = Simulator.BINS for oib, pib in ipairs(khb) do if not pib.farm and pib.u.uuid ~= lhb.train then local qib = Simulator.mask(jhb.map, Units.range(pib.u, 0) * jhb.rangeScale) for rib = 1, Simulator.BINS do if qib [rib] then nib = math.min(nib, rib) break end end end end end local sib, tib, uib, vib = false, 0, 1, false local wib, xib = { }, { } local yib, zib, Aib = 0, 0, 0 local Bib = 0 local function Cib() return 1 + Units.BLESS.dmg * math.min(Units.BLESS.cap, Bib) end local Dib = jhb.zone and not lhb.noZone and not lhb.layout local Eib = lhb.layout and lhb.layout.style local Fib = Dib or Eib == "box" local function Gib(Hib) Hib.dps = Hib.e.farm and 0 or Rhb(Hib.e, Hib.lvl, true) Hib.dpsAll = Hib.e.farm and 0 or Rhb(Hib.e, Hib.lvl) local Iib = Units.galeOf(Hib.e.id) Hib.gale = Iib and Hib.lvl >= Iib.from and Iib or nil if Hib.gale then vib = true end local Jib = lhb.layout and not Hib.later and lhb.layout.spots [Hib.e.id] local Kib = Units.range(Hib.e.u, Hib.lvl) * jhb.rangeScale * (Units.SUMMONER [Hib.e.id] and Units.SUMMONER [Hib.e.id].reach * (1 + Units.BLESS.range * math.min(Units.BLESS.cap, Bib)) or 1) Hib.mask = Jib and Simulator.spotMask(lhb.layout, Jib, Kib) or Simulator.mask(jhb.map, Kib) local Lib = lhb.place and lhb.place [Hib.e.id] if Lib and not Hib.e.farm then local Mib = 0 for Nib = 1, Simulator.BINS do if Hib.mask [Nib] then Mib = Mib + 1 end end Mib = math.max(1, Mib) if Lib == "end" then Mib = Simulator.boxBins(jhb, Kib, Mib) end local Oib = { } for Pib = 1, Simulator.BINS do Oib [Pib] = (Lib == "start" and Pib <= Mib) or (Lib == "end" and Pib > Simulator.BINS - Mib) or (Lib == "best" and Hib.mask [Pib] == true) or false end Hib.mask = Oib end if (Dib or (Eib == "box" and Hib.later)) and not Lib and not Hib.e.farm and Hib.e.u.uuid ~= lhb.train then local Qib = 0 for Rib = 1, Simulator.BINS do if Hib.mask [Rib] then Qib = Qib + 1 end end Qib = Simulator.boxBins(jhb, Kib, Qib) local Sib = Hib.later and Simulator.BINS - 1 or Simulator.BINS local Tib = { } for Uib = 1, Simulator.BINS do Tib [Uib] = Uib > Sib - Qib and Uib <= Sib end Hib.mask = Tib end if Eib == "fast" and Hib.later and not Lib and not Hib.e.farm and Hib.e.u.uuid ~= lhb.train then local Vib = 0 for Wib = 1, Simulator.BINS do if Hib.mask [Wib] then Vib = Vib + 1 end end local Xib = { } for Yib = 1, Simulator.BINS do Xib [Yib] = Yib <= Vib end Hib.mask = Xib end if Fib and Hib.ctlN and Hib.e.u.uuid ~= lhb.train then local Zib = 0 for _ib = 1, Simulator.BINS do if Hib.mask [_ib] then Zib = Zib + 1 end end Zib = math.max(1, Zib) local ajb, bjb for cjb, djb in ipairs(Ehb) do if djb ~= Hib and not djb.ctlN and not djb.e.farm and not djb.buff and djb.mask then for ejb = 1, Simulator.BINS do if djb.mask [ejb] then ajb, bjb = math.min(ajb or ejb, ejb), math.max(bjb or ejb, ejb) end end end end ajb, bjb = ajb or 16, bjb or Simulator.BINS local fjb = ({ ajb, bjb, (ajb + bjb) / 2, ajb - (bjb - ajb) / 2 }) [Hib.ctlN] if fjb then local gjb = math.max(1, math.min(Simulator.BINS - Zib + 1, math.floor(fjb - (Zib - 1) / 2 + 0.5))) local hjb = { } for ijb = 1, Simulator.BINS do hjb [ijb] = ijb >= gjb and ijb <= gjb + Zib - 1 end Hib.mask = hjb end end if nib and Hib.e.u.uuid == lhb.train then local jjb = 0 for kjb = 1, Simulator.BINS do if Hib.mask [kjb] then jjb = jjb + 1 end end local ljb = math.max(1, nib - jjb + 1) local mjb = { } for njb = 1, Simulator.BINS do mjb [njb] = njb >= ljb and njb <= math.min(Simulator.BINS, ljb + jjb - 1) end Hib.mask = mjb end if not Dib and not lhb.layout and not Hib.later and not Hib.lo and Hib.e.id == Yhb and dib > 0 then local ojb = 0 for pjb = 1, Simulator.BINS do if Hib.mask [pjb] then ojb = ojb + 1 end end Hib.lo = ojb + 1 end if Hib.lo then local qjb = 0 for rjb = 1, Simulator.BINS do if Hib.mask [rjb] then qjb = qjb + 1 end end local sjb = math.max(1, math.min(Hib.lo, Simulator.BINS - qjb + 1)) local tjb = { } for ujb = 1, Simulator.BINS do tjb [ujb] = ujb >= sjb and ujb <= sjb + qjb - 1 end Hib.mask = tjb end Hib.width = 0 for vjb = 1, Simulator.BINS do if Hib.mask [vjb] then Hib.width = Hib.width + 1 end end local wjb = Hib.e.rows [math.min(Hib.lvl, Hib.e.rows.max)] local xjb = Units.attackInfo(wjb.atk, wjb.range) Hib.aoe, Hib.hits, Hib.bleed = xjb.n, xjb.hits, xjb.bleed Hib.stun, Hib.slowP, Hib.slowT, Hib.ccCd = xjb.stun, xjb.slowPct, xjb.slowTime, xjb.ccCd or 8 local yjb, zjb = Units.types(Hib.e.id) Hib.base = zjb Hib.full, Hib.fireDmg = xjb.full, yjb.fire_damage and true or false Hib.hill = (Hib.e.data.hill_unit or Hib.e.data.hybrid_placement) and true or false Hib.buff = Units.allyBuff(Hib.e.id) Hib.fx = Hib.e.farm and 0 or Units.teamEffect(wjb.atk, wjb.range, wjb.cd, true) Hib.breaks = xjb.breakShields Hib.mark = Units.passive(Hib.e.id).mark if Hib.mark then sib = true end Hib.drone = Units.DRONES [Hib.e.id] Hib.droneNow = Hib.drone and (Hib.lvl >= Hib.drone.from and Hib.drone.late or Hib.drone.early) or nil Hib.summonSh = Units.summonShare(Hib.e.id) local Ajb = wjb.active and Units.attackInfo(wjb.active) Hib.act = Ajb and not Hib.buff and (Ajb.stun > 0 or Ajb.allyCd or Ajb.selfDmg or Ajb.baseSafe) and Ajb or nil Hib.actId, Hib.actCd = wjb.active, tonumber(wjb.activeCd) or 60 Hib.train = lhb.train ~= nil and Hib.e.u.uuid == lhb.train Hib.cd = (tonumber(wjb.cd) or 2) * (jhb.fx and jhb.fx.cd or 1) local Bjb = Units.airFrom(Hib.e.id) Hib.air = Bjb ~= nil and Hib.lvl >= Bjb end local Cjb, Djb = (jhb.flyChance or 0) > 0, lhb.flyAcc or 0 local Ejb, Fjb = 1, 1 local function Gjb(Hjb) local Ijb, Jjb = 0, 0 for Kjb, Ljb in ipairs(Hjb) do Ijb = Ijb + (Ljb.sh or 0) Jjb = Jjb + Ljb.hp end if # Hjb > 0 then dib, Ejb, Fjb = Ijb / # Hjb, Jjb / # Hjb, # Hjb end end Gjb(Phb) local Mjb = jhb.fx and jhb.fx.cd or 1 local Njb, Ojb, Pjb = { }, 1, nil local function Qjb(Rjb) local Sjb = Brain.stack [Rjb.attack] return Sjb and Sjb.stacks and (Sjb.cap or 1) or nil end local function Tjb() table.clear(Njb) local Ujb, Vjb = 0, 0 local Wjb = { } local Xjb = { } for Yjb, Zjb in ipairs(Ehb) do if Zjb.buff then Xjb [Zjb.buff.attack] = (Xjb [Zjb.buff.attack] or 0) + 1 end end for _jb, akb in ipairs(Ehb) do if akb.buff then local bkb = Qjb(akb.buff) for ckb, dkb in pairs(akb.buff.types) do if bkb then Wjb [ckb] = math.min(bkb, (Wjb [ckb] or 0) + dkb * akb.buff.uptime) Njb [ckb] = math.max(Njb [ckb] or 0, Wjb [ckb]) else Njb [ckb] = math.max(Njb [ckb] or 0, dkb * math.min(1, Xjb [akb.buff.attack] * akb.buff.uptime)) end end end Ujb = Ujb + (akb.fx or 0) if akb.dps > 0 then for ekb = Simulator.BINS, Vjb + 1, - 1 do if akb.mask [ekb] then Vjb = ekb break end end end end Ojb = 1 + math.min(0.6, Ujb) Pjb = Vjb > 0 and Vjb / Simulator.BINS or nil end local function fkb(gkb) local hkb = gkb.dps * Ojb * (1 + (Njb [gkb.base] or 0)) if zhb < yib then hkb = hkb / (1 - zib) end if zhb < (gkb.selfTo or 0) then hkb = hkb * (1 + gkb.selfAdd) end return hkb end local function ikb(jkb, kkb, lkb) if not Cjb then return 1 end local mkb = Units.airFrom(jkb) if not mkb then return 1 end local nkb, okb = 0, 0 for pkb, qkb in ipairs(Ehb) do local rkb = fkb(qkb) nkb = nkb + rkb if qkb.air then okb = okb + rkb end end local skb = (nkb > 0 and okb / nkb or 0) < 0.5 and 2.5 or 1.5 if lkb then return mkb == 0 and skb or 1 + (skb - 1) * 0.4 end return kkb >= mkb and skb or 1 + (skb - 1) * math.min(1, (kkb + 1) / math.max(1, mkb)) end local function tkb() local ukb, vkb = 0, 0 for wkb, xkb in ipairs(Ehb) do local ykb = fkb(xkb) ukb = ukb + ykb vkb = vkb + Economy.shieldDps(ykb, xkb.cd, xkb.aoe, dib, Ejb, gib, xkb.hits) end return ukb > 0 and math.max(0.05, vkb / ukb) or 1 end local function zkb(Akb) if not Units.BLESS.by [Akb.id] or Bib >= Units.BLESS.cap then return 0 end local Bkb = 0 for Ckb, Dkb in ipairs(Ehb) do if Dkb.dps > 0 then Bkb = Bkb + fkb(Dkb) * (Units.SUMMONER [Dkb.e.id] and 1 or (Dkb.summonSh or 0)) end end for Ekb, Fkb in ipairs(wib) do Bkb = Bkb + fkb(Fkb.owner) * math.max(0.3, Fkb.owner.cd) / math.max(1, Fkb.owner.aoe) * Fkb.dmg * Fkb.share / Fkb.cd end return Bkb * Units.BLESS.dmg end local function Gkb(Hkb) local Ikb = Units.allyBuff(Hkb.id) if not Ikb then return 0 end local Jkb = 0 for Kkb, Lkb in ipairs(Ehb) do if Lkb.buff and Lkb.buff.attack == Ikb.attack then Jkb = Jkb + 1 end end local Mkb = Qjb(Ikb) if Jkb >= Units.buffCopies(Ikb) and not Mkb then return 0 end local Nkb = 0 for Okb, Pkb in ipairs(Ehb) do local Qkb = Ikb.types [Pkb.base] if Qkb then local Rkb = Mkb and math.max(0, math.min(Mkb, (Jkb + 1) * Qkb * Ikb.uptime) - math.min(Mkb, Jkb * Qkb * Ikb.uptime)) or Qkb * Ikb.uptime Nkb = Nkb + Pkb.dps * Rkb end end return Nkb end local function Skb() if Ohb then return end Ohb = true local Tkb = Mhb [Ahb] or 0 for Ukb, Vkb in ipairs(Ehb) do if Vkb.e.farm then Tkb = Tkb + Units.farm(Vkb.e.u, Vkb.lvl) end end yhb = yhb + Tkb table.insert(Ihb, { t = zhb, y = Tkb }) end local function Wkb(Xkb) if lhb.oldBuy then return 1 end local Ykb, Zkb = 0, 0 for _kb, alb in ipairs(Ehb) do if not alb.e.farm then Ykb, Zkb = Ykb + (alb.width or 0), Zkb + 1 end end if Zkb == 0 or Ykb <= 0 then return 1 end return Utils.clamp((Xkb.width or 0) / (Ykb / Zkb), 0.4, 1.6) end local function blb(clb, dlb) if lhb.oldBuy or lhb.noHold or ((dlb.stun or 0) <= 0 and (dlb.slowTime or 0) <= 0) then return 0 end local elb, flb, glb = 0, 0, 0 for hlb, ilb in ipairs(Ehb) do if (ilb.stun or 0) > 0 or (ilb.slowT or 0) > 0 then elb = elb + 1 end flb = flb + fkb(ilb) end local jlb = Simulator.mask(jhb.map, Units.range(clb.u, 0) * jhb.rangeScale) for klb = 1, Simulator.BINS do if jlb [klb] then glb = glb + 1 end end local llb = glb / (math.max(0.1, Pjb or 1) * Simulator.BINS) local mlb = Units.airFrom(clb.id) local nlb = (mlb == 0) and 1 or (1 - (1 - Core.FLY_GROUND) * math.min(1, jhb.flyChance or 0)) return Economy.holdGain(dlb, flb, llb * nlb, elb) end local function olb(plb) if lhb.oldBuy or Ahb < jhb.total or # Dhb == 0 or # Dhb > 2 then return 1 end local qlb = Dhb [1] for rlb, slb in ipairs(Dhb) do if slb.hp > qlb.hp then qlb = slb end end if qlb.fly and not plb.air then return Core.FLY_GROUND end local tlb = math.min(Simulator.BINS, math.floor(qlb.p * Simulator.BINS) + 1) if plb.mask [tlb] then return 1 end return (plb.mask [math.max(1, tlb - 2)] or plb.mask [math.min(Simulator.BINS, tlb + 2)]) and 0.3 or 0.02 end local ulb local function vlb(wlb, xlb) local ylb = (Ahb >= jhb.total and # Dhb <= 2) and math.max(gib, 0.95) or gib if Dhb [1] and not lhb.oldBuy then local zlb = xlb * math.max(0.3, wlb.cd) / math.max(1, wlb.aoe) ylb = math.max(ylb, math.min(lhb.landCap or ((dib > 0 or wlb.aoe <= 1.01) and 0.95 or Simulator.LAND_CAP), Ejb / math.max(1, zlb))) end local Alb = xlb * ylb * nhb / wlb.aoe local Blb = xlb * ylb * nhb - Alb local Clb = 0 local Dlb = false if wlb.train then wlb.tag = (wlb.tag or 0) + nhb / math.max(0.3, wlb.cd) Dlb = wlb.tag >= 1 end local Elb = (wlb.stun > 0 or wlb.slowT > 0) and zhb >= (wlb.ccAt or 0) and not jhb.noCC local Flb = wlb.base == "magic" for Glb, Hlb in ipairs(wlb.air and ulb or Dhb) do if Hlb.hp > 0 and not (Hlb.soar and not wlb.hill) and shb(wlb, Hlb, math.min(Simulator.BINS, math.floor(Hlb.p * Simulator.BINS) + 1)) and not (jhb.shroud and Hlb.p < jhb.shroud) then local Ilb = Hlb.fly and not wlb.air if wlb.bleed then Hlb.noRegen = zhb + 5 end if wlb.drone then Hlb.droneBy = wlb end if Elb and not Hlb.boss and not Ilb and not Hlb.fire and zhb >= (Hlb.ccFree or 0) then if wlb.stun > 0 then Hlb.stunTo, Hlb.ccFree = zhb + wlb.stun, zhb + wlb.stun + wlb.ccCd else Hlb.slowTo, Hlb.slowP, Hlb.ccFree = zhb + wlb.slowT, wlb.slowP, zhb + wlb.slowT + wlb.ccCd end end if (Hlb.sh or 0) > 0 and wlb.base ~= "true" then if wlb.breaks then Hlb.sh = 0 elseif Clb < math.ceil(wlb.aoe) then Hlb.sh = Hlb.sh - wlb.hits * nhb / math.max(0.3, wlb.cd) end else local Jlb = (Flb and Hlb.marks) and 1 + tib * math.min(uib, Hlb.marks) or 1 local Klb = Clb == 0 and Alb or math.min(Alb, Blb) if lhb.dealt then local Llb = wlb.owner and "drones" or Units.name(wlb.e.id) lhb.dealt [Llb] = (lhb.dealt [Llb] or 0) + math.min(Klb * Jlb, math.max(0, Hlb.hp) + (Hlb.bar or 0)) end local Mlb = Klb * Jlb * (Ilb and Core.FLY_GROUND or 1) * Simulator.modHit(Hlb, wlb) if Hlb.bar then local Nlb = math.min(Hlb.bar, Mlb) Hlb.bar, Mlb = Hlb.bar - Nlb, Mlb - Nlb if Hlb.bar <= 0 then Hlb.bar = nil end end Hlb.hp = Hlb.hp - Mlb if Clb > 0 then Blb = Blb - Klb end end if Dlb and Clb < math.ceil(wlb.aoe) then Hlb.tr = true end Clb = Clb + 1 if Clb > math.ceil(wlb.aoe) or (Clb > 1 and Blb <= 0) then break end end end if Dlb and Clb > 0 then wlb.tag = wlb.tag - 1 end if Elb and Clb > 0 then wlb.ccAt = zhb + math.max(0.3, wlb.cd) end end local function Olb(Plb) if Config.actives == false then return end for Qlb, Rlb in ipairs(Ehb) do local Slb = Rlb.act local Tlb = Slb and (Slb.globalCd and Rlb.actId or Rlb) if Slb and zhb >= (xib [Tlb] or 0) then Rlb.actReady = Rlb.actReady or zhb local Ulb, Vlb = 0, false for Wlb, Xlb in ipairs(Dhb) do if Slb.wholeMap or shb(Rlb, Xlb, math.min(Simulator.BINS, math.floor(Xlb.p * Simulator.BINS) + 1)) then Ulb = Ulb + 1 if Xlb.boss then Vlb = true end end end local Ylb = math.min((tonumber(Config.abilityHold) or 20) * math.max(1, Rlb.actCd / 60), Rlb.actCd * 0.3) local Zlb if Slb.baseSafe then Zlb = Plb >= 0.98 elseif Slb.allyCd then Zlb = # Dhb > 0 else Zlb = (Ulb > 0 and Vlb) or Ulb >= math.max(3, math.floor(# Dhb * 0.3)) or (Ulb >= 3 and zhb - Rlb.actReady >= Ylb * 0.5) or (Ulb >= 1 and zhb - Rlb.actReady >= Ylb) end if Zlb then xib [Tlb], Rlb.actReady = zhb + Rlb.actCd, nil if Slb.baseSafe then Aib = zhb + Slb.baseSafe end if Slb.allyCd then zib = zhb < yib and math.max(zib, Slb.allyCd.reduce) or Slb.allyCd.reduce yib = zhb + Slb.allyCd.time end if Slb.selfDmg then Rlb.selfTo, Rlb.selfAdd = zhb + Slb.selfDmg.time, Slb.selfDmg.add end if Slb.stun > 0 then for amb, bmb in ipairs(Dhb) do if (Slb.wholeMap or shb(Rlb, bmb, math.min(Simulator.BINS, math.floor(bmb.p * Simulator.BINS) + 1))) and not bmb.boss and zhb >= (bmb.ccFree or 0) then bmb.stunTo, bmb.ccFree = zhb + Slb.stun, zhb + Slb.stun + (Slb.ccCd or 8) end end end end end end end local cmb, dmb, emb = 0, math.huge, false while zhb < 3600 do if not Ohb and Ahb > 0 and ((# Chb == 0 and # Dhb == 0) or (jhb.autoskip and zhb >= dmb)) then Skb() Bhb = math.min(Bhb, zhb + jhb.skipDelay) end if Ahb < jhb.total and zhb >= Bhb then Skb() Ahb = Ahb + 1 local fmb = 0 for gmb, hmb in ipairs(Dhb) do fmb = fmb + hmb.hp end Ghb [Ahb] = fmb local imb = 0 for jmb, kmb in ipairs(Ehb) do imb = imb + fkb(kmb) end Jhb [Ahb] = imb local lmb = Simulator.wave(jhb, Ahb) for mmb, nmb in ipairs(lmb) do nmb.hp = nmb.hp * mhb nmb.max = nmb.hp if nmb.bar then nmb.bar = nmb.bar * mhb end nmb.at = zhb + (mmb - 1) * jhb.spawnGap if not nmb.fly and (jhb.flyChance or 0) > 0 then Djb = Djb + jhb.flyChance if Djb >= 1 then Djb = Djb - 1 nmb.fly = true end end if nmb.fly then Cjb = true local omb = (Core.GS.FLYING_HEALTH_MUL or { }) [(Core.Worlds [jhb.ld.world or ""] or { }).legend_stage and "legend_stage" or (jhb.ld._event_contract and "event_contract") or (jhb.gm == "story" and "story") or ""] if omb then nmb.hp = nmb.hp * (1 + omb) nmb.max = nmb.hp end end if qhb >= 2 then rhb = rhb + 1 nmb.lane = phb [(rhb - 1) % qhb + 1] end table.insert(Chb, nmb) end Gjb(lmb) Ohb = false Bhb = zhb + math.max(tonumber(jhb.timerAt [tostring(Ahb)]) or jhb.waveTimer, # lmb * jhb.spawnGap + 5) dmb = zhb + Simulator.voteSec(jhb, Ahb) end for pmb = # Chb, 1, - 1 do if Chb [pmb].at <= zhb then table.insert(Dhb, table.remove(Chb, pmb)) end end local qmb = 0 for rmb, smb in ipairs(Dhb) do local tmb = smb.speed if (smb.stunTo or 0) > zhb then tmb = 0 else local umb = (smb.slowTo or 0) > zhb and (smb.slowP or 0) or 0 tmb = tmb * (1 - math.max(umb, smb.galeSlow or 0)) end if smb.p < 1 then smb.p = math.min(1, smb.p + jhb.walk * tmb * nhb) elseif zhb >= Aib then Lhb = Lhb - jhb.baseRel * smb.max * nhb end local vmb = jhb.regen + (smb.regen or 0) if vmb > 0 and (smb.noRegen or 0) <= zhb then smb.hp = math.min(smb.max, smb.hp + smb.max * vmb * nhb) end if smb.p > qmb then qmb = smb.p end end Hhb [Ahb] = math.max(Hhb [Ahb] or 0, qmb) table.sort(Dhb, function(wmb, xmb) return wmb.p > xmb.p end) ulb = nil for ymb, zmb in ipairs(Dhb) do if zmb.fly then ulb = ulb or { } table.insert(ulb, zmb) end end if ulb then for Amb, Bmb in ipairs(Dhb) do if not Bmb.fly then table.insert(ulb, Bmb) end end end Olb(qmb) if sib then for Cmb, Dmb in ipairs(Dhb) do Dmb.marks = nil end for Emb, Fmb in ipairs(Ehb) do if Fmb.mark and Fmb.dps > 0 then for Gmb, Hmb in ipairs(Fmb.air and ulb or Dhb) do if shb(Fmb, Hmb, math.min(Simulator.BINS, math.floor(Hmb.p * Simulator.BINS) + 1)) then Hmb.marks = (Hmb.marks or 0) + 1 tib, uib = Fmb.mark.amp, Fmb.mark.max if (Hmb.sh or 0) <= 0 then local Imb = Fmb.mark.dot * fkb(Fmb) * math.max(0.3, Fmb.cd) / math.max(1, Fmb.aoe) * nhb if lhb.dealt then lhb.dealt [Units.name(Fmb.e.id)] = (lhb.dealt [Units.name(Fmb.e.id)] or 0) + math.min(Imb, math.max(0, Hmb.hp)) end Hmb.hp = Hmb.hp - Imb end break end end end end end if vib then for Jmb, Kmb in ipairs(Dhb) do Kmb.galeCov, Kmb.galeBy = nil, nil end for Lmb, Mmb in ipairs(Ehb) do local Nmb = Mmb.gale if Nmb and Mmb.dps > 0 then local Omb = fkb(Mmb) * math.max(0.3, Mmb.cd) / math.max(1, Mmb.aoe) for Pmb, Qmb in ipairs(Mmb.air and ulb or Dhb) do if Qmb.hp > 0 and not (Qmb.soar and not Mmb.hill) and shb(Mmb, Qmb, math.min(Simulator.BINS, math.floor(Qmb.p * Simulator.BINS) + 1)) then local Rmb = Qmb.fly and not Mmb.air Qmb.galeCov = math.min(1, (Qmb.galeCov or 0) + Nmb.duty) if not Rmb and not Qmb.fire then Qmb.galeBy = Mmb end if (Qmb.sh or 0) <= 0 then local Smb = Omb * Nmb.tick * Nmb.duty * nhb * (Rmb and Core.FLY_GROUND or 1) * Simulator.modHit(Qmb, Mmb) if lhb.dealt then lhb.dealt [Units.name(Mmb.e.id)] = (lhb.dealt [Units.name(Mmb.e.id)] or 0) + math.min(Smb, math.max(0, Qmb.hp)) end Qmb.hp = Qmb.hp - Smb end end end end end for Tmb, Umb in ipairs(Dhb) do local Vmb = Umb.galeBy Umb.galeSlow = (Vmb and not Umb.boss and not jhb.noCC) and (lhb.galeSlow or Simulator.GALE_SLOW) * Umb.galeCov or nil if Vmb then Umb.chill = (Umb.chill or 0) + nhb * Umb.galeCov if Umb.chill >= Vmb.gale.chill and zhb >= (Umb.galeFree or 0) then Umb.chill, Umb.galeFree = 0, zhb + Vmb.gale.freeze + Vmb.gale.immune if not Umb.boss and not jhb.noCC then Umb.stunTo = math.max(Umb.stunTo or 0, zhb + Vmb.gale.freeze) end if (Umb.sh or 0) <= 0 then local Wmb = fkb(Vmb) * math.max(0.3, Vmb.cd) / math.max(1, Vmb.aoe) if lhb.dealt then lhb.dealt [Units.name(Vmb.e.id)] = (lhb.dealt [Units.name(Vmb.e.id)] or 0) + math.min(Wmb, math.max(0, Umb.hp)) end Umb.hp = Umb.hp - Wmb end end else Umb.chill = nil end end end local Xmb = Cib() for Ymb, Zmb in ipairs(Ehb) do if Zmb.dps > 0 then vlb(Zmb, fkb(Zmb) * (Units.SUMMONER [Zmb.e.id] and Xmb or (1 + (Xmb - 1) * (Zmb.summonSh or 0)))) end end for _mb = # wib, 1, - 1 do local anb = wib [_mb] if zhb >= anb.till then table.remove(wib, _mb) else vlb(anb, fkb(anb.owner) * math.max(0.3, anb.owner.cd) / math.max(1, anb.owner.aoe) * anb.dmg * anb.share / anb.cd * (anb.bless or 1)) end end for bnb = # Dhb, 1, - 1 do local cnb = Dhb [bnb] if cnb.hp <= 0 then yhb = yhb + cnb.bounty table.insert(Ihb, { t = zhb, y = cnb.bounty }) table.remove(Dhb, bnb) if cnb.tr then ohb = ohb + 1 end if cnb.split then local dnb = Core.Units [cnb.split.unit] or { } for enb = 1, cnb.split.n do local fnb = { hp = cnb.max * cnb.split.ratio, max = cnb.max * cnb.split.ratio, speed = cnb.speed, bounty = 0, boss = false, p = cnb.p, at = zhb, sh = (tonumber(dnb.shield) or 0) + (jhb.shield or 0), fly = false, lane = cnb.lane } if (jhb.flyChance or 0) > 0 then Djb = Djb + jhb.flyChance if Djb >= 1 then Djb = Djb - 1 fnb.fly = true end end Simulator.enemyTraits(fnb, dnb, fnb.hp) table.insert(Dhb, fnb) end end local gnb = cnb.droneBy if gnb and gnb.owner then gnb = gnb.owner end if gnb and gnb.drone then gnb.takes = (gnb.takes or 0) + 1 if gnb.takes >= gnb.drone.every then gnb.takes = gnb.takes - gnb.drone.every local hnb = 0 for inb, jnb in ipairs(wib) do if jnb.owner == gnb then hnb = hnb + 1 end end if hnb < gnb.drone.max then local knb = gnb.droneNow table.insert(wib, { owner = gnb, till = zhb + knb.life, dmg = knb.dmg, share = gnb.drone.share, cd = knb.cd, hits = gnb.drone.hits, aoe = 1, base = gnb.base, mask = gnb.mask, air = gnb.air, hill = gnb.hill, fireDmg = gnb.fireDmg, stun = 0, slowT = 0, ccCd = 8, drone = gnb.drone, bless = Cib() }) end end end end end while Ihb [1] and zhb - Ihb [1].t > 30 do table.remove(Ihb, 1) end local lnb = 0 for mnb, nnb in ipairs(Ihb) do lnb = lnb + nnb.y end local onb = zhb >= 8 and lnb / math.min(30, zhb) or ((Mhb [Ahb + 1] or Mhb [Ahb] or 500) / jhb.waveSec) if Lhb <= 0 then local pnb, qnb = 0, 0 for rnb, snb in ipairs(Dhb) do pnb = pnb + math.max(0, snb.hp) qnb = qnb + snb.max end for tnb, unb in ipairs(Chb) do pnb = pnb + unb.hp qnb = qnb + unb.max end local vnb = { } for wnb, xnb in ipairs(Ehb) do vnb [xnb.e.id] = math.max(vnb [xnb.e.id] or 0, xnb.lvl) end return { won = false, wave = Ahb, t = zhb, aliveAt = Ghb, dpsAt = Jhb, spentAt = Khb, money = yhb, tk = ohb, left = qnb > 0 and pnb / qnb or 1, levels = vnb } end if Ahb >= jhb.total and # Chb == 0 and # Dhb == 0 then local ynb = { } for znb, Anb in ipairs(Ehb) do ynb [Anb.e.id] = math.max(ynb [Anb.e.id] or 0, Anb.lvl) end return { won = true, wave = Ahb, t = zhb, aliveAt = Ghb, dpsAt = Jhb, spentAt = Khb, money = yhb, tk = ohb, levels = ynb } end local Bnb, Cnb = 0, 0 for Dnb, Enb in ipairs(Dhb) do Bnb = Bnb + Enb.hp end for Fnb, Gnb in ipairs(Ehb) do Cnb = Cnb + fkb(Gnb) end table.insert(Fhb, # Dhb) local Hnb = Fhb [# Fhb - math.floor(12 / nhb)] local Inb = (# Dhb >= 8 and Hnb and # Dhb > Hnb + 1) or # Dhb >= 18 local Jnb = math.max(Hhb [Ahb] or 0, Hhb [Ahb - 1] or 0) if Ahb >= jhb.total and not emb and not jhb.infinite and not lhb.oldBuy then emb = true for Knb = # Ehb, 1, - 1 do local Lnb = Ehb [Knb] if Lnb.e.farm and not Lnb.e.data.unsellable then yhb = yhb + 0.25 * (Lnb.spent or 0) table.remove(Ehb, Knb) end end Tjb() end local function Mnb(Nnb, Onb, Pnb) if (jhb.regen or 0) <= 0 then return 0 end local Qnb = Units.bleeds(Nnb.id) if not Qnb then return 0 end local Rnb = false for Snb, Tnb in ipairs(Ehb) do if (Units.bleeds(Tnb.e.id) or 99) <= Tnb.lvl then Rnb = true break end end local Unb = { } for Vnb = 1, 5 do local Wnb = iib(Ahb + Vnb) if not Wnb then break end table.insert(Unb, { hp = Wnb }) end return Economy.bleedShare(Economy.bleedGain(jhb.regen, Bnb, Unb, Rnb), Qnb, Onb, Pnb) end for Xnb = 1, 3 do local Ynb, Znb, _nb, aob = { }, 0, 0, 0 for bob, cob in ipairs(khb) do local dob = { } local eob = lhb.trial and cob.id ~= lhb.trial and not cob.farm and (not Units.allyBuff(cob.id)) and Jnb < (lhb.trialAt or 0.6) for fob, gob in ipairs(Ehb) do if gob.e == cob then table.insert(dob, gob) end end if not cob.farm and # dob > 0 then aob = aob + 1 end if eob then dob = nil end for hob, iob in ipairs(dob or { }) do if cob.farm then _nb = _nb + Units.farm(cob.u, iob.lvl) end local job = cob.rows [iob.lvl + 1] if job and iob.lvl < cob.rows.max then local kob = job.cost * jhb.costScale if cob.farm then table.insert(Ynb, { farm = true, cost = kob, gain = Units.farm(cob.u, iob.lvl + 1) - Units.farm(cob.u, iob.lvl), c = iob }) else local lob = dib > 0 and not lhb.oldBuy and Economy.shieldGain(iob.dpsAll or iob.dps, cob.rows [iob.lvl], Rhb(cob, iob.lvl + 1), job, dib, Ejb, Mjb, gib) or Economy.upgradeGain(iob.dpsAll or iob.dps, cob.rows, iob.lvl, kob, jhb.costScale, Rhb(cob, iob.lvl + 1)) table.insert(Ynb, { cost = kob, gain = lob * (iob.train and Training.BONUS or 1) * ikb(cob.id, iob.lvl) * Wkb(iob) * olb(iob) + Mnb(cob, iob.lvl, false), c = iob }) for mob = iob.lvl + 1, cob.rows.max do Znb = Znb + cob.rows [mob].cost * jhb.costScale end end end end local nob = Units.allyBuff(cob.id) local oob = false if nob and not lhb.oldBuy then oob = true for pob, qob in ipairs(khb) do if not qob.farm and not Units.allyBuff(qob.id) and not (lhb.trial and qob.id ~= lhb.trial and Jnb < (lhb.trialAt or 0.6)) then local rob, sob = Units.types(qob.id) if nob.types [sob] then oob = false break end end end end if dob and not oob and # dob < (nob and math.min(cob.cap, Units.buffCopies(nob)) or cob.cap) and cob.placeable then local tob = (cob.data.cost or 0) * jhb.costScale if cob.farm then table.insert(Ynb, { farm = true, place = true, cost = tob, gain = Units.farm(cob.u, 0), e = cob }) else local uob = lhb.train ~= nil and cob.u.uuid == lhb.train local vob = (# dob == 0 and 2.5 or 1) * (aob < 3 and 1.3 or 1) * (uob and Training.BONUS or 1) * ikb(cob.id, 0, true) local wob = cob.rows [0] local xob = wob and Units.attackInfo(wob.atk, wob.range) or { n = 1, hits = 1 } local yob = (tonumber(wob and wob.cd) or 2) * Mjb local zob = lhb.oldBuy and Rhb(cob, 0) or Economy.shieldDps(Rhb(cob, 0), yob, xob.n, dib, Ejb, gib, xob.hits) if Rhb(cob, 0) > 0 and not lhb.oldBuy then local Aob = 0 for Bob, Cob in ipairs(Ehb) do if Cob.dps > 0 then Aob = Aob + Cob.aoe * Cob.hits / math.max(0.3, Cob.cd) end end zob = zob + Economy.shieldWork(xob.n, yob, Aob, dib, Fjb, jhb.waveSec, Zhb * gib, Ejb, xob.hits) end table.insert(Ynb, { place = true, cost = tob, gain = zob * vob + Gkb(cob) + zkb(cob) + blb(cob, xob) + Mnb(cob, 0, true), e = cob, train = uob and (# dob == 0 and "first" or "more") or nil }) for Dob = 0, cob.rows.max do Znb = Znb + cob.rows [Dob].cost * jhb.costScale end end end end if # Ynb == 0 then break end if lhb.train then Training.boost(Ynb, aob, Jnb, Pjb) end local Eob = math.max(0, jhb.total - Ahb) local Fob = yhb + _nb * Eob for Gob = Ahb + 1, jhb.total do Fob = Fob + (Mhb [Gob] or 0) end local Hob = { } for Iob = 1, 5 do local Job = iib(Ahb + Iob) if not Job then break end table.insert(Hob, { hp = Job, reward = Mhb [Ahb + Iob] or 0 }) end local Kob = lhb.oldBuy and 1 or tkb() local Lob = Economy.decide(Ynb, { money = yhb, left = Eob, attackers = aob, pressure = Jnb, piling = Inb, clearSec = Bnb > 0 and (Cnb > 0 and Bnb / (Cnb * (lhb.oldBuy and gib or 1) * Kob) or math.huge) or 0, enemies = # Dhb, future = Fob, wantLeft = Znb, coming = Hob, waveSec = jhb.waveSec, eff = gib, teamDps = Cnb * gib, shieldK = Kob, farmNow = _nb, hpAlive = Bnb, incomeRate = onb, roadSec = 1 / math.max(0.001, jhb.walk * (jhb.speedScale or 1)), cover = Pjb, leak = (Pjb and qmb > Pjb + 0.02) and qmb or nil }) if not Lob or Lob.cost > yhb then break end yhb = yhb - Lob.cost Khb [Ahb] = (Khb [Ahb] or 0) + Lob.cost if Lob.place then local Mob = { e = Lob.e, lvl = 0, spent = Lob.cost } for Nob, Oob in ipairs(Ehb) do if Oob.e == Lob.e then Mob.later = true break end end if Fib and Simulator.holds(Lob.e.id) then Mob.ctlN = 1 for Pob, Qob in ipairs(Ehb) do if Qob.ctlN then Mob.ctlN = Mob.ctlN + 1 end end end if not Fib and not Lob.e.farm and Pjb and qmb > Pjb then Mob.lo = math.floor(qmb * Simulator.BINS) + 2 end Gib(Mob) table.insert(Ehb, Mob) if Units.BLESS.by [Mob.e.id] then Bib = Bib + 1 for Rob, Sob in ipairs(Ehb) do if Units.SUMMONER [Sob.e.id] then Gib(Sob) end end end if lhb.buys then table.insert(lhb.buys, string.format("%.0fs w%d place %s", zhb, Ahb, Units.name(Mob.e.id))) end else Lob.c.lvl = Lob.c.lvl + 1 Lob.c.spent = (Lob.c.spent or 0) + Lob.cost Gib(Lob.c) if lhb.buys then table.insert(lhb.buys, string.format("%.0fs w%d up %s L%d", zhb, Ahb, Units.name(Lob.c.e.id), Lob.c.lvl)) end end Tjb() Cnb = 0 for Tob, Uob in ipairs(Ehb) do Cnb = Cnb + fkb(Uob) end end if lhb.trace then lhb.trace(zhb, Ahb, yhb, Ehb, Dhb, Lhb) end zhb = zhb + nhb cmb = cmb + 1 if cmb % 120 == 0 then task.wait() end end return { won = false, wave = Ahb, t = zhb, aliveAt = Ghb, dpsAt = Jhb, spentAt = Khb, money = yhb, timeout = true, tk = ohb } end Simulator.INF_CAP = 80 function Simulator.evaluate(Vob, Wob) local Xob = Simulator.run(Vob, Wob, { hpMul = 1 }) local Yob = { won = Xob.won, wave = Xob.wave, minutes = Xob.t / 60, aliveAt = Xob.aliveAt, total = Vob.total, at = os.time() } if Vob.infinite and Simulator.unbound(Vob.ld) then Yob.p = 1 Yob.margin = Xob.won and string.format("lasts past wave %d", Vob.total) or string.format("lasts to wave %d (%.0f min)", Xob.wave, Xob.t / 60) elseif Xob.won then local Zob = Simulator.run(Vob, Wob, { hpMul = 1.3 }) Yob.p = Zob.won and 0.92 or 0.7 Yob.margin = Zob.won and "wins with room to spare" or "wins, but only just" else local _ob = Simulator.run(Vob, Wob, { hpMul = 0.75 }) Yob.p = _ob.won and 0.25 or 0.05 Yob.margin = _ob.won and string.format("loses at wave %d (would win vs 25%% weaker enemies)", Xob.wave) or string.format("loses at wave %d of %d", Xob.wave, Vob.total) if not _ob.won then local apb = Simulator.run(Vob, Wob, { hpMul = 0.25 }) if not apb.won then Yob.p, Yob.hopeless = 0.005, true Yob.margin = string.format("loses at wave %d of %d, even against enemies at a quarter of their health", Xob.wave, Vob.total) end end end return Yob end function Simulator.trust(bpb, cpb) local dpb = bpb.cal or { } if type(dpb.alive) ~= "table" or dpb.teamSig ~= Simulator.sig((function() local epb = { } for fpb, gpb in ipairs(cpb) do table.insert(epb, gpb.u) end return epb end)()) then return nil end if bpb.key == "ch" and not dpb.aliveLvl then return nil end if dpb.aliveLvl and bpb.ld and bpb.ld.id and dpb.aliveLvl ~= tostring(bpb.ld.id) .. (Hunt.is(bpb.ld.id) and ("@" .. tostring(bpb.ld.map)) or "") then return nil end local hpb = Simulator.run(bpb, cpb, { hpMul = 1 }) local ipb, jpb, kpb = 0, 0, 0 for lpb = 3, math.min(hpb.wave, tonumber(dpb.lastWave) or 99) do local mpb = tonumber(dpb.alive [tostring(lpb)]) local npb = hpb.aliveAt [lpb] if mpb and npb and mpb > 0 then ipb = ipb + mpb jpb = jpb + npb kpb = kpb + 1 end end if kpb < 2 or jpb <= 0 then return nil end local opb = ipb / jpb return { ratio = opb, ok = opb >= 0.75 and opb <= 1.33, waves = kpb } end Simulator.GROWN_LEVEL, Simulator.GROWN_MIN = 50, 6 function Simulator.grownKinds() local ppb = Core.session() local qpb, rpb = { }, 0 for spb, tpb in pairs(ppb.profile_data.collection.owned_units) do local upb = tpb.unit_id if not qpb [upb] and Core.Units [upb] and not Units.isFarm(upb) and Units.rows(upb) and not Units.hidden(upb) then local vpb, wpb = pcall(Core.USC.calculate_level, upb, tonumber(tpb.xp) or 0) if vpb and (tonumber(wpb) or 0) >= Simulator.GROWN_LEVEL then qpb [upb] = true rpb = rpb + 1 end end end return rpb end function Simulator.pool(xpb, ypb) local zpb = Core.session() local Apb, Bpb = { }, { } local Cpb = Simulator.grownKinds() >= Simulator.GROWN_MIN for Dpb, Epb in pairs(zpb.profile_data.collection.owned_units) do local Fpb = zpb.collection:get_unit_by_uuid(Epb.uuid) or Epb local Gpb, Hpb = pcall(Core.USC.calculate_level, Fpb.unit_id, tonumber(Fpb.xp) or 0) local Ipb = Units.isFarm(Fpb.unit_id) or not Cpb or (Gpb and (tonumber(Hpb) or 0) >= Simulator.GROWN_LEVEL) if Core.Units [Fpb.unit_id] and Units.rows(Fpb.unit_id) and not Units.hidden(Fpb.unit_id) and Ipb then local Jpb = Units.isFarm(Fpb.unit_id) local Kpb, Lpb = pcall(Jpb and function() return Units.farm(Fpb, Units.rows(Fpb.unit_id).max) end or Units.score, Fpb, xpb) Lpb = (Kpb and Lpb or 0) * (Jpb and 1 or Units.skinMul(Fpb, ypb and ypb.skinSet)) local Mpb = Jpb and Bpb or Apb if not Mpb [Fpb.unit_id] or Lpb > Mpb [Fpb.unit_id].sc then Mpb [Fpb.unit_id] = { u = Fpb, sc = Lpb } end end end local Npb, Opb = { }, { } for Ppb, Qpb in pairs(Apb) do table.insert(Npb, Qpb) end for Rpb, Spb in pairs(Bpb) do table.insert(Opb, Spb) end table.sort(Npb, function(Tpb, Upb) return Tpb.sc > Upb.sc end) table.sort(Opb, function(Vpb, Wpb) return Vpb.sc > Wpb.sc end) local Xpb = { } for Ypb = 1, math.min(10, # Npb) do table.insert(Xpb, Npb [Ypb].u) end local Zpb, _pb = { }, { } for aqb = 11, # Npb do local bqb = Units.rows(Npb [aqb].u.unit_id) for cqb = 0, bqb.max do if bqb [cqb] and bqb [cqb].atk and Units.attackInfo(bqb [cqb].atk, bqb [cqb].range).dot > 0 then table.insert(cqb == 0 and Zpb or _pb, Npb [aqb].u) break end end end for dqb = 1, math.min(4, # Zpb) do table.insert(Xpb, Zpb [dqb]) end for eqb = 1, math.min(2, # _pb) do table.insert(Xpb, _pb [eqb]) end for fqb = 1, math.min(3, # Opb) do table.insert(Xpb, Opb [fqb].u) end local gqb = { } for hqb = 11, # Npb do local iqb = Units.rows(Npb [hqb].u.unit_id) [0] local jqb = iqb and iqb.atk and Units.attackInfo(iqb.atk, iqb.range) if jqb and (jqb.slowTime > 0 or jqb.stun > 0) then table.insert(gqb, Npb [hqb].u) end end for kqb = 1, math.min(3, # gqb) do table.insert(Xpb, gqb [kqb]) end for lqb = 11, # Npb do local mqb = Npb [lqb].u.unit_id if Units.allyBuff(mqb) or Units.BLESS.by [mqb] or Units.GALE [mqb] then table.insert(Xpb, Npb [lqb].u) end end if ypb and ((ypb.flyChance or 0) > 0 or ypb.mapFly or (ypb.shield or 0) > 0) then local nqb, oqb = { }, { } for pqb = 11, # Npb do local qqb = Npb [pqb].u local rqb = Units.rows(qqb.unit_id) [0] if Units.airFrom(qqb.unit_id) == 0 then table.insert(nqb, qqb) end local sqb, tqb = tonumber(rqb and rqb.cd) or 0, tonumber((Core.Units [qqb.unit_id] or { }).cost) or 0 if sqb > 0 and tqb > 0 and Units.range(qqb, 0) >= 15 then local uqb = Units.attackInfo(rqb.atk, rqb.range) table.insert(oqb, { u = qqb, v = uqb.n * uqb.hits / sqb / tqb * math.min(6, Units.cap(qqb)) }) end end table.sort(oqb, function(vqb, wqb) return vqb.v > wqb.v end) for xqb = 1, math.min(4, # nqb) do table.insert(Xpb, nqb [xqb]) end for yqb = 1, math.min(6, # oqb) do table.insert(Xpb, oqb [yqb].u) end end local zqb, Aqb = { }, { } for Bqb, Cqb in ipairs(Xpb) do if not zqb [Cqb] then zqb [Cqb] = true table.insert(Aqb, Cqb) end end Aqb.rest = { } for Dqb, Eqb in ipairs(Npb) do if not zqb [Eqb.u] then table.insert(Aqb.rest, Eqb.u) end end return Aqb end function Simulator.objective(Fqb) return (Fqb.won and 1000 or 0) + Fqb.wave * 10 - (Fqb.won and Fqb.t / 60 or ((Fqb.left or 1) * 9 - math.min(9, Fqb.t / 60))) end Simulator.LADDER = { 1, 1.3, 1.7, 2.2, 3, 4, 6, 8, 12, 16, 24 } function Simulator.cliff(Gqb, Hqb, Iqb) local Jqb for Kqb, Lqb in ipairs(Simulator.LADDER) do Jqb = Simulator.run(Gqb, Hqb, { hpMul = Lqb, layout = Iqb }) if not Jqb.won then return Lqb, Jqb end end return Simulator.LADDER [# Simulator.LADDER], Jqb end function Simulator.exam(Mqb, Nqb) local Oqb, Pqb, Qqb = Simulator.teamFrom(Nqb, Mqb.map), 0, 0 for Rqb, Sqb in ipairs(Simulator.LADDER) do local Tqb, Uqb = 0, 0 for Vqb, Wqb in ipairs({ 0.92, 1, 1.08 }) do local Xqb = Simulator.run(Mqb, Oqb, { hpMul = Sqb * Wqb }) if Xqb.won then Tqb = Tqb + 1 end Uqb = Uqb + Xqb.wave end Pqb, Qqb = Pqb + Tqb, Uqb / 3 task.wait() if Tqb == 0 then break end end return Pqb, Qqb end Simulator.ZONE_CLOSE, Simulator.ZONE_GAIN = 1.3, 20 function Simulator.zoneKey(Yqb) return tostring(Yqb.key) .. ":" .. tostring(Yqb.map) end function Simulator.zoneChoice(Zqb, _qb, arb, brb) if Config.killZone == false or Zqb.zone or (Zqb.infinite and Simulator.unbound(Zqb.ld)) then return false end local crb = Brain.zoneRec [Simulator.zoneKey(Zqb)] local drb = crb and (crb.style == "box" or (crb.style == nil and crb.zone == true)) if crb and crb.won then return drb == true, drb and "the kill box won here last time" or nil end if crb and drb then return false, "the kill box lost here last time" end local erb = Brain.modes [Zqb.key] local frb = erb and tonumber(erb.streak) or 0 if not crb and erb and (tonumber(erb.wins) or 0) > 0 and frb == 0 and Zqb.key ~= "ch" and Zqb.key ~= "dc" then return false end local grb = crb ~= nil or frb > 0 local hrb = brb and arb <= 1 or (not brb and arb <= Simulator.ZONE_CLOSE) if not (grb or hrb) then return false end local irb = table.clone(Zqb) irb.zone = true local function jrb(krb) local lrb = 0 for mrb, nrb in ipairs({ 0.92, 1, 1.08 }) do lrb = lrb + Simulator.objective(Simulator.run(krb, _qb, { hpMul = arb * nrb })) end return lrb / 3 end local orb = jrb(irb) - jrb(Zqb) if grb and orb > - Simulator.ZONE_GAIN then return true, "spread-out units lost here last time", orb end if hrb and orb > Simulator.ZONE_GAIN then return true, string.format("the simulator says your team is close to losing here (enemies ร—%.1f), and it plays better stacked", arb), orb end return false, nil, orb end function Simulator.strength(prb, qrb) local rrb, srb, trb, urb, vrb = Simulator.teamFrom(qrb, prb.map), 0, 0, 0, 0 for wrb, xrb in ipairs(Simulator.LADDER) do local yrb, zrb = 0, 0 for Arb, Brb in ipairs({ 0.92, 1, 1.08 }) do local Crb = Simulator.run(prb, rrb, { hpMul = xrb * Brb }) if Crb.won then yrb = yrb + 1 end zrb = zrb + Crb.wave end srb, vrb = srb + yrb, zrb / 3 if xrb == 1 then trb, urb = yrb, zrb / 3 end task.wait() if yrb == 0 then break end end return srb, trb, urb, vrb end function Simulator.bestTeam(Drb, Erb, Frb, Grb) local Hrb = Utils.now() Grb = Grb or 25 local Irb = table.clone(Erb) local Jrb, Krb = Simulator.cliff(Drb, Simulator.teamFrom(Irb, Drb.map)) local function Lrb(Mrb) local Nrb = 0 for Orb, Prb in ipairs(Mrb) do if Units.isFarm(Prb.unit_id) then Nrb = Nrb + 1 end end return Nrb end local Qrb = Lrb(Frb) > 0 local function Rrb(Srb) local Trb = 0 for Urb, Vrb in ipairs(Srb) do if not Units.isFarm(Vrb.unit_id) and not Units.allyBuff(Vrb.unit_id) then Trb = Trb + Units.cap(Vrb) end end return Trb end local Wrb while # Irb < 6 do local Xrb, Yrb local Zrb = Qrb and Lrb(Irb) == 0 for _rb, asb in ipairs(Frb) do local bsb = false for csb, dsb in ipairs(Irb) do if dsb.unit_id == asb.unit_id then bsb = true end end if not bsb and (not Zrb or Units.isFarm(asb.unit_id)) then local esb = table.clone(Irb) table.insert(esb, asb) local fsb = Simulator.run(Drb, Simulator.teamFrom(esb, Drb.map), { hpMul = Jrb }) local gsb = Simulator.objective(fsb) if not Yrb or gsb > Yrb then Xrb, Yrb, Wrb = asb, gsb, fsb end end end if not Xrb then break end table.insert(Irb, Xrb) end if Qrb and Lrb(Irb) == 0 then local hsb, isb for jsb = 1, # Irb do for ksb, lsb in ipairs(Frb) do if Units.isFarm(lsb.unit_id) then local msb = table.clone(Irb) msb [jsb] = lsb local nsb = Simulator.run(Drb, Simulator.teamFrom(msb, Drb.map), { hpMul = Jrb }) local osb = Simulator.objective(nsb) if not isb or osb > isb then hsb, isb, Wrb = msb, osb, nsb end end end end if hsb then Irb = hsb end end local function psb(qsb, rsb) local ssb = Simulator.teamFrom(qsb, Drb.map) return (Simulator.objective(rsb) + Simulator.objective(Simulator.run(Drb, ssb, { hpMul = Jrb * 0.92 })) + Simulator.objective(Simulator.run(Drb, ssb, { hpMul = Jrb * 1.08 }))) / 3 end local tsb, usb = Wrb or Krb, psb(Irb, Wrb or Krb) if type(Frb.rest) == "table" and # Frb.rest > 0 then local vsb, wsb for xsb, ysb in ipairs(Irb) do if not Units.isFarm(ysb.unit_id) and not Units.allyBuff(ysb.unit_id) then local zsb, Asb = pcall(Units.score, ysb, Drb.mod) Asb = (zsb and Asb or 0) * Units.skinMul(ysb, Drb.skinSet) if not wsb or Asb < wsb then vsb, wsb = xsb, Asb end end end if vsb then local Bsb, Csb = { }, Utils.now() for Dsb, Esb in ipairs(Frb.rest) do if Utils.now() - Csb > 5 then break end local Fsb = false for Gsb, Hsb in ipairs(Irb) do if Hsb.unit_id == Esb.unit_id then Fsb = true end end if not Fsb then local Isb = table.clone(Irb) Isb [vsb] = Esb table.insert(Bsb, { u = Esb, o = Simulator.objective(Simulator.run(Drb, Simulator.teamFrom(Isb, Drb.map), { hpMul = Jrb })) }) end end table.sort(Bsb, function(Jsb, Ksb) return Jsb.o > Ksb.o end) Frb = table.clone(Frb) for Lsb = 1, math.min(4, # Bsb) do table.insert(Frb, 1, Bsb [Lsb].u) end Hrb = Hrb + (Utils.now() - Csb) end end for Msb = 1, 2 do local Nsb = false for Osb = 1, # Irb do for Psb, Qsb in ipairs(Frb) do if Utils.now() - Hrb > Grb then return Irb, tsb, Jrb, Krb end local Rsb = false for Ssb, Tsb in ipairs(Irb) do if Tsb.unit_id == Qsb.unit_id then Rsb = true end end if not Rsb then local Usb = table.clone(Irb) Usb [Osb] = Qsb if (not Qrb or Lrb(Usb) > 0) and Rrb(Usb) >= math.min(8, Rrb(Irb)) then local Vsb = Simulator.run(Drb, Simulator.teamFrom(Usb, Drb.map), { hpMul = Jrb }) local Wsb = Simulator.objective(Vsb) if Wsb > usb + 1 then Wsb = psb(Usb, Vsb) if Wsb > usb + 1 then Irb, usb, tsb, Nsb = Usb, Wsb, Vsb, true end end end end end end if not Nsb then break end end return Irb, tsb, Jrb, Krb end function Simulator.evolutions() local Xsb = Core.session() local Ysb, Zsb = { }, { } local _sb = Xsb.inventory local function atb(btb) local ctb = 0 pcall(function() ctb = _sb:get_number_of_owned_item(btb) end) return ctb end for dtb, etb in pairs(Xsb.profile_data.collection.owned_units) do local ftb = Core.Units [etb.unit_id] if ftb and ftb.evolve and ftb.evolve.evolve_unit and not ftb.evolve.hide_from_ui and not Zsb [etb.unit_id] and Core.Units [ftb.evolve.evolve_unit] then Zsb [etb.unit_id] = true local gtb = etb.shiny and ftb.evolve.shiny or ftb.evolve.normal if gtb then local htb = { } for itb, jtb in pairs(gtb.item_requirement or { }) do if atb(jtb.item_id) < (jtb.amount or 1) then table.insert(htb, string.format("%s %d/%d", (Core.Items [jtb.item_id] or { }).name or jtb.item_id, atb(jtb.item_id), jtb.amount)) end end for ktb, ltb in pairs(gtb.unit_requirement or { }) do local mtb = 0 for ntb, otb in pairs(Xsb.profile_data.collection.owned_units) do if otb.unit_id == ltb.unit_id and (not ltb.shiny or otb.shiny) then mtb = mtb + 1 end end if mtb < (ltb.amount or 1) then table.insert(htb, string.format("%s %d/%d", Units.name(ltb.unit_id), mtb, ltb.amount)) end end local ptb = Adventure.killsShort(Xsb.collection:get_unit_by_uuid(etb.uuid) or etb, gtb) if ptb then table.insert(htb, string.format("%s %s/%s", ptb.what, Utils.fmt(ptb.have), Utils.fmt(ptb.need))) end if # htb <= 1 then local qtb = Xsb.collection:get_unit_by_uuid(etb.uuid) or etb table.insert(Ysb, { u = qtb, from = etb.unit_id, to = ftb.evolve.evolve_unit, missing = htb, evolved = { unit_id = ftb.evolve.evolve_unit, uuid = "evo:" .. tostring(qtb.uuid), xp = qtb.xp, traits = qtb.traits, trait_stats = qtb.trait_stats, further_modifiers = qtb.further_modifiers } }) end end end end return Ysb end function Simulator.levelFor(rtb) local stb = Brain.simCal [rtb.key] if rtb.kind == "daily" and Brain.live.daily and Brain.live.daily.ld then return Brain.live.daily.ld, Brain.live.daily.challenge end if Hunt.is(rtb.level) and Brain.live.event and Brain.live.event.ld then return Brain.live.event.ld, nil end local ttb = rtb.kind ~= "challenge" or (stb and type(stb.ld) == "table" and stb.ld.id == rtb.level and stb.ch == rtb.challenge) if stb and type(stb.ld) == "table" and ttb then return stb.ld, stb.ch end if rtb.kind == "challenge" and Core.Levels [rtb.level] then local utb = Simulator.slim(Core.Levels [rtb.level]) utb._difficulty = "Hard" utb._gamemode = "challenge" utb._challenge = rtb.challenge return utb, rtb.challenge end if Core.Levels [rtb.level] then local vtb = Simulator.slim(Core.Levels [rtb.level]) vtb._difficulty = rtb.diff vtb._gamemode = vtb._OVERRIDE_GAMEMODE or ((rtb.kind == "raid" or rtb.kind == "artifact") and "raid") or (vtb.infinite and "infinite") or "story" return vtb, nil end end function Simulator.plan(wtb, xtb, ytb, ztb) local Atb, Btb = Simulator.levelFor(wtb) if wtb.ldOverride then Atb = wtb.ldOverride end if not Atb then return nil end local Ctb = wtb.mod or (wtb.kind == "daily" and Brain.live.daily and Brain.live.daily.mod) or Modes.levelMod(wtb.level) local Dtb = Simulator.info(Atb, wtb.key, Btb, Ctb) local Etb = ztb if not Etb then Etb = { } for Ftb, Gtb in ipairs(Units.equipped()) do table.insert(Etb, Gtb.u) end end local Htb = Simulator.teamFrom(Etb, Dtb.map) local Itb = Simulator.evaluate(Dtb, Htb) local Jtb, Ktb = pcall(Simulator.zoneChoice, Dtb, Htb, Itb.p >= 0.92 and 1.7 or Itb.p >= 0.7 and 1.3 or 1, false) if Jtb and Ktb then Dtb.zone = true Itb = Simulator.evaluate(Dtb, Htb) Itb.zone = true Itb.margin = Itb.margin .. " with the kill box" end Itb.sig = Simulator.sig(Etb) Itb.trust = Simulator.trust(Dtb, Htb) Itb.label = wtb.label Itb.calibrated = (tonumber(Dtb.cal.n) or 0) > 0 Brain.simRes [wtb.key] = Itb Itb.aliveAt = nil App.log(1, "SIM", string.format("%s: your team %s (%s, %s)", wtb.label, Itb.margin, Itb.calibrated and "calibrated on real runs" or "not calibrated yet", Itb.trust and string.format("real/sim HP %.2f", Itb.trust.ratio) or "no same-team run to compare")) local Ltb = Modes.rec(wtb.key) if Config.simTeams and xtb and type(Ltb.winTeam) == "table" and # Ltb.winTeam > 0 then local Mtb = Core.session() local Ntb = { } for Otb, Ptb in ipairs(Ltb.winTeam) do local Qtb = Mtb.collection:get_unit_by_uuid(Ptb) if Qtb then table.insert(Ntb, Qtb) end end if # Ntb == # Ltb.winTeam then local Rtb = Simulator.teamFrom(Ntb, Dtb.map) local Stb = Simulator.run(Dtb, Rtb, { hpMul = 1 }) local Ttb = Simulator.trust(Dtb, Rtb) local Utb = Ttb and not Ttb.ok if not Stb.won or Utb then local Vtb, Wtb = { }, { } for Xtb, Ytb in ipairs(Ntb) do table.insert(Vtb, Units.name(Ytb.unit_id)) table.insert(Wtb, Ytb.unit_id) end Brain.simTeam [wtb.key] = { uuids = Ltb.winTeam, names = Vtb, ids = Wtb, won = true, wave = Dtb.total, better = Simulator.sig(Ntb) ~= Itb.sig, cur = { won = Itb.won, wave = Itb.wave }, at = os.time(), label = wtb.label, base = Itb.sig, why = "the team that won here last run, no search" } App.log(1, "SIM", string.format("%s: the team that won here last run plays again (%s): %s, and real runs won %d in a row", wtb.label, table.concat(Vtb, ", "), Utb and string.format((Ttb.ratio or 0) < 0.1 and "the simulator is off ร—%.2f against real runs here" or "the simulator is off ร—%.1f against real runs here", Ttb.ratio or 0) or string.format("the simulator calls it a loss at wave %d", Stb.wave), Ltb.winTeamN or 1)) return Itb end end end if Config.simTeams and xtb then local Ztb, _tb, aub, bub, cub = pcall(Simulator.bestTeam, Dtb, Etb, Simulator.pool(Ctb, Dtb), ytb) if Ztb and _tb then local dub = Simulator.sig(_tb) ~= Itb.sig if dub and Itb.won and (Itb.p or 0) >= 0.92 then local eub = { } for fub, gub in ipairs(_tb) do table.insert(eub, Units.name(gub.unit_id)) end App.log(1, "SIM", string.format("%s: your team already wins with room to spare, so it stays (the search had %s)", wtb.label, table.concat(eub, ", "))) _tb, dub = Etb, false end local hub = "" if dub then local iub, jub = Simulator.exam(Dtb, Etb) local kub, lub = Simulator.exam(Dtb, _tb) local mub = # _tb > # Etb and kub == iub and lub >= jub if kub > iub or (kub == iub and lub > jub) or mub then hub = string.format(" ยท up the rising test it won %d tries, yours %d", kub, iub) .. (mub and string.format(", a tie, and it fills %d slots to your %d", # _tb, # Etb) or "") else local nub = { } for oub, pub in ipairs(_tb) do table.insert(nub, Units.name(pub.unit_id)) end App.log(1, "SIM", string.format("%s: the search tried %s, but it doesn't go further than your team up the rising test (it won %d tries, yours %d): yours stays", wtb.label, table.concat(nub, ", "), kub, iub)) _tb, dub = Etb, false end end local qub = Simulator.run(Dtb, Simulator.teamFrom(_tb, Dtb.map), { hpMul = 1 }) if dub then Itb.playing = qub.won and "the best team wins" or string.format("the best team reaches wave %d of %d", qub.wave, Dtb.total) end local rub, tub = { }, { } for uub, vub in ipairs(_tb) do table.insert(rub, vub.uuid) table.insert(tub, Units.name(vub.unit_id)) end local wub = { } for xub, yub in ipairs(_tb) do table.insert(wub, yub.unit_id) end Brain.simTeam [wtb.key] = { uuids = rub, names = tub, ids = wub, won = qub.won, wave = qub.wave, better = dub, cur = { won = Itb.won, wave = Itb.wave }, at = os.time(), label = wtb.label, base = Itb.sig } App.log(1, "SIM", string.format("%s: best team from your units %s (%s)%s", wtb.label, qub.won and "wins" or ("reaches wave " .. qub.wave), table.concat(tub, ", "), dub and (string.format(" ยท it plays ยท against enemies ร—%.1f: %s at wave %d, yours %s at wave %d", bub, aub.won and "wins" or "falls", aub.wave, cub.won and "wins" or "falls", cub.wave) .. hub) or " ยท the same as yours")) local zub = { } for Aub, Bub in ipairs(Simulator.evolutions()) do local Cub if Units.hidden(Bub.from) or Units.hidden(Bub.to) then Cub = "strength unknown (the game shows its upgrades only in a match)" else local Dub = table.clone(_tb) local Eub = false for Fub, Gub in ipairs(Dub) do if Gub.unit_id == Bub.from then Dub [Fub] = Bub.evolved Eub = true end end if not Eub then local Hub, Iub = nil, math.huge for Jub, Kub in ipairs(Dub) do if not Units.isFarm(Kub.unit_id) then local Lub, Mub = pcall(Units.score, Kub, Ctb) if Lub and Mub < Iub then Hub, Iub = Jub, Mub end end end if Hub then Dub [Hub] = Bub.evolved end end local Nub = Simulator.run(Dtb, Simulator.teamFrom(Dub, Dtb.map), { hpMul = 1 }) if Nub.won and not qub.won then Cub = "turns " .. wtb.label .. " into a win" elseif Nub.wave > qub.wave then Cub = string.format("%s: reaches wave %d instead of %d", wtb.label, Nub.wave, qub.wave) elseif Nub.won and qub.won and Nub.t < qub.t - 6 then Cub = string.format("clears %s %.1f min faster", wtb.label, (qub.t - Nub.t) / 60) else Cub = qub.won and ("no difference in " .. wtb.label .. " (already a win)") or ("doesn't help in " .. wtb.label) end end table.insert(zub, { from = Units.name(Bub.from), to = Units.name(Bub.to), ready = # Bub.missing == 0, missing = Bub.missing [1], gain = Cub, mode = wtb.label }) end Brain.simEvo = zub end end App.saveSoon() return Itb end Dungeon.T = { CURSE = 0, SHOP = 1, MONEY = 2, GOLD = 3, CHEST = 4, BOSS = 5, OPEN = 6, SHRINE = 7 } Dungeon.TAG_NAMES = { [0] = "curse", [1] = "shop", [2] = "coins", [3] = "rare chest", [4] = "chest", [5] = "final boss", [6] = "chest opening", [7] = "shrine" } Dungeon.KNOWN = { _started = true, _items = true, selected_next = true, next = true, _resources = true } Dungeon.ROOMS = 20 function Dungeon.core() if not Dungeon._core then Dungeon._core = Core.core("DungeonServiceCore") end return Dungeon._core end function Dungeon.data() local Oub = Core.prof() return Oub and type(Oub.dungeon_data) == "table" and Oub.dungeon_data or nil end function Dungeon.item(Pub) local Qub = Dungeon.core() return Pub and Qub and type(Qub.ITEMS) == "table" and Qub.ITEMS [Pub] or nil end function Dungeon.isCurse(Rub) return tostring(Rub):sub(1, 7) == "_curse_" end function Dungeon.tags(Sub) local Tub = { } for Uub, Vub in pairs(type(Sub) == "table" and type(Sub._t) == "table" and Sub._t or { }) do if Vub then Tub [tonumber(Uub) or - 1] = true end end return Tub end function Dungeon.list(Wub) local Xub = { } for Yub, Zub in pairs(type(Wub) == "table" and Wub or { }) do local _ub = tonumber(Yub) if _ub and type(Zub) == "table" then table.insert(Xub, { choice = _ub, key = Yub, r = Zub, tags = Dungeon.tags(Zub), d = tonumber(Zub._d) or 1 }) end end table.sort(Xub, function(avb, bvb) return avb.choice < bvb.choice end) return Xub end function Dungeon.rooms(cvb) return Dungeon.list(cvb and cvb.next) end function Dungeon.after(dvb, evb) return Dungeon.list(dvb and type(dvb.next) == "table" and dvb.next ["next" .. tostring(evb)] or nil) end function Dungeon.depth(fvb) local gvb = Dungeon.rooms(fvb) [1] return gvb and gvb.d or nil end function Dungeon.items(hvb) local ivb = { } for jvb, kvb in pairs(hvb and type(hvb._items) == "table" and hvb._items or { }) do local lvb = type(kvb) == "table" and kvb.id or (type(kvb) == "string" and kvb) or nil if lvb then ivb [lvb] = (ivb [lvb] or 0) + 1 end end return ivb end function Dungeon.curses(mvb) local nvb = 0 for ovb, pvb in pairs(Dungeon.items(mvb)) do if Dungeon.isCurse(ovb) then nvb = nvb + pvb end end return nvb end function Dungeon.res(qvb) local rvb = qvb and type(qvb._resources) == "table" and qvb._resources or { } return tonumber(rvb.money) or 0, tonumber(rvb.treasure) or 0, tonumber(rvb.treasure_gold) or 0 end function Dungeon.lives(svb) local tvb = tonumber(svb and svb.lives) or tonumber(svb and type(svb.selected_next) == "table" and svb.selected_next.lives) if tvb then if tvb ~= Brain.dg.lives then Brain.dg.lives = tvb App.saveSoon() end return tvb end local uvb = tonumber(Brain.dg.lives) return (uvb and uvb > 0) and uvb or 5 end function Dungeon.luck(vvb) local wvb = Dungeon.core() local xvb, yvb = pcall(function() return wvb.calculate_luck_multiplier(vvb) end) return xvb and tonumber(yvb) or 1 end function Dungeon.tagText(zvb) local Avb = { } for Bvb = 0, 7 do if zvb [Bvb] then table.insert(Avb, Dungeon.TAG_NAMES [Bvb]) end end return # Avb > 0 and table.concat(Avb, " + ") or "fight only" end function Dungeon.share(Cvb) local Dvb, Evb = 0, 0 for Fvb, Gvb in ipairs(Units.equipped()) do local Hvb = Gvb.id or (Gvb.u and Gvb.u.unit_id) if Hvb and Gvb.u and not Units.isFarm(Hvb) and not Units.allyBuff(Hvb) then local Ivb = Units.rows(Hvb) local Jvb, Kvb = pcall(Units.dps, Gvb.u, Ivb and Ivb.max or 0, nil) Kvb = Jvb and tonumber(Kvb) or 0 local Lvb, Mvb = Units.types(Hvb) Dvb = Dvb + Kvb if Mvb == Cvb or Mvb == Cvb .. "_damage" then Evb = Evb + Kvb end end end return Dvb > 0 and Evb / Dvb or 0.5 end function Dungeon.itemValue(Nvb, Ovb) local Pvb = Dungeon.item(Nvb) if not Pvb or Dungeon.isCurse(Nvb) then return 0 end local Qvb = 0 if tonumber(Pvb.luck_increase) then Qvb = Qvb + 300 * Pvb.luck_increase end if tonumber(Pvb.star_remnant_chest_add) then Qvb = Qvb + 150 * Pvb.star_remnant_chest_add end if Nvb == "philospher_stone" then local Rvb, Svb = Dungeon.res(Ovb) Qvb = Qvb + 60 + 25 * Svb end if Nvb == "chest_mock_item" then Qvb = Qvb + 120 end for Tvb, Uvb in pairs(type(Pvb.wave_modifier_effects) == "table" and Pvb.wave_modifier_effects or { }) do local Vvb = math.abs(tonumber(Uvb.amount) or 0) if Uvb.type == "stat_mul" and Uvb.stat == "damage" then Qvb = Qvb + 100 * Vvb elseif Uvb.type == "stat_mul" and Uvb.stat == "attack_cooldown" then Qvb = Qvb + 100 * Vvb / math.max(0.2, 1 - Vvb) elseif Uvb.type == "stat_mul" and Uvb.stat == "range" then Qvb = Qvb + 50 * Vvb elseif Uvb.type == "stat_attr_add" and Uvb.stat == "_dmg_mul_physical" then Qvb = Qvb + 100 * Vvb * Dungeon.share("physical") elseif Uvb.type == "stat_attr_add" and Uvb.stat == "_dmg_mul_magic" then Qvb = Qvb + 100 * Vvb * Dungeon.share("magic") elseif Uvb.type == "stat_attr_add" and Uvb.stat == "_dmg_mul_true_damage" then Qvb = Qvb + 100 * Vvb * Dungeon.share("true") elseif Uvb.type == "stat_add" and Uvb.stat == "physical_crit_chance" then Qvb = Qvb + 60 * Vvb * Dungeon.share("physical") elseif Uvb.type == "boss_damage" then Qvb = Qvb + 40 * Vvb elseif Uvb.type == "enemy_speedup" then Qvb = Qvb + 80 * Vvb elseif Uvb.type == "unit_cost" then Qvb = Qvb + 80 * Vvb end end return Qvb end Dungeon.CURSE_COST = { _curse_money = 0.5, _curse_shop = 0.5, _curse_health = 0.5, _curse_speed = 0.5, _curse_regen = 0.5, _curse_shield = 1, _curse_explode = 1.5, _curse_random = 2, _curse_super_health = 1, _curse_super_speed = 1, _curse_super_regen = 1, _curse_super_armored = 3, _curse_super_burst = 3, _curse_super_explode = 3, _curse_super_chaos = 3 } function Dungeon.curseCost(Wvb) if Wvb == "_curse_fly" then local Xvb, Yvb = 0, 0 for Zvb, _vb in ipairs(Units.equipped()) do local awb = _vb.id or (_vb.u and _vb.u.unit_id) if awb and not Units.isFarm(awb) then Xvb = Xvb + 1 local bwb, cwb = pcall(Units.airFrom, awb) if bwb and cwb then Yvb = Yvb + (cwb == 0 and 1 or 0.5) end end end return 1 + 2 * (Xvb > 0 and 1 - Yvb / Xvb or 0.5) end return Dungeon.CURSE_COST [Wvb] or 1.5 end function Dungeon.roomValue(dwb, ewb) local fwb, gwb = dwb.tags, dwb.d local hwb = 0 if fwb [Dungeon.T.GOLD] then hwb = hwb + 12 end if fwb [Dungeon.T.CHEST] then hwb = hwb + 4 end if fwb [Dungeon.T.MONEY] then hwb = hwb + (gwb <= 15 and 2 or 0.5) end if fwb [Dungeon.T.SHOP] then if gwb >= 10 and gwb <= 15 then hwb = hwb + (ewb >= 300 and 5 or 1) else hwb = hwb + (ewb >= 300 and (gwb > 15 and 3 or 1) or 0.3) end end if fwb [Dungeon.T.SHRINE] then hwb = hwb + 1.5 end if fwb [Dungeon.T.CURSE] then hwb = hwb - Dungeon.curseCost(Dungeon.roomCurse(dwb.r)) end return hwb end function Dungeon.mod(iwb) local jwb = { weak = { }, strong = { } } for kwb, lwb in pairs(type(iwb) == "table" and type(iwb._weak_against) == "table" and iwb._weak_against or { }) do if type(lwb) == "table" and lwb.damage_type then jwb.weak [lwb.damage_type] = Modes.weakAmp(lwb.amplification) end end for mwb, nwb in pairs(type(iwb) == "table" and type(iwb._strong_against) == "table" and iwb._strong_against or { }) do jwb.strong [type(nwb) == "table" and nwb.damage_type or nwb] = true end return (next(jwb.weak) or next(jwb.strong)) and jwb or nil end function Dungeon.roomCurse(owb) local pwb = type(owb) == "table" and type(owb._t) == "table" and owb._t or { } local qwb = pwb [0] or pwb ["0"] return type(qwb) == "table" and qwb.id or nil end function Dungeon.fx(rwb, swb, twb) local uwb = { dmg = 1, cd = 1, range = 1, cost = 1, hp = 1, speed = 1, regen = 0, crit = 0, kinds = { }, shield = 0, fly = 0 } local vwb = Dungeon.items(rwb) if swb and Dungeon.item(swb) then vwb [swb] = (vwb [swb] or 0) + 1 end if twb and Dungeon.item(twb) then vwb [twb] = (vwb [twb] or 0) + 1 end for wwb, xwb in pairs(vwb) do local ywb = Dungeon.item(wwb) for zwb, Awb in pairs(type(ywb and ywb.wave_modifier_effects) == "table" and ywb.wave_modifier_effects or { }) do local Bwb = (tonumber(Awb.amount) or 0) * xwb if Awb.type == "stat_mul" and Awb.stat == "damage" then uwb.dmg = uwb.dmg + Bwb elseif Awb.type == "stat_mul" and Awb.stat == "attack_cooldown" then uwb.cd = uwb.cd + Bwb elseif Awb.type == "stat_mul" and Awb.stat == "range" then uwb.range = uwb.range + Bwb elseif Awb.type == "stat_attr_add" and tostring(Awb.stat):sub(1, 9) == "_dmg_mul_" then local Cwb = tostring(Awb.stat):sub(10) uwb.kinds [Cwb] = (uwb.kinds [Cwb] or 0) + Bwb elseif Awb.type == "stat_add" and Awb.stat == "physical_crit_chance" then uwb.crit = uwb.crit + Bwb elseif Awb.type == "unit_cost" then uwb.cost = uwb.cost + Bwb elseif Awb.type == "enemy_stat_mul" and Awb.stat == "health" then uwb.hp = uwb.hp + Bwb elseif Awb.type == "enemy_speedup" then uwb.speed = uwb.speed + Bwb elseif Awb.type == "enemy_regen" then uwb.regen = uwb.regen + Bwb elseif Awb.type == "enemy_shield" then uwb.shield = uwb.shield + Bwb elseif Awb.type == "enemy_passive_chance" and Awb.passive == "Flying" then uwb.fly = uwb.fly + Bwb end end end uwb.cd, uwb.cost, uwb.speed = math.max(0.1, uwb.cd), math.max(0.1, uwb.cost), math.max(0.1, uwb.speed) return uwb end function Dungeon.fxText(Dwb) local Ewb = { } local function Fwb(Gwb, Hwb) if math.abs(Hwb - 1) > 0.001 then table.insert(Ewb, string.format("%s ร—%.2f", Gwb, Hwb)) end end Fwb("damage", Dwb.dmg) Fwb("cooldown", Dwb.cd) Fwb("range", Dwb.range) Fwb("unit prices", Dwb.cost) for Iwb, Jwb in pairs(Dwb.kinds) do table.insert(Ewb, string.format("%s +%d%%", Iwb, math.floor(Jwb * 100 + 0.5))) end if Dwb.crit > 0 then table.insert(Ewb, string.format("physical crit +%d%%", math.floor(Dwb.crit * 100 + 0.5))) end Fwb("enemy HP", Dwb.hp) Fwb("enemy speed", Dwb.speed) if Dwb.regen > 0 then table.insert(Ewb, string.format("enemy regen %d%%", math.floor(Dwb.regen * 100 + 0.5))) end if Dwb.shield > 0 then table.insert(Ewb, string.format("enemy shield +%d", math.floor(Dwb.shield + 0.5))) end if Dwb.fly > 0 then table.insert(Ewb, string.format("flyers +%d%%", math.floor(Dwb.fly * 100 + 0.5))) end return # Ewb > 0 and table.concat(Ewb, ", ") or "none" end function Dungeon.sim(Kwb, Lwb, Mwb) if not Config.sim or type(Kwb.r._l) ~= "table" then return nil end local Nwb = tostring(Kwb.r._r or (Kwb.d .. ":" .. Kwb.choice)) .. (Lwb and ("+" .. tostring(Lwb)) or "") .. (Mwb and "#spread" or "") local Owb = Dungeon.data() local Pwb = Dungeon.fx(Owb, Dungeon.roomCurse(Kwb.r), Lwb) local Qwb = Modes.teamSig() .. "#" .. Dungeon.fxText(Pwb) .. (Config.killZone ~= false and "#stack" or "") local Rwb = Brain.dg.sim [Nwb] if Rwb and Rwb.sig == Qwb then return not Rwb.none and Rwb or nil end local Swb = { } for Twb, Uwb in ipairs(Units.equipped()) do table.insert(Swb, Uwb.u) end if # Swb == 0 then return nil end local Vwb = Simulator.applyFx(Simulator.info(Simulator.slim(Kwb.r._l), "dg", nil, Dungeon.mod(Kwb.r._l)), Pwb) if Mwb then Vwb.zone = false end local Wwb = Simulator.wave(Vwb, 1, true) if # Wwb == 0 then Rwb = { none = true, sig = Qwb, at = os.time() } else local Xwb = Simulator.evaluate(Vwb, Simulator.teamFrom(Swb, Vwb.map)) Rwb = { p = Xwb.p, wave = Xwb.wave, won = Xwb.won, total = Vwb.total, margin = Xwb.margin, sig = Qwb, at = os.time() } end Brain.dg.sim [Nwb] = Rwb if Utils.count(Brain.dg.sim) > 60 then local Ywb, Zwb for _wb, axb in pairs(Brain.dg.sim) do if not Zwb or (axb.at or 0) < Zwb then Ywb, Zwb = _wb, axb.at or 0 end end Brain.dg.sim [Ywb] = nil end App.saveSoon() return not Rwb.none and Rwb or nil end function Dungeon.winP(bxb) local cxb = tostring(math.min(4, math.max(1, math.ceil(bxb.d / 5)))) local dxb = Brain.dg.depth [cxb] or { runs = 0, wins = 0 } local exb = Utils.clamp(0.95 - 0.015 * bxb.d, 0.5, 0.95) local fxb = ((tonumber(dxb.wins) or 0) + exb * 2) / ((tonumber(dxb.runs) or 0) + 2) local gxb, hxb = pcall(Dungeon.sim, bxb) if gxb and hxb and hxb.p then fxb = (fxb * 2 + hxb.p) / 3 end return fxb, gxb and hxb or nil end function Dungeon.hopeless(ixb) return ixb ~= nil and ixb.won == false and (tonumber(ixb.p) or 1) <= 0.05 end function Dungeon.stake(jxb) local kxb, lxb, mxb = Dungeon.res(jxb) return lxb + 2 * mxb end function Dungeon.curseBar(nxb, oxb) local pxb = Dungeon.stake(nxb) if oxb >= 15 or pxb >= 8 then return 0.9, "beat them against 30% tougher enemies", pxb end if oxb >= 10 or pxb >= 4 then return 0.6, "beat them", pxb end return 0.1, "have a chance at them", pxb end function Dungeon.blindCurse(qxb, rxb) return Dungeon.fxText(Dungeon.fx(qxb, rxb)) == Dungeon.fxText(Dungeon.fx(qxb)) end function Dungeon.curseOk(sxb, txb, uxb, vxb, wxb) local xxb = tostring((Dungeon.item(sxb) or { }).name or sxb) local yxb, zxb, Axb = Dungeon.curseBar(txb, uxb) if yxb > 0.2 and Dungeon.blindCurse(txb, sxb) then return false, string.format("the simulator can't play %s, and %d chests are riding on the run", xxb, Axb) end local function Bxb(Cxb) return Cxb == nil or (tonumber(Cxb.p) or 1) >= yxb end if vxb then local Dxb, Exb = pcall(Dungeon.sim, vxb, nil, true) if Dxb and not Bxb(Exb) then return false, string.format("with %s your team wouldn't %s (this room: %s)", xxb, (zxb:gsub("them", "this room")), tostring(Exb.margin or "?")) end end if # wxb == 0 then return true end local Fxb for Gxb, Hxb in ipairs(wxb) do local Ixb, Jxb = pcall(Dungeon.sim, Hxb, sxb, true) if not Ixb or Bxb(Jxb) then return true end if not Fxb or (tonumber(Jxb.p) or 0) > (tonumber(Fxb.p) or 0) then Fxb = Jxb end end return false, string.format("with %s your team wouldn't %s (the rooms after it, at best: %s)", xxb, zxb, tostring(Fxb and Fxb.margin or "?")) end function Dungeon.curseGate(Kxb, Lxb) local Mxb = Dungeon.roomCurse(Kxb.r) if not Mxb then return true end return Dungeon.curseOk(Mxb, Lxb, Kxb.d, Kxb, Dungeon.after(Lxb, Kxb.choice)) end function Dungeon.payValue() local Nxb, Oxb = 0, 0 for Pxb, Qxb in ipairs(Brain.dg.pay or { }) do if type(Qxb.items) == "table" then local Rxb = tonumber(Qxb.gems) or 0 for Sxb, Txb in pairs(Qxb.items) do if (tonumber(Txb) or 0) > 0 then Rxb = Rxb + Modes.itemValue(Sxb, Txb) end end Nxb, Oxb = Nxb + Rxb, Oxb + 1 end end if Oxb < 5 then return nil end return Nxb / Oxb * 0.7 end function Dungeon.pick(Uxb) local Vxb, Wxb = Dungeon.curses(Uxb), Dungeon.lives(Uxb) local Xxb = Dungeon.res(Uxb) local Yxb = Dungeon.rooms(Uxb) local Zxb = { } for _xb, ayb in ipairs(Yxb) do table.insert(Zxb, tostring(ayb.r._r)) end local byb = table.concat(Zxb, ",") .. string.format("|%d|%d|%d|%s", Wxb, Xxb, Vxb, Modes.teamSig()) if Dungeon._pick and Dungeon._pick.sig == byb then return Dungeon._pick.x end local cyb, dyb for eyb, fyb in ipairs(Yxb) do local gyb = Dungeon.roomValue(fyb, Xxb) local hyb, iyb for jyb, kyb in ipairs(Dungeon.after(Uxb, fyb.choice)) do local lyb = Dungeon.roomValue(kyb, Xxb) if not hyb or lyb > hyb then hyb, iyb = lyb, kyb end end local myb, nyb = Dungeon.winP(fyb) local oyb = 1 - (1 - myb) ^ math.max(1, Wxb) local pyb = Wxb >= 3 and 1 or (Wxb == 2 and 2 or 6) fyb.p, fyb.sim, fyb.value, fyb.nextValue, fyb.nextRoom = myb, nyb, gyb, hyb or 0, iyb fyb.score = oyb * (gyb + 0.6 * (hyb or 0)) - (1 - myb) * pyb local qyb, ryb, syb = pcall(Dungeon.curseGate, fyb, Uxb) if qyb and not ryb then fyb.blocked = syb end if not fyb.blocked and (not cyb or fyb.score > cyb.score) then cyb = fyb end if not dyb or fyb.score > dyb.score then dyb = fyb end end cyb = cyb or dyb Dungeon._pick = { sig = byb, x = cyb, rooms = Yxb } return cyb end function Dungeon.roomName(tyb) local uyb = type(tyb.r._l) == "table" and tyb.r._l or { } return string.format("%s (%s)", tostring(uyb._location_name or uyb.map or "?"), Dungeon.tagText(tyb.tags)) end function Dungeon.screens(vyb) local wyb = { } for xyb, yyb in pairs(type(vyb.current_shops) == "table" and vyb.current_shops or { }) do if type(yyb) == "table" then table.insert(wyb, { i = tonumber(xyb) or 0, t = tonumber(yyb._t), data = yyb.data }) end end table.sort(wyb, function(zyb, Ayb) return zyb.i < Ayb.i end) return wyb end function Dungeon.cleared(Byb) return math.max(1, (Dungeon.depth(Byb) or (Dungeon.ROOMS + 1)) - 1) end function Dungeon.state(Cyb) if Cyb._dead then return "dead" end if Cyb._finished then return "finished" end local Dyb = Dungeon.screens(Cyb) [1] if Dyb then if Dyb.t == Dungeon.T.SHOP then return "shop", Dyb end if Dyb.t == Dungeon.T.OPEN then return "chests", Dyb end if Dyb.t == Dungeon.T.SHRINE then return "shrine", Dyb end return "screen", Dyb end return "rooms" end function Dungeon.logState(Eyb, Fyb) local Gyb = { } for Hyb, Iyb in pairs(Eyb) do if not Dungeon.KNOWN [Hyb] and not (type(Iyb) == "table" and next(Iyb) == nil) then table.insert(Gyb, tostring(Hyb)) end end table.sort(Gyb) local Jyb = { } for Kyb, Lyb in ipairs(Dungeon.screens(Eyb)) do table.insert(Jyb, tostring(Lyb.t)) end local Myb = table.concat(Gyb, ",") .. (# Jyb > 0 and ("|" .. table.concat(Jyb, ",")) or "") if Myb == "" or Brain.dg.seen [Myb] then return end Brain.dg.seen [Myb] = os.time() local Nyb = { } for Oyb, Pyb in ipairs(Gyb) do local Qyb = Eyb [Pyb] table.insert(Nyb, Pyb .. "=" .. (type(Qyb) == "table" and Utils.json(Utils.plain(Qyb)):sub(1, 500) or tostring(Qyb))) end App.log(1, "DUNGEON", "new screen (" .. tostring(Fyb) .. "): " .. table.concat(Nyb, " ยท ")) App.saveSoon() end function Dungeon.note(Ryb) App.log(1, "DUNGEON", Ryb) table.insert(Brain.dg.log, 1, { at = os.time(), msg = Ryb }) while # Brain.dg.log > 15 do table.remove(Brain.dg.log) end App.saveSoon() end function Dungeon.offers(Syb, Tyb) local Uyb = { } if type(Syb) ~= "table" then return Uyb end for Vyb, Wyb in pairs(Syb) do local Xyb, Yyb if type(Wyb) == "table" then Xyb = Wyb.id or Wyb.item_id or Wyb.item Yyb = tonumber(Wyb.cost) or tonumber(Wyb.price) elseif type(Wyb) == "string" then Xyb = Wyb elseif Dungeon.item(Vyb) then Xyb = Vyb Yyb = tonumber(Wyb) end if type(Xyb) == "string" and Dungeon.item(Xyb) then table.insert(Uyb, { key = Vyb, id = Xyb, cost = Yyb or tonumber(Dungeon.item(Xyb).cost) or 0, bought = type(Wyb) == "table" and (Wyb.bought or Wyb.sold or Wyb.purchased) or nil }) end end if # Uyb == 0 and not Tyb then for Zyb, _yb in pairs(Syb) do if type(_yb) == "table" then Uyb = Dungeon.offers(_yb, true) if # Uyb > 0 then break end end end end return Uyb end function Dungeon.buy(azb) local bzb = Dungeon.res(Dungeon.data()) local czb = { } if Brain.dg.buyArg == "id" then czb = { azb.id, azb.key } else czb = { azb.key, azb.id } end if czb [1] == czb [2] then czb [2] = nil end for dzb, ezb in ipairs(czb) do local fzb, gzb = App.request("dgBuy", "dungeon_buy_shop", ezb) local hzb = Utils.now() while Utils.now() - hzb < 3 and Dungeon.res(Dungeon.data()) >= bzb do task.wait(0.25) end if Dungeon.res(Dungeon.data()) < bzb then Brain.dg.buyArg = (ezb == azb.id and azb.id ~= azb.key) and "id" or "key" return true end if not fzb then break end end return false end function Dungeon.shopAhead(izb) for jzb, kzb in ipairs(Dungeon.rooms(izb)) do if kzb.tags [Dungeon.T.SHOP] then return true end for lzb, mzb in ipairs(Dungeon.after(izb, kzb.choice)) do if mzb.tags [Dungeon.T.SHOP] then return true end end end return false end Dungeon.LUCK_RESERVE = 500 Dungeon.MIN_WORTH = 0.02 Dungeon.TOP_WORTH = 0.08 function Dungeon.shop(nzb, ozb) local pzb = Dungeon.cleared(nzb) local qzb = Dungeon.res(nzb) local rzb = 1 + 0.1 * (Dungeon.items(nzb)._curse_shop or 0) local szb = Dungeon.shopAhead(nzb) and math.ceil(Dungeon.LUCK_RESERVE * rzb) or 0 local tzb = { } local uzb, vzb = { }, 0 for wzb, xzb in ipairs(Dungeon.offers(ozb and ozb.data)) do xzb.cost = math.ceil(xzb.cost * rzb) table.insert(uzb, tostring(Dungeon.item(xzb.id).name or xzb.id)) if not xzb.bought and not Dungeon.isCurse(xzb.id) then xzb.luck = tonumber(Dungeon.item(xzb.id).luck_increase) ~= nil xzb.v = Dungeon.itemValue(xzb.id, nzb) if not xzb.luck and xzb.v / math.max(1, xzb.cost) < Dungeon.MIN_WORTH then vzb = vzb + 1 elseif xzb.v > 0 and (xzb.luck or pzb >= 10 or xzb.v / math.max(1, xzb.cost) >= Dungeon.TOP_WORTH) then table.insert(tzb, xzb) end end end table.sort(tzb, function(yzb, zzb) if yzb.luck ~= zzb.luck then return yzb.luck end return yzb.v / math.max(1, yzb.cost) > zzb.v / math.max(1, zzb.cost) end) local Azb, Bzb, Czb = { }, 0, 0 for Dzb, Ezb in ipairs(tzb) do if Ezb.cost <= qzb - (Ezb.luck and 0 or szb) then if Dungeon.buy(Ezb) then qzb = Dungeon.res(Dungeon.data()) table.insert(Azb, string.format("%s (%d coins)", tostring(Dungeon.item(Ezb.id).name or Ezb.id), Ezb.cost)) end elseif Ezb.cost <= qzb then Czb = Czb + 1 else Bzb = Bzb + 1 end end Dungeon.note(string.format("shop at depth %d (%s): %s ยท %d coins left%s%s%s", pzb, # uzb > 0 and table.concat(uzb, ", ") or "offer not read", # Azb > 0 and ("bought " .. table.concat(Azb, ", ")) or (pzb < 10 and "no chest luck on sale: coins kept for the shops at floors 10โ€“15" or (Czb > 0 and "nothing bought" or "nothing worth buying")), qzb, Bzb > 0 and string.format(" (%d too dear)", Bzb) or "", Czb > 0 and string.format(" (%d left on the shelf: %d coins kept for chest luck, a shop room is ahead)", Czb, szb) or "", vzb > 0 and string.format(" (%d would do nothing for this team's damage type)", vzb) or "")) App.request("dgCont", "dungeon_continue_shop") end function Dungeon.shrine(Fzb, Gzb) local Hzb = Dungeon.offers(Gzb and Gzb.data) local Izb, Jzb = 0, { } local Kzb for Lzb, Mzb in ipairs(Hzb) do local Nzb = Dungeon.item(Mzb.id) table.insert(Jzb, tostring(Nzb and Nzb.name or Mzb.id)) if Dungeon.isCurse(Mzb.id) then local Ozb, Pzb, Qzb = pcall(Dungeon.curseOk, Mzb.id, Fzb, Dungeon.cleared(Fzb) + 1, nil, Dungeon.rooms(Fzb)) if Ozb and not Pzb then Kzb = Qzb end else Izb = Izb + Dungeon.itemValue(Mzb.id, Fzb) end end local Rzb = # Hzb > 0 and Izb > 0 and not Kzb if Rzb then App.request("dgShrine", "dungeon_shrine_accept") else App.request("dgCont", "dungeon_continue_shop") end Dungeon.note(string.format("shrine (%s): %s", # Jzb > 0 and table.concat(Jzb, ", ") or "offer not read", Rzb and "taken" or (Kzb and ("passed: " .. Kzb) or "passed"))) end function Dungeon.wallet() local Szb = Core.session() and Core.session().profile_data or { } local Tzb = Szb.inventory or { } local Uzb = { gems = tonumber(Core.cur("gems")) or 0, gold = tonumber(Core.cur("gold")) or 0, items = { }, units = { }, unique = 0 } for Vzb, Wzb in pairs(Tzb.normal_items or { }) do Uzb.items [Vzb] = tonumber(Wzb) or 0 end for Xzb, Yzb in pairs(Szb.collection and Szb.collection.owned_units or { }) do Uzb.units [Xzb] = Yzb.unit_id end for Zzb in pairs(Tzb.unique_items or { }) do Uzb.unique = Uzb.unique + 1 end return Uzb end function Dungeon.walletDiff(_zb, aAb) local bAb = { } if aAb.gems ~= _zb.gems then table.insert(bAb, string.format("%+d gems", aAb.gems - _zb.gems)) end if aAb.gold ~= _zb.gold then table.insert(bAb, string.format("%+d gold", aAb.gold - _zb.gold)) end for cAb, dAb in pairs(aAb.items) do if dAb ~= (_zb.items [cAb] or 0) then table.insert(bAb, string.format("%+d %s", dAb - (_zb.items [cAb] or 0), tostring(cAb))) end end for eAb, fAb in pairs(aAb.units) do if not _zb.units [eAb] then table.insert(bAb, "new unit " .. Units.name(fAb)) end end if aAb.unique ~= _zb.unique then table.insert(bAb, string.format("%+d relics", aAb.unique - _zb.unique)) end return # bAb > 0 and table.concat(bAb, ", ") or "nothing seen" end function Dungeon.openChests(gAb, hAb) local iAb, jAb, kAb = Dungeon.res(gAb) local lAb = Dungeon.lives(gAb) local mAb = Dungeon.cleared(gAb) if not (hAb or mAb >= Dungeon.ROOMS or lAb <= 1) then Dungeon.note(string.format("chest room at depth %d: keeping %d chests and %d rare chests for floor 20 (luck ร—%.1f now)", mAb, jAb, kAb, Dungeon.luck(gAb))) App.request("dgCont", "dungeon_continue_shop") return end local nAb, oAb, pAb = 0, 0, 0 local qAb = tonumber(Core.cur("gems")) or 0 local rAb = Dungeon.wallet() for sAb = 1, jAb + kAb + 4 do local tAb, uAb, vAb = Dungeon.res(Dungeon.data()) if uAb + vAb <= 0 or pAb >= 2 then break end local wAb, xAb if uAb > 0 then wAb, xAb = App.request("dgChest", "dungeon_open_chest") else wAb, xAb = App.request("dgChest", "dungeon_open_chest", true) end if wAb and xAb ~= nil then App.log(1, "DUNGEON", "chest: " .. (type(xAb) == "table" and Utils.json(Utils.plain(xAb)):sub(1, 300) or tostring(xAb))) end local yAb = Utils.now() while Utils.now() - yAb < 4 do local zAb, AAb, BAb = Dungeon.res(Dungeon.data()) if AAb < uAb or BAb < vAb then break end task.wait(0.25) end local CAb, DAb, EAb = Dungeon.res(Dungeon.data()) if DAb < uAb then nAb = nAb + (uAb - DAb) pAb = 0 elseif EAb < vAb then oAb = oAb + (vAb - EAb) pAb = 0 else pAb = pAb + 1 end task.wait(1) if DAb < uAb or EAb < vAb then local FAb = Dungeon.wallet() local GAb = Dungeon.walletDiff(rAb, FAb) App.log(1, "DUNGEON", (EAb < vAb and "rare chest: " or "chest: ") .. GAb) Brain.dg.pay = Brain.dg.pay or { } local HAb = { } for IAb, JAb in pairs(FAb.items) do if JAb ~= (rAb.items [IAb] or 0) then HAb [IAb] = JAb - (rAb.items [IAb] or 0) end end table.insert(Brain.dg.pay, { at = os.time(), rare = EAb < vAb, gems = FAb.gems - rAb.gems, luck = Dungeon.luck(gAb), paid = GAb, items = HAb }) while # Brain.dg.pay > 60 do table.remove(Brain.dg.pay, 1) end rAb = FAb end end Dungeon.note(string.format("opened %d chests and %d rare chests (%s, luck ร—%.1f) ยท gems %+d", nAb, oAb, hAb and "last room" or (lAb <= 1 and "one life left" or "floor 20"), Dungeon.luck(gAb), (tonumber(Core.cur("gems")) or 0) - qAb)) local KAb, LAb, MAb = Dungeon.res(Dungeon.data()) if (hAb or mAb >= Dungeon.ROOMS) and LAb + MAb > 0 then Dungeon.note(string.format("%d chests and %d rare chests didn't open: the last room stays open, trying again in 30 min", LAb, MAb)) Modes.rec("dg").restUntil = os.time() + 1800 return "hold" end if not hAb then App.request("dgCont", "dungeon_continue_shop") end end function Dungeon.act(NAb) local OAb, PAb = Dungeon.state(NAb) Dungeon.logState(NAb, OAb) if OAb == "rooms" then return false end if OAb == "screen" then Dungeon.note(string.format("a screen of a kind not known yet (tag %s): closed with Continue", tostring(PAb and PAb.t))) App.request("dgCont", "dungeon_continue_shop") return true end if OAb == "dead" then App.request("dgDeath", "dungeon_continue_death") Dungeon.note("the run is over: that was the last life, and its unopened chests went with it") Brain.dg.lives, Brain.dg.lost = 5, { } elseif OAb == "finished" then if Dungeon.openChests(NAb, true) == "hold" then return "hold" end App.request("dgCont", "dungeon_continue_shop") local QAb, RAb, SAb = Dungeon.res(Dungeon.data()) table.insert(Brain.dg.runs, 1, { at = os.time(), depth = Dungeon.ROOMS, items = Utils.count(Dungeon.items(NAb)), left = RAb + SAb }) while # Brain.dg.runs > 10 do table.remove(Brain.dg.runs) end Dungeon.note("dungeon finished: all 20 rooms") elseif OAb == "shop" then Dungeon.shop(NAb, PAb) elseif OAb == "shrine" then Dungeon.shrine(NAb, PAb) elseif OAb == "chests" then if Dungeon.openChests(NAb, false) == "hold" then return "hold" end end return true end function Dungeon.launch(TAb) local UAb = Dungeon.data() if not UAb then App.backoff("launch", 30) return false end if not UAb._started then if not (Config.dungeonStart or Config.onlyDungeons) then return false end local VAb, WAb = App.request("dgStart", "dungeon_start") task.wait(2.5) UAb = Dungeon.data() if not (VAb and UAb and UAb._started) then App.log(1, "DUNGEON", "the game didn't start a dungeon run (" .. tostring(WAb) .. "): trying again in 30 min") Modes.rec("dg").restUntil = os.time() + 1800 App.backoff("launch", 10) return false end Brain.dg.lost = { } Dungeon.note("new dungeon run started (4 lives)") end if Dungeon.enter(UAb, TAb.value, false) then return true end App.backoff("launch", 10) return false end function Dungeon.playLast() local XAb = Brain.dg.playLast return type(XAb) == "table" and os.time() - (tonumber(XAb.at) or 0) < 1800 and XAb or nil end function Dungeon.forced(YAb) local ZAb = Brain.dg.force if type(ZAb) ~= "table" or type(ZAb.uuids) ~= "table" or ZAb.seed ~= tostring(YAb.r._r) then return nil end if os.time() - (tonumber(ZAb.at) or 0) > 3600 then Brain.dg.force = nil return nil end return ZAb end function Dungeon.startTeam(_Ab, aBb, bBb, cBb) Brain.startTeam = { uuids = table.clone(_Ab), must = true, own = cBb or nil, seed = tostring(aBb.r._r), why = bBb } end function Dungeon.planTeam(dBb, eBb, fBb) if not (Config.sim and Config.simTeams) or type(dBb.r._l) ~= "table" then return end if Dungeon.playLast() then App.log(1, "SIM", string.format("dungeon room %d: your own pick stays, no search", dBb.d)) return end local gBb = Dungeon.forced(dBb) if gBb then if fBb then App.log(1, "SIM", string.format("dungeon room %d: your call โ€” %s goes in as the room starts, no search", dBb.d, tostring(gBb.label))) if not Training.same(Training.team(), gBb.uuids) then Dungeon.startTeam(gBb.uuids, dBb, tostring(gBb.label)) end return end App.log(1, "SIM", string.format("dungeon room %d: your call โ€” %s goes in, no search", dBb.d, tostring(gBb.label))) FarmHub.equipTeam(gBb.uuids, tostring(gBb.label)) task.wait(0.5) return end if fBb and not Config.autoTeam then return end local hBb = Brain.simTeam.dg if not fBb and Config.autoTeam and hBb and hBb.better and hBb.seed == tostring(dBb.r._r) and os.time() - (tonumber(hBb.at) or 0) < 300 then FarmHub.equipTeam(hBb.uuids, "the best team found for dungeon room " .. dBb.d) task.wait(0.5) return end local iBb = Dungeon.fx(eBb, Dungeon.roomCurse(dBb.r)) local jBb = { } if fBb and type(Brain.savedTeam) == "table" and # Brain.savedTeam > 0 then local kBb = Core.session() for lBb, mBb in ipairs(Brain.savedTeam) do local nBb = kBb.collection:get_unit_by_uuid(mBb) if nBb then table.insert(jBb, nBb) end end else for oBb, pBb in ipairs(Units.equipped()) do table.insert(jBb, pBb.u) end end if # jBb == 0 then return end local qBb = Dungeon.mod(dBb.r._l) local rBb = Simulator.applyFx(Simulator.info(Simulator.slim(dBb.r._l), "dg", nil, qBb), iBb) App.status("Mode", "looking for the best team for dungeon room " .. dBb.d) Simulator.memoOn() local sBb = Simulator.pool(qBb, rBb) local tBb, uBb, vBb, wBb = Simulator.bestTeam(rBb, jBb, sBb, Dungeon.lives(eBb) <= 1 and 45 or 20) local function xBb(yBb) local zBb = Simulator.sig(yBb) local ABb = type(Brain.dg.teams) == "table" and Brain.dg.teams [zBb] or { } local BBb = type(Brain.dg.lost) == "table" and Brain.dg.lost [tostring(dBb.r._r)] and Brain.dg.lost [tostring(dBb.r._r)] [zBb] or 0 if type(Brain.dg.bail) == "table" and Brain.dg.bail.sig == zBb and os.time() - (tonumber(Brain.dg.bail.at) or 0) < 1800 then BBb = BBb + 1 end local CBb, DBb, EBb, FBb = Simulator.strength(rBb, yBb) local GBb = math.clamp((tonumber(ABb.w) or 0) - 2 * (tonumber(ABb.l) or 0), - 6, 3) if dBb.d >= 13 then GBb = math.min(GBb, 1) end GBb = GBb - 3 * BBb return { score = CBb + GBb, wins = CBb, atOne = DBb, reach = EBb, far = FBb, lostHere = BBb, w = tonumber(ABb.w) or 0, l = tonumber(ABb.l) or 0 } end local HBb = Simulator.sig(tBb) == Simulator.sig(jBb) local IBb = xBb(jBb) local JBb = HBb and IBb or xBb(tBb) if (rBb.regen or 0) > 0 then local KBb = false for LBb, MBb in ipairs(jBb) do if Units.bleeds(MBb.unit_id) then KBb = true end end for NBb, OBb in ipairs(tBb) do if Units.bleeds(OBb.unit_id) then KBb = true end end local PBb = not KBb and Units.bestBleeder(jBb) or nil if PBb then local QBb = { } for RBb, SBb in ipairs(jBb) do if not Units.isFarm(SBb.unit_id) and not Units.allyBuff(SBb.unit_id) then local TBb = table.clone(jBb) TBb [RBb] = PBb local UBb = xBb(TBb) table.insert(QBb, string.format("%s out: %d tries", Units.name(SBb.unit_id), UBb.wins)) if UBb.score > JBb.score then tBb, JBb, HBb = TBb, UBb, false uBb = { won = UBb.wins > 0, wave = UBb.reach } end task.wait() end end App.log(1, "SIM", string.format("dungeon room %d: enemies regenerate, so %s (bleeds from upgrade %d) was tried in each slot: %s", dBb.d, Units.name(PBb.unit_id), Units.bleeds(PBb.unit_id) or 0, table.concat(QBb, " ยท "))) end end Simulator.memo = nil local VBb = JBb.wins > IBb.wins or (JBb.wins == IBb.wins and (JBb.far or 0) > (IBb.far or 0)) local WBb = not HBb and VBb and JBb.lostHere <= IBb.lostHere local XBb, YBb = { }, { } for ZBb, _Bb in ipairs(tBb) do table.insert(XBb, _Bb.uuid) table.insert(YBb, Units.name(_Bb.unit_id)) end Brain.simTeam.dg = { uuids = XBb, names = YBb, won = uBb.won, wave = uBb.wave, better = WBb, cur = { won = wBb.won, wave = wBb.wave }, at = os.time(), label = "Dungeon", seed = tostring(dBb.r._r) } local function aCb(bCb) return string.format("wins %d simulated tries up the rising test, gets to wave %d at normal strength, real rooms %d won and %d lost", bCb.wins, math.floor(bCb.reach + 0.5), bCb.w, bCb.l) end if not WBb then if HBb then App.log(1, "SIM", string.format("dungeon room %d: tried %d units, none beats your team (%s)", dBb.d, # sBb, aCb(IBb))) elseif not VBb then App.log(1, "SIM", string.format("dungeon room %d: your team goes in (%s): the search's pick, %s, doesn't go further up the rising test (%s)", dBb.d, aCb(IBb), table.concat(YBb, ", "), aCb(JBb))) else App.log(1, "SIM", string.format("dungeon room %d: your team goes in (%s): the search's pick, %s, already lost here %d times (%s)", dBb.d, aCb(IBb), table.concat(YBb, ", "), JBb.lostHere, aCb(JBb))) end if fBb and type(Brain.savedTeam) == "table" and not Training.same(Training.team(), Brain.savedTeam) then Dungeon.startTeam(Brain.savedTeam, dBb, "your own team back for dungeon room " .. dBb.d, true) end return end App.log(1, "SIM", string.format("dungeon room %d: the search's top pick: %s (%s; yours: %s)", dBb.d, table.concat(YBb, ", "), aCb(JBb), aCb(IBb))) if fBb then if not Training.same(Training.team(), XBb) then Dungeon.startTeam(XBb, dBb, "the simulator's best team for dungeon room " .. dBb.d) end return end if Config.autoTeam then FarmHub.equipTeam(XBb, "the simulator's best team for dungeon room " .. dBb.d) task.wait(0.5) else App.log(1, "SIM", "Auto-Equip Best Team is off, so your team stays as it is") end end function Dungeon.enter(cCb, dCb, eCb) for fCb = 1, 5 do local gCb, hCb = pcall(Dungeon.act, cCb) if not gCb then App.log(1, "ERROR", "dungeon screen: " .. tostring(hCb)) break end if hCb == "hold" then return false end if not hCb then break end task.wait(2) cCb = Dungeon.data() if not (cCb and cCb._started) then return false end end Dungeon._pick = nil local iCb = Dungeon.pick(cCb) if not iCb then Dungeon.logState(cCb, "no rooms on offer") local jCb, kCb, lCb = Dungeon.res(cCb) if kCb + lCb > 0 then App.log(1, "DUNGEON", string.format("no room on offer and %d + %d chests unopened: nothing pressed, trying again in 30 min", kCb, lCb)) Modes.rec("dg").restUntil = os.time() + 1800 return false end App.log(1, "DUNGEON", "no room on offer: leaving the screen it's on") App.request("dgCont", "dungeon_continue_shop") return false end local mCb, nCb = pcall(Dungeon.planTeam, iCb, cCb, eCb) if not mCb then App.log(1, "ERROR", "dungeon team: " .. tostring(nCb)) end if not eCb then local oCb = Brain.simTeam.dg local pCb = tostring(iCb.r._r) Brain.dg.swapFail = type(Brain.dg.swapFail) == "table" and Brain.dg.swapFail or { } local qCb = tonumber(Brain.dg.swapFail [pCb]) or 0 local rCb = Dungeon.forced(iCb) local sCb = (rCb and rCb.uuids) or ((oCb and oCb.better and oCb.seed == pCb and qCb < 2) and oCb.uuids) or Brain.savedTeam local tCb = Training.team() local uCb = # tCb == 6 and (not sCb or Training.same(tCb, sCb)) if not uCb then Brain.dg.swapFail [pCb] = qCb + 1 local vCb = { } for wCb, xCb in ipairs(Units.equipped()) do table.insert(vCb, Units.name(xCb.id)) end if qCb + 1 >= 2 and oCb and oCb.better and oCb.seed == pCb then oCb.better = false App.log(1, "TEAM", string.format("the planned team for room %d wouldn't equip twice: your own team goes in instead", iCb.d)) pcall(FarmHub.restoreTeam) end App.log(1, "DUNGEON", string.format("room %d not entered: the team isn't right yet (%d units: %s). Trying the swap again in a moment", iCb.d, # tCb, table.concat(vCb, ", "))) App.status("Mode", "waiting for the right team before dungeon room " .. iCb.d) Modes.rec("dg").restUntil = os.time() + 45 Brain.pending = nil App.saveSoon() return false end Brain.dg.swapFail [pCb] = nil local yCb, zCb = pcall(Strategy.relicsApply) if not yCb then App.log(1, "ERROR", "relics: " .. tostring(zCb)) end end if Dungeon.lives(cCb) <= 1 then Brain.dg.lost = Brain.dg.lost or { } local ACb = Dungeon.sim(iCb) local BCb = (Brain.dg.lost [tostring(iCb.r._r)] or { }) [Modes.teamSig()] or 0 local CCb = # Units.equipped() < 6 if (not ACb or BCb >= 2 or CCb) and not (Dungeon.playLast() and not CCb) then local DCb = CCb and string.format("the team has only %d units", # Units.equipped()) or BCb >= 2 and string.format("this team already lost here %d times", BCb) or "the simulator can't play this room" Dungeon.note(string.format("last life on room %d: holding it (%s). Looks again in an hour", iCb.d, DCb)) Modes.rec("dg").restUntil = os.time() + 3600 Brain.pending = nil App.saveSoon() return false end end Brain.dg.room = { seed = iCb.r._r, d = iCb.d } Brain.pending = { key = "dg", kind = "dungeon", level = "dungeon", diff = "Hard", label = "Dungeon room " .. iCb.d, at = os.time(), value = dCb, depth = iCb.d } if Brain.nextMode == "dg" then Brain.nextMode = nil end local ECb, FCb, GCb = Dungeon.res(cCb) local HCb = string.format("win โ‰ˆ%d%%%s ยท leads to %s ยท %d lives ยท %d coins ยท chests %d + %d rare", math.floor(iCb.p * 100 + 0.5), iCb.sim and (" (sim: " .. tostring(iCb.sim.margin) .. ")") or "", iCb.nextRoom and Dungeon.roomName(iCb.nextRoom) or "the last room", Dungeon.lives(cCb), ECb, FCb, GCb) App.log(1, "MODE", string.format("entering dungeon room %d/%d%s: %s โ€” %s", iCb.d, Dungeon.ROOMS, eCb and " from the results screen" or "", Dungeon.roomName(iCb), HCb)) local ICb = { } for JCb, KCb in ipairs(Dungeon._pick and Dungeon._pick.rooms or { }) do if KCb ~= iCb then table.insert(ICb, string.format("%s, win โ‰ˆ%d%%%s", Dungeon.roomName(KCb), math.floor((KCb.p or 0) * 100 + 0.5), KCb.blocked and (" (skipped: " .. KCb.blocked .. ")") or "")) end end if # ICb > 0 then App.log(1, "DUNGEON", "chosen over: " .. table.concat(ICb, " ยท ")) end App.saveNow() App.status("Mode", "entering dungeon room " .. iCb.d) local LCb = type(cCb.selected_next) == "table" and cCb.selected_next.choice_str and tostring(cCb.selected_next.choice_str) or nil local MCb = { tostring(iCb.key) } if LCb and LCb ~= tostring(iCb.key) and not eCb then table.insert(MCb, LCb) end for NCb, OCb in ipairs(MCb) do if NCb == 2 then local PCb for QCb, RCb in ipairs(Dungeon.rooms(cCb)) do if tostring(RCb.key) == OCb then PCb = RCb end end App.log(1, "DUNGEON", string.format("the game only lets the run enter its selected room %s: %s", OCb, PCb and Dungeon.roomName(PCb) or "?")) end local SCb, TCb = App.request("dgEnter", "dungeon_enter_room", OCb) local UCb = Utils.now() while App.alive and Utils.now() - UCb < 15 do task.wait(1) end if not App.alive then return true end App.log(1, "DUNGEON", string.format("no teleport after entering room %s (answer %s)", OCb, tostring(TCb))) end Dungeon.logState(cCb, "enter didn't teleport") Brain.pending = nil if not eCb then Modes.rec("dg").restUntil = os.time() + 900 end App.saveSoon() return false end function Dungeon.chain() task.wait(4) local VCb = Dungeon.data() if not (VCb and VCb._started) then return false end return Dungeon.enter(VCb, Config.dungeonValue, true) end function Dungeon.roomDone(WCb) Brain.dg.playLast = nil local XCb = tonumber(type(Match.ld._dungeon) == "table" and Match.ld._dungeon.depth) or (Brain.pending and tonumber(Brain.pending.depth)) or 1 local YCb = tostring(math.min(4, math.max(1, math.ceil(XCb / 5)))) local ZCb = Brain.dg.depth [YCb] or { runs = 0, wins = 0 } ZCb.runs, ZCb.wins = (tonumber(ZCb.runs) or 0) + 1, (tonumber(ZCb.wins) or 0) + (WCb and 1 or 0) Brain.dg.depth [YCb] = ZCb local _Cb = Dungeon.data() local aDb, bDb, cDb = Dungeon.res(_Cb) Dungeon.note(string.format("room %d/%d %s ยท %d coins ยท chests %d + %d rare ยท %d lives", XCb, Dungeon.ROOMS, WCb and "cleared" or "lost", aDb, bDb, cDb, Dungeon.lives(_Cb))) Brain.dg.teams = type(Brain.dg.teams) == "table" and Brain.dg.teams or { } local dDb = Brain.dg.teams [Modes.teamSig()] or { w = 0, l = 0 } if WCb then dDb.w = (tonumber(dDb.w) or 0) + 1 else dDb.l = (tonumber(dDb.l) or 0) + 1 end Brain.dg.teams [Modes.teamSig()] = dDb if WCb then Brain.dg.lives = 5 elseif Brain.dg.room and Brain.dg.room.d == XCb then Brain.dg.lost = Brain.dg.lost or { } local eDb, fDb = tostring(Brain.dg.room.seed), Modes.teamSig() Brain.dg.lost [eDb] = Brain.dg.lost [eDb] or { } Brain.dg.lost [eDb] [fDb] = (Brain.dg.lost [eDb] [fDb] or 0) + 1 end end function Dungeon.text() if not Config.dungeon then return "Off." end local gDb = Dungeon.data() if not gDb then return "No dungeon data yet (shows in the lobby)." end if not gDb._started then return Config.dungeonStart and "No run going: the farm starts one when the dungeon ranks first." or "No run going (starting new runs is off)." end local hDb, iDb, jDb = Dungeon.res(gDb) local kDb, lDb = { }, { } for mDb, nDb in pairs(Dungeon.items(gDb)) do local oDb = tostring((Dungeon.item(mDb) or { }).name or mDb) .. (nDb > 1 and (" ร—" .. nDb) or "") table.insert(Dungeon.isCurse(mDb) and lDb or kDb, oDb) end local pDb = { string.format("Room %s/%d ยท %d lives ยท %d coins ยท chests %d + %d rare ยท luck ร—%.1f", tostring(Dungeon.depth(gDb) or "?"), Dungeon.ROOMS, Dungeon.lives(gDb), hDb, iDb, jDb, Dungeon.luck(gDb)) } table.insert(pDb, "Items: " .. (# kDb > 0 and table.concat(kDb, ", ") or "none") .. " ยท Curses: " .. (# lDb > 0 and table.concat(lDb, ", ") or "none")) table.insert(pDb, "They add up to: " .. Dungeon.fxText(Dungeon.fx(gDb))) local qDb = Dungeon._pick and Dungeon._pick.x if qDb then table.insert(pDb, string.format("Next: %s, win โ‰ˆ%d%%", Dungeon.roomName(qDb), math.floor(qDb.p * 100 + 0.5))) end for rDb, sDb in ipairs(Brain.dg.log) do if rDb <= 4 then table.insert(pDb, os.date("%H:%M ", sDb.at) .. sDb.msg) end end return table.concat(pDb, "\n") end Match.refused, Match.spent, Match.actions = { }, 0, { } Match.waveMax = { } function Match.mapKey() return tostring(Match.ld and Match.ld.map or "?") .. (Match.laneCut and ("#L" .. Match.laneCut) or "") end function Match.label() if Match.kind == "dungeon" then local tDb = type(Match.ld._dungeon) == "table" and Match.ld._dungeon.depth or "?" return string.format("Dungeon room %s/%d ยท %s", tostring(tDb), Dungeon.ROOMS, tostring(Match.ld._location_name or Match.ld.map or "?")) end return Modes.levelName(Match.ld.id or "?") .. (Match.ld._difficulty and (" (" .. Match.ld._difficulty .. ")") or "") end function Match.money() local uDb = LocalPlayer:FindFirstChild("_stats") return uDb and uDb.resource.Value or 0 end function Match.wave() return workspace:FindFirstChild("_wave_num") and workspace._wave_num.Value or 0 end function Match.buildPath() Match.samples = { } local vDb = Core.PATH and Core.PATH.PATH_CACHE and Core.PATH.PATH_CACHE [workspace._BASES.pve] if not vDb then return end for wDb, xDb in pairs(vDb) do if not (Core.PATH.HIDDEN_MOCK_LANES and Core.PATH.HIDDEN_MOCK_LANES [wDb.Name]) then local yDb = { } for zDb, ADb in ipairs(xDb.points or { }) do table.insert(yDb, ADb.part.Position) end local BDb = 0 for CDb = 2, # yDb do BDb = BDb + (yDb [CDb] - yDb [CDb - 1]).Magnitude end local DDb = 0 for EDb = 2, # yDb do local FDb, GDb = yDb [EDb - 1], yDb [EDb] local HDb = (GDb - FDb).Magnitude local IDb = math.max(1, math.floor(HDb / 2)) for JDb = 0, IDb - 1 do local KDb = FDb:Lerp(GDb, JDb / IDb) table.insert(Match.samples, { pos = KDb, prog = (DDb + HDb * JDb / IDb) / math.max(1, BDb), lane = wDb.Name }) end DDb = DDb + HDb end if yDb [# yDb] then table.insert(Match.samples, { pos = yDb [# yDb], prog = 1, lane = wDb.Name }) end end end Match.laneCut, Match.laneWant, Match.laneFirst = nil, nil, nil local LDb = tonumber(Match.ld and Match.ld._portal_lanes) local MDb = Simulator.laneList(Match.samples) if LDb and LDb >= 1 and MDb and # MDb > LDb then Match.laneWant = LDb end Match.laneTotal = MDb and # MDb or 1 Match.countLanes() end function Match.countLanes() local NDb, ODb, PDb = { }, 0, 0 for QDb, RDb in ipairs(Match.samples) do local SDb = tostring(RDb.lane) if not NDb [SDb] then ODb = ODb + 1 end NDb [SDb] = (NDb [SDb] or 0) + 1 PDb = PDb + 1 end Match.laneCount = { n = ODb, by = NDb, avg = ODb > 0 and PDb / ODb or 1 } end function Match.portalLanes() if not Match.laneWant or Match.laneCut then return true end if Match.planning then return false end local TDb, UDb = { }, 0 for VDb, WDb in ipairs(workspace._UNITS:GetChildren()) do local XDb = WDb:FindFirstChild("_stats") if XDb and XDb:FindFirstChild("player") and XDb.player.Value == nil and XDb:FindFirstChild("lane") and WDb:FindFirstChild("HumanoidRootPart") then local YDb = tostring(XDb.lane.Value) if not TDb [YDb] then TDb [YDb] = true UDb = UDb + 1 end end end if UDb == 0 then if Utils.now() - (Match.startT or Utils.now()) > 45 then Match.laneWant = nil App.log(1, "MATCH", "no enemy seen in 45 s: placing for every lane") return true end return false end Match.laneFirst = Match.laneFirst or Utils.now() if UDb < Match.laneWant and Utils.now() - Match.laneFirst < 10 then return false end local ZDb, _Db = { }, { } for aEb, bEb in ipairs(Match.samples) do if TDb [tostring(bEb.lane)] then table.insert(ZDb, bEb) end end if # ZDb == 0 then Match.laneWant = nil return true end for cEb in pairs(TDb) do table.insert(_Db, cEb) end table.sort(_Db) Match.samples, Match.laneCut = ZDb, table.concat(_Db, "+") Match.countLanes() local dEb, eEb = pcall(Match.buildCandidates) if not dEb then App.log(1, "ERROR", "spots on the portal's lane: " .. tostring(eEb)) end App.log(1, "MATCH", string.format("this portal opens %d of the map's lanes, and the enemies walk lane %s: placing there", Match.laneWant, Match.laneCut)) if Match.info and Config.simSpots and not Match.joinedMid and not Match.random and not Match.infinite and not Match.trialId then Match.planning = Utils.now() task.spawn(function() local fEb, gEb = pcall(Match.planSpots) Match.planning = nil if not fEb then App.log(1, "ERROR", "spot search: " .. tostring(gEb)) end end) end return true end function Match.buildWaves() local hEb = Match.ld local iEb = hEb and hEb._dungeon and type(hEb._override_waves) == "table" and hEb._override_waves or nil local jEb = hEb and Core.Waves [(iEb and iEb.template) or hEb.waves or hEb.id] local kEb if type(jEb) == "table" then kEb = jEb.override_waves or jEb.waves if not kEb and jEb.template and Core.Waves [jEb.template] then kEb = Core.Waves [jEb.template].waves or Core.Waves [jEb.template].override_waves end end Match.waveList = Utils.list(kEb) Match.infinite = hEb and (hEb._gamemode == "infinite" or hEb.infinite) and Simulator.unbound(hEb) or false if Match.infinite then Match.waveList = { } Match.total = math.max(30, math.floor(Modes.infReach(Match.key) + 0.5)) return end Match.total = (hEb and type(hEb._dungeon) == "table" and 15) or tonumber(hEb and hEb._override_infinite_bound_waves) or Simulator.boundEnd(hEb, # Match.waveList, Match.key) or (# Match.waveList > 0 and # Match.waveList) or 15 end function Match.infExtend() if Match.infinite and Match.wave() + 5 > Match.total then Match.total = Match.wave() + 10 end end function Match.infWave(lEb) Match.infExtend() if Config.trainLeave and not Match.stopping then local mEb, nEb = pcall(Match.trainLeave, lEb) if not mEb then App.log(1, "ERROR", "leave when trained: " .. tostring(nEb)) end end if Config.infiniteStop and not Match.stopping then local oEb, pEb = pcall(Match.infStop, lEb) if not oEb then App.log(1, "ERROR", "Infinite stop: " .. tostring(pEb)) end end local qEb = Modes.infTier(lEb - 1) if qEb <= 0 or qEb == Match.infTierSeen then return end Match.infTierSeen = qEb task.delay(5, function() if not App.alive then return end local rEb = Core.prof() local sEb = rEb and rEb.daily_infinite_reward_claims and rEb.daily_infinite_reward_claims [Match.ld.id] local tEb = type(sEb) == "table" and (tonumber(sEb.highest_claimed_wave) or 0) >= qEb and os.time() - (tonumber(sEb.highest_claim_time) or 0) < 900 if tEb then Brain.infLive = true end App.log(1, "MATCH", string.format("Infinite: wave %d passed โ€” daily gems %s", qEb, tEb and "claimed now" or "not in your profile yet (paid at the end?)")) end) end function Match.infStop(uEb) local vEb = 0 for wEb, xEb in ipairs(Utils.list(Core.GS.DAILY_INFINITE_REWARDS)) do vEb = math.max(vEb, tonumber(xEb.wave) or 0) end if vEb <= 0 or uEb <= vEb + 1 then return end local yEb = Brain.train.on if Match.trainee and yEb and yEb.have + (yEb.got or 0) + (Match.trainTk or 0) < yEb.need then return end if not FarmHub.every("infStop", 60) then return end local zEb = Modes.rec(Match.key) local AEb = tonumber(zEb.timed) or 0 local BEb = (AEb > 0 and (zEb.sec or 0) > 0) and math.max(0.5, ((zEb.gems or 0) - 200 * AEb) / (zEb.sec / 60)) or 2.5 local CEb = pcall(Modes.pick) local DEb for EEb, FEb in ipairs(CEb and Modes.lastScored or { }) do if FEb.kind ~= "infinite" and (not DEb or (FEb.score or 0) > (DEb.score or 0)) then DEb = FEb end end if not DEb or (DEb.score or 0) <= BEb then return end Match.stopping = true App.log(1, "MATCH", string.format("Infinite: wave %d, the daily gems are in and no unit trains here โ€” ending the run (โ‰ˆ%.1f gems/min from here, %.1f in %s): selling your units", uEb, BEb, DEb.score, DEb.label)) Match.sellAll() end function Match.trainLeave(GEb) local HEb = Brain.train.on if not (Match.infinite and Match.trainee and HEb) then return end Match.trainCount() local IEb = HEb.have + (HEb.got or 0) + (Match.trainTk or 0) if IEb < HEb.need then return end local JEb, KEb, LEb = pcall(Modes.infDaily, Core.prof(), Match.ld.id, (Modes.infReach(Match.key))) if JEb and (KEb or 0) > 0 and LEb > 0 and GEb <= LEb then App.status("Training", string.format("%s has its %s: playing to wave %d for the daily gems, then ending the run", Units.name(HEb.id), HEb.what, LEb)) return end Match.stopping = true App.log(1, "MATCH", string.format("Infinite: %s has its %s (%s of %s) โ€” ending the run at wave %d so it can evolve: selling your units", Units.name(HEb.id), HEb.what, Utils.fmt(IEb), Utils.fmt(HEb.need), GEb)) Match.sellAll() end function Match.sellAll() task.spawn(function() local MEb = { } for NEb, OEb in ipairs(Match.mine or { }) do local PEb = OEb:FindFirstChild("_stats") local QEb = PEb and PEb:FindFirstChild("id") and Core.Units [PEb.id.Value] if not (QEb and QEb.unsellable) then table.insert(MEb, OEb) end end local function REb(SEb) local TEb = SEb:FindFirstChild("_stats") return TEb and TEb:FindFirstChild("id") and Units.isFarm(TEb.id.Value) or false end table.sort(MEb, function(UEb, VEb) return (REb(UEb) and 1 or 0) < (REb(VEb) and 1 or 0) end) local WEb = { } for XEb = 1, 2 do local YEb = { } for ZEb, _Eb in ipairs(XEb == 1 and MEb or WEb) do if not App.alive or Match.finished then return end if _Eb.Parent and _Eb.Parent.Name == "_UNITS" then local aFb, bFb = App.request("sell", "sell_unit_ingame", _Eb) if bFb ~= true then table.insert(YEb, _Eb) end task.wait(1.3) end end WEb = YEb end if # WEb > 0 then local cFb = { } for dFb, eFb in ipairs(WEb) do table.insert(cFb, eFb.Name) end App.log(1, "MATCH", "the game didn't sell " .. table.concat(cFb, ", ") .. " (the run still ends: nothing else is defending)") end end) end function Match.waveReward(fFb) if Match.info then return Simulator.waveYen(Match.info, fFb) end local gFb = Match.waveList and Match.waveList [fFb] return (gFb and tonumber(gFb.wave_reward) or 0) * 2.6 end function Match.progressAt(hFb) local iFb, jFb, kFb = 0, math.huge, nil local lFb = Utils.flat(hFb) for mFb, nFb in ipairs(Match.samples) do local oFb = (Utils.flat(nFb.pos) - lFb).Magnitude if oFb < jFb then jFb, iFb, kFb = oFb, nFb.prog, nFb.lane end end return iFb, jFb, kFb ~= nil and tostring(kFb) or nil end function Match.scan() local pFb, qFb = { }, { } for rFb, sFb in ipairs({ workspace._UNITS }) do for tFb, uFb in ipairs(sFb:GetChildren()) do local vFb = uFb:FindFirstChild("_stats") if vFb and vFb:FindFirstChild("player") then local wFb = vFb:FindFirstChild("parent_unit") local xFb = wFb and wFb.Value local yFb = xFb and xFb:FindFirstChild("_stats") local zFb = Core.Units [vFb.id.Value] local AFb = xFb ~= nil and zFb ~= nil and (tonumber(zFb.cost) or 0) >= 100 and (not yFb or (yFb:FindFirstChild("id") and yFb.id.Value == vFb.id.Value)) if vFb.id.Value == "mahoraga" then AFb = true end local BFb = (xFb ~= nil and not AFb) or not vFb:FindFirstChild("attack_cooldown") or not vFb:FindFirstChild("upgrade") or not vFb:FindFirstChild("range") if vFb.player.Value == LocalPlayer then if not BFb then table.insert(qFb, uFb) end elseif vFb.player.Value == nil and uFb:FindFirstChild("HumanoidRootPart") then local CFb, DFb, EFb = Match.progressAt(uFb.HumanoidRootPart.Position) local FFb = uFb:FindFirstChildOfClass("Humanoid") local GFb = FFb and FFb.Health or (vFb:FindFirstChild("health") and vFb.health.Value) or 0 local HFb = vFb:FindFirstChild("damage_reduction") and tonumber(vFb.damage_reduction.Value) or 0 local IFb = Match.info and Match.info.cfgById [vFb.id.Value] local JFb = vFb:FindFirstChild("Flying") and (tonumber(vFb.Flying.Value) or 0) > 0 or false local KFb = vFb:FindFirstChild("shield") and tonumber(vFb.shield.Value) or 0 table.insert(pFb, { m = uFb, pos = uFb.HumanoidRootPart.Position, prog = CFb, lane = EFb, hp = GFb, red = HFb, fly = JFb, sh = KFb, boss = IFb and tostring(IFb):find("boss") ~= nil or false }) if tostring(vFb.id.Value):find("^star_golem") then pcall(Match.noteStar, uFb, FFb) end end end end end Match.enemies, Match.mine = pFb, qFb if not Match.flyerSeen then for LFb, MFb in ipairs(pFb) do if MFb.fly then Match.flyerSeen = math.max(1, Match.wave()) if not Match.random then App.log(1, "MATCH", string.format("flyers from wave %d: units that hit them get more of the yen now", Match.flyerSeen)) end break end end end if not Match.shieldSeen then for NFb, OFb in ipairs(pFb) do if (OFb.sh or 0) > 0 then Match.shieldSeen = true local PFb = OFb.m._stats App.log(1, "MATCH", string.format("enemy shield seen at wave %d: %s (its most %s), %s, %.3g HP ยท the simulator has +%s on every enemy", Match.wave(), tostring(OFb.sh), tostring(PFb:FindFirstChild("max_shield") and PFb.max_shield.Value), tostring(PFb:FindFirstChild("id") and PFb.id.Value), OFb.hp, tostring(Match.info and Match.info.shield))) break end end end local QFb = Match.wave() local RFb = 0 local SFb, TFb, UFb = 0, 0, 0 for VFb, WFb in ipairs(pFb) do RFb = math.max(RFb, WFb.prog) SFb = SFb + WFb.hp / math.max(0.1, 1 - WFb.red) UFb = UFb + WFb.hp end Match.hpAliveRaw = UFb for XFb, YFb in ipairs(qFb) do local ZFb = YFb._stats.id.Value if not Units.isFarm(ZFb) and YFb._stats:FindFirstChild("damage") then local _Fb, aGb = pcall(Match.liveDps, YFb, ZFb) if _Fb and aGb then TFb = TFb + aGb end end end Match.hpAlive, Match.teamDps = SFb, TFb Match.clearSec = SFb > 0 and (TFb > 0 and SFb / TFb or math.huge) or 0 Match.waveMax [QFb] = math.max(Match.waveMax [QFb] or 0, RFb) Match.front = RFb Match.pressure = math.max(Match.waveMax [QFb] or 0, Match.waveMax [QFb - 1] or 0) Match.depth = Match.coverage(qFb, pFb) local bGb = Utils.now() Match.aliveHist = Match.aliveHist or { } table.insert(Match.aliveHist, { t = bGb, n = # pFb }) while Match.aliveHist [1] and bGb - Match.aliveHist [1].t > 12 do table.remove(Match.aliveHist, 1) end local cGb = Match.aliveHist [1] Match.piling = # pFb >= 8 and cGb and bGb - cGb.t >= 8 and # pFb > cGb.n + 1 or false if # pFb >= 18 then Match.piling = true end end function Match.setupChecks() Match.probe = Instance.new("Part") Match.probe.Name = "_aa_probe" Match.probe.Anchored = true Match.probe.CanCollide = false Match.probe.CanTouch = false Match.probe.CanQuery = false Match.probe.Transparency = 1 Match.probe.Parent = workspace:FindFirstChild("ignore") or workspace local dGb = ReplicatedStorage:FindFirstChild("_bounds") Match.bounds = Instance.new("Folder") Match.bounds.Name = "_aa_bounds" if dGb then for eGb, fGb in ipairs(dGb:GetDescendants()) do if fGb:IsA("BasePart") then local gGb = fGb:Clone() for hGb, iGb in ipairs(gGb:GetChildren()) do iGb:Destroy() end gGb.Anchored = true gGb.CanCollide = false gGb.CanTouch = false gGb.CanQuery = true gGb.Transparency = 1 gGb.CFrame = fGb.CFrame gGb.Parent = Match.bounds end end end Match.bounds.Parent = workspace:FindFirstChild("ignore") or workspace Match.overlap = OverlapParams.new() Match.overlap.FilterType = Enum.RaycastFilterType.Include Match.overlap.FilterDescendantsInstances = { workspace._road, Match.bounds } end function Match.spotKey(jGb) return string.format("%d,%d", math.floor(jGb.X + 0.5), math.floor(jGb.Z + 0.5)) end function Match.killZone() return Config.killZone ~= false and (Match.style == "box" or (Match.style == nil and Match.kind == "dungeon")) end function Match.blockedSpot(kGb) local lGb = Match.spotKey(kGb) if Match.refused [lGb] then return true end local mGb = Brain.blocked [Match.mapKey()] return mGb and (tonumber(mGb [lGb]) or 0) >= 2 end function Match.valid(nGb, oGb) local pGb = Core.PC.raycast(nGb + Vector3.new(0, 40, 0), Vector3.new(0, - 120, 0)) if not pGb then return nil end if not Core.PC.is_valid_raycast_hit(pGb.Instance, oGb) then return nil end local qGb = pGb.Position if Match.blockedSpot(qGb) then return nil end local rGb = (tonumber(oGb.override_hitbox_radius) or 1.5) + 0.25 Match.probe.Size = Vector3.new(rGb * 2, 2.5, rGb * 2) Match.probe.CFrame = CFrame.new(qGb + Vector3.new(0, 1.35, 0)) if # workspace:GetPartsInPart(Match.probe, Match.overlap) > 0 then return nil end for sGb, tGb in ipairs(Match.mine or { }) do local uGb = tGb:FindFirstChild("_hitbox") local vGb = uGb and uGb.Position or tGb:GetPivot().Position local wGb = uGb and math.max(uGb.Size.X, uGb.Size.Z) / 2 or 1.5 if (Utils.flat(qGb) - Utils.flat(vGb)).Magnitude < rGb + wGb then return nil end end return qGb end function Match.buildCandidates() Match.cands = { } local xGb = { } for yGb = 1, # Match.samples, 3 do local zGb = Match.samples [yGb] local AGb = Match.samples [math.min(# Match.samples, yGb + 1)] local BGb = Utils.flat(AGb.pos - zGb.pos) if BGb.Magnitude < 0.01 then BGb = Vector3.new(1, 0, 0) end BGb = BGb.Unit local CGb = Vector3.new(- BGb.Z, 0, BGb.X) for DGb, EGb in ipairs({ 3, 4.5, 6, 8, 10.5, 13.5, 17 }) do for FGb, GGb in ipairs({ 1, - 1 }) do local HGb = zGb.pos + CGb * EGb * GGb local IGb = string.format("%d,%d", math.floor(HGb.X / 1.5), math.floor(HGb.Z / 1.5)) if not xGb [IGb] then xGb [IGb] = true table.insert(Match.cands, HGb) end end end end end function Match.coverage(JGb, KGb) local LGb = { } for MGb, NGb in ipairs(JGb) do local OGb = NGb._stats if not Units.isFarm(OGb.id.Value) and OGb:FindFirstChild("range") then table.insert(LGb, { p = Utils.flat(NGb:GetPivot().Position), r = OGb.range.Value * (Units.SUMMONER [OGb.id.Value] and Units.SUMMONER [OGb.id.Value].reach or 1), air = Match.canAir(NGb) }) end end local PGb, QGb = 0, 0 if # LGb > 0 then for RGb, SGb in ipairs(Match.samples) do if SGb.prog > QGb then local TGb = Utils.flat(SGb.pos) for UGb, VGb in ipairs(LGb) do if (TGb - VGb.p).Magnitude <= VGb.r then if SGb.prog > PGb then PGb = SGb.prog end if VGb.air then QGb = SGb.prog break end end end end end end local WGb, XGb = { }, Match.laneCount if XGb and XGb.n >= 2 and # LGb > 0 then local YGb = { } for ZGb, _Gb in ipairs(Match.samples) do local aHb = Utils.flat(_Gb.pos) for bHb, cHb in ipairs(LGb) do if (aHb - cHb.p).Magnitude <= cHb.r then local dHb = tostring(_Gb.lane) YGb [dHb] = (YGb [dHb] or 0) + 1 if _Gb.prog > (WGb [dHb] or 0) then WGb [dHb] = _Gb.prog end break end end end local eHb, fHb = { }, 0 for gHb, hHb in pairs(XGb.by) do eHb [gHb] = (YGb [gHb] or 0) / math.max(1, hHb) fHb = math.max(fHb, eHb [gHb]) end Match.laneW = { } for iHb, jHb in pairs(XGb.by) do Match.laneW [iHb] = (XGb.avg / math.max(1, jHb)) * (1 + 1.5 * (fHb > 0 and (1 - eHb [iHb] / fHb) or 0)) end Match.laneFrac = eHb else Match.laneW, Match.laneFrac = nil, nil end local kHb, lHb, mHb if # LGb > 0 then for nHb, oHb in ipairs(KGb) do local pHb = (Match.laneW and oHb.lane and WGb [oHb.lane]) or PGb if oHb.hp > 0 and oHb.prog > pHb + 0.02 and oHb.prog > (kHb or 0) then kHb, mHb = oHb.prog, Match.laneW and oHb.lane or nil end if oHb.hp > 0 and oHb.fly and oHb.prog > QGb + 0.02 then lHb = true end end end local qHb = # LGb > 0 and (Match.pressure or 0) >= PGb - 0.08 return { covEnd = PGb, airEnd = QGb, leak = kHb, leakLane = mHb, covL = WGb, airLeak = lHb, push = qHb, units = # LGb } end function Match.needLastLine(rHb) if Units.isFarm(rHb.id) or Units.allyBuff(rHb.id) or Match.fieldCount() < 5 then return false end local sHb, tHb = 0, 0 for uHb, vHb in ipairs(Match.mine or { }) do local wHb = vHb._stats if not Units.isFarm(wHb.id.Value) and wHb:FindFirstChild("range") then local xHb, yHb = Utils.flat(vHb:GetPivot().Position), wHb.range.Value for zHb, AHb in ipairs(Match.samples) do if AHb.prog >= 0.85 and (Utils.flat(AHb.pos) - xHb).Magnitude <= yHb then sHb = sHb + 1 if Match.canAir(vHb) then tHb = tHb + 1 end break end end end end return sHb < 2 or (tHb < 1 and Match.flyMode() and Match.cardAir(rHb.id)) end Match.airCache = setmetatable({ }, { __mode = "k" }) function Match.canAir(BHb) local CHb = BHb._stats:FindFirstChild("upgrade") and BHb._stats.upgrade.Value or 0 local DHb = Match.airCache [BHb] if not DHb or DHb.lvl ~= CHb then local EHb = BHb:GetAttribute("_on_hill") == true or BHb:GetAttribute("_anti_air") == true DHb = { lvl = CHb, v = EHb } Match.airCache [BHb] = DHb end return DHb.v end Units._airFrom = { } function Units.airFrom(FHb) local GHb = Units._airFrom [FHb] if GHb ~= nil then return GHb or nil end local HHb = Core.Units [FHb] if HHb and (HHb.hill_unit or HHb.hybrid_placement or Units.SUMMONER [FHb]) then Units._airFrom [FHb] = 0 return 0 end local IHb = Units.rows(FHb) if not (IHb and IHb.max) then return Match.cardAir(FHb) and 0 or nil end local JHb = false for KHb = 0, IHb.max do local LHb = IHb [KHb] and IHb [KHb].atk if LHb and Core.Attacks [LHb] and Core.Attacks [LHb].targets_flying then JHb = KHb break end end if JHb == false and Match.cardAir(FHb) then JHb = 0 end Units._airFrom [FHb] = JHb return JHb or nil end function Match.cardAir(MHb) local NHb = Core.Units [MHb] if not NHb then return false end if NHb.hill_unit or NHb.hybrid_placement then return true end local OHb = Units.field(MHb, "primary_attack_no_upgrades") or Units.field(MHb, "primary_attack") return OHb and Core.Attacks [OHb] and Core.Attacks [OHb].targets_flying and true or false end function Match.airShare() local PHb, QHb = 0, 0 for RHb, SHb in ipairs(Match.mine or { }) do local THb = SHb._stats.id.Value if not Units.isFarm(THb) then local UHb, VHb = pcall(Match.liveDps, SHb, THb) VHb = UHb and VHb or 0 PHb = PHb + VHb if Match.canAir(SHb) then QHb = QHb + VHb end end end return PHb > 0 and QHb / PHb or 0 end function Match.cover(WHb, XHb, YHb, ZHb) local _Hb = 0 local aIb = Utils.flat(WHb) local bIb = YHb and Brain.maps [Match.mapKey()] and Brain.maps [Match.mapKey()].dens local cIb = Match.laneW for dIb, eIb in ipairs(Match.samples) do if (Utils.flat(eIb.pos) - aIb).Magnitude <= XHb then local fIb = 1 if bIb then fIb = 0.15 + 0.85 * (tonumber(bIb [math.min(Simulator.BINS, math.floor(eIb.prog * Simulator.BINS) + 1)]) or 0) end if cIb then fIb = fIb * (cIb [tostring(eIb.lane)] or 1) end if ZHb then if ZHb.leak and eIb.prog >= ZHb.leak - 0.01 and (not ZHb.leakLane or tostring(eIb.lane) == ZHb.leakLane) then fIb = fIb + 3 elseif ZHb.push and eIb.prog > ZHb.covEnd then fIb = fIb + 0.6 end end _Hb = _Hb + fIb end end return _Hb end function Match.bleedGain(gIb, hIb, iIb) local jIb = Match.info and tonumber(Match.info.regen) or 0 if jIb <= 0 then return 0 end local kIb = Units.bleeds(gIb) if not kIb then return 0 end local lIb = false for mIb, nIb in ipairs(Match.mine or { }) do local oIb = nIb:FindFirstChild("_stats") local pIb = oIb and oIb:FindFirstChild("id") and oIb.id.Value local qIb = oIb and oIb:FindFirstChild("upgrade") and tonumber(oIb.upgrade.Value) or 0 if pIb and (Units.bleeds(pIb) or 99) <= qIb then lIb = true break end end return Economy.bleedShare(Economy.bleedGain(jIb, Match.hpAlive or 0, Match.coming, lIb), kIb, hIb, iIb) end function Match.fieldBuffers() local rIb = { } for sIb, tIb in ipairs(Match.mine or { }) do local uIb = Units.allyBuff(tIb._stats.id.Value) if uIb then table.insert(rIb, { pos = tIb:GetPivot().Position, range = tIb._stats.range.Value, b = uIb }) end end return rIb end function Match.buffShort(vIb) local wIb = { } for xIb, yIb in ipairs(Match.fieldBuffers()) do if yIb.b.attack == vIb.attack then table.insert(wIb, yIb) end end local zIb = Units.buffCopies(vIb) local AIb = { } for BIb, CIb in ipairs(Match.mine or { }) do local DIb = CIb._stats.id.Value local EIb, FIb = Units.types(DIb) if not Units.isFarm(DIb) and not Units.allyBuff(DIb) and vIb.types [FIb] then local GIb = Utils.flat(CIb:GetPivot().Position) local HIb = 0 for IIb, JIb in ipairs(wIb) do if (Utils.flat(JIb.pos) - GIb).Magnitude <= JIb.range then HIb = HIb + 1 end end if HIb < zIb then local KIb, LIb = pcall(Match.liveDps, CIb, DIb) table.insert(AIb, { pos = GIb, base = FIb, n = HIb, dps = math.max(1, KIb and tonumber(LIb) or 1) }) end end end return AIb end function Match.spotRec(MIb, NIb, OIb) local PIb = Match.mapKey() Brain.maps [PIb] = Brain.maps [PIb] or { } local QIb = Brain.maps [PIb].lay if not QIb then if not OIb then return nil end QIb = { } Brain.maps [PIb].lay = QIb end local RIb = QIb [MIb] if not RIb then if not OIb then return nil end RIb = { } QIb [MIb] = RIb end local SIb = Match.spotKey(NIb) if not RIb [SIb] and OIb then RIb [SIb] = { runs = 0, wins = 0 } end return RIb [SIb] end function Match.lastLife(TIb, UIb) if Match.kind ~= "dungeon" then return end local VIb, WIb = pcall(Dungeon.lives, Dungeon.data()) if not (VIb and WIb <= 1) then return end local XIb = TIb > 1 or UIb.won Dungeon.note(string.format("last life, on this map: the simulator says your team %s", XIb and "wins" or ("falls at wave " .. UIb.wave))) if not XIb and Dungeon.playLast() then Dungeon.note("last life: played all the same, the user's call") elseif not XIb then Match.noBuy = true Modes.rec("dg").restUntil = os.time() + 3600 Brain.pending = nil Dungeon.note("last life kept: back to the lobby before a wave starts. Looks again in an hour") App.saveNow() task.wait(1) App.request("lobby", "teleport_back_to_lobby") end end Match.STYLE_FAST, Match.STYLE_BALANCED, Match.FAST_ROAD, Match.PLAN_TOTAL = 1.5, 1.1, 0.35, 14 Match.STYLE_WORDS = { fast = "fast clear, at the road's start", balanced = "balanced, spread over the road", box = "kill box, on the road's last stretch" } function Match.styleRec(YIb) local ZIb, _Ib if Match.kind == "dungeon" then Brain.dg.styles = type(Brain.dg.styles) == "table" and Brain.dg.styles or { } ZIb, _Ib = Brain.dg.styles, tostring(Brain.dg.room and Brain.dg.room.seed) else ZIb, _Ib = Brain.zoneRec, Simulator.zoneKey(Match.info) end if not ZIb [_Ib] and YIb then ZIb [_Ib] = { at = os.time() } if ZIb ~= Brain.zoneRec and Utils.count(ZIb) > 40 then local aJb, bJb for cJb, dJb in pairs(ZIb) do if not bJb or (tonumber(dJb.at) or 0) < bJb then aJb, bJb = cJb, tonumber(dJb.at) or 0 end end ZIb [aJb] = nil end end return ZIb [_Ib] end function Match.styleRecord(eJb) if not Match.style or not Match.info then return end local fJb = Match.styleRec(true) fJb.style, fJb.zone, fJb.won, fJb.at = Match.style, Match.style == "box", eJb and true or false, os.time() fJb.lost = type(fJb.lost) == "table" and fJb.lost or { } fJb.lost [Match.style] = (not eJb) and { at = os.time(), sig = Match.styleSig } or nil fJb.far = type(fJb.far) == "table" and fJb.far or { } fJb.far [Match.style] = math.max(tonumber(fJb.far [Match.style]) or 0, Match.wave()) end function Match.planSpots() local gJb = Utils.now() local hJb = Match.info local iJb = tonumber(hJb.rangeScale) or 1 local jJb, kJb = { }, { } for lJb, mJb in ipairs(Match.team or { }) do table.insert(jJb, mJb.u) if not Units.isFarm(mJb.id) and not Units.allyBuff(mJb.id) and mJb.uuid ~= Match.trainee and Units.rows(mJb.id) then local nJb, oJb = pcall(Units.score, mJb.u, Match.mod) table.insert(kJb, { e = mJb, id = mJb.id, sc = nJb and oJb or 0 }) end end if # kJb == 0 then return end table.sort(kJb, function(pJb, qJb) return pJb.sc > qJb.sc end) local rJb = { } local function sJb(tJb) local uJb = (tJb.hill_unit and "hill" or "ground") .. ":" .. tostring(tJb.override_hitbox_radius or "") if rJb [uJb] then return rJb [uJb] end local vJb = { } for wJb, xJb in ipairs(Match.cands) do local yJb = Match.valid(xJb, tJb) if yJb then table.insert(vJb, yJb) end if wJb % 40 == 0 then task.wait() end end rJb [uJb] = vJb return vJb end local function zJb(AJb, BJb) local CJb, DJb, EJb, FJb = 0, 0, 0, BJb * BJb for GJb, HJb in ipairs(Match.samples) do local IJb, JJb = HJb.pos.X - AJb.X, HJb.pos.Z - AJb.Z if IJb * IJb + JJb * JJb <= FJb then CJb = CJb + 1 if HJb.prog <= Match.FAST_ROAD then DJb = DJb + 1.5 - HJb.prog end if HJb.prog >= 0.6 then EJb = EJb + HJb.prog - 0.5 end end end return CJb, DJb, EJb end local function KJb(LJb, MJb, NJb) for OJb, PJb in ipairs(LJb) do if MJb <= 0 then break end local QJb = true for RJb, SJb in ipairs(NJb) do if (Utils.flat(SJb) - Utils.flat(PJb.p)).Magnitude < 6 then QJb = false break end end if QJb then table.insert(NJb, PJb.p) MJb = MJb - 1 end end end for TJb, UJb in ipairs(kJb) do local VJb = Units.rows(UJb.id) local WJb = Units.range(UJb.e.u, VJb.max) * iJb local XJb = Units.range(UJb.e.u, math.min(3, VJb.max)) * iJb local YJb, ZJb, _Jb, aKb = { }, { }, { }, { } for bKb, cKb in ipairs(sJb(UJb.e.data)) do local dKb, eKb, fKb = zJb(cKb, WJb) local gKb, hKb = zJb(cKb, XJb) table.insert(YJb, { p = cKb, v = dKb + 0.25 * gKb }) table.insert(ZJb, { p = cKb, v = Match.cover(cKb, XJb, true) }) table.insert(_Jb, { p = cKb, v = eKb + 0.5 * hKb }) table.insert(aKb, { p = cKb, v = fKb }) if bKb % 60 == 0 then task.wait() end end for iKb, jKb in ipairs({ YJb, ZJb, _Jb, aKb }) do table.sort(jKb, function(kKb, lKb) return kKb.v > lKb.v end) end UJb.cands = { } KJb(ZJb, 1, UJb.cands) UJb.old = UJb.cands [1] KJb(YJb, 4, UJb.cands) KJb(ZJb, 2, UJb.cands) local function mKb(nKb) local oKb, pKb = nKb [1] and nKb [1].v or 0, { } for qKb, rKb in ipairs(nKb) do if oKb <= 0 or rKb.v < oKb * 0.7 then break end table.insert(pKb, rKb) end return # pKb > 0 and pKb or nKb end UJb.fast, UJb.box = { }, { } KJb(mKb(_Jb), 5, UJb.fast) KJb(mKb(aKb), 5, UJb.box) end local sKb = Simulator.teamFrom(jJb, hJb.map) local tKb, uKb = { }, 0 local function vKb(wKb, xKb) uKb = uKb + 1 local yKb = Simulator.run(hJb, sKb, { hpMul = xKb, layout = wKb }) task.wait() return yKb end local function zKb(AKb) local BKb = 0 for CKb, DKb in pairs(AKb.spots) do local EKb = Match.spotRec(CKb, DKb) if EKb then BKb = BKb + (tonumber(EKb.wins) or 0) * 200 - ((tonumber(EKb.runs) or 0) - (tonumber(EKb.wins) or 0)) * 1500 end end return BKb end local function FKb(GKb, HKb) return Simulator.objective(HKb) * 100 + zKb(GKb) end local function IKb(JKb, KKb, LKb) for MKb, NKb in pairs(JKb.spots) do if MKb ~= KKb and (Utils.flat(NKb) - Utils.flat(LKb)).Magnitude < 4.5 then return true end end return false end local function OKb(PKb, QKb) local RKb = { samples = Match.samples, cache = tKb, spots = { }, style = PKb, list = QKb } for SKb, TKb in ipairs(kJb) do local UKb for VKb, WKb in ipairs(TKb [QKb]) do if not IKb(RKb, TKb.id, WKb) then UKb = WKb break end end RKb.spots [TKb.id] = UKb or TKb [QKb] [1] or TKb.old end return RKb end local function XKb(YKb) local ZKb, _Kb = 4, nil for aLb, bLb in ipairs({ 1, 1.3, 1.7, 2.2, 3, 4 }) do _Kb = vKb(YKb, bLb) if not _Kb.won then ZKb = bLb break end end return ZKb, _Kb end local function cLb(dLb, eLb, fLb, gLb, hLb) local iLb, jLb = Utils.now(), uKb local kLb = FKb(dLb, hLb or vKb(dLb, eLb)) for lLb = 1, 2 do local mLb = false for nLb, oLb in ipairs(kJb) do local pLb = dLb.spots [oLb.id] local qLb = pLb for rLb, sLb in ipairs(oLb [dLb.list]) do if Utils.now() - iLb > fLb or uKb - jLb >= gLb or Utils.now() - gJb > Match.PLAN_TOTAL then break end if sLb ~= pLb and not IKb(dLb, oLb.id, sLb) then dLb.spots [oLb.id] = sLb local tLb = FKb(dLb, vKb(dLb, eLb)) if tLb > kLb + 1 then kLb, qLb = tLb, sLb mLb = true end end end dLb.spots [oLb.id] = qLb end if not mLb then break end end end local function uLb(vLb, wLb) local xLb = 0 for yLb, zLb in ipairs({ 0.92, 1, 1.08 }) do if vKb(vLb, wLb * zLb).won then xLb = xLb + 1 end end return xLb end local ALb = Match.styleRec() local BLb = Simulator.sig(jJb) Match.styleSig = BLb local CLb = Match.kind ~= "dungeon" and Brain.modes [hJb.key] or nil local DLb = CLb and tonumber(CLb.streak) or 0 local ELb = Config.dailyLoop and Match.kind == "daily" local FLb = ELb and ALb and type(ALb.far) == "table" and (tonumber(ALb.far.balanced) or 0) > 0 and (tonumber(ALb.far.balanced) or 0) >= (tonumber(ALb.far.box) or 0) and tonumber(ALb.far.balanced) or nil local function GLb(HLb) if ELb then return false end local ILb = ALb and type(ALb.lost) == "table" and ALb.lost [HLb] if ILb then return ILb.sig == BLb and os.time() - (tonumber(ILb.at) or 0) < 7 * 86400 end if ALb and not ALb.style and ALb.won == false then return HLb == (ALb.zone and "box" or "balanced") end return not ALb and DLb > 0 and HLb == "balanced" end local function JLb(KLb) if ALb and ALb.won then return (ALb.style or (ALb.zone and "box" or "balanced")) == KLb end return not ALb and KLb == "balanced" and CLb ~= nil and (tonumber(CLb.wins) or 0) > 0 and DLb == 0 and hJb.key ~= "ch" and hJb.key ~= "dc" end local LLb = Config.killZone ~= false local MLb, NLb = pcall(Dungeon.lives, Dungeon.data()) local OLb = LLb and Match.kind == "dungeon" and MLb and NLb <= 1 local PLb = ELb and Config.dailyBox and LLb local QLb, RLb, SLb local TLb = (PLb and "you asked for the kill box here") or (OLb and "it's this room's last life") or (Match.trainee and "a unit is training here and needs enemies left to hit") or (GLb("fast") and "it lost here") or (GLb("balanced") and "spread-out units already lost here") or ((tonumber(hJb.cal and hJb.cal.n) or 0) == 0 and "no real run here yet to check the simulator against") or nil if not TLb then uKb = uKb + 1 if not Simulator.run(hJb, sKb, { hpMul = Match.STYLE_FAST * 0.92, noZone = true }).won then TLb = string.format("your team doesn't beat enemies ร—%.1f", Match.STYLE_FAST) end end if TLb then App.log(1, "SPOTS", "no fast clear: " .. TLb) else local ULb, VLb = Utils.now(), uKb local WLb = OKb("fast", "fast") cLb(WLb, Match.STYLE_FAST, 4, 40) local XLb = uLb(WLb, Match.STYLE_FAST) App.log(1, "SPOTS", string.format("fast clear: tried %d layouts in %.1f s, the best wins %d of 3 tries against enemies ร—%.1f", uKb - VLb, Utils.now() - ULb, XLb, Match.STYLE_FAST)) if XLb >= 2 then QLb, RLb = WLb, string.format("your team beats enemies ร—%.1f from there", Match.STYLE_FAST) end end if not QLb and not OLb and not PLb and (LLb == false or not GLb("balanced")) then local YLb, ZLb = Utils.now(), uKb local _Lb = OKb("balanced", "cands") local aMb, bMb = XKb(_Lb) for cMb, dMb in ipairs(kJb) do if cMb > 1 then local eMb = Units.rows(dMb.id) local fMb = math.min(eMb.max, math.max(tonumber(bMb.levels and bMb.levels [dMb.id]) or 0, 1)) local gMb = Units.range(dMb.e.u, fMb) * iJb local hMb, iMb = gMb * gMb, { } for jMb, kMb in ipairs(sJb(dMb.e.data)) do local lMb = 0 for mMb, nMb in ipairs(Match.samples) do if nMb.prog <= 0.5 then local oMb, pMb = nMb.pos.X - kMb.X, nMb.pos.Z - kMb.Z if oMb * oMb + pMb * pMb <= hMb then lMb = lMb + 1 end end end table.insert(iMb, { p = kMb, v = lMb }) if jMb % 60 == 0 then task.wait() end end table.sort(iMb, function(qMb, rMb) return qMb.v > rMb.v end) local sMb = dMb.cands dMb.cands = { } KJb(iMb, 3, dMb.cands) for tMb, uMb in ipairs(sMb) do table.insert(dMb.cands, uMb) end end end cLb(_Lb, aMb, 5, 50, bMb) local vMb = uLb(_Lb, Match.STYLE_BALANCED) App.log(1, "SPOTS", string.format("balanced: tried %d layouts in %.1f s, the best wins %d of 3 tries against enemies ร—%.1f", uKb - ZLb, Utils.now() - YLb, vMb, Match.STYLE_BALANCED)) SLb = _Lb if vMb >= 2 then QLb, RLb = _Lb, string.format("your team beats enemies ร—%.1f spread out%s", Match.STYLE_BALANCED, TLb and "" or string.format(", not ร—%.1f from the road's start", Match.STYLE_FAST)) elseif JLb("balanced") then QLb, RLb = _Lb, "it won here last time, and the simulator reads spread-out units low" elseif not LLb then QLb, RLb = _Lb, "the kill box is switched off" elseif FLb then QLb, RLb = _Lb, string.format("it got furthest here, to wave %d", FLb) elseif GLb("box") then QLb, RLb = _Lb, "the kill box lost here" end end if not QLb then local wMb, xMb = Utils.now(), uKb local yMb = OKb("box", "box") local zMb, AMb = XKb(yMb) cLb(yMb, zMb, 4, 40, AMb) local BMb = uLb(yMb, 1) App.log(1, "SPOTS", string.format("kill box: tried %d layouts in %.1f s, the best wins %d of 3 tries against normal enemies", uKb - xMb, Utils.now() - wMb, BMb)) if BMb == 0 and SLb and uLb(SLb, 1) >= 2 then QLb, RLb = SLb, "the kill box loses at normal strength, spread-out units win" else QLb, RLb = yMb, PLb and "you asked for it: a control unit works best in one" or OLb and "it's this room's last life" or (not SLb and "spread-out units lost here") or string.format("your team doesn't beat enemies ร—%.1f spread out", Match.STYLE_BALANCED) end end Match.style = QLb.style Match.styleWhy = RLb hJb.zone = QLb.style == "box" Match.layout = { } local CMb = { } for DMb, EMb in ipairs(kJb) do local FMb = QLb.spots [EMb.id] local GMb = { FMb } for HMb, IMb in ipairs(EMb [QLb.list]) do if IMb ~= FMb then table.insert(GMb, IMb) end end Match.layout [EMb.id] = GMb if FMb then table.insert(CMb, string.format("%s %s (%d%% of the road at full range)", Units.name(EMb.id), Match.spotKey(FMb), math.floor((zJb(FMb, Units.range(EMb.e.u, Units.rows(EMb.id).max) * iJb)) / math.max(1, # Match.samples) * 100 + 0.5))) end end Match.layoutSim = QLb local JMb, KMb = XKb(QLb) App.log(1, "SPOTS", string.format("placement: %s, because %s ยท %s ยท against enemies ร—%.1f the simulator %s at wave %d ยท %d runs in %.1f s", Match.STYLE_WORDS [QLb.style], RLb, table.concat(CMb, " ยท "), JMb, KMb.won and "wins" or "falls", KMb.wave, uKb, Utils.now() - gJb)) App.status("Placement", Match.STYLE_WORDS [QLb.style]) Match.lastLife(JMb, KMb) end function Match.spotResult(LMb) for MMb, NMb in pairs(Match.layoutUsed or { }) do local OMb = Match.spotRec(MMb, NMb, true) OMb.runs, OMb.wins = (tonumber(OMb.runs) or 0) + 1, (tonumber(OMb.wins) or 0) + (LMb and 1 or 0) end if not Match.infinite then Match.styleRecord(LMb) end end Match.CONTROL_WORDS = { "at the front of", "at the back of", "in the middle of", "ahead of" } function Match.controlSpot(PMb, QMb) if not Match.killZone() then return nil end local RMb = Simulator.holds if not RMb(PMb.id) or Units.toggleOf(PMb.id) then return nil end local SMb, TMb, UMb, VMb = 1, 0, 0, { } for WMb, XMb in ipairs(Match.mine or { }) do local YMb = XMb._stats local ZMb = YMb.id.Value if YMb:FindFirstChild("range") and not Units.isFarm(ZMb) and not Units.allyBuff(ZMb) then local _Mb, aNb = Utils.flat(XMb:GetPivot().Position), YMb.range.Value if RMb(ZMb) and not Units.toggleOf(ZMb) then UMb = UMb + 1 table.insert(VMb, { p = _Mb, r = aNb }) else for bNb, cNb in ipairs(Match.samples) do if (Utils.flat(cNb.pos) - _Mb).Magnitude <= aNb then SMb, TMb = math.min(SMb, cNb.prog), math.max(TMb, cNb.prog) end end end end end if TMb <= SMb then SMb, TMb = 0.75, 0.98 end local dNb = (QMb or UMb) + 1 local eNb = ({ SMb, TMb, (SMb + TMb) / 2, SMb - (TMb - SMb) / 2 }) [dNb] if not eNb then return nil end local fNb = Units.range(PMb.u, 0) * ((Match.info and tonumber(Match.info.rangeScale)) or 1) local gNb = { } for hNb, iNb in ipairs(Match.cands) do local jNb, kNb, lNb = Utils.flat(iNb), 0, 0 for mNb, nNb in ipairs(Match.samples) do if (Utils.flat(nNb.pos) - jNb).Magnitude <= fNb then lNb = lNb + 1 if math.abs(nNb.prog - eNb) <= 0.05 then kNb = kNb + 1 local oNb = Utils.flat(nNb.pos) for pNb, qNb in ipairs(VMb) do if (oNb - qNb.p).Magnitude <= qNb.r then kNb = kNb - 0.5 break end end end end end if kNb > 0 then table.insert(gNb, { pos = iNb, sc = kNb, cov = lNb }) end if hNb % 60 == 0 then task.wait() end end table.sort(gNb, function(rNb, sNb) return rNb.sc > sNb.sc end) for tNb, uNb in ipairs(gNb) do local vNb = Match.valid(uNb.pos, PMb.data) if vNb then App.log(1, "PLACE", string.format("%s goes %s the kill box (%d%% of the road)", Units.name(PMb.id), Match.CONTROL_WORDS [dNb], math.floor(eNb * 100 + 0.5))) return vNb, uNb.cov, eNb end if tNb % 20 == 0 then task.wait() end end return nil end function Match.holdSpot(wNb) local xNb, yNb = Simulator.holds(wNb.id) if not xNb then return nil end local zNb, ANb = { }, 0 for BNb, CNb in ipairs(Match.mine or { }) do local DNb = CNb._stats local ENb = DNb.id.Value if DNb:FindFirstChild("range") and not Units.isFarm(ENb) and not Units.allyBuff(ENb) and ENb ~= wNb.id then local FNb, GNb = pcall(Match.liveDps, CNb, ENb) GNb = FNb and tonumber(GNb) or 0 if GNb > 0 then table.insert(zNb, { p = Utils.flat(CNb:GetPivot().Position), r = DNb.range.Value, d = GNb }) ANb = ANb + GNb end end end if # zNb == 0 or ANb <= 0 then return nil end local HNb = { } for INb, JNb in ipairs(Match.samples) do local KNb, LNb = Utils.flat(JNb.pos), 0 for MNb, NNb in ipairs(zNb) do if (KNb - NNb.p).Magnitude <= NNb.r then LNb = LNb + NNb.d end end HNb [INb] = LNb / ANb end local ONb = Units.range(wNb.u, math.min(tonumber(yNb) or 0, 3, Units.rows(wNb.id).max)) * ((Match.info and tonumber(Match.info.rangeScale)) or 1) local PNb, QNb, RNb for SNb, TNb in ipairs(Match.cands) do local UNb, VNb, WNb = Utils.flat(TNb), 0, 0 for XNb, YNb in ipairs(Match.samples) do if (Utils.flat(YNb.pos) - UNb).Magnitude <= ONb then VNb = VNb + HNb [XNb] WNb = WNb + 1 end end if VNb > 0 and (not QNb or VNb > QNb) then local ZNb = Match.valid(TNb, wNb.data) if ZNb then PNb, QNb, RNb = ZNb, VNb, WNb end end if SNb % 40 == 0 then task.wait() end end if not PNb then return nil end Match.holdSaid = Match.holdSaid or { } if not Match.holdSaid [wNb.id] then Match.holdSaid [wNb.id] = true App.log(1, "PLACE", string.format("%s holds enemies, so it stands where your attackers' damage is: what it holds stays under all of them", Units.name(wNb.id))) end return PNb, RNb or 0 end function Match.bestSpot(_Nb, aOb, bOb) if Match.focus and not aOb and not bOb and not Units.allyBuff(_Nb.id) then local cOb, dOb = Match.focusSpot(_Nb) if cOb then return cOb, dOb end end if not aOb and not bOb and not (Match.depth and Match.depth.leak) then local eOb, fOb, gOb = pcall(Match.controlSpot, _Nb) if eOb and fOb then return fOb, gOb end local hOb, iOb, jOb = pcall(Match.holdSpot, _Nb) if hOb and iOb then return iOb, jOb end end local kOb = Match.depth and Match.depth.leak if Match.layout and Match.layout [_Nb.id] and not aOb and not bOb then local lOb = false for mOb, nOb in ipairs(Match.mine or { }) do local oOb = nOb._stats:FindFirstChild("uuid") if oOb and oOb.Value == _Nb.uuid then lOb = true break end end if not lOb then for pOb, qOb in ipairs(Match.layout [_Nb.id]) do local rOb = Match.valid(qOb, _Nb.data) if rOb and kOb then local sOb, tOb, uOb = false, Utils.flat(rOb), Units.range(_Nb.u, 0) * ((Match.info and tonumber(Match.info.rangeScale)) or 1) for vOb, wOb in ipairs(Match.samples) do if wOb.prog >= kOb + 0.02 and (Utils.flat(wOb.pos) - tOb).Magnitude <= uOb then sOb = true break end end if not sOb then rOb = nil end end if rOb then Match.layoutUsed = Match.layoutUsed or { } Match.layoutUsed [_Nb.id] = rOb return rOb, Match.cover(rOb, Units.range(_Nb.u, math.min(3, Units.rows(_Nb.id).max)) * ((Match.info and tonumber(Match.info.rangeScale)) or 1)) end end end end local xOb = _Nb.data local yOb = Match.depth local zOb if bOb then zOb = 1 for AOb, BOb in ipairs(Match.mine or { }) do local COb = BOb._stats if not Units.isFarm(COb.id.Value) and not (COb:FindFirstChild("uuid") and COb.uuid.Value == _Nb.uuid) then local DOb, EOb = Utils.flat(BOb:GetPivot().Position), COb.range.Value for FOb, GOb in ipairs(Match.samples) do if GOb.prog < zOb and (Utils.flat(GOb.pos) - DOb).Magnitude <= EOb then zOb = GOb.prog end end end end if Units.ENTRY [_Nb.id] or (Match.studyEntry and _Nb.id == Match.trialId) then zOb = math.min(zOb, 0.1) end end local HOb = (Match.info and tonumber(Match.info.rangeScale)) or 1 local IOb = Units.range(_Nb.u, (yOb and yOb.leak) and 0 or math.min(3, Units.rows(_Nb.id).max)) * HOb local JOb = (Match.kind == "dungeon" or Match.style == "box" or Match.style == "fast") and not aOb and not (yOb and yOb.leak) and not Units.isFarm(_Nb.id) and Units.range(_Nb.u, Units.rows(_Nb.id).max) * HOb or nil local KOb = Units.allyBuff(_Nb.id) if Match.killZone() and not aOb and not bOb and not KOb and not (yOb and yOb.leak) and not Units.isFarm(_Nb.id) then local LOb = JOb or IOb local MOb = { } for NOb, OOb in ipairs(Match.mine or { }) do local POb = OOb._stats local QOb = POb.id.Value if not Units.isFarm(QOb) and not Units.allyBuff(QOb) and POb:FindFirstChild("range") then table.insert(MOb, { p = Utils.flat(OOb:GetPivot().Position), r = POb.range.Value }) end end local ROb = { } for SOb, TOb in ipairs(Match.samples) do local UOb = 0 local VOb = Utils.flat(TOb.pos) for WOb, XOb in ipairs(MOb) do if (VOb - XOb.p).Magnitude <= XOb.r then UOb = UOb + 1 end end ROb [SOb] = UOb end local YOb, ZOb, _Ob local aPb = 0 for bPb, cPb in ipairs(Match.cands) do local dPb = Match.valid(cPb, xOb) aPb = aPb + 1 if aPb % 40 == 0 then task.wait() end if dPb then local ePb, fPb, gPb = Utils.flat(dPb), 0, 0 for hPb, iPb in ipairs(Match.samples) do if (Utils.flat(iPb.pos) - ePb).Magnitude <= LOb then gPb = gPb + 1 if iPb.prog >= 0.6 then fPb = fPb + (iPb.prog - 0.5) * (1 + ROb [hPb]) end end end if not ZOb or fPb > ZOb then YOb, ZOb, _Ob = dPb, fPb, gPb end end end if YOb and ZOb > 0 then if not Match.zoneSaid then Match.zoneSaid = true App.log(1, "PLACE", "attackers stack on the road's last stretch, so what walks in takes everyone's damage at once") end return YOb, _Ob or 0 end end if Match.style == "fast" and not aOb and not bOb and not KOb and not (yOb and yOb.leak) and not Units.isFarm(_Nb.id) then local jPb = JOb or IOb local kPb, lPb, mPb local nPb = 0 for oPb, pPb in ipairs(Match.cands) do local qPb = Match.valid(pPb, xOb) nPb = nPb + 1 if nPb % 40 == 0 then task.wait() end if qPb then local rPb, sPb, tPb = Utils.flat(qPb), 0, 0 for uPb, vPb in ipairs(Match.samples) do if (Utils.flat(vPb.pos) - rPb).Magnitude <= jPb then tPb = tPb + 1 if vPb.prog <= Match.FAST_ROAD then sPb = sPb + 1.5 - vPb.prog end end end if not lPb or sPb > lPb then kPb, lPb, mPb = qPb, sPb, tPb end end end if kPb and lPb > 0 then return kPb, mPb or 0 end end local wPb = Match.infinite and Match.trainee and not bOb and _Nb.uuid ~= Match.trainee and not Units.isFarm(_Nb.id) and not KOb and not aOb and not (yOb and yOb.leak) and (Match.pressure or 0) < Config.dangerReach and Training.ZONE or nil local xPb = wPb and Units.range(_Nb.u, Units.rows(_Nb.id).max) or nil local yPb, zPb = Units.types(_Nb.id) local APb = KOb and { } or Match.fieldBuffers() local BPb, CPb = { }, 0 if KOb then BPb = Match.buffShort(KOb) for DPb, EPb in ipairs(BPb) do CPb = CPb + EPb.dps end end local FPb = Config.community and not zOb and Core.TIPS [_Nb.id] local GPb = ((FPb and FPb.spot == "exit") or (not zOb and Match.needLastLine(_Nb))) and not (yOb and yOb.leak) local HPb = not (zOb or aOb or KOb) and Match.mergeSpot(_Nb) local IPb = HPb and Units.range(_Nb.u, 0) * 0.7 local JPb, KPb if aOb and Units.isFarm(_Nb.id) then JPb, KPb = Match.farmMergeSpot(_Nb) KPb = JPb and KPb * 0.8 end local LPb = aOb and not JPb and Match.random and Units.isFarm(_Nb.id) and Match.farmMergeOk() and { } or nil local MPb = Match.cands local NPb, OPb = JPb or HPb, JPb and KPb or IPb if NPb and OPb and OPb > 0 then MPb = table.clone(Match.cands) for PPb, QPb in ipairs({ 0.5, 0.7, 0.9 }) do for RPb = 0, 11 do local SPb = RPb * math.pi / 6 table.insert(MPb, NPb + Vector3.new(math.cos(SPb), 0, math.sin(SPb)) * OPb * QPb) end end end local TPb, UPb, VPb local WPb = 0 local XPb = Brain.maps [Match.mapKey()] and Brain.maps [Match.mapKey()].dens for YPb, ZPb in ipairs(MPb) do local _Pb = Match.valid(ZPb, xOb) WPb = WPb + 1 if WPb % 40 == 0 then task.wait() end if _Pb then local aQb if aOb then aQb = - Match.cover(_Pb, IOb) elseif zOb then aQb = 0 local bQb = Utils.flat(_Pb) for cQb, dQb in ipairs(Match.samples) do if (Utils.flat(dQb.pos) - bQb).Magnitude <= IOb then aQb = aQb + (dQb.prog <= zOb + 0.05 and 1 or 0.1) end end elseif wPb then aQb = 0 local eQb = Utils.flat(_Pb) for fQb, gQb in ipairs(Match.samples) do local hQb = (Utils.flat(gQb.pos) - eQb).Magnitude if gQb.prog < wPb then if hQb <= xPb then aQb = aQb - 2 end elseif hQb <= IOb then aQb = aQb + 1 end end elseif GPb then aQb = 0 local iQb = Utils.flat(_Pb) for jQb, kQb in ipairs(Match.samples) do if (Utils.flat(kQb.pos) - iQb).Magnitude <= IOb then aQb = aQb + kQb.prog * kQb.prog end end elseif KOb and CPb > 0 then local lQb = 0 for mQb, nQb in ipairs(BPb) do local oQb = (Utils.flat(nQb.pos) - Utils.flat(_Pb)).Magnitude if oQb <= IOb then lQb = lQb + nQb.dps * (1.1 - 0.1 * oQb / math.max(1, IOb)) end end aQb = lQb / CPb * 100 + Match.cover(_Pb, IOb, true) * 0.05 else local pQb = Match.cover(_Pb, IOb, true, yOb) if JOb then pQb = Match.cover(_Pb, JOb) + pQb * 0.25 end aQb = pQb + (XPb and 0 or (1 - Match.progressAt(_Pb)) * 0.5) local qQb = 0 for rQb, sQb in ipairs(APb) do local tQb = sQb.b.types [zPb] if tQb and (Utils.flat(sQb.pos) - Utils.flat(_Pb)).Magnitude <= sQb.range then qQb = math.max(qQb, math.min(tQb, qQb + tQb * sQb.b.uptime)) end end aQb = aQb * (1 + qQb) end if HPb and (Utils.flat(HPb) - Utils.flat(_Pb)).Magnitude <= IPb then aQb = aQb + 1e6 end if JPb and (Utils.flat(JPb) - Utils.flat(_Pb)).Magnitude <= KPb then aQb = aQb + 1e6 end if LPb then table.insert(LPb, { p = _Pb, sc = aQb }) end if not UPb or aQb > UPb then TPb, UPb, VPb = _Pb, aQb, Match.cover(_Pb, IOb) end end end if KOb and # BPb > 0 and TPb then local uQb = false for vQb, wQb in ipairs(BPb) do if (Utils.flat(wQb.pos) - Utils.flat(TPb)).Magnitude <= IOb then uQb = true break end end if not uQb then return nil, 0 end end if LPb and # LPb > 0 then table.sort(LPb, function(xQb, yQb) return xQb.sc > yQb.sc end) for zQb = 1, math.min(15, # LPb) do local AQb, BQb = LPb [zQb].p, 0 for CQb = 0, 7 do local DQb = CQb * math.pi / 4 if Match.valid(AQb + Vector3.new(math.cos(DQb), 0, math.sin(DQb)) * 5, xOb) then BQb = BQb + 1 end if BQb >= 2 then break end end if BQb >= 2 then return AQb, Match.cover(AQb, IOb) end end end return TPb, VPb or 0 end function Match.lookAt(EQb) local FQb, GQb = math.huge, nil for HQb, IQb in ipairs(Match.samples) do local JQb = (Utils.flat(IQb.pos) - Utils.flat(EQb)).Magnitude if JQb < FQb then FQb, GQb = JQb, IQb.pos end end local KQb = GQb and Utils.flat(GQb - EQb) or Vector3.new(0, 0, - 1) return KQb.Magnitude > 0.01 and KQb.Unit or Vector3.new(0, 0, - 1) end function Match.place(LQb, MQb) local NQb = Units.isFarm(LQb.id) local OQb, PQb = Match.bestSpot(LQb, NQb or (Match.random and Units.idol(LQb.id)), (Match.trainee ~= nil and LQb.uuid == Match.trainee) or Units.ENTRY [LQb.id] == true or (Match.studyEntry == true and LQb.id == Match.trialId)) if not OQb then Match.noSpot [LQb.uuid] = (Match.noSpot [LQb.uuid] or 0) + 1 App.status("Placement", "no free spot for " .. Units.name(LQb.id)) return false end local QQb = Match.money() local RQb, SQb = App.request("place", "spawn_unit", LQb.uuid, Core.Util.CFramePLV(OQb, Match.lookAt(OQb))) if RQb and SQb then Match.spent = Match.spent + (LQb.data.cost or 0) table.insert(Match.actions, string.format("placed %s", Units.name(LQb.id))) App.log(1, "PLACE", string.format("%s at %s (covers %d road samples) โ€” %s", Units.name(LQb.id), Match.spotKey(OQb), PQb, MQb)) Match.placeLog = Match.placeLog or { } table.insert(Match.placeLog, { t = math.floor(Utils.now() - (Match.startT or Utils.now())), w = Match.wave(), id = LQb.id, x = math.floor(OQb.X), z = math.floor(OQb.Z), cover = math.floor((PQb or 0) + 0.5), why = tostring(MQb):sub(1, 90) }) App.status("Placement", string.format("placed %s โ€” %s", Units.name(LQb.id), MQb)) Match.placedAt = Match.placedAt or { } Match.placedAt [LQb.uuid] = (Match.placedAt [LQb.uuid] or 0) + 1 return true end if QQb >= (LQb.data.cost or 0) and Match.random then Match.placeFail = Match.placeFail or { } Match.placeFail [LQb.uuid] = (Match.placeFail [LQb.uuid] or 0) + 1 if Match.placeFail [LQb.uuid] >= 2 then Match.noSpot [LQb.uuid] = 3 App.log(1, "PLACE", string.format("the game refused %s twice (%d/%d units counted) โ€” buying something else", Units.name(LQb.id), Match.fieldCount(), Match.FIELD_MAX)) end end if RQb and QQb >= (LQb.data.cost or 0) and not Match.random then local TQb = Match.spotKey(OQb) Match.refused [TQb] = true local UQb = Match.mapKey() Brain.blocked [UQb] = Brain.blocked [UQb] or { } if not Match.refusedRun [TQb] then Brain.blocked [UQb] [TQb] = (tonumber(Brain.blocked [UQb] [TQb]) or 0) + 1 Match.refusedRun [TQb] = true end Match.refusals = (Match.refusals or 0) + 1 Match.unitRefusals [LQb.uuid] = (Match.unitRefusals [LQb.uuid] or 0) + 1 if Match.unitRefusals [LQb.uuid] >= 3 then local VQb = math.max(1, Match.count(LQb.uuid)) if Brain.caps [LQb.uuid] ~= VQb then Brain.caps [LQb.uuid] = VQb App.log(1, "PLACE", string.format("%s: the server allows %d on the field (learned)", Units.name(LQb.id), VQb)) end end App.log(2, "PLACE", "refused spot " .. TQb .. " for " .. Units.name(LQb.id)) App.saveSoon() end return false end function Match.count(WQb) local XQb = 0 for YQb, ZQb in ipairs({ workspace._UNITS, workspace:FindFirstChild("_DEAD_UNITS") }) do if ZQb then for _Qb, aRb in ipairs(ZQb:GetChildren()) do local bRb = aRb:FindFirstChild("_stats") if bRb and bRb:FindFirstChild("uuid") and bRb.player.Value == LocalPlayer and bRb.uuid.Value == WQb then XQb = XQb + 1 end end end end return XQb end function Match.measure() local cRb, dRb = Match.calRun, Match.info if not (cRb and dRb) then return end pcall(Match.sampleHelpers) local eRb = Utils.now() local fRb = Match.wave() local gRb = Match.money() if not cRb.start and gRb > 0 and # Match.mine == 0 then cRb.start = gRb end if fRb ~= cRb.lastW then if Match.trialId and fRb > 0 then Match.trialTrace = Match.trialTrace or { } local hRb, iRb, jRb, kRb = 0, { }, 0, 0 for lRb, mRb in ipairs(Match.mine) do local nRb = mRb._stats if nRb.id.Value == Match.trialId then hRb = hRb + 1 table.insert(iRb, tostring(nRb.upgrade.Value)) elseif not Units.isFarm(nRb.id.Value) then kRb = kRb + 1 end end pcall(Match.sampleHelpers, true) for oRb, pRb in pairs(Match.ownDealt) do if pRb.id == Match.trialId then jRb = jRb + pRb.v end end local qRb = 0 for rRb, sRb in pairs(Match.helperDealt) do if sRb.owner == Match.trialId then qRb = qRb + sRb.v end end table.insert(Match.trialTrace, string.format("w%d t=%.0f ยฅ%d copies[%s] others %d dealt %.0f alive %d hp %.0f front %.2f dps %.0f", fRb, eRb - (Match.startT or eRb), gRb, table.concat(iRb, ","), kRb, jRb + qRb, # (Match.enemies or { }), Match.hpAliveRaw or 0, Match.waveMax [fRb - 1] or 0, Match.teamDps or 0)) end if cRb.lastW and cRb.lastW > 0 and cRb.lastWT then table.insert(cRb.waveSec, eRb - cRb.lastWT) end if cRb.paidT and fRb > 0 and eRb - cRb.paidT < 15 then table.insert(cRb.skip, eRb - cRb.paidT) end cRb.paidT = nil if fRb > 0 then cRb.alive [tostring(fRb)] = Match.hpAliveRaw or 0 end cRb.lastW, cRb.lastWT = fRb, eRb end local tRb = workspace:FindFirstChild("_wave_time") if tRb and fRb > 0 then cRb.timer [tostring(fRb)] = math.max(cRb.timer [tostring(fRb)] or 0, tonumber(tRb.Value) or 0) end local uRb = gRb - (Match.lastMoneyR or gRb) Match.lastMoneyR = gRb Match.incomeLog = Match.incomeLog or { } if uRb > 0 then table.insert(Match.incomeLog, { t = eRb, y = uRb }) end while Match.incomeLog [1] and eRb - Match.incomeLog [1].t > 30 do table.remove(Match.incomeLog, 1) end local vRb = 0 for wRb, xRb in ipairs(Match.incomeLog) do vRb = vRb + xRb.y end local yRb = math.min(30, eRb - (Match.startT or eRb)) Match.incomeRate = yRb >= 8 and vRb / yRb or nil for zRb, ARb in ipairs(Match.enemies) do local BRb = ARb.m local CRb = Match.track [BRb] if not CRb then local DRb = BRb._stats local ERb = BRb:FindFirstChildOfClass("Humanoid") local FRb = dRb.cfgById [DRb.id.Value] local GRb = FRb and Simulator.baseStats(dRb, FRb) if ERb and GRb and fRb > 0 then local HRb = GRb * dRb.hpRaw * Simulator.waveScale(dRb, fRb, dRb.list [fRb]) if HRb > 0 and ERb.MaxHealth > 0 then table.insert(cRb.hp, ERb.MaxHealth / HRb) end end Match.track [BRb] = { t = eRb, p = ARb.prog, sp = (DRb:FindFirstChild("speed") and tonumber(DRb.speed.Value)) or 1 } if fRb > 0 then cRb.spawn [tostring(fRb)] = (cRb.spawn [tostring(fRb)] or 0) + 1 cRb.spawnLast [tostring(fRb)] = eRb - (cRb.lastWT or eRb) end elseif not CRb.done and eRb - CRb.t >= 15 then CRb.done = true local IRb = (ARb.prog - CRb.p) / (eRb - CRb.t) / math.max(0.1, CRb.sp) if IRb > 0.001 and ARb.prog < 0.95 then table.insert(cRb.walk, IRb) end end end local JRb = 0 for KRb, LRb in ipairs(Match.mine) do local MRb = LRb._stats:FindFirstChild("total_damage") if MRb then JRb = JRb + MRb.Value end end cRb.potSum = cRb.potSum or 0 if cRb.lastDealtT then local NRb = eRb - cRb.lastDealtT local ORb = 0 for PRb, QRb in ipairs(Match.mine) do local RRb = QRb._stats local SRb = RRb.id.Value if not Units.isFarm(SRb) and RRb:FindFirstChild("target") and RRb.target.Value ~= nil then local TRb, URb = pcall(Match.liveDps, QRb, SRb) if TRb and URb then ORb = ORb + URb * NRb end end end cRb.potWin = (cRb.potWin or 0) + ORb if eRb - (cRb.winT or eRb) >= 5 then local VRb = JRb - (cRb.winDealt or JRb) if cRb.potWin > 0 and VRb >= 0 then cRb.dealtSum = (cRb.dealtSum or 0) + VRb cRb.potSum = cRb.potSum + cRb.potWin end cRb.winT, cRb.winDealt, cRb.potWin = eRb, JRb, 0 end end if not cRb.winT then cRb.winT, cRb.winDealt = eRb, JRb end cRb.lastDealtT = eRb if fRb ~= cRb.dpsW and fRb > 0 then cRb.dpsAt [tostring(fRb)] = Match.teamDps or 0 cRb.dpsW = fRb end local WRb = math.min(1, eRb - (cRb.densT or eRb)) cRb.densT = eRb for XRb, YRb in ipairs(Match.enemies) do local ZRb = math.min(Simulator.BINS, math.floor(YRb.prog * Simulator.BINS) + 1) cRb.dens [ZRb] = (cRb.dens [ZRb] or 0) + WRb end local _Rb = workspace._BASES.player:FindFirstChild("base") local aSb = _Rb and _Rb:FindFirstChild("fake_unit") local bSb = aSb and aSb:FindFirstChildOfClass("Humanoid") if bSb then cRb.baseHp = math.max(cRb.baseHp or 0, bSb.MaxHealth) local cSb = 0 for dSb, eSb in ipairs(Match.enemies) do if eSb.prog >= 0.97 then local fSb = eSb.m:FindFirstChildOfClass("Humanoid") cSb = cSb + (fSb and fSb.MaxHealth or eSb.hp) end end if cRb.lastBase and bSb.Health < cRb.lastBase and cSb > 0 and cRb.lastBaseT then table.insert(cRb.baseRel, (cRb.lastBase - bSb.Health) / math.max(0.1, eRb - cRb.lastBaseT) / cSb) end cRb.lastBase, cRb.lastBaseT = bSb.Health, eRb end end function Match.recordMap() local gSb = Match.mapKey() local hSb = Brain.maps [gSb] if hSb and hSb.v == 1 and hSb.laneV and (hSb.densN or 0) == (hSb.binsDensN or 0) then return end if hSb and hSb.v == 1 and hSb.laneV and (hSb.densN or 0) > 5 and (hSb.binsDensN or 0) >= 5 then return end local iSb = { override_hitbox_radius = 1.75 } local jSb = { hill_unit = true, override_hitbox_radius = 1.75 } local kSb, lSb = { }, false for mSb, nSb in ipairs(Match.cands) do local oSb = Match.valid(nSb, iSb) if oSb then table.insert(kSb, oSb) end if not lSb and Match.valid(nSb, jSb) then lSb = true end if mSb % 30 == 0 then task.wait() end end local pSb, qSb = { }, { } local rSb = Simulator.laneList(Match.samples) for sSb = 5, 60, 5 do local tSb, uSb for vSb, wSb in ipairs(kSb) do local xSb = Match.cover(wSb, sSb, true) if not uSb or xSb > uSb then tSb, uSb = wSb, xSb end end if tSb then local ySb, zSb = { }, { } for ASb = 1, Simulator.BINS do ySb [ASb] = "0" end for BSb, CSb in ipairs(rSb or { }) do zSb [CSb] = { } for DSb = 1, Simulator.BINS do zSb [CSb] [DSb] = "0" end end local ESb = Utils.flat(tSb) for FSb, GSb in ipairs(Match.samples) do if (Utils.flat(GSb.pos) - ESb).Magnitude <= sSb then local HSb = math.min(Simulator.BINS, math.floor(GSb.prog * Simulator.BINS) + 1) ySb [HSb] = "1" if zSb [tostring(GSb.lane)] then zSb [tostring(GSb.lane)] [HSb] = "1" end end end pSb [tostring(sSb)] = table.concat(ySb) if rSb then qSb [tostring(sSb)] = { } for ISb, JSb in pairs(zSb) do qSb [tostring(sSb)] [ISb] = table.concat(JSb) end end end task.wait() end local KSb = Brain.maps [gSb] or { } Brain.maps [gSb] = { v = 1, laneV = 1, bins = pSb, binsL = rSb and qSb or nil, lanes = rSb, hill = lSb, spots = # kSb, dens = KSb.dens, densN = KSb.densN, binsDensN = KSb.densN or 0, lay = KSb.lay, walk = KSb.walk, walkN = KSb.walkN } Simulator._masks = { } App.log(1, "SIM", string.format("mapped %s: %d free spots%s", gSb, # kSb, lSb and ", hill spots too" or ", no hill spots")) App.saveSoon() end Match.helperDealt, Match.helperAt, Match.ownDealt = { }, 0, { } function Match.sampleHelpers(LSb) if not LSb and Utils.now() - Match.helperAt < 3 then return end Match.helperAt = Utils.now() local MSb = workspace:FindFirstChild("_UNITS") local NSb = { } for OSb, PSb in ipairs(Match.mine or { }) do NSb [PSb] = true local QSb = PSb:FindFirstChild("_stats") local RSb = QSb and QSb:FindFirstChild("total_damage") if RSb and QSb:FindFirstChild("id") then local SSb = Match.ownDealt [PSb] or { id = QSb.id.Value, v = 0, lvl = 0 } SSb.v = math.max(SSb.v, RSb.Value) SSb.lvl = math.max(SSb.lvl, QSb:FindFirstChild("upgrade") and tonumber(QSb.upgrade.Value) or 0) Match.ownDealt [PSb] = SSb end end for TSb, USb in ipairs(MSb and MSb:GetChildren() or { }) do local VSb = USb:FindFirstChild("_stats") local WSb = VSb and VSb:FindFirstChild("parent_unit") local XSb = VSb and VSb:FindFirstChild("total_damage") if WSb and XSb and WSb.Value ~= nil and NSb [WSb.Value] then local YSb = WSb.Value:FindFirstChild("_stats") local ZSb = Match.helperDealt [USb] or { id = VSb.id.Value, owner = YSb and YSb:FindFirstChild("id") and YSb.id.Value or nil, v = 0 } ZSb.v = math.max(ZSb.v, XSb.Value) Match.helperDealt [USb] = ZSb end end end function Match.watchHelpers() local _Sb = workspace:FindFirstChild("_UNITS") if not _Sb then return end table.insert(App.conns, _Sb.ChildAdded:Connect(function(aTb) task.spawn(function() local bTb = aTb:WaitForChild("_stats", 3) task.wait(0.1) local cTb = bTb and bTb:FindFirstChild("parent_unit") local dTb = bTb and bTb:FindFirstChild("total_damage") if not (dTb and cTb and cTb.Value) then return end local eTb = cTb.Value:FindFirstChild("_stats") local fTb = eTb and eTb:FindFirstChild("id") and eTb.id.Value or nil if not fTb then return end local gTb = false for hTb, iTb in ipairs(Match.mine or { }) do if iTb == cTb.Value then gTb = true break end end if not gTb then return end local function jTb() local kTb = Match.helperDealt [aTb] or { id = bTb.id.Value, owner = fTb, v = 0 } kTb.v = math.max(kTb.v, dTb.Value) Match.helperDealt [aTb] = kTb end jTb() table.insert(App.conns, dTb.Changed:Connect(jTb)) end) end)) end function Match.damageShares() pcall(Match.sampleHelpers, true) local lTb, mTb = { }, 0 local function nTb(oTb, pTb) lTb [oTb] = (lTb [oTb] or 0) + pTb mTb = mTb + pTb end for qTb, rTb in pairs(Match.ownDealt) do if not Units.isFarm(rTb.id) then nTb(rTb.id, rTb.v) end end local sTb = 0 for tTb, uTb in pairs(Match.helperDealt) do nTb(uTb.owner or "helpers", uTb.v) sTb = sTb + 1 end if mTb <= 0 then return nil end for vTb, wTb in pairs(lTb) do lTb [vTb] = wTb / mTb end return lTb, mTb, sTb end function Match.summonSplit() local xTb, yTb = { }, { } for zTb, ATb in pairs(Match.ownDealt) do xTb [ATb.id] = (xTb [ATb.id] or 0) + (tonumber(ATb.v) or 0) end for BTb, CTb in pairs(Match.helperDealt) do if CTb.owner then yTb [CTb.owner] = (yTb [CTb.owner] or 0) + (tonumber(CTb.v) or 0) end end local DTb = { } for ETb, FTb in pairs(yTb) do local GTb = FTb + (xTb [ETb] or 0) if GTb > 0 then DTb [ETb] = { share = FTb / GTb, total = GTb } end end return DTb end function Match.damageLine() local HTb, ITb, JTb = Match.damageShares() if not HTb then return nil end local KTb = { } for LTb, MTb in pairs(HTb) do table.insert(KTb, { name = LTb == "helpers" and "helpers" or Units.name(LTb), v = MTb }) end table.sort(KTb, function(NTb, OTb) return NTb.v > OTb.v end) local PTb = { } for QTb, RTb in ipairs(KTb) do table.insert(PTb, string.format("%s %d%%", RTb.name, math.floor(RTb.v * 100 + 0.5))) end local STb, TTb = pcall(Match.summonSplit) local UTb = { } for VTb, WTb in pairs(STb and TTb or { }) do if WTb.share >= 0.05 and WTb.total >= 0.01 * ITb then table.insert(UTb, string.format("%s %d%%", Units.name(VTb), math.floor(WTb.share * 100 + 0.5))) end end table.sort(UTb) return string.format("%s dealt: %s%s%s", Utils.fmt(ITb), table.concat(PTb, " ยท "), JTb > 0 and string.format(" (%d helpers)", JTb) or "", # UTb > 0 and (" ยท from summons: " .. table.concat(UTb, ", ")) or "") end function Match.calMerge(XTb) local YTb = Match.calRun if not (YTb and Match.key) then return end local ZTb = Simulator.cal(Match.key) ZTb.n = (tonumber(ZTb.n) or 0) + 1 local _Tb = math.max(0.2, 1 / ZTb.n) local function aUb(bUb) if # bUb == 0 then return nil end table.sort(bUb) return bUb [math.ceil(# bUb / 2)] end local function cUb(dUb, eUb) if eUb then ZTb [dUb] = ZTb [dUb] and (ZTb [dUb] + (eUb - ZTb [dUb]) * _Tb) or eUb end end Match.hpRun = aUb(YTb.hp) cUb("hpCal", Match.hpRun) cUb("walk", aUb(YTb.walk)) do local fUb, gUb = aUb(YTb.walk), Match.mapKey() if fUb then local hUb = Simulator.mapWalk(gUb) Brain.maps [gUb] = Brain.maps [gUb] or { } local iUb = Brain.maps [gUb] iUb.walkN = (tonumber(iUb.walkN) or 0) + 1 iUb.walk = hUb and (hUb + (fUb - hUb) * math.max(0.2, 1 / (iUb.walkN + 1))) or fUb Simulator._walk [gUb] = iUb.walk end end if (YTb.potSum or 0) > 0 and (YTb.dealtSum or 0) > 0 then cUb("eff", Utils.clamp(YTb.dealtSum / YTb.potSum, 0.1, 2)) end local jUb = Match.mapKey() local kUb = 0 for lUb, mUb in pairs(YTb.dens) do kUb = math.max(kUb, mUb) end if kUb > 0 then Brain.maps [jUb] = Brain.maps [jUb] or { } local nUb = type(Brain.maps [jUb].dens) == "table" and Brain.maps [jUb].dens or { } local oUb = (tonumber(Brain.maps [jUb].densN) or 0) + 1 for pUb = 1, Simulator.BINS do local qUb = (YTb.dens [pUb] or 0) / kUb local rUb = tonumber(nUb [pUb]) nUb [pUb] = rUb and (rUb + (qUb - rUb) * math.max(0.2, 1 / oUb)) or qUb end Brain.maps [jUb].dens, Brain.maps [jUb].densN = nUb, oUb end if # YTb.pay >= 2 then cUb("yenCal", Utils.clamp(aUb(YTb.pay), 0.5, 2)) end if not Match.joinedMid then cUb("waveSec", aUb(YTb.waveSec)) cUb("baseRel", aUb(YTb.baseRel)) ZTb.baseDps = nil cUb("skipDelay", aUb(YTb.skip)) local sUb = { } for tUb = 2, Match.wave() - 1 do local uUb, vUb = tonumber(YTb.spawn [tostring(tUb)]), tonumber(YTb.spawnLast [tostring(tUb)]) if uUb and vUb and uUb >= 3 then table.insert(sUb, vUb / (uUb - 1)) end end if # sUb >= 3 then cUb("spawnGap", Utils.clamp(aUb(sUb), 0.05, 5)) end local wUb = tonumber(ZTb.voteAt) or (Match.info and Match.info.voteAt) or Simulator.PRIOR.voteAt local xUb, yUb = { }, { } for zUb, AUb in ipairs(YTb.votes) do local BUb = tonumber(AUb.n) or 0 if BUb > 0 and Simulator.PRIOR.voteBase + Simulator.PRIOR.votePer * BUb >= wUb + 3 then table.insert(xUb, AUb.t) elseif BUb > 0 and AUb.t < wUb - 2 then table.insert(yUb, (AUb.t - Simulator.PRIOR.voteBase) / BUb) end end cUb("voteAt", aUb(xUb)) cUb("votePer", yUb [1] and Utils.clamp(aUb(yUb), 0.2, 6) or nil) ZTb.voteWave = type(ZTb.voteWave) == "table" and ZTb.voteWave or { } for CUb, DUb in ipairs(YTb.votes) do if DUb.w and DUb.t then local EUb, FUb = tostring(DUb.w), tonumber(ZTb.voteWave [tostring(DUb.w)]) ZTb.voteWave [EUb] = FUb and (FUb + (DUb.t - FUb) * _Tb) or DUb.t end end ZTb.timerAt = type(ZTb.timerAt) == "table" and ZTb.timerAt or { } for GUb, HUb in pairs(YTb.timer) do if HUb > 5 then local IUb = tonumber(ZTb.timerAt [GUb]) ZTb.timerAt [GUb] = IUb and (IUb + (HUb + 1 - IUb) * _Tb) or (HUb + 1) end end if YTb.start then ZTb.start = YTb.start end if YTb.baseHp then ZTb.baseHp = YTb.baseHp end ZTb.yen = nil if Match.info then local JUb, KUb = 0, 0 for LUb = 2, Match.wave() - 1 do local MUb = tonumber(YTb.spawn [tostring(LUb)]) if MUb then JUb = JUb + MUb KUb = KUb + # (Simulator.wave(Match.info, LUb, true)) end end if JUb > 0 and KUb > 0 then cUb("countCal", Utils.clamp(JUb / KUb, 0.25, 6)) end end local NUb = { } for OUb, PUb in ipairs(Match.team or { }) do table.insert(NUb, PUb.u) end local QUb = Simulator.sig(NUb) local RUb = tostring(Match.ld.id or "?") .. (Hunt.is(Match.ld.id) and ("@" .. tostring(Match.ld.map)) or "") if ZTb.teamSig ~= QUb or ZTb.aliveLvl ~= RUb then ZTb.alive = { } ZTb.teamSig = QUb ZTb.aliveLvl = RUb ZTb.aliveN = 0 ZTb.lastWave = 0 ZTb.dpsAt = { } end ZTb.aliveN = (tonumber(ZTb.aliveN) or 0) + 1 local SUb = 1 / ZTb.aliveN ZTb.alive = type(ZTb.alive) == "table" and ZTb.alive or { } for TUb, UUb in pairs(YTb.alive) do local VUb = tonumber(ZTb.alive [TUb]) ZTb.alive [TUb] = VUb and (VUb + (UUb - VUb) * SUb) or UUb end ZTb.lastWave = math.max(tonumber(ZTb.lastWave) or 0, Match.wave()) ZTb.dpsAt = type(ZTb.dpsAt) == "table" and ZTb.dpsAt or { } for WUb, XUb in pairs(YTb.dpsAt) do local YUb = tonumber(ZTb.dpsAt [WUb]) ZTb.dpsAt [WUb] = YUb and (YUb + (XUb - YUb) * SUb) or XUb end end ZTb.ld = Simulator.slim(Match.ld) ZTb.ch = Match.ld._challenge Brain.simRes [Match.key] = nil Simulator._masks = { } if Match.pred and Match.infinite then App.log(1, "SIM", string.format("predicted the base falls at wave %d%s ยท real wave %d", Match.pred.wave, Match.pred.won and "+" or "", Match.wave())) ZTb.lastPred = { won = Match.pred.won, wave = Match.pred.wave, realWon = XTb, realWave = Match.wave(), at = os.time() } elseif Match.pred then App.log(1, "SIM", string.format("predicted %s at wave %d ยท real %s at wave %d", Match.pred.won and "a win" or "a loss", Match.pred.wave, XTb and "a win" or "a loss", Match.wave())) ZTb.lastPred = { won = Match.pred.won, wave = Match.pred.wave, realWon = XTb, realWave = Match.wave(), at = os.time() } end end function Match.holdGain(ZUb, _Ub) if Match.random or not _Ub or not Match.info then return 0 end local aVb = Units.attackInfo(_Ub.atk, _Ub.range) if (aVb.stun or 0) <= 0 and (aVb.slowTime or 0) <= 0 then return 0 end local bVb = 0 for cVb, dVb in ipairs(Match.mine or { }) do local eVb = dVb._stats local fVb = Units.rows(eVb.id.Value) local gVb = fVb and fVb [math.min(eVb.upgrade.Value, fVb.max)] local hVb = gVb and gVb.atk and Units.attackInfo(gVb.atk, gVb.range) if hVb and ((hVb.stun or 0) > 0 or (hVb.slowTime or 0) > 0) then bVb = bVb + 1 end end local iVb, jVb = Simulator.mask(Match.info.map, Units.range(ZUb.u, 0) * (Match.info.rangeScale or 1)), 0 for kVb = 1, Simulator.BINS do if iVb [kVb] then jVb = jVb + 1 end end local lVb = Match.depth and Match.depth.units > 0 and Match.depth.covEnd or 1 local mVb = (Units.airFrom(ZUb.id) == 0) and 1 or (1 - (1 - Core.FLY_GROUND) * math.min(1, Match.info.flyChance or 0)) return Economy.holdGain(aVb, Match.teamDps or 0, jVb / (math.max(0.1, lVb) * Simulator.BINS) * mVb, bVb) end function Match.buffGain(nVb) local oVb = Units.allyBuff(nVb.id) if not oVb then return 0 end local pVb = Match.buffShort(oVb) local qVb = Brain.stack [oVb.attack] local rVb = qVb and qVb.stacks and (qVb.cap or 1) or nil local sVb, tVb = pcall(Units.range, nVb.u, 0) tVb = (sVb and tonumber(tVb) or 15) * ((Match.info and tonumber(Match.info.rangeScale)) or 1) local uVb = rVb and Config.community and tonumber(oVb.time) and tonumber(oVb.cd) local function vVb(wVb, xVb) local yVb = math.floor(wVb * oVb.time / math.max(1, oVb.cd) + 0.001) return math.min(rVb, (1 + xVb) ^ yVb - 1) end local zVb = 0 for AVb, BVb in ipairs(pVb) do local CVb = 0 for DVb, EVb in ipairs(pVb) do if (EVb.pos - BVb.pos).Magnitude <= tVb then local FVb = oVb.types [EVb.base] local GVb = FVb * oVb.uptime if uVb then local HVb, IVb = vVb(EVb.n, FVb), EVb.n + 1 while IVb < EVb.n + 6 and vVb(IVb, FVb) <= HVb + 0.001 do IVb = IVb + 1 end GVb = math.max(0, (vVb(IVb, FVb) - HVb) / (IVb - EVb.n)) elseif rVb then GVb = math.max(0, math.min(rVb, (EVb.n + 1) * FVb * oVb.uptime) - math.min(rVb, EVb.n * FVb * oVb.uptime)) end CVb = CVb + EVb.dps * GVb end end zVb = math.max(zVb, CVb) end return zVb * (uVb and 1 or 0.7) end function Match.buffWanted(JVb) return Match.buffGain(JVb) > 0.01 * (Match.teamDps or 0) end Match.summonHist = { } function Match.summonRate() local KVb, LVb = Utils.now(), 0 for MVb, NVb in pairs(Match.helperDealt) do LVb = LVb + (tonumber(NVb.v) or 0) end local OVb = Match.summonHist if not OVb [# OVb] or KVb - OVb [# OVb].t >= 1 then table.insert(OVb, { t = KVb, v = LVb }) end while # OVb > 2 and KVb - OVb [1].t > 30 do table.remove(OVb, 1) end local PVb = OVb [1] return (PVb and KVb - PVb.t >= 5) and math.max(0, LVb - PVb.v) / (KVb - PVb.t) or 0 end function Match.blessGain(QVb) if not Units.BLESS.by [QVb.id] then return 0 end local RVb, SVb = 0, 0 for TVb, UVb in ipairs(Match.mine or { }) do local VVb = UVb._stats.id.Value if Units.BLESS.by [VVb] then RVb = RVb + 1 end if Units.SUMMONER [VVb] then SVb = SVb + Match.liveDps(UVb, VVb) end end if RVb >= Units.BLESS.cap then return 0 end local WVb = math.max(Match.summonRate(), SVb * (1 + Units.BLESS.dmg * RVb)) return WVb / (1 + Units.BLESS.dmg * RVb) * Units.BLESS.dmg end function Match.liveDps(XVb, YVb) local ZVb = XVb:FindFirstChild("_stats") if not (ZVb and ZVb:FindFirstChild("attack_cooldown") and ZVb:FindFirstChild("damage") and ZVb:FindFirstChild("range")) then return 0 end local _Vb = math.max(0.1, ZVb.attack_cooldown.Value) local aWb = ZVb:FindFirstChild("primary_attack") and ZVb.primary_attack.Value or nil local bWb = 0 for cWb, dWb in ipairs({ "physical_crit_chance", "magic_crit_chance" }) do local eWb = ZVb:FindFirstChild(dWb) if eWb then bWb = math.max(bWb, tonumber(eWb.Value) or 0) end end local fWb, gWb = Units.types(YVb) local hWb = "_dmg_mul_" .. tostring(gWb) local iWb = 1 + (tonumber(XVb:GetAttribute(hWb)) or tonumber(ZVb:GetAttribute(hWb)) or 0) local jWb = ZVb.damage.Value * Units.galeDps(YVb, ZVb:FindFirstChild("upgrade") and tonumber(ZVb.upgrade.Value) or 0) return (ZVb.damage.Value / _Vb * Units.aoe(aWb, ZVb.range.Value) * (1 + bWb * 0.5) + jWb) * Units.elem(YVb, Match.mod) * Units.nudge(YVb) * iWb end Match.upFail, Match.upFailAt = setmetatable({ }, { __mode = "k" }), setmetatable({ }, { __mode = "k" }) Match.TRIAL_DEAR, Match.TRIAL_KEEP_ALIVE = 30000, 20000 function Match.trialSaving() local kWb = Core.Units [Match.trialId] if (tonumber(kWb and kWb.cost) or 0) < Match.TRIAL_DEAR or (Match.spent or 0) < Match.TRIAL_KEEP_ALIVE then return false end for lWb, mWb in ipairs(Match.mine or { }) do if mWb._stats.id.Value == Match.trialId then return false end end return true end function Match.econ() if Match.noBuy then return App.status("Economy", "keeping the last life: back to the lobby") end if Match.laneWant and not Match.laneCut then local nWb, oWb = pcall(Match.portalLanes) if nWb and not oWb then return App.status("Economy", string.format("this portal opens %d of the map's lanes and the game picks which: waiting for the first enemies", Match.laneWant)) end if not nWb then Match.laneWant = nil App.log(1, "ERROR", "portal lanes: " .. tostring(oWb)) end end if Match.planning and Utils.now() - Match.planning < 20 then return App.status("Economy", "the simulator is choosing spots on this mapโ€ฆ") end local pWb = Match.money() local qWb = Match.wave() local rWb = math.max(0, Match.total - qWb) local sWb = Match.team local tWb = 1 local uWb = 1 pcall(function() uWb = Core.USC.calculate_spawn_cost_multiplier(Core.session()) end) local vWb = { } local wWb, xWb, yWb = 0, 0, 0 local zWb = 0 for AWb, BWb in ipairs(Match.mine) do if not Units.isFarm(BWb._stats.id.Value) then zWb = zWb + 1 end end local CWb = { } for DWb, EWb in ipairs(Match.mine) do local FWb = EWb._stats local GWb = FWb:FindFirstChild("uuid") and FWb.uuid.Value if GWb then CWb [GWb] = CWb [GWb] or { } table.insert(CWb [GWb], EWb) end end for HWb, IWb in ipairs(sWb) do local JWb = Match.trialId and Brain.trial and Brain.trial.study and qWb >= (tonumber(Match.total) or math.huge) if Match.trialId and not Match.studyTeam and not JWb and IWb.id ~= Match.trialId and not Units.isFarm(IWb.id) and (Match.trialSaving() or (not Units.allyBuff(IWb.id) and (Match.pressure or 0) < 0.6)) then continue end if Match.trialId == IWb.id and (tonumber(IWb.data.cost) or 0) >= Match.TRIAL_DEAR and # (CWb [IWb.uuid] or { }) == 0 and pWb >= (tonumber(IWb.data.cost) or 0) * uWb then Match.place(IWb, "on trial") return end if Match.studyTeam and Match.trialId == IWb.id and (Match.pressure or 0) < 0.6 then local KWb = tonumber(Units.field(IWb.id, "spawn_cap")) or tonumber(IWb.data.spawn_cap) or 1 if # (CWb [IWb.uuid] or { }) < KWb and pWb >= (tonumber(IWb.data.cost) or 0) * uWb then Match.place(IWb, "on study: down first") return end end local LWb = Units.rows(IWb.id) local MWb = Units.isFarm(IWb.id) local NWb = CWb [IWb.uuid] or { } local OWb = Match.trainee == IWb.uuid and Training.BONUS or 1 if not MWb and # NWb > 0 then wWb = wWb + 1 end for PWb, QWb in ipairs(NWb) do if QWb._stats.id.Value == IWb.id and not ((Match.upFail [QWb] or 0) >= 3 and Utils.now() - (Match.upFailAt [QWb] or 0) < 60) then local RWb = QWb._stats.upgrade.Value if MWb then xWb = xWb + (tonumber(QWb._stats.farm_amount and QWb._stats.farm_amount.Value) or Units.farm(IWb.u, RWb)) end local SWb = LWb [RWb + 1] if SWb and RWb < LWb.max then pcall(function() tWb = Core.USC.calculate_upgrade_cost_multiplier(QWb, Core.session()) end) local TWb = SWb.cost * tWb if MWb then local UWb = Units.farm(IWb.u, RWb + 1) - Units.farm(IWb.u, RWb) table.insert(vWb, { farm = true, cost = TWb, gain = UWb, m = QWb, e = IWb, what = string.format("upgrade %s to level %d", Units.name(IWb.id), RWb + 1) }) elseif Match.random and Units.idol(IWb.id) then table.insert(vWb, { cost = TWb, gain = (Match.teamDps or 0) * Match.idolStep(), m = QWb, e = IWb, idol = true, what = string.format("upgrade %s to level %d", Units.name(IWb.id), RWb + 1) }) elseif Match.mergeHold(QWb) then else local VWb, WWb = Match.shieldNow() local XWb = (VWb > 0 and Economy.shieldGain(Match.liveDps(QWb, IWb.id), LWb [RWb], Units.dps(IWb.u, RWb + 1, Match.mod), SWb, VWb, WWb, 1, Match.info and Match.info.eff) or Economy.upgradeGain(Match.liveDps(QWb, IWb.id), LWb, RWb, TWb, tWb, Units.dps(IWb.u, RWb + 1, Match.mod))) * OWb * Match.mergeBoost(QWb) * Match.airBoost(QWb) * Match.reach(QWb) * Match.focusGain(QWb) local YWb = Match.bleedGain(IWb.id, RWb, false) XWb = XWb + YWb table.insert(vWb, { cost = TWb, gain = XWb, m = QWb, e = IWb, what = string.format("upgrade %s to level %d%s", Units.name(IWb.id), RWb + 1, YWb > 0 and " (towards its bleed, which stops regen)" or "") }) Match.upRate [QWb] = XWb / math.max(1, TWb) for ZWb = RWb + 1, LWb.max do yWb = yWb + LWb [ZWb].cost * tWb end end end end end local _Wb = Units.cap(IWb.u) if Match.random then _Wb = IWb.hand and 1 or 0 elseif Units.allyBuff(IWb.id) and OWb == 1 and not Match.buffWanted(IWb) then _Wb = 0 end if # NWb < _Wb and (Match.noSpot [IWb.uuid] or 0) < 3 then local aXb = (IWb.data.cost or 0) * uWb if MWb then table.insert(vWb, { farm = true, place = true, cost = aXb, gain = Units.farm(IWb.u, 0), e = IWb, what = "place " .. Units.name(IWb.id) }) else local bXb = (# NWb == 0 and 2.5 or 1) * OWb if wWb < 3 then bXb = bXb * 1.3 end if Match.random then bXb = bXb * (1 + 2 * Match.carry(IWb.id)) * (1 - Match.resistOf(IWb.id)) * Match.airNeed(IWb.id) * (Match.mergeIds [IWb.id] and 2 or 1) elseif Match.flyMode() then bXb = bXb * Match.airNeed(IWb.id) end if Match.random and zWb < 8 then bXb = bXb * (1 + (8 - zWb) * 0.25) end local cXb = Match.random and Units.idol(IWb.id) and (Match.teamDps or 0) * Match.idolStep() * 2 or 0 local dXb = Match.shieldOf(Match.random and Match.cardDps(IWb) or Units.dps(IWb.u, 0, Match.mod), LWb [0]) + Match.shieldWork(IWb.id, LWb [0]) local eXb = Match.blessGain(IWb) table.insert(vWb, { place = true, cost = aXb, gain = dXb * bXb + Match.buffGain(IWb) + eXb + cXb + Match.holdGain(IWb, LWb [0]) + Match.bleedGain(IWb.id, 0, true), e = IWb, what = "place " .. Units.name(IWb.id) .. (eXb > 0 and string.format(" (its blessing: +%s summon damage a second)", Utils.fmt(eXb)) or ""), train = OWb ~= 1 and (# NWb == 0 and "first" or "more") or nil }) for fXb = 0, LWb.max do yWb = yWb + LWb [fXb].cost * (fXb == 0 and uWb or tWb) end end end end if Match.random and FarmHub.every("summonCheck", 1) then Match.summonWait = false for gXb, hXb in pairs(Core.session().ingame_spells or { }) do if hXb == "summon" and Match.summonWorth() then Match.summonWait = true end end end do local iXb = Match.depth and Match.depth.units > 0 and Match.depth.covEnd or 1 if Match.summonWait and pWb < 10000 and (Match.pressure or 0) < math.min(Config.dangerReach, iXb * 0.9) then return App.status("Economy", string.format("saving ยฅ10k for Astral Summon (a unit from your own team): ยฅ%s", Utils.fmt(pWb))) end local jXb = Match.random and Match.spellWant if jXb and pWb < jXb.cost and ((Match.pressure or 0) < math.min(Config.dangerReach, iXb * 0.9) or (jXb.rate or 0) > (Match.bestRate or 0)) then return App.status("Economy", string.format("saving ยฅ%s for %s: ยฅ%s", Utils.fmt(jXb.cost), jXb.why, Utils.fmt(pWb))) end end if Match.fieldCount() >= Match.FIELD_MAX then local kXb = { } for lXb, mXb in ipairs(vWb) do if not mXb.place then table.insert(kXb, mXb) end end vWb = kXb local nXb = Match.depth and Match.depth.units > 0 and Match.depth.covEnd or 1 if Match.roomPlan and pWb < Match.roomPlan.need and (Match.pressure or 0) < math.min(Config.dangerReach, nXb * 0.9) then return App.status("Economy", string.format("saving ยฅ%s to swap %s for %s (the field is full)", Utils.fmt(Match.roomPlan.need), Match.roomPlan.out, Match.roomPlan.name)) end end if Match.infinite and Match.trainee then local oXb, pXb = Training.hold(Match.pressure or 0, wWb, Match.holding) if oXb then local qXb = { } for rXb, sXb in ipairs(vWb) do if sXb.farm or (sXb.e and sXb.e.uuid == Match.trainee) then table.insert(qXb, sXb) end end vWb = qXb if # vWb == 0 then return App.status("Economy", pXb .. " ยท everything for the trainee is bought: saving ยฅ" .. Utils.fmt(pWb)) end end if oXb ~= (Match.holding or false) then Match.holding = oXb if FarmHub.every("holdLog", 60) then App.log(1, "TRAIN", oXb and pXb or "enemies get further now: your other units are bought for again") end end end Match.bestRate = 0 for tXb, uXb in ipairs(vWb) do if not uXb.farm and not uXb.place and uXb.cost > 0 then Match.bestRate = math.max(Match.bestRate, uXb.gain / uXb.cost) end end if # vWb == 0 then return App.status("Economy", "everything is placed and fully upgraded โœ“") end if Match.trainee then Training.boost(vWb, wWb, Match.pressure or 0, Match.depth and Match.depth.units > 0 and Match.depth.covEnd or nil) end local vXb = pWb + xWb * rWb for wXb = qWb + 1, Match.total do vXb = vXb + Match.waveReward(wXb) end Match.farmNow, Match.future, Match.wantLeft = xWb, vXb, yWb if Match.info and Match.comingW ~= qWb then local xXb = { } local yXb = math.min(0.9, Match.info.red or 0) for zXb = 1, 5 do local AXb = qWb + zXb if AXb > Match.total then break end local BXb, CXb, DXb = pcall(Simulator.wave, Match.info, AXb) local EXb = 0 for FXb, GXb in ipairs(BXb and CXb or { }) do EXb = EXb + GXb.hp + (GXb.bar or 0) end table.insert(xXb, { hp = EXb / (1 - yXb), reward = (BXb and tonumber(DXb)) or Match.waveReward(AXb) }) end Match.comingW, Match.coming = qWb, xXb end local HXb = Match.info and Match.info.eff or Simulator.PRIOR.eff local IXb = Match.teamShield() if Match.focus and (Match.focusInRange or 0) > 0 then local JXb = { } for KXb, LXb in ipairs(vWb) do if not LXb.place then table.insert(JXb, LXb) end end if # JXb > 0 then vWb = JXb end end local MXb, NXb, OXb = Economy.decide(vWb, { money = pWb, left = rWb, attackers = wWb, pressure = Match.pressure or 0, piling = Match.piling, clearSec = (Match.clearSec or 0) / IXb, enemies = # Match.enemies, future = vXb, wantLeft = yWb, coming = Match.coming, waveSec = Match.info and Match.info.waveSec, eff = HXb, teamDps = (Match.teamDps or 0) * HXb, shieldK = IXb, farmNow = xWb, hpAlive = Match.hpAlive or 0, roadSec = 1 / math.max(0.001, ((Match.info and Match.info.walk) or Simulator.PRIOR.walk) * ((Match.info and Match.info.speedScale) or 1)), leak = Match.depth and Match.depth.leak, airLeak = Match.depth and Match.depth.airLeak, cover = Match.depth and Match.depth.units > 0 and Match.depth.covEnd or nil, incomeRate = Match.incomeRate or (((Match.coming and Match.coming [1] and Match.coming [1].reward) or 500) + xWb) / ((Match.info and Match.info.waveSec) or 20) }) if OXb then return App.status("Economy", OXb) end if not MXb then return end if not Config.upgrade and not MXb.place then return end if not Config.place and MXb.place then return end App.status("Economy", string.format("%s โ€” %s", MXb.what, NXb)) if MXb.place then Match.place(MXb.e, NXb) else local PXb = pWb local QXb, RXb = App.request("upg", "upgrade_unit_ingame", MXb.m) if QXb and RXb then Match.spent = Match.spent + MXb.cost table.insert(Match.actions, MXb.what) if MXb.idol then pcall(Match.idolMeasure) end App.log(2, "UPGRADE", MXb.what .. " for ยฅ" .. Utils.fmt(MXb.cost) .. " โ€” " .. NXb) App.status("Upgrade", MXb.what) elseif QXb then App.backoff("upg", 1.5) if PXb >= MXb.cost then App.log(2, "UPGRADE", "refused: " .. MXb.what) Match.upFail [MXb.m] = (Match.upFail [MXb.m] or 0) + 1 Match.upFailAt [MXb.m] = Utils.now() if Match.upFail [MXb.m] == 3 then App.log(1, "UPGRADE", "the game refused " .. MXb.what .. " 3 times โ€” buying something else for a minute") end end end end end Match.readySince, Match.buffedTill = setmetatable({ }, { __mode = "k" }), { } Match.buffSeen = { } function Match.buffOn(SXb, TXb) if (tonumber(SXb:GetAttribute("_dmg_buff_" .. tostring(TXb))) or 0) > 0 then return true end local UXb = SXb:FindFirstChild("buff_damage_multiplier") return UXb ~= nil and (tonumber(UXb.Value) or 0) > 0 end function Match.buffRotation(VXb, WXb, XXb, YXb, ZXb, _Xb) if # Match.enemies == 0 then return false end local aYb = 0 for bYb, cYb in pairs(YXb.ally.types or { }) do aYb = math.max(aYb, tonumber(cYb) or 0) end if aYb <= 0 then return false end local dYb = tonumber(Brain.stack [XXb].cap) or ((1 + aYb) ^ 2 - 1) local eYb = 1 while eYb < 6 and (1 + aYb) ^ eYb - 1 < dYb - 0.02 do eYb = eYb + 1 end local fYb = 0 for gYb, hYb in ipairs(Match.mine) do local iYb = hYb._stats:FindFirstChild("active_attack") if iYb and tostring(iYb.Value) == XXb then fYb = fYb + 1 end end local jYb = Units.allyBuff(WXb.id.Value) local kYb = (jYb and tonumber(jYb.cd)) or 60 local lYb = math.max(1, math.min(eYb, math.floor(fYb * ZXb / math.max(1, kYb) + 0.001))) local mYb = math.min(dYb, (1 + aYb) ^ lYb - 1) local nYb = ZXb / lYb local oYb = VXb:GetPivot().Position local pYb, qYb = 0, 0 for rYb, sYb in ipairs(Match.mine) do local tYb = sYb._stats local uYb = tYb.id.Value if sYb ~= VXb and not Units.isFarm(uYb) and not Units.allyBuff(uYb) and (Utils.flat(sYb:GetPivot().Position) - Utils.flat(oYb)).Magnitude <= WXb.range.Value then local vYb, wYb = Units.types(uYb) if YXb.ally.types [wYb] then pYb = pYb + 1 if (tonumber(tYb:GetAttribute("_dmg_buff_" .. tostring(wYb))) or 0) < mYb - 0.02 then qYb = qYb + 1 end end end end if pYb == 0 or qYb == 0 then return false end Match.buffNext = Match.buffNext or { } if _Xb < (Match.buffNext [XXb] or 0) then return false end Match.buffNext [XXb] = _Xb + nYb return true, string.format("rotation: %d of the %d units it reaches are under +%d%% (%d buffs at once from %d copies, one every %.0f s)", qYb, pYb, math.floor(mYb * 100 + 0.5), lYb, fYb, nYb) end function Match.buffLevel(xYb) local yYb = 0 for zYb, AYb in ipairs(Match.mine) do local BYb = AYb._stats local CYb = BYb.id.Value if not Units.isFarm(CYb) and not Units.allyBuff(CYb) then local DYb, EYb = Units.types(CYb) local FYb = BYb:FindFirstChild("buff_damage_multiplier") if FYb and not xYb then yYb = math.max(yYb, tonumber(FYb.Value) or 0) end if not xYb or xYb [EYb] then yYb = math.max(yYb, tonumber(BYb:GetAttribute("_dmg_buff_" .. tostring(EYb))) or 0) end end end return yYb end Match.galeIdle = setmetatable({ }, { __mode = "k" }) function Match.isGale(GYb, HYb) return GYb:FindFirstChild("unit_bar") ~= nil and (HYb:match(":active$") or HYb:match(":off$")) ~= nil end Match.barSeen = setmetatable({ }, { __mode = "k" }) function Match.learnToggle(IYb, JYb, KYb, LYb) local MYb = JYb.id.Value local NYb = Brain.toggles [MYb] local OYb = tonumber(JYb:FindFirstChild("upgrade") and JYb.upgrade.Value) or 0 local PYb = NYb == nil if not NYb then NYb = { from = OYb, fill = 0, drain = 0, nf = 0, nd = 0, at = os.time() } Brain.toggles [MYb] = NYb Simulator._holds [MYb] = nil end if OYb < (tonumber(NYb.from) or OYb) then NYb.from = OYb end Match.togglesSeen = Match.togglesSeen or { } Match.togglesSeen [MYb] = true local QYb, RYb = tonumber(JYb.unit_bar.Value) or 0, KYb:match(":off$") ~= nil local SYb = Match.barSeen [IYb] if SYb and SYb.on == RYb and LYb - SYb.t >= 1.5 then if QYb > 0.5 and QYb < 99.5 and SYb.bar > 0.5 and SYb.bar < 99.5 then local TYb = (QYb - SYb.bar) / (LYb - SYb.t) if RYb and TYb < 0 then NYb.drain = (NYb.drain * NYb.nd - TYb) / (NYb.nd + 1) NYb.nd = math.min(NYb.nd + 1, 50) elseif not RYb and TYb > 0 then NYb.fill = (NYb.fill * NYb.nf + TYb) / (NYb.nf + 1) NYb.nf = math.min(NYb.nf + 1, 50) end end Match.barSeen [IYb] = { bar = QYb, t = LYb, on = RYb } elseif not SYb or SYb.on ~= RYb then Match.barSeen [IYb] = { bar = QYb, t = LYb, on = RYb } end if PYb then App.log(1, "UNITS", string.format("%s has a switch ability with a bar (from upgrade %d): learned, so its copies take turns and it stands where your damage is", Units.name(MYb), OYb)) App.saveSoon() end end function Match.gale(UYb, VYb, WYb, XYb) local YYb = WYb:match("^(.+):") local function ZYb(_Yb) local aZb, bZb = Utils.flat(_Yb:GetPivot().Position), _Yb._stats.range.Value local cZb, dZb = 0, { } for eZb, fZb in ipairs(Match.enemies) do if (Utils.flat(fZb.pos) - aZb).Magnitude <= bZb then cZb = cZb + 1 if fZb.boss then table.insert(dZb, fZb) end end end return cZb, dZb end local gZb = tonumber(VYb.unit_bar.Value) or 0 local hZb, iZb = ZYb(UYb) local jZb = # iZb > 0 if WYb:match(":off$") then if hZb > 0 then Match.galeIdle [UYb] = nil return nil end Match.galeIdle [UYb] = Match.galeIdle [UYb] or XYb if XYb - Match.galeIdle [UYb] >= 3 and gZb >= 15 then return true, string.format("Gale off: nothing in range, keeping its bar (%d)", math.floor(gZb)) end return nil end Match.galeIdle [UYb] = nil if hZb == 0 or gZb < 20 then return nil end local kZb = Utils.flat(UYb:GetPivot().Position) for lZb, mZb in ipairs(Match.mine) do local nZb = mZb._stats local oZb = nZb:FindFirstChild("active_attack") local pZb = oZb and tostring(oZb.Value) or "" if mZb ~= UYb and nZb:FindFirstChild("unit_bar") and pZb:match("^(.+):") == YYb then local qZb, rZb = Utils.flat(mZb:GetPivot().Position), tonumber(nZb.unit_bar.Value) or 0 local sZb = (qZb - kZb).Magnitude <= math.max(VYb.range.Value, nZb.range.Value) local tZb = false for uZb, vZb in ipairs(iZb) do if (Utils.flat(vZb.pos) - qZb).Magnitude <= nZb.range.Value then tZb = true end end if pZb:match(":off$") then if rZb > 5 and (tZb or (sZb and not jZb)) then return nil end elseif sZb and rZb > gZb + 10 and rZb >= 20 and ZYb(mZb) > 0 then return nil end end end if not Match.galeSaid then Match.galeSaid = true App.log(1, "ABILITY", string.format("%s: %s in use, copies take turns so enemies stay slowed and freezing", Units.name(VYb.id.Value), YYb == "rukia_bankai" and "Gale of the Moon" or YYb)) end return true, string.format("Gale on: %d enemies in range%s, bar %d (copies take turns)", hZb, jZb and ", a boss" or "", math.floor(gZb)) end function Match.actives() if not Config.actives or Match.activeBusy then return end local wZb = tick() local xZb = Utils.now() for yZb, zZb in ipairs(Match.mine) do local AZb = zZb._stats local BZb, CZb, DZb = AZb:FindFirstChild("active_attack"), AZb:FindFirstChild("active_attack_cooldown"), AZb:FindFirstChild("last_active_cast") if BZb and CZb and DZb and BZb.Value ~= "nil" and BZb.Value ~= "" then if Match.isGale(AZb, tostring(BZb.Value)) then pcall(Match.learnToggle, zZb, AZb, tostring(BZb.Value), xZb) end if CZb.Value - (wZb - DZb.Value) > 0 then Match.readySince [zZb] = nil else Match.readySince [zZb] = Match.readySince [zZb] or xZb local EZb = Units.attackInfo(BZb.Value) local FZb = zZb:GetPivot().Position local GZb = AZb.range.Value local HZb, IZb, JZb local KZb = Match.trialId == AZb.id.Value and Brain.trial and Brain.trial.study and # Match.enemies > 0 and not Match.isGale(AZb, tostring(BZb.Value)) if KZb then HZb, IZb = true, "on study: what it does" elseif Match.isGale(AZb, tostring(BZb.Value)) then HZb, IZb = Match.gale(zZb, AZb, tostring(BZb.Value), xZb) elseif EZb.ally and Brain.stack [BZb.Value] and Brain.stack [BZb.Value].stacks and Config.community then HZb, IZb = Match.buffRotation(zZb, AZb, tostring(BZb.Value), EZb, tonumber(EZb.ally.time) or 30, xZb) elseif EZb.ally or EZb.allyCd or EZb.allyRange then local LZb = (EZb.ally and EZb.ally.time) or (EZb.allyCd and EZb.allyCd.time) or (EZb.allyRange and EZb.allyRange.time) or 30 local MZb = Match.buffedTill [BZb.Value] if not MZb then MZb = setmetatable({ }, { __mode = "k" }) Match.buffedTill [BZb.Value] = MZb end local NZb, OZb, PZb, QZb = { }, 0, 0, 0 for RZb, SZb in ipairs(Match.mine) do local TZb = SZb._stats local UZb = TZb.id.Value if SZb ~= zZb and not Units.isFarm(UZb) and not Units.allyBuff(UZb) and (Utils.flat(SZb:GetPivot().Position) - Utils.flat(FZb)).Magnitude <= GZb then local VZb, WZb = Units.types(UZb) if not EZb.ally or EZb.ally.types [WZb] then table.insert(NZb, SZb) if TZb:FindFirstChild("target") and TZb.target.Value ~= nil then local XZb = (MZb [SZb] or 0) >= xZb if EZb.ally then XZb = Match.buffOn(TZb, WZb) or (MZb [SZb] or 0) - LZb + 2 >= xZb end if XZb then PZb = PZb + 1 QZb = math.max(QZb, MZb [SZb] or 0) else OZb = OZb + 1 end end end end end local YZb = Brain.stack [BZb.Value] if YZb and not YZb.stacks and os.time() - (YZb.at or 0) > 3 * 86400 then YZb = nil end local ZZb = EZb.ally and PZb > 0 and Match.buffLevel(EZb.ally.types) or 0 local _Zb = PZb > 0 and EZb.ally and Config.community and ZZb > 0 and ((YZb == nil and QZb - xZb > 5) or (YZb and YZb.stacks and ZZb < (YZb.cap or 1) - 0.02)) if OZb > PZb or _Zb then HZb, IZb = true, string.format("buffs %d of your units that are fighting", OZb + PZb) if _Zb and YZb == nil then JZb = { key = BZb.Value, types = EZb.ally.types, before = ZZb } IZb = IZb .. string.format(" (testing whether a 2nd buff adds up: now +%d%%)", math.floor(ZZb * 100 + 0.5)) elseif _Zb then IZb = IZb .. string.format(" (chaining: now +%d%%)", math.floor(ZZb * 100 + 0.5)) end for a_b, b_b in ipairs(NZb) do MZb [b_b] = xZb + LZb end end elseif EZb.baseSafe then for c_b, d_b in ipairs(Match.enemies) do if d_b.prog >= 0.98 then HZb, IZb = true, "an enemy is at the base" break end end else local e_b, f_b = 0, false for g_b, h_b in ipairs(Match.enemies) do if (Utils.flat(h_b.pos) - Utils.flat(FZb)).Magnitude <= GZb then e_b = e_b + 1 if h_b.boss then f_b = true end end end local i_b = xZb - Match.readySince [zZb] local j_b = math.min(Config.abilityHold * math.max(1, CZb.Value / 60), CZb.Value * 0.3) local k_b = math.max(3, math.floor(# Match.enemies * 0.3)) if Match.trialId == AZb.id.Value and (tonumber((Core.Units [Match.trialId] or { }).damage) or 0) <= 0 and # Match.enemies > 0 then HZb, IZb = true, "on trial" elseif e_b > 0 and f_b then HZb, IZb = true, "a boss is in range" elseif e_b >= k_b then HZb, IZb = true, e_b .. " enemies in range" elseif e_b >= 3 and i_b >= j_b * 0.5 then HZb, IZb = true, string.format("%d enemies in range after %ds", e_b, math.floor(i_b)) elseif e_b >= 1 and i_b >= j_b then HZb, IZb = true, string.format("held %ds for a crowd, firing on %d", math.floor(i_b), e_b) end end if HZb then Match.activeBusy = true Match.readySince [zZb] = nil task.spawn(function() local l_b = App.request("active", "use_active_attack", zZb) if l_b then Match.casts = Match.casts or { } Match.casts [AZb.id.Value] = (Match.casts [AZb.id.Value] or 0) + 1 App.status("Abilities", string.format("%s used its ability โ€” %s", Units.name(AZb.id.Value), IZb)) App.log(2, "ABILITY", Units.name(AZb.id.Value) .. ": " .. IZb) if EZb.ally then task.delay(2, function() if not App.alive then return end local m_b = Match.buffSeen [AZb.id.Value] or { casts = 0, levels = { } } Match.buffSeen [AZb.id.Value] = m_b m_b.casts = m_b.casts + 1 if # m_b.levels < 12 then table.insert(m_b.levels, math.floor(Match.buffLevel(EZb.ally.types) * 100 + 0.5)) end end) end if JZb then task.wait(2) local n_b = Match.buffLevel(JZb.types) local o_b = n_b > JZb.before + 0.05 Brain.stack [JZb.key] = { stacks = o_b, cap = o_b and 1 or nil, before = JZb.before, after = n_b, at = os.time() } App.log(1, "ABILITY", string.format("%s's buff %s: +%d%% โ†’ +%d%% with a second copy", Units.name(AZb.id.Value), o_b and "adds up (chaining it from now on)" or "doesn't add up (copies take turns)", math.floor(JZb.before * 100 + 0.5), math.floor(n_b * 100 + 0.5))) App.saveSoon() end end Match.activeBusy = false end) return end end end end end Match.prioAt = setmetatable({ }, { __mode = "k" }) function Match.targeting() for p_b, q_b in ipairs(Match.trainee and Match.mine or { }) do local r_b = q_b._stats local s_b = r_b:FindFirstChild("priority") if s_b and r_b:FindFirstChild("uuid") and r_b.uuid.Value == Match.trainee and s_b.Value ~= "last" and Utils.now() - (Match.prioAt [q_b] or 0) > 8 then Match.prioAt [q_b] = Utils.now() if App.request("prio", "change_priority", q_b, "last") then App.log(2, "TARGET", Units.name(r_b.id.Value) .. " โ†’ last (takedown training)") end return end end for t_b, u_b in ipairs(Config.community and Match.mine or { }) do local v_b = u_b._stats local w_b = Core.TIPS [v_b.id.Value] local x_b = v_b:FindFirstChild("priority") if w_b and w_b.target and x_b and x_b.Value ~= w_b.target and Utils.now() - (Match.prioAt [u_b] or 0) > 8 then Match.prioAt [u_b] = Utils.now() if App.request("prio", "change_priority", u_b, w_b.target) then App.log(2, "TARGET", Units.name(v_b.id.Value) .. " โ†’ " .. w_b.target .. " (" .. w_b.why .. ")") end return end end local y_b = false for z_b, A_b in ipairs(Match.enemies or { }) do if A_b.fly then y_b = true break end end for B_b, C_b in ipairs(y_b and Match.mine or { }) do local D_b = C_b._stats local E_b = D_b:FindFirstChild("priority") local F_b = Config.community and Core.TIPS [D_b.id.Value] and Core.TIPS [D_b.id.Value].target if E_b and E_b.Value ~= "flying" and not F_b and Match.canAir(C_b) and not Units.isFarm(D_b.id.Value) and not Units.allyBuff(D_b.id.Value) and Utils.now() - (Match.prioAt [C_b] or 0) > 8 then local G_b, H_b = pcall(Core.USC.get_valid_priorities, C_b) if G_b and type(H_b) == "table" and H_b.flying then Match.prioAt [C_b] = Utils.now() if App.request("prio", "change_priority", C_b, "flying") then App.log(2, "TARGET", Units.name(D_b.id.Value) .. " โ†’ flying (a flyer is on the road)") end return end end end if not Config.smartTarget then return end local I_b = false for J_b, K_b in ipairs(Match.enemies) do if K_b.boss then I_b = true break end end for L_b, M_b in ipairs(Match.mine) do local N_b = M_b._stats local O_b = N_b.id.Value local P_b = N_b:FindFirstChild("priority") local Q_b = Match.trainee and N_b:FindFirstChild("uuid") and N_b.uuid.Value == Match.trainee local R_b = Config.community and Core.TIPS [O_b] and Core.TIPS [O_b].target local S_b = y_b and Match.canAir(M_b) if P_b and not Q_b and not R_b and not S_b and not Units.isFarm(O_b) and not Units.allyBuff(O_b) then local T_b = N_b:FindFirstChild("primary_attack") and N_b.primary_attack.Value or nil local U_b = Units.attackInfo(T_b, N_b.range.Value) local V_b = U_b.n <= 1.4 and U_b.slow == 0 and U_b.stun == 0 local W_b = (V_b and I_b) and "strongest" or "first" if P_b.Value ~= W_b and Utils.now() - (Match.prioAt [M_b] or 0) > 8 then local X_b, Y_b = pcall(Core.USC.get_valid_priorities, M_b) if X_b and type(Y_b) == "table" and Y_b [W_b] then Match.prioAt [M_b] = Utils.now() local Z_b = App.request("prio", "change_priority", M_b, W_b) if Z_b then App.status("Targeting", string.format("%s โ†’ %s (%s)", Units.name(O_b), W_b, W_b == "strongest" and "boss on the road" or (V_b and "no boss now" or "holds the front"))) App.log(2, "TARGET", Units.name(O_b) .. " โ†’ " .. W_b) end return end end end end end Hunt.CARD_PICKS = { "Secret Luck", "Gems", "Easiest Win", "Smart" } function Hunt.cardLuck(__b) local a0b = 0 for b0b, c0b in pairs(type(__b) == "table" and type(__b.effects) == "table" and __b.effects or { }) do if type(c0b) == "table" and c0b.type == "secret_drop" then a0b = a0b + (tonumber(c0b.amount) or 0) end end return a0b end function Match.huntChoose(d0b, e0b) Match.fhP = Match.fhP or 1 local f0b = Hunt.floor() local g0b = (Match.pressure or 0) >= Config.safeReach or (Match.depth and Match.depth.leak) local h0b = Hunt.weatherStrength(Hunt.weatherNow()) local i0b = table.find(Hunt.CARD_PICKS, Config.huntCards) and Config.huntCards or "Smart" local j0b, k0b, l0b, m0b local n0b = { } for o0b, p0b in pairs(d0b) do local q0b, r0b = 1, { } for s0b, t0b in pairs(type(p0b) == "table" and p0b.effects or { }) do if type(t0b) == "table" then local u0b = Match.rogueEffect(t0b) q0b = q0b * u0b table.insert(r0b, Match.effectText(t0b)) end end if type(p0b) == "table" and p0b.weather then q0b = q0b * Hunt.weatherStrength(p0b.weather) / math.max(0.05, h0b) end local v0b = type(p0b) == "table" and Hunt.cardGems(p0b, e0b) or 0 local w0b = q0b >= 1 or (Match.fhP * q0b >= f0b and not g0b) local x0b = math.log(math.max(0.05, q0b)) local y0b = x0b * Hunt.STRENGTH_GEMS + (w0b and v0b or - 1000) local z0b = Hunt.cardLuck(p0b) if i0b == "Secret Luck" and w0b and z0b > 0 then y0b = 2e6 + z0b * 1e4 + v0b elseif i0b == "Gems" and w0b then y0b = 1e6 + v0b + x0b elseif i0b == "Easiest Win" then y0b = x0b * 1e6 + v0b end if not k0b or y0b > k0b then j0b, k0b, l0b, m0b = o0b, y0b, q0b, v0b end table.insert(n0b, string.format("%s) %s%s [%s] ร—%.2f, %d gems: %s", tostring(o0b), tostring(type(p0b) == "table" and p0b.title or "?"), (type(p0b) == "table" and p0b.weather) and (" (" .. tostring(p0b.weather) .. ")") or "", tostring(type(p0b) == "table" and p0b.option_type or "?"), q0b, math.floor(v0b + 0.5), table.concat(r0b, " + "))) end return j0b, l0b, m0b, n0b, f0b, g0b, i0b end function Match.huntPick(A0b) local B0b = Match.wave() local C0b, D0b, E0b, F0b, G0b, H0b, I0b = Match.huntChoose(A0b, B0b) Match.fhP = Match.fhP * (D0b or 1) Match.rogueOptions = nil local J0b, K0b = pcall(HttpService.JSONEncode, HttpService, Hunt.plain(A0b)) if J0b then Hunt.write("hunt_cards.txt", string.format("%s wave %d map %s weather %s: %s", os.date("%m-%d %H:%M"), B0b, tostring(Match.ld.map), tostring(Hunt.weatherNow()), K0b)) end local L0b = App.request("rogue", "select_roguelike_option", C0b) local M0b = C0b and Hunt.cardLuck(A0b [C0b]) or 0 App.log(1, "ROGUE", string.format("wave %d ยท picked %s by %s (worth %d gems%s, strength now ร—%.2f, floor ร—%.2f%s) of: %s", B0b, tostring(C0b), tostring(I0b), math.floor((E0b or 0) + 0.5), M0b > 0 and string.format(", secret luck +%g", M0b) or "", Match.fhP, G0b, H0b and ", under pressure" or "", table.concat(F0b, " | "))) return L0b end function Match.huntRecord(N0b, O0b, P0b, Q0b) local R0b, S0b = pcall(Hunt.starfallRecord) Match.starSummary = R0b and S0b or nil local T0b = Hunt.floor() Brain.hunt.pmin = Utils.clamp(T0b + (P0b and - 0.1 or 0.1), 0.4, 2) Brain.hunt.runs = Brain.hunt.runs or { } table.insert(Brain.hunt.runs, 1, { at = os.time(), wave = Match.wave(), won = P0b or nil, pearls = O0b, mult = Match.fhMult or 1, strength = Match.fhP or 1, floor = T0b, map = Match.ld.map, weather = Hunt.weatherNow(), sec = Q0b, picks = Match.fhPicks }) while # Brain.hunt.runs > 20 do table.remove(Brain.hunt.runs) end local U0b = Hunt.board() App.log(1, "HUNT", string.format("%s at wave %d in %.0f min: +%d Summer Pearls at ร—%.2f ยท map %s, weather %s%s", P0b and "won" or "lost", Match.wave(), (tonumber(Q0b) or 0) / 60, O0b, Match.fhMult or 1, tostring(Match.ld.map), tostring(Hunt.weatherNow()), U0b and string.format(" ยท bingo %d of %d squares", U0b.squares, # U0b.board) or "")) local V0b = { } for W0b, X0b in ipairs({ "victory", "resource_reward", "gem_reward", "new_items", "new_units", "waves_completed", "first_completion_gems", "party_bonus" }) do if type(N0b) == "table" then V0b [X0b] = N0b [X0b] end end local Y0b = { } for Z0b in pairs(type(N0b) == "table" and N0b or { }) do table.insert(Y0b, tostring(Z0b)) end table.sort(Y0b) local _0b, a1b = pcall(HttpService.JSONEncode, HttpService, Hunt.plain(V0b)) if _0b then Hunt.write("hunt_results.txt", os.date("%m-%d %H:%M ") .. a1b .. " ยท fields: " .. table.concat(Y0b, ", ")) end end function Match.pickRogue() local b1b = Match.rogueOptions if b1b and Match.hunt then return Match.huntPick(b1b) end if not b1b then local c1b = App.request("rogue", "select_roguelike_option", 1) return c1b end Match.fhP = Match.fhP or 1 local d1b = tonumber(Brain.fh.pmin) or 1 local e1b = (Match.pressure or 0) >= Config.safeReach or (Match.depth and Match.depth.leak) local f1b, g1b, h1b for i1b, j1b in pairs(b1b) do local k1b, l1b = 1, 0 for m1b, n1b in pairs(type(j1b) == "table" and j1b.effects or { }) do if type(n1b) == "table" then local o1b, p1b = Match.rogueEffect(n1b) k1b = k1b * o1b l1b = l1b + p1b end end local q1b = math.log(math.max(0.05, k1b)) * 10 if l1b > 0 and Match.fhP * k1b >= d1b and not e1b then q1b = q1b + 100 * l1b elseif l1b > 0 then q1b = q1b - 50 + l1b end if not g1b or q1b > g1b then f1b, g1b, h1b = i1b, q1b, k1b end end Match.fhP = Match.fhP * (h1b or 1) local r1b = { } for s1b, t1b in pairs(b1b) do local u1b = { } for v1b, w1b in pairs(type(t1b) == "table" and t1b.effects or { }) do if type(w1b) == "table" then table.insert(u1b, Match.effectText(w1b)) end end table.insert(r1b, tostring(s1b) .. ") " .. table.concat(u1b, " + ")) end Match.rogueOptions = nil local x1b = App.request("rogue", "select_roguelike_option", f1b) App.log(1, "ROGUE", string.format("wave %d ยท picked %s (score %.1f, strength now ร—%.2f, floor ร—%.2f) of: %s", Match.wave(), tostring(f1b), g1b or 0, Match.fhP, d1b, table.concat(r1b, " | "))) return x1b end function Match.rogueShare(y1b) if not y1b then return 0.5 end local z1b = tostring(y1b):gsub("_damage$", "") local A1b, B1b = 0, 0 for C1b, D1b in ipairs(Match.mine or { }) do local E1b = D1b._stats.id.Value if not Units.isFarm(E1b) then local F1b, G1b = pcall(Match.liveDps, D1b, E1b) G1b = F1b and G1b or 0 local H1b = Units.types(E1b) A1b = A1b + G1b if H1b [z1b] or H1b [z1b .. "_damage"] then B1b = B1b + G1b end end end local I1b = 0 for J1b, K1b in ipairs(Match.mine or { }) do if not Units.isFarm(K1b._stats.id.Value) then I1b = I1b + 1 end end local L1b = I1b / (I1b + 4) return L1b * (A1b > 0 and B1b / A1b or 0.5) + (1 - L1b) * 0.5 end function Match.resistOf(M1b) local N1b = Units.types(M1b) local O1b = 0 for P1b, Q1b in pairs(Match.fhResist or { }) do local R1b = tostring(P1b):gsub("_damage$", "") if N1b [R1b] or N1b [R1b .. "_damage"] then O1b = math.max(O1b, Q1b / 100) end end return math.min(0.95, O1b) end function Match.rogueEffect(S1b) local T1b, U1b, V1b = S1b.type, tonumber(S1b.amount) or 0, tostring(S1b.stat or "") if T1b == "candy_multiplier" then return 1, math.abs(U1b) end if T1b == "stat_mul" or T1b == "stat_add" or T1b == "stat_attr_add" then if V1b == "damage" then return math.max(0.3, 1 + U1b), 0 end if V1b == "range" then return math.max(0.5, 1 + U1b / 2), 0 end if V1b:find("cooldown") then return 1 / math.max(0.3, 1 + U1b), 0 end return 1, 0 end if T1b == "stat_mul_real" then if V1b == "damage" then return math.max(0.3, U1b), 0 end if V1b == "range" then return math.max(0.5, 1 + (U1b - 1) / 2), 0 end if V1b:find("cooldown") then return 1 / math.max(0.3, U1b), 0 end return 1, 0 end if T1b == "boss_damage" then return 1 + U1b / 3, 0 end if T1b == "enemy_stat_mul" then return 1 / math.max(0.3, 1 + U1b), 0 end if T1b == "enemy_resistance_specific" then return math.max(0.2, 1 - Match.rogueShare(S1b.damage_type) * (1 - 100 / (100 + U1b))), 0 end if T1b == "enemy_amp_specific" then return 1 + Match.rogueShare(S1b.damage_type) * U1b, 0 end if T1b == "weather_flying_chance" then return math.max(0.5, 1 - U1b * 0.5), 0 end if T1b == "weather_soaring_share" then return math.max(0.5, 1 - U1b * 0.2), 0 end if T1b == "flying_damage" then return 1 + U1b * 0.1, 0 end if T1b == "burst_speed" then return 1 / math.max(0.3, 1 + U1b * 0.5), 0 end if T1b == "lightning_strikes" then return math.max(0.5, 1 - U1b * 0.1), 0 end if T1b == "enemy_speedup" then return 1 / math.max(0.3, 1 + U1b), 0 end if T1b == "enemy_regen" then return math.max(0.3, 1 - U1b * 20), 0 end if T1b == "enemy_shield" then return math.max(0.5, 1 - U1b * 0.03), 0 end if T1b == "enemy_bomb" then return 0.95, 0 end if T1b == "enemy_passive_chance" then return math.max(0.5, 1 - U1b), 0 end if T1b == "resource_gain" then return 1 + math.min(0.3, U1b / 40000), 0 end if T1b == "unit_cost" then return 1 / math.max(0.3, 1 + U1b / 2), 0 end if T1b == "new_path" then return 0.8, 0 end if T1b == "random_effects" then local W1b = math.max(1, math.abs(U1b)) * math.max(1, math.abs(tonumber(S1b.tier) or 1)) return S1b._type == "Good" and 1.1 ^ W1b or 0.8 ^ W1b, 0 end return 1, 0 end function Match.effectText(X1b) return string.format("%s%s %s", tostring(X1b.type), X1b.stat and ("." .. tostring(X1b.stat)) or "", tostring(X1b.amount)) end function Match.learn() local Y1b, Z1b = { }, { } local _1b, a2b = 0, 0 for b2b, c2b in ipairs(Match.mine) do local d2b = c2b._stats local e2b = d2b.id.Value local f2b = d2b:FindFirstChild("total_damage") and d2b.total_damage.Value or 0 if not Units.isFarm(e2b) then Y1b [e2b] = (Y1b [e2b] or 0) + f2b Z1b [e2b] = (Z1b [e2b] or 0) + Match.liveDps(c2b, e2b) / math.max(0.5, Units.nudge(e2b)) local g2b = Units.passive(e2b).mark if g2b then Z1b [e2b] = Z1b [e2b] + g2b.dot * d2b.damage.Value end _1b = _1b + f2b end end for h2b, i2b in pairs(Z1b) do a2b = a2b + i2b end if _1b <= 0 or a2b <= 0 then return end Match.lastEff = { } for j2b, k2b in pairs(Y1b) do local l2b, m2b = k2b / _1b, Z1b [j2b] / a2b if m2b > 0.02 then local n2b = Brain.learn [j2b] or { eff = 1, n = 0 } local o2b = math.max(0.02, 0.25 * math.sqrt(30 / (30 + n2b.n))) local p2b = Utils.clamp(l2b / m2b, 0.3, 3) Match.lastEff [j2b] = math.floor(p2b * 1000 + 0.5) / 1000 n2b.eff = n2b.eff + (p2b - n2b.eff) * o2b n2b.n = n2b.n + 1 Brain.learn [j2b] = n2b end end end function Match.randomTeam() local q2b = Core.session() local r2b = q2b and q2b.collection.temporary_units or { } local s2b, t2b = { }, { } local function u2b(v2b, w2b, x2b) local y2b = v2b and r2b [v2b] if y2b and Core.Units [y2b.unit_id] and not t2b [v2b] then t2b [v2b] = true table.insert(s2b, { slot = x2b or 99, uuid = v2b, u = y2b, id = y2b.unit_id, data = Core.Units [y2b.unit_id], hand = w2b }) end end for z2b, A2b in pairs(q2b and q2b.override_equipped_units or { }) do u2b(A2b, true, tonumber(z2b)) end for B2b, C2b in ipairs(Match.mine or { }) do local D2b = C2b._stats u2b(D2b:FindFirstChild("uuid") and D2b.uuid.Value, false) end table.sort(s2b, function(E2b, F2b) return E2b.slot < F2b.slot end) return s2b end function Match.fhSnap() local G2b, H2b = nil, nil pcall(function() for I2b, J2b in ipairs(workspace._BASES:GetDescendants()) do if J2b:IsA("Humanoid") then G2b, H2b = J2b.Health, J2b.MaxHealth break end end end) local K2b = G2b and H2b and H2b > 0 and math.floor(G2b / H2b * 100) or - 1 local L2b, M2b, N2b = 0, { }, { } for O2b, P2b in ipairs(Match.enemies or { }) do if (P2b.prog or 0) > 0.8 then L2b = L2b + 1 M2b [tostring(P2b.m)] = (M2b [tostring(P2b.m)] or 0) + 1 end N2b [tostring(P2b.m)] = (N2b [tostring(P2b.m)] or 0) + 1 end if not next(M2b) then M2b = N2b end local Q2b = 0 for R2b, S2b in ipairs(Match.mine or { }) do local T2b, U2b = pcall(Match.liveDps, S2b, S2b._stats.id.Value) if T2b and U2b then Q2b = Q2b + U2b end end Match.fhWaves = Match.fhWaves or { } local V2b = 0 for W2b, X2b in ipairs(Match.enemies or { }) do if X2b.fly then V2b = V2b + 1 end end table.insert(Match.fhWaves, string.format("%d:%d:%d:%d:%s:%d:%.2f:%s:air%d%%:fly%d", Match.wave(), K2b, # (Match.enemies or { }), L2b, Utils.fmt(Q2b), Match.fieldCount(), Match.fhMult or 1, Utils.fmt(Match.money()), math.floor(Match.airShare() * 100 + 0.5), V2b)) if K2b >= 0 and Match.fhLastPct and K2b < Match.fhLastPct and not Match.fhLeak then local Y2b = { } for Z2b, _2b in pairs(M2b) do table.insert(Y2b, Units.name(Z2b) .. "ร—" .. _2b) end Match.fhLeak = string.format("wave %d: base %d%% โ†’ %d%%, near it: %s", Match.wave(), Match.fhLastPct, K2b, # Y2b > 0 and table.concat(Y2b, ", ") or "nothing seen") App.log(1, "MATCH", "first damage to the base โ€” " .. Match.fhLeak) end Match.fhLastPct = K2b end function Match.fhRecord(a3b, b3b) local c3b, d3b = { }, 0 for e3b, f3b in ipairs(Match.mine or { }) do local g3b = f3b._stats local h3b = g3b:FindFirstChild("total_damage") and tonumber(g3b.total_damage.Value) or 0 local i3b = g3b.id.Value if h3b > 0 then c3b [i3b] = (c3b [i3b] or 0) + h3b d3b = d3b + h3b end end local j3b = { } for k3b, l3b in pairs(c3b) do local m3b = d3b > 0 and l3b / d3b or 0 local n3b = Brain.fh.units [k3b] or { n = 0, share = 0 } n3b.share = (n3b.share * n3b.n + m3b) / (n3b.n + 1) n3b.n = n3b.n + 1 Brain.fh.units [k3b] = n3b table.insert(j3b, { id = k3b, share = m3b }) end table.sort(j3b, function(o3b, p3b) return o3b.share > p3b.share end) local q3b = { } for r3b = 1, math.min(4, # j3b) do table.insert(q3b, string.format("%s %d%%", Units.name(j3b [r3b].id), math.floor(j3b [r3b].share * 100 + 0.5))) end pcall(Match.fhSnap) local s3b = tonumber(Brain.fh.pmin) or 1 Brain.fh.pmin = Utils.clamp(s3b * (b3b and 0.9 or 1.1), 0.5, 2.5) table.insert(Brain.fh.runs, 1, { at = os.time(), wave = Match.wave(), won = b3b or nil, coins = a3b, top = q3b, mult = Match.fhMult or 1, discards = Match.discards, strength = Match.fhP, floor = s3b, effects = Match.fhEffects, picks = Match.fhPicks, leak = Match.fhLeak, waves = Match.fhWaves }) while # Brain.fh.runs > 20 do table.remove(Brain.fh.runs) end App.log(1, "RESULT", string.format("dealt team reached wave %d; most damage: %s", Match.wave(), table.concat(q3b, ", "))) end function Match.carry(t3b) local u3b = Brain.fh.units [t3b] return u3b and u3b.n > 0 and u3b.share or 0 end function Match.fhText() local v3b = Brain.fh.runs if # v3b == 0 then return "No Fool's Hunt run recorded yet." end local w3b, x3b = 0, 0 for y3b, z3b in ipairs(v3b) do w3b = w3b + (z3b.wave or 0) x3b = x3b + (z3b.coins or 0) end local A3b = { } for B3b, C3b in pairs(Brain.fh.units) do if C3b.n >= 2 then table.insert(A3b, { id = B3b, share = C3b.share, n = C3b.n }) end end table.sort(A3b, function(D3b, E3b) return D3b.share > E3b.share end) local F3b = { } for G3b = 1, math.min(6, # A3b) do table.insert(F3b, string.format("%s %d%% (%d runs)", Units.name(A3b [G3b].id), math.floor(A3b [G3b].share * 100 + 0.5), A3b [G3b].n)) end local H3b = 0 for I3b in pairs(Brain.unitData) do H3b = H3b + 1 end local J3b = 0 for K3b, L3b in ipairs(v3b) do if L3b.won then J3b = J3b + 1 end end local M3b = v3b [1] local N3b, O3b = Brain.fh.merge, Brain.fh.idol local P3b = "" if N3b and N3b.n > 0 then P3b = P3b .. string.format("\nMerges cast: %d, each gave %.0f%% of what the game's description promises.", N3b.n, N3b.ratio / N3b.n * 100) end if O3b and O3b.n > 0 then P3b = P3b .. string.format("\nIdol: each upgrade adds %.1f%% damage and makes upgrades %.1f%% cheaper (measured %d times).", O3b.dmg / O3b.n * 100, O3b.cost / O3b.n * 100, O3b.n) end local Q3b, R3b = Brain.fh.clone, Brain.fh.ritual if Q3b and Q3b.n > 0 then P3b = P3b .. string.format("\nClones: %d made, each with %.0f%% of the original's damage and %.0f%% of the yen in it.", Q3b.n, Q3b.ratio / Q3b.n * 100, Q3b.spent / Q3b.n * 100) end if R3b and R3b.n > 0 then P3b = P3b .. string.format("\nRitual: %d cast, the Divine General came out at %.1fร— the sacrificed unit's damage.", R3b.n, R3b.ratio / R3b.n) end local S3b = Brain.fh.farmMerge if S3b and S3b.n > 0 then P3b = P3b .. string.format("\nFarm merges: %d tested, each kept %.0f%% of 1.5ร— the income it took in.", S3b.n, S3b.ratio / S3b.n * 100) end return string.format("Fool's Hunt: %d runs recorded (%d won), wave %.0f and %d AprilCoins a run on average. Last run: wave %s, coin multiplier ร—%.2f, team strength ร—%.2f%s; most damage: %s.\n" .. "Coin-multiplier picks need team strength ร—%.2f or more (it goes up after a lost run, down after a won one, until it wins with the highest multiplier).\n" .. "Dealt units that carry (average share of the damage): %s.\nHidden unit stats learned so far: %d units.%s", # v3b, J3b, w3b / # v3b, math.floor(x3b / # v3b + 0.5), tostring(M3b.wave), tonumber(M3b.mult) or 1, tonumber(M3b.strength) or 1, M3b.leak and (" ยท first base hit: " .. M3b.leak) or "", table.concat(M3b.top or { }, ", "), tonumber(Brain.fh.pmin) or 1, # F3b > 0 and table.concat(F3b, " ยท ") or "needs 2 runs with a unit", H3b, P3b) end function Match.cardDps(T3b) local U3b, V3b = pcall(Units.dps, T3b.u, 0, Match.mod) V3b = U3b and tonumber(V3b) or 0 if V3b > 0 or Units.isFarm(T3b.id) or Units.idol(T3b.id) or Units.allyBuff(T3b.id) then return V3b end local W3b = table.clone(Match.cardVals or { }) if # W3b == 0 then return 1000 end table.sort(W3b) return W3b [math.max(1, math.ceil(# W3b * 0.6))] end function Match.cardValue(X3b) if Units.isFarm(X3b.id) then return 0 end if Units.idol(X3b.id) then return (Match.teamDps or 0) * Match.idolStep() * 3 end local Y3b = Units.allyBuff(X3b.id) and Match.buffGain(X3b) or Match.cardDps(X3b) return Y3b * (1 + 2 * Match.carry(X3b.id)) * (1 - Match.resistOf(X3b.id)) * Match.airNeed(X3b.id) * (Match.mergeIds [X3b.id] and 2 or 1) end function Match.flyMode() if Match.random or Match.flyerSeen then return true end if Match.kind == "dungeon" then if Match._curseFly == nil then local Z3b, _3b = pcall(Dungeon.items, Dungeon.data()) Match._curseFly = Z3b and (_3b._curse_fly or 0) > 0 or false end return Match._curseFly end return false end function Match.shieldNow() local a4b = Match.wave() if Match._shieldW ~= a4b then Match._shieldW, Match._shieldS, Match._shieldH = a4b, 0, 1 local b4b, c4b = pcall(Simulator.wave, Match.info, math.max(1, a4b), true) if Match.info and b4b and # c4b > 0 then local d4b, e4b = 0, 0 for f4b, g4b in ipairs(c4b) do d4b, e4b = d4b + (g4b.sh or 0), e4b + g4b.hp end Match._shieldS, Match._shieldH = d4b / # c4b, e4b / # c4b end end return Match._shieldS, Match._shieldH end function Match.shieldOf(h4b, i4b) local j4b, k4b = Match.shieldNow() if j4b <= 0 or not i4b then return h4b end local l4b = Units.attackInfo(i4b.atk, i4b.range) return Economy.shieldDps(h4b, tonumber(i4b.cd) or 2, l4b.n, j4b, k4b, Match.info and Match.info.eff, l4b.hits) end function Match.shieldWork(m4b, n4b) local o4b, p4b = Match.shieldNow() if o4b <= 0 or not n4b or not Match.info then return 0 end local q4b, r4b = 0, 0 for s4b, t4b in ipairs(Match.team or { }) do local u4b = Units.rows(t4b.id) u4b = u4b and u4b [0] if u4b and not Units.isFarm(t4b.id) then q4b = math.max(q4b, Units.dps(t4b.u, 0, Match.mod) * (tonumber(u4b.cd) or 2) / math.max(1, Units.attackInfo(u4b.atk, u4b.range).n)) end end for v4b, w4b in ipairs(Match.mine or { }) do local x4b = w4b._stats.id.Value local y4b = Units.rows(x4b) local z4b = y4b and y4b [w4b._stats.upgrade and w4b._stats.upgrade.Value or 0] if z4b and not Units.isFarm(x4b) and not Units.allyBuff(x4b) then local A4b = Units.attackInfo(z4b.atk, z4b.range) r4b = r4b + A4b.n * A4b.hits / math.max(0.3, tonumber(z4b.cd) or 2) end end local B4b, C4b = pcall(Simulator.wave, Match.info, math.max(1, Match.wave()), true) local D4b = Units.attackInfo(n4b.atk, n4b.range) return Economy.shieldWork(D4b.n, tonumber(n4b.cd) or 2, r4b, o4b, B4b and # C4b or 1, Match.info.waveSec, q4b * (Match.info.eff or 1), p4b, D4b.hits) end function Match.teamShield() if Match._shieldAt and Utils.now() - Match._shieldAt < 2 then return Match._shieldTeam end local E4b, F4b = 0, 0 for G4b, H4b in ipairs(Match.mine or { }) do local I4b = H4b._stats.id.Value if not Units.isFarm(I4b) and H4b._stats:FindFirstChild("upgrade") then local J4b = Units.rows(I4b) local K4b, L4b = pcall(Match.liveDps, H4b, I4b) if K4b and J4b then E4b = E4b + L4b F4b = F4b + Match.shieldOf(L4b, J4b [H4b._stats.upgrade.Value]) end end end Match._shieldTeam, Match._shieldAt = E4b > 0 and math.max(0.05, F4b / E4b) or 1, Utils.now() return Match._shieldTeam end function Match.airLow() if not Match.flyMode() then return false end if FarmHub.every("airShare", 1) or not Match.airShareNow then Match.airShareNow = Match.airShare() end return Match.airShareNow < 0.5 end function Match.airWeight(M4b) if not (M4b and Match.flyMode()) then return 1 end return Match.airLow() and 2.5 or 1.5 end function Match.reach(N4b) if not Match.reachAt or Utils.now() - Match.reachAt > 5 then local O4b, P4b = 0, 0 for Q4b, R4b in ipairs(Match.mine or { }) do local S4b = R4b._stats if not Units.isFarm(S4b.id.Value) and S4b:FindFirstChild("range") then O4b, P4b = O4b + Match.cover(R4b:GetPivot().Position, S4b.range.Value), P4b + 1 end end Match.reachAvg, Match.reachAt = P4b > 0 and O4b / P4b or 0, Utils.now() end local T4b = N4b._stats if (Match.reachAvg or 0) <= 0 or not T4b:FindFirstChild("range") then return 1 end local U4b = Match.reachCache [N4b] if U4b and Utils.now() - U4b.t < 5 then return U4b.v end local V4b = Utils.clamp(Match.cover(N4b:GetPivot().Position, T4b.range.Value) / Match.reachAvg, 0.4, 1.6) Match.reachCache [N4b] = { v = V4b, t = Utils.now() } return V4b end Match.reachCache = setmetatable({ }, { __mode = "k" }) function Match.airNeed(W4b) local X4b = Units.airFrom(W4b) if X4b == 0 then return Match.airWeight(true) end if X4b then return 1 + (Match.airWeight(true) - 1) * 0.4 end return 1 end function Match.airBoost(Y4b) if Match.canAir(Y4b) then return Match.airWeight(true) end local Z4b = Units.airFrom(Y4b._stats.id.Value) if not Z4b then return 1 end return 1 + (Match.airWeight(true) - 1) * math.min(1, (Y4b._stats.upgrade.Value + 1) / math.max(1, Z4b)) end Match.cardSeen, Match.cardVals = { }, { } function Match.fish() if not Match.random then return end local _4b = { } for a5b, b5b in ipairs(Match.team) do if b5b.hand then table.insert(_4b, b5b) if not Match.cardSeen [b5b.uuid] then Match.cardSeen [b5b.uuid] = Utils.now() if not Units.isFarm(b5b.id) and not Units.idol(b5b.id) then table.insert(Match.cardVals, Match.cardValue(b5b)) end end end end if # Match.cardVals < 8 or not FarmHub.every("fish", 5) then return end local c5b = table.clone(Match.cardVals) table.sort(c5b) local d5b = c5b [math.max(1, math.ceil(# c5b * 0.6))] local e5b = 0 for f5b, g5b in ipairs(Match.mine or { }) do if Units.isFarm(g5b._stats.id.Value) then e5b = e5b + 1 end end local h5b = Match.fieldCount() < 16 local i5b = false for j5b, k5b in ipairs(_4b) do local l5b = Units.isFarm(k5b.id) and Match.wave() < 12 and e5b < 3 and not i5b if l5b then i5b = true end local m5b = Match.roomPlan and Match.roomPlan.name == Units.name(k5b.id) local n5b = (Match.noSpot [k5b.uuid] or 0) >= 3 local o5b = not n5b and (l5b or m5b or Units.idol(k5b.id) or Match.mergeIds [k5b.id]) local p5b = Utils.now() - (tonumber(Match.cardSeen [k5b.uuid]) or Utils.now()) > 60 if n5b or (not o5b and Match.cardValue(k5b) < d5b * ((h5b and not p5b) and 0.25 or 1)) then if Match.discardCard(k5b) then App.log(2, "PLACE", string.format("discarded %s (%s) for a new card", Units.name(k5b.id), n5b and "no spot it fits" or ("worth " .. Utils.fmt(Match.cardValue(k5b)) .. ", the bar is " .. Utils.fmt(d5b)))) Match.discards = (Match.discards or 0) + 1 end return end end end function Match.discardCard(q5b) local r5b = tostring(q5b.slot) local s5b = Core.session().override_equipped_units or { } if s5b [r5b] ~= q5b.uuid then return false end if not App.request("discard", "delete_ingame_temporary_unit", r5b) then return false end task.wait(0.3) local t5b = (Core.session().override_equipped_units or { }) [r5b] ~= q5b.uuid if not t5b and FarmHub.every("discardFail", 30) then App.log(1, "PLACE", "the game didn't take the discard of " .. Units.name(q5b.id)) end return t5b end function Match.watchLine() local u5b = Core.session() local v5b, w5b = { }, { } for x5b, y5b in ipairs(Match.team or { }) do if y5b.hand then table.insert(v5b, Units.name(y5b.id) .. " " .. Utils.fmt(Match.cardValue(y5b))) table.insert(w5b, y5b.uuid) end end local z5b = { } for A5b, B5b in pairs((u5b and u5b.ingame_spells) or { }) do table.insert(z5b, Match.SPELL_NAMES [B5b] or tostring(B5b)) end local C5b = table.concat(w5b, ",") if C5b ~= Match.handKey then Match.handKey, Match.handSince = C5b, Utils.now() end local D5b = # w5b > 0 and Utils.now() - Match.handSince >= 90 App.log(1, "WATCH", string.format("wave %d ยท %d/%d units ยท ยฅ%s ยท hand: %s%s ยท merge: %s%s ยท economy: %s%s", Match.wave(), Match.fieldCount(), Match.FIELD_MAX, Utils.fmt(Match.money()), # v5b > 0 and table.concat(v5b, ", ") or "no unit cards", # z5b > 0 and (" + " .. table.concat(z5b, ", ")) or "", tostring(Status.Merge or "-"), Match.cloneNote and (" ยท clone: " .. Match.cloneNote) or "", tostring(Status.Economy or "-"), D5b and " ยท HAND STUCK 90s+" or "")) end Match.FIELD_MAX = 20 Match.SPELL_NAMES = { sell_refund = "Refund", sacrifice = "Merge", clone = "Clone", random_buff = "Random Buff", summon = "Astral Summon", gate = "Astral Gate", ritual = "Ritual" } Match.SPELL_ORDER = { random_buff = 1, clone = 2, sacrifice = 3, ritual = 4, summon = 5, gate = 6 } Match.buffCount = setmetatable({ }, { __mode = "k" }) function Match.summonWorth() if Match.summonAvg == nil then local E5b, F5b, G5b = 0, 0, 0 for H5b, I5b in ipairs(Units.equipped()) do local J5b, K5b = pcall(Units.dps, I5b.u, 0, Match.mod) E5b, F5b, G5b = E5b + ((not Units.isFarm(I5b.id) and J5b and K5b) or 0), F5b + 1, G5b + (I5b.data.cost or 0) end Match.summonAvg, Match.summonCost = F5b > 0 and E5b / F5b or 0, F5b > 0 and G5b / F5b or 0 end local L5b = Match.bestRate or 0 return L5b <= 0 or Match.summonAvg * 2 >= L5b * (10000 + Match.summonCost) end function Match.hasGeneral() for M5b, N5b in ipairs(Match.mine or { }) do if N5b._stats.id.Value == "mahoraga" then return true end end return false end function Match.ritualWorth() local O5b = Brain.fh.ritual return O5b and O5b.n >= 2 and O5b.ratio / O5b.n or nil end function Match.ritualTarget() local P5b = Match.mergePlan local Q5b = { } for R5b, S5b in ipairs(Match.mine) do local T5b = S5b._stats.id.Value if not Units.isFarm(T5b) and not Units.allyBuff(T5b) and not Units.idol(T5b) and not (P5b and P5b.id == T5b) and not (Match.airLow() and Match.canAir(S5b)) then local U5b, V5b = pcall(Match.liveDps, S5b, T5b) if U5b and V5b and V5b > 0 then table.insert(Q5b, { m = S5b, dps = V5b }) end end end if # Q5b < 3 then return nil end table.sort(Q5b, function(W5b, X5b) return W5b.dps > X5b.dps end) local Y5b = Match.ritualWorth() if not Y5b then return Q5b [3].m, "a test: how strong the Divine General comes out of this unit" end if Y5b >= 1.5 then return Q5b [1].m, string.format("the Divine General came out at %.1fร— the unit's damage before", Y5b) end return nil end Match.ADAPT_ORDER = { "Divine Wings", "Adapted Slash", "Heavenly Force" } function Match.adapt() if not getconnections then return end local Z5b = false for _5b, a6b in ipairs(Match.mine or { }) do if a6b._stats.id.Value == "mahoraga" then Z5b = true break end end if not Z5b then return end if not (Match.adaptPrompt and Match.adaptPrompt.Parent) then Match.adaptPrompt = nil local b6b = LocalPlayer:FindFirstChild("PlayerGui") for c6b, d6b in ipairs(b6b and b6b:GetDescendants() or { }) do if d6b.Name == "MahoragaAdaptationPrompt" and not d6b:FindFirstAncestorOfClass("Configuration") then Match.adaptPrompt = d6b break end end end local e6b = Match.adaptPrompt if not (e6b and e6b:IsA("GuiObject") and e6b.Visible) then Match.adaptTries = 0 return end Match.adaptTries = (Match.adaptTries or 0) + 1 if Match.adaptTries > 3 then return end local f6b = { } for g6b, h6b in ipairs(e6b:GetDescendants()) do if h6b:IsA("TextLabel") and h6b.Name == "Mode" and h6b.Parent then f6b [h6b.Text] = h6b.Parent end end for i6b, j6b in ipairs(Match.ADAPT_ORDER) do local k6b = f6b [j6b] local l6b = k6b and (k6b:IsA("GuiButton") and k6b or k6b:FindFirstChildWhichIsA("GuiButton", true)) if l6b and k6b.Visible then local m6b = 0 for n6b, o6b in ipairs({ l6b.Activated, l6b.MouseButton1Click, l6b.MouseButton1Down }) do for p6b, q6b in ipairs(getconnections(o6b)) do if pcall(function() q6b:Fire() end) then m6b = m6b + 1 end end end App.log(1, "SPELL", string.format("Divine General adapts: %s (%d click handler%s, try %d)", j6b, m6b, m6b == 1 and "" or "s", Match.adaptTries)) return end end end function Match.newUnitMeasure(r6b, s6b, t6b, u6b, v6b) task.spawn(function() local w6b for x6b = 1, 10 do task.wait(1) for y6b, z6b in ipairs(workspace._UNITS:GetChildren()) do local A6b = z6b:FindFirstChild("_stats") if not v6b [z6b] and A6b and A6b:FindFirstChild("player") and A6b.player.Value == LocalPlayer and A6b:FindFirstChild("damage") and (r6b == "ritual" or A6b.id.Value == u6b.id) then w6b = z6b break end end if w6b then task.wait(1) break end end if not w6b then App.log(2, "SPELL", (Match.SPELL_NAMES [r6b] or r6b) .. ": couldn't find the new unit to measure") return end local B6b = w6b._stats local C6b, D6b = pcall(Match.liveDps, w6b, B6b.id.Value) D6b = C6b and tonumber(D6b) or 0 local E6b = B6b:FindFirstChild("total_spent") and tonumber(B6b.total_spent.Value) or 0 local F6b = Brain.fh [r6b] or { n = 0, ratio = 0, spent = 0 } if u6b.dps > 0 then F6b.n, F6b.ratio, F6b.spent = F6b.n + 1, F6b.ratio + D6b / u6b.dps, F6b.spent + (u6b.spent > 0 and E6b / u6b.spent or 0) end Brain.fh [r6b] = F6b App.log(1, "SPELL", string.format("%s result: %s at level %s does %s damage a second (%s had %s at level %d) ยท ยฅ%s in it (%s had ยฅ%s) ยท %.0f studs away", Match.SPELL_NAMES [r6b] or r6b, Units.name(B6b.id.Value), tostring(B6b:FindFirstChild("upgrade") and B6b.upgrade.Value or "?"), Utils.fmt(D6b), t6b, Utils.fmt(u6b.dps), u6b.lvl, Utils.fmt(E6b), t6b, Utils.fmt(u6b.spent), (Utils.flat(w6b:GetPivot().Position) - Utils.flat(u6b.pos)).Magnitude)) end) end function Match.fieldCount() local G6b = 0 for H6b, I6b in ipairs(Match.mine or { }) do local J6b = Core.Units [I6b._stats.id.Value] if J6b and (tonumber(J6b.cost) or 0) > 0 then G6b = G6b + 1 end end return G6b end function Match.makeRoom() local K6b = Core.session() if not (Match.random and K6b) or Match.spellBusy then return end local L6b = { } for M6b, N6b in pairs(K6b.ingame_spells or { }) do L6b [N6b] = (L6b [N6b] or 0) + 1 local O6b = Match.ritualWorth() local P6b = N6b == "ritual" and ((O6b and O6b < 1.5) and 0 or 1) or (N6b == "sacrifice" and Match.mergePlan) and 2 or 1 if (N6b == "summon" or N6b == "gate") and not Match.summonWorth() then P6b = 0 end if N6b == "ritual" and Match.hasGeneral() then P6b = 0 end if N6b == "sell_refund" and Match.wave() < 22 and Match.fieldCount() < 18 then P6b = 0 end if L6b [N6b] > P6b then if App.request("discard", "delete_ingame_spell", M6b) and (task.wait(0.3) and (Core.session().ingame_spells or { }) [M6b] ~= N6b) then local Q6b = P6b > 0 and "one is enough in the hand" or N6b == "ritual" and (Match.hasGeneral() and "a Divine General is already on the field" or "the Divine General came out too weak") or N6b == "sell_refund" and "Refund is for late waves" or "not worth its price now" App.log(1, "SPELL", string.format("discarded %s (%s) for a new card", Match.SPELL_NAMES [N6b] or tostring(N6b), Q6b)) end return end end if Match.fieldCount() < Match.FIELD_MAX or Utils.now() - (Match.roomSoldAt or - 99) < 15 then return end local R6b, S6b for T6b, U6b in ipairs(Match.team) do if U6b.hand and not Units.isFarm(U6b.id) and (Match.noSpot [U6b.uuid] or 0) < 3 then local V6b = Match.cardValue(U6b) if not S6b or V6b > S6b then R6b, S6b = U6b, V6b end end end local W6b = math.max(0, (Match.total or 30) - Match.wave()) local X6b = Match.mergePlan local Y6b, Z6b if R6b and (tonumber(R6b.data.override_hitbox_radius) or 0) > 3 and not Match.bestSpot(R6b, true, false) then Match.noSpot [R6b.uuid] = 3 R6b = nil end local _6b = Match.airLow() for a7b, b7b in ipairs(Match.mine) do local c7b = b7b._stats.id.Value local d7b = Core.Units [c7b] if d7b and (tonumber(d7b.cost) or 0) > 0 and not d7b.unsellable and not Units.allyBuff(c7b) and not Units.idol(c7b) and not (X6b and X6b.id == c7b) and not (_6b and Match.canAir(b7b)) then local e7b if Units.isFarm(c7b) then local f7b = b7b._stats:FindFirstChild("farm_amount") and tonumber(b7b._stats.farm_amount.Value) or 0 e7b = (Match.bestRate or 0) > 0 and f7b * W6b * Match.bestRate or math.huge else local g7b, h7b = pcall(Match.liveDps, b7b, c7b) e7b = g7b and h7b or math.huge end if not Z6b or e7b < Z6b then Y6b, Z6b = b7b, e7b end end end Match.roomPlan = nil if R6b and Y6b and S6b > 2 * Z6b then local i7b for j7b, k7b in pairs(K6b.ingame_spells or { }) do if k7b == "sell_refund" then i7b = j7b end end local l7b = Y6b._stats:FindFirstChild("total_spent") and tonumber(Y6b._stats.total_spent.Value) or 0 local m7b = (R6b.data.cost or 0) - l7b * (i7b and 1 or 0.5) local n7b = Units.name(Y6b._stats.id.Value) if Match.money() >= m7b then local o7b = i7b and App.request("spell", "use_ingame_spell", i7b, Y6b) or (not i7b and App.request("sell", "sell_unit_ingame", Y6b)) if o7b then App.log(1, "PLACE", string.format("made room: sold %s%s for %s (2ร— the damage or more)", n7b, i7b and " (full refund)" or "", Units.name(R6b.id))) Match.roomSoldAt = Utils.now() task.wait(0.6) Match.scan() Match.place(R6b, "the spot just made for it") end else Match.roomPlan = { name = Units.name(R6b.id), out = n7b, need = m7b } end return end if not FarmHub.every("discard", 20) then return end local p7b, q7b for r7b, s7b in ipairs(Match.team) do if s7b.hand and not Units.idol(s7b.id) and not Match.mergeIds [s7b.id] then local t7b = Units.isFarm(s7b.id) and 0 or Match.cardDps(s7b) if not q7b or t7b < q7b then p7b, q7b = s7b, t7b end end end if p7b and Match.discardCard(p7b) then App.log(2, "PLACE", "discarded " .. Units.name(p7b.id) .. " from the hand (the field is full, it isn't worth a spot)") end end Match.upRate = setmetatable({ }, { __mode = "k" }) function Match.refund() local u7b = Core.session() if not (Match.random and u7b) or Match.spellBusy or Match.roomPlan or not FarmHub.every("refund", 20) then return end local v7b for w7b, x7b in pairs(u7b.ingame_spells or { }) do if x7b == "sell_refund" then v7b = w7b end end if not v7b then return end local y7b = (Match.pressure or 0) >= math.min(Config.dangerReach, ((Match.depth and Match.depth.units > 0 and Match.depth.covEnd) or 1) * 0.9) local z7b = Match.bestRate or 0 if y7b or z7b <= 0 then return end local A7b = Match.wave() local B7b = math.max(0, (Match.total or 30) - A7b) local C7b = Match.mergePlan local D7b = Match.airLow() local E7b, F7b, G7b for H7b, I7b in ipairs(Match.mine) do local J7b = I7b._stats local K7b = J7b.id.Value local L7b = Core.Units [K7b] local M7b = J7b:FindFirstChild("total_spent") and tonumber(J7b.total_spent.Value) or 0 if L7b and not L7b.unsellable and M7b > 0 and not Units.allyBuff(K7b) and not Units.idol(K7b) and not (C7b and C7b.id == K7b) then local N7b, O7b if Units.isFarm(K7b) then local P7b = J7b:FindFirstChild("farm_amount") and tonumber(J7b.farm_amount.Value) or 0 local Q7b = P7b * B7b if M7b > Q7b * 1.2 then N7b = (M7b - Q7b) * z7b O7b = string.format("a farm that still pays ยฅ%s but gives back ยฅ%s now", Utils.fmt(Q7b), Utils.fmt(M7b)) end elseif A7b >= 10 and B7b > 2 and M7b >= 5000 and (Match.upRate [I7b] or 0) < z7b / 3 and Match.progressAt(I7b:GetPivot().Position) < 0.8 and not (D7b and Match.canAir(I7b)) then local R7b, S7b = pcall(Match.liveDps, I7b, K7b) S7b = R7b and S7b or math.huge if M7b * z7b > 3 * S7b and S7b < 0.1 * (Match.teamDps or 0) then N7b = M7b * z7b - S7b O7b = string.format("its ยฅ%s buys over 3ร— its damage in upgrades", Utils.fmt(M7b)) end end if N7b and (not F7b or N7b > F7b) then E7b, F7b, G7b = I7b, N7b, O7b end end end if not E7b then return end Match.spellBusy = true task.spawn(function() local T7b = Units.name(E7b._stats.id.Value) local U7b = App.request("spell", "use_ingame_spell", v7b, E7b) task.wait(1.5) if U7b and (Core.session().ingame_spells or { }) [v7b] ~= "sell_refund" then Match.refunds = (Match.refunds or 0) + 1 App.log(1, "SPELL", string.format("Refund on %s โ€” %s", T7b, G7b)) App.status("Abilities", "Refund on " .. T7b) else App.backoff("spell", 15) App.log(2, "SPELL", "Refund wasn't taken on " .. T7b) end Match.spellBusy = false end) end function Match.bossStep() Match.focus = nil if Match.random or Match.infinite or not (Match.total and Match.wave() >= Match.total) then return end local V7b = Match.enemies or { } for W7b, X7b in ipairs(V7b) do local Y7b = X7b.m and X7b.m:FindFirstChildOfClass("Humanoid") if Y7b and Y7b.MaxHealth > ((Match.bossTop and Match.bossTop.hp) or 0) then local Z7b = X7b.m:FindFirstChild("_stats") Match.bossTop = { hp = Y7b.MaxHealth, id = Z7b and Z7b:FindFirstChild("id") and Z7b.id.Value, regen = Z7b and Z7b:FindFirstChild("Regen") and Z7b.Regen.Value } end end if # V7b <= 2 and Match.bossTop and not Match.bossSaid then Match.bossSaid = true pcall(function() local _7b = 0 if Match.info then for a8b, b8b in ipairs((Simulator.wave(Match.info, Match.total))) do if b8b.boss and b8b.hp > _7b then _7b = b8b.hp end end end App.log(1, "MATCH", string.format("the boss: %s, %.3g HP, heals %s%% a second ยท the simulator plays one of %.3g HP", tostring(Match.bossTop.id), Match.bossTop.hp, tostring(Match.bossTop.regen), _7b)) end) end if # V7b == 0 or # V7b > 2 then Match.focusTrack = nil return end local c8b, d8b = nil, 1 for e8b, f8b in ipairs(V7b) do if not c8b or f8b.hp > c8b.hp then c8b = f8b end d8b = math.min(d8b, f8b.prog) end Match.focus = { pos = c8b.pos, prog = c8b.prog, fly = c8b.fly, m = c8b.m } local g8b = Utils.now() local h8b = Match.focusTrack if not h8b or h8b.m ~= c8b.m then Match.focusTrack = { m = c8b.m, t = g8b, hp = c8b.hp, prog = c8b.prog } return end if g8b - h8b.t >= 10 then h8b.net, h8b.rate = (h8b.hp - c8b.hp) / (g8b - h8b.t), (c8b.prog - h8b.prog) / (g8b - h8b.t) h8b.t, h8b.hp, h8b.prog = g8b, c8b.hp, c8b.prog end if not h8b.rate or h8b.rate <= 0 then return end local i8b = Utils.flat(c8b.pos) local j8b, k8b, l8b, m8b = { }, { }, 0, 0 for n8b, o8b in ipairs(Match.mine or { }) do local p8b = o8b._stats local q8b = p8b.id.Value local r8b = Core.Units [q8b] if not Units.isFarm(q8b) and not Units.allyBuff(q8b) and not Units.BLESS.by [q8b] and r8b and not r8b.unsellable and p8b:FindFirstChild("range") then local s8b, t8b = Utils.flat(o8b:GetPivot().Position), p8b.range.Value local u8b = not c8b.fly or Match.canAir(o8b) if (s8b - i8b).Magnitude <= t8b and u8b then l8b = l8b + 1 end if not ((s8b - i8b).Magnitude <= t8b and u8b) then local v8b for w8b, x8b in ipairs(Match.samples) do if x8b.prog >= d8b and (Utils.flat(x8b.pos) - s8b).Magnitude <= t8b and (not v8b or x8b.prog < v8b) then v8b = x8b.prog end end local y8b = p8b:FindFirstChild("total_spent") and tonumber(p8b.total_spent.Value) or 0 local z8b = Match.focusMoved and Match.focusMoved [q8b] local A8b = y8b <= 2.5 * (tonumber(r8b.cost) or 0) if v8b and u8b then m8b = m8b + 1 end if not v8b or not u8b then if not z8b then table.insert(j8b, { m = o8b, id = q8b, spent = y8b }) end elseif not z8b and A8b then table.insert(k8b, { m = o8b, id = q8b, spent = y8b, eta = (v8b - c8b.prog) / h8b.rate }) end end end end local B8b = (1 - c8b.prog) / h8b.rate Match.focusLeft, Match.focusInRange = B8b, l8b local C8b = h8b.net and (h8b.net <= 0 or c8b.hp / h8b.net > B8b * 0.8) if C8b and FarmHub.every("bossBehind", 30) then App.log(1, "MATCH", string.format("boss: %s HP left, %s HP a second after its regen, at the base in %ds, %d units reach it: moving units up to it", Utils.fmt(c8b.hp), Utils.fmt(h8b.net), math.floor(B8b), l8b)) end local D8b = tostring(Brain.dg.room and Brain.dg.room.seed) Brain.dg.bails = type(Brain.dg.bails) == "table" and Brain.dg.bails or { } if Match.kind == "dungeon" and not Match.bailed and Config.bailOut ~= false and (m8b == 0 or B8b < 15) and h8b.net and B8b > 4 and B8b < 60 and (tonumber(Brain.dg.bails [D8b]) or 0) < 3 then local E8b = c8b.m and c8b.m:FindFirstChildOfClass("Humanoid") local F8b = E8b and E8b.MaxHealth or c8b.hp local G8b = workspace:FindFirstChild("_BASES") and workspace._BASES:FindFirstChild("player") local H8b = G8b and G8b:FindFirstChild("base") and G8b.base:FindFirstChild("fake_unit") local I8b = H8b and H8b:FindFirstChildOfClass("Humanoid") local J8b = I8b and I8b.Health or (Match.info and tonumber(Match.info.baseHp)) or 100 local K8b = (Match.info and tonumber(Match.info.baseRel)) or Simulator.PRIOR.baseRel local L8b = c8b.hp - math.max(0, h8b.net) * B8b local M8b = J8b / math.max(1, K8b * F8b) local N8b = L8b / math.max(1, h8b.net) if L8b > 0 and (h8b.net <= 0 or N8b > M8b * 1.3) then Match.bailed = true Brain.dg.bails [D8b] = (tonumber(Brain.dg.bails [D8b]) or 0) + 1 Brain.dg.bail = { sig = Modes.teamSig(), at = os.time() } pcall(Match.styleRecord, false) App.log(1, "MATCH", string.format("bailing out: the boss would reach the base with %s HP, your units deal %s a second to it and the base would last %ds against it: back to the lobby to keep the life", Utils.fmt(L8b), Utils.fmt(h8b.net), math.floor(M8b))) App.flush() App.request("lobby", "teleport_back_to_lobby") return end end local O8b, P8b if l8b == 0 and B8b < 30 then return end if j8b [1] and FarmHub.every("bossSell", 2) then table.sort(j8b, function(Q8b, R8b) return Q8b.spent < R8b.spent end) O8b, P8b = j8b [1], l8b > 0 and "the boss has walked past it: its money goes to the units that reach it" or "the boss has walked past it" elseif C8b and k8b [1] and B8b >= 30 and FarmHub.every("bossSell", 8) then table.sort(k8b, function(S8b, T8b) return S8b.eta > T8b.eta end) if k8b [1].eta > 15 then O8b, P8b = k8b [1], string.format("the boss wouldn't reach it for %ds", math.floor(k8b [1].eta)) end end if O8b then local U8b, V8b = App.request("sell", "sell_unit_ingame", O8b.m) if V8b == true then Match.focusMoved = Match.focusMoved or { } Match.focusMoved [O8b.id] = true App.log(1, "PLACE", string.format("boss: sold %s, ยฅ%s spent on it, to move up: %s", Units.name(O8b.id), Utils.fmt(O8b.spent), P8b)) end end end function Match.focusSpot(W8b) local X8b = Match.focus local Y8b = Units.range(W8b.u, 0) * ((Match.info and Match.info.rangeScale) or 1) local Z8b = Utils.flat(X8b.pos) local _8b, a9b local b9b = 0 for c9b, d9b in ipairs(Match.cands) do if (Utils.flat(d9b) - Z8b).Magnitude <= Y8b then local e9b = Match.valid(d9b, W8b.data) b9b = b9b + 1 if b9b % 40 == 0 then task.wait() end if e9b then local f9b, g9b = 0, Utils.flat(e9b) for h9b, i9b in ipairs(Match.samples) do if i9b.prog >= X8b.prog and (Utils.flat(i9b.pos) - g9b).Magnitude <= Y8b then f9b = f9b + 1 end end if not a9b or f9b > a9b then _8b, a9b = e9b, f9b end end end end return _8b, a9b or 0 end function Match.focusGain(j9b) local k9b = Match.focus if not k9b then return 1 end local l9b = j9b._stats if not l9b:FindFirstChild("range") then return 1 end local m9b, n9b = Utils.flat(j9b:GetPivot().Position), l9b.range.Value if k9b.fly and not Match.canAir(j9b) then return 0.02 end if (m9b - Utils.flat(k9b.pos)).Magnitude <= n9b then return 1 end return (m9b - Utils.flat(k9b.pos)).Magnitude <= n9b * 1.5 and 0.3 or 0.02 end Match.cashTried = setmetatable({ }, { __mode = "k" }) function Match.lastWaveCash() if not (Match.total and Match.wave() >= Match.total and not Match.infinite) or Match.spellBusy then return end local o9b = Core.session() local p9b for q9b, r9b in pairs((o9b and o9b.ingame_spells) or { }) do if r9b == "sell_refund" then p9b = q9b end end local s9b, t9b for u9b, v9b in ipairs(Match.mine) do local w9b = v9b._stats local x9b = Core.Units [w9b.id.Value] if Units.isFarm(w9b.id.Value) and not (x9b and x9b.unsellable) and (Match.cashTried [v9b] or 0) < 2 then local y9b = w9b:FindFirstChild("total_spent") and tonumber(w9b.total_spent.Value) or 0 if not t9b or y9b > t9b then s9b, t9b = v9b, y9b end end end if not s9b then return end Match.cashTried [s9b] = (Match.cashTried [s9b] or 0) + 1 local z9b = Units.name(s9b._stats.id.Value) if p9b then Match.spellBusy = true local A9b = App.request("spell", "use_ingame_spell", p9b, s9b) Match.spellBusy = false if A9b then App.log(1, "SPELL", string.format("last wave: Refund on %s โ€” ยฅ%s back (farms don't pay after this wave)", z9b, Utils.fmt(t9b))) end else local B9b, C9b = App.request("sell", "sell_unit_ingame", s9b) if C9b == true then App.log(1, "PLACE", string.format("last wave: sold %s, ยฅ%s spent on it (farms don't pay after this wave: the yen goes into damage)", z9b, Utils.fmt(t9b))) elseif Match.cashTried [s9b] >= 2 then App.log(1, "PLACE", string.format("last wave: the game won't sell %s here", z9b)) end end end Match.mergeIds = { } function Match.mergeScan() Match.mergePlan, Match.mergeIds = nil, { } if not Match.random then return end local D9b = Core.session() local E9b = false Match.cloneCard = false for F9b, G9b in pairs((D9b and D9b.ingame_spells) or { }) do if G9b == "sacrifice" then E9b = true elseif G9b == "clone" then Match.cloneCard = true end end local H9b = { } local I9b = Brain.fh.noMerge or { } for J9b, K9b in ipairs(Match.mine or { }) do local L9b = K9b._stats local M9b = L9b.id.Value if not Units.isFarm(M9b) and not Units.allyBuff(M9b) and not Units.idol(M9b) and L9b:FindFirstChild("max_upgrade") and (I9b [M9b] or 0) < 2 then local N9b, O9b = pcall(Match.liveDps, K9b, M9b) H9b [M9b] = H9b [M9b] or { } table.insert(H9b [M9b], { m = K9b, lvl = L9b.upgrade.Value, max = L9b.max_upgrade.Value, dps = N9b and O9b or 0 }) end end local P9b = { } for Q9b, R9b in ipairs(Match.team or { }) do if R9b.hand then P9b [R9b.id] = true end end local S9b = { } for T9b, U9b in pairs(H9b) do table.sort(U9b, function(V9b, W9b) if V9b.dps ~= W9b.dps then return V9b.dps > W9b.dps end return V9b.lvl > W9b.lvl end) table.insert(S9b, { id = T9b, dps = U9b [1].dps * Match.airBoost(U9b [1].m) * Match.reach(U9b [1].m) }) end table.sort(S9b, function(X9b, Y9b) return X9b.dps > Y9b.dps end) for Z9b = 1, math.min(3, # S9b) do Match.mergeIds [S9b [Z9b].id] = true end local _9b for aac, bac in pairs(H9b) do local cac = table.clone(bac) table.sort(cac, function(dac, eac) if dac.lvl ~= eac.lvl then return dac.lvl < eac.lvl end return dac.dps < eac.dps end) for fac, gac in ipairs(cac) do if gac.m == Match.freshM and fac > 1 then table.remove(cac, fac) table.insert(cac, 1, gac) break end end local hac, iac, jac, kac for lac, mac in ipairs(cac) do local nac, oac = Utils.flat(mac.m:GetPivot().Position), mac.m._stats.range.Value local pac, qac, rac = 0, 0, nil for sac, tac in ipairs(bac) do if tac ~= mac and (Utils.flat(tac.m:GetPivot().Position) - nac).Magnitude <= oac then pac, qac = pac + tac.dps, qac + 1 if not rac or tac.dps > rac.dps then rac = tac end end end if qac > 0 then hac, iac, jac, kac = mac, pac, qac, rac break end end kac = kac or bac [1] local uac = kac.dps * Match.airBoost(kac.m) * Match.reach(kac.m) * (aac == Match.planId and 2 or 1) if (Match.mergeIds [aac] or aac == Match.planId) and (hac or P9b [aac]) and (not _9b or uac > _9b.score) then _9b = { id = aac, top = kac, fresh = hac, near = iac or 0, n = jac or 0, coming = P9b [aac], score = uac } end end Match.planId, Match.freshM = _9b and _9b.id, _9b and _9b.fresh and _9b.fresh.m Match.mergeHeldAt = (E9b and _9b and _9b.fresh) and (Match.mergeHeldAt or Match.wave()) or nil if not _9b then return App.status("Merge", "no second copy of a strong unit yet") end local vac, wac = Match.wave(), (Match.total or 30) - Match.wave() local xac = (Match.pressure or 0) >= math.min(Config.dangerReach, ((Match.depth and Match.depth.units > 0 and Match.depth.covEnd) or 1) * 0.9) local yac = _9b.fresh local zac = not xac and wac > 3 _9b.waits = yac and yac.lvl <= _9b.top.lvl and _9b.top.lvl < _9b.top.max and zac or false local Aac = (not yac or yac.lvl >= _9b.top.lvl) and _9b.coming and zac local Bac = Match.cloneCard and _9b.top.lvl >= _9b.top.max and zac local Cac = yac and yac.lvl > _9b.top.lvl and _9b.near < 0.3 * yac.dps _9b.ready = E9b and yac ~= nil and not Cac and (not (_9b.waits or Aac or Bac) or vac - (Match.mergeHeldAt or vac) >= 5) Match.holdNoCardAt = (_9b.waits and not E9b) and (Match.holdNoCardAt or vac) or nil _9b.hold = _9b.waits and (E9b or vac - (Match.holdNoCardAt or vac) < 4) Match.mergePlan = _9b App.status("Merge", yac and string.format("%s: merge %d cop%s into the one with %d upgrades %s", Units.name(_9b.id), _9b.n, _9b.n == 1 and "y" or "ies", yac.lvl, _9b.ready and "now" or (E9b and "once the top copy is maxed" or "when a Merge card comes")) or string.format("%s: a second copy in the hand goes next to it", Units.name(_9b.id))) end function Match.mergeHold(Dac) local Eac = Match.mergePlan return Eac and Eac.hold and Eac.fresh and Eac.fresh.m == Dac or false end function Match.mergeBoost(Fac) local Gac = Match.mergePlan if not (Gac and Gac.top.m == Fac and (Gac.fresh or Gac.coming)) then return 1 end return Match.mergeHeldAt and 2 or 1.3 end function Match.mergeSpot(Hac) if not (Match.random and Match.mergeIds [Hac.id]) then return nil end local Iac, Jac for Kac, Lac in ipairs(Match.mine or { }) do if Lac._stats.id.Value == Hac.id then local Mac, Nac = pcall(Match.liveDps, Lac, Hac.id) Nac = Mac and Nac or 0 if not Jac or Nac > Jac then Iac, Jac = Lac, Nac end end end return Iac and Iac:GetPivot().Position end function Match.mergeMeasure(Oac) local Pac = Match.mergeBefore if not Pac then return end task.delay(2, function() if not (Oac and Oac.Parent) then return end local Qac, Rac = pcall(Match.liveDps, Oac, Oac._stats.id.Value) if not Qac then return end local Sac = Pac.own + 1.5 * Pac.near local Tac = Brain.fh.merge or { n = 0, ratio = 0 } if Sac > 0 then Tac.n, Tac.ratio = Tac.n + 1, Tac.ratio + Rac / Sac end Brain.fh.merge = Tac local Uac = Oac._stats.id.Value Brain.fh.noMerge = Brain.fh.noMerge or { } local Vac, Wac = Rac - Pac.own, 1.5 * Pac.near if Wac > 0 and Vac < 0.1 * Wac then Brain.fh.noMerge [Uac] = (Brain.fh.noMerge [Uac] or 0) + 1 elseif Wac > 0 and Vac >= 0.5 * Wac then Brain.fh.noMerge [Uac] = nil end App.log(1, "SPELL", string.format("Merge result: %s went from %s to %s damage a second at %d upgrades (its own + 1.5ร— the copies' %s = %s expected; the top copy had %d)", Units.name(Oac._stats.id.Value), Utils.fmt(Pac.own), Utils.fmt(Rac), Pac.lvl, Utils.fmt(Pac.near), Utils.fmt(Sac), Pac.top)) end) end function Match.farmMergeOk() local Xac = Brain.fh.farmMerge if not (Xac and Xac.n > 0) then return true end return Xac.ratio / Xac.n >= 0.8 end function Match.farmMergePair() local Yac = { } for Zac, _ac in ipairs(Match.mine or { }) do local abc = _ac._stats.id.Value if Units.isFarm(abc) and _ac._stats:FindFirstChild("farm_amount") then Yac [abc] = Yac [abc] or { } table.insert(Yac [abc], _ac) end end for bbc, cbc in pairs(Yac) do if # cbc >= 2 then table.sort(cbc, function(dbc, ebc) return dbc._stats.upgrade.Value < ebc._stats.upgrade.Value end) for fbc, gbc in ipairs(cbc) do local hbc, ibc = Utils.flat(gbc:GetPivot().Position), gbc._stats.range.Value local jbc, kbc = 0, 0 for lbc, mbc in ipairs(cbc) do if mbc ~= gbc and (Utils.flat(mbc:GetPivot().Position) - hbc).Magnitude <= ibc then jbc, kbc = jbc + (tonumber(mbc._stats.farm_amount.Value) or 0), kbc + 1 end end if kbc > 0 then return gbc, jbc, kbc end end end end end function Match.farmMergeSpot(nbc) if not (Match.random and Match.farmMergeOk()) then return nil end for obc, pbc in ipairs(Match.mine or { }) do if pbc._stats.id.Value == nbc.id then return pbc:GetPivot().Position, tonumber(pbc._stats.range.Value) or 0 end end end function Match.farmMergeMeasure(qbc) local rbc = Match.farmMergeBefore if not rbc then return end task.delay(2, function() if not (qbc and qbc.Parent) then return end local sbc = tonumber(qbc._stats.farm_amount.Value) or 0 local tbc = rbc.own + 1.5 * rbc.inc local ubc = Brain.fh.farmMerge or { n = 0, ratio = 0 } ubc.n, ubc.ratio = ubc.n + 1, ubc.ratio + (tbc > 0 and sbc / tbc or 0) Brain.fh.farmMerge = ubc App.log(1, "SPELL", string.format("Farm merge result: %s pays ยฅ%s a wave now (it paid ยฅ%s and took in farms paying ยฅ%s; keeping 1.5ร— would make ยฅ%s)", Units.name(qbc._stats.id.Value), Utils.fmt(sbc), Utils.fmt(rbc.own), Utils.fmt(rbc.inc), Utils.fmt(tbc))) end) end Match.IDOL_PRIOR = { dmg = 0.05, cost = 0.05 } function Match.idolStep() local vbc = Brain.fh.idol local wbc, xbc = Match.IDOL_PRIOR.dmg, Match.IDOL_PRIOR.cost if vbc and vbc.n > 0 then wbc, xbc = vbc.dmg / vbc.n, vbc.cost / vbc.n end return math.max(0, wbc) + math.max(0, xbc) * 0.5 end function Match.idolMeasure() local ybc for zbc, Abc in ipairs(Match.mine) do local Bbc = Abc._stats.id.Value local Cbc = Abc._stats:FindFirstChild("damage") if not Units.isFarm(Bbc) and not Units.idol(Bbc) and Cbc and Cbc.Value > 0 then ybc = Abc break end end if not ybc then return end local Dbc = ybc._stats local function Ebc() local Fbc, Gbc = pcall(Core.USC.calculate_upgrade_cost_multiplier, ybc, Core.session()) local Hbc = Dbc:FindFirstChild("buff_damage_multiplier") and tonumber(Dbc.buff_damage_multiplier.Value) or 0 return Dbc.damage.Value * (1 + Hbc), Fbc and tonumber(Gbc) or nil end local Ibc, Jbc = Ebc() local Kbc = Dbc.upgrade.Value task.delay(3, function() if not ybc.Parent or Dbc.upgrade.Value ~= Kbc then return end local Lbc, Mbc = Ebc() if not (Jbc and Mbc and Jbc > 0 and Ibc > 0) then return end if Lbc == Ibc and Mbc == Jbc then return App.log(2, "UPGRADE", "Idol upgrade: no change seen yet, not counted") end local Nbc = Brain.fh.idol or { n = 0, dmg = 0, cost = 0 } Nbc.n, Nbc.dmg, Nbc.cost = Nbc.n + 1, Nbc.dmg + (Lbc / Ibc - 1), Nbc.cost + (1 - Mbc / Jbc) Brain.fh.idol = Nbc App.log(1, "UPGRADE", string.format("Idol upgrade measured on %s: %+.1f%% damage, upgrades %.1f%% cheaper", Units.name(Dbc.id.Value), (Lbc / Ibc - 1) * 100, (1 - Mbc / Jbc) * 100)) end) end function Match.spells() local Obc = Core.session() local Pbc = Obc and Obc.ingame_spells if type(Pbc) ~= "table" or Match.spellBusy then return end local Qbc = Match.money() local Rbc = (Match.pressure or 0) >= math.min(Config.dangerReach, ((Match.depth and Match.depth.units > 0 and Match.depth.covEnd) or 1) * 0.9) local Sbc, Tbc, Ubc for Vbc, Wbc in ipairs(Match.mine) do local Xbc = Wbc._stats.id.Value if not Units.isFarm(Xbc) and not Units.allyBuff(Xbc) and Xbc ~= "mahoraga" then local Ybc, Zbc = pcall(Match.liveDps, Wbc, Xbc) local _bc = Ybc and Zbc and Zbc * Match.airBoost(Wbc) * Match.reach(Wbc) if _bc and (not Ubc or _bc > Ubc) then Sbc, Tbc, Ubc = Wbc, Zbc, _bc end end end local acc = Match.mergePlan local bcc = { } for ccc, dcc in pairs(Pbc) do table.insert(bcc, { slot = ccc, spell = dcc, rank = Match.SPELL_ORDER [dcc] or 9 }) end table.sort(bcc, function(ecc, fcc) return ecc.rank < fcc.rank end) Match.spellWant = nil Match.cloneNote = nil for gcc, hcc in ipairs(bcc) do local icc, jcc = hcc.slot, hcc.spell local kcc, lcc local mcc for ncc, occ in ipairs(Match.mine) do if occ._stats.id.Value == "mahoraga" then mcc = occ end end local pcc = mcc and (Match.buffCount [mcc] or 0) < 2 if jcc == "random_buff" and Qbc >= 1000 and (pcc or Sbc) then if pcc then kcc, lcc = mcc, "the Divine General (two buffs make it a boss killer; its range is only 8.5)" else kcc, lcc = Sbc, "+100% attack or range on your strongest unit" end elseif jcc == "sacrifice" and acc and acc.ready then if Qbc >= 1000 then kcc, lcc = acc.fresh.m, string.format("the %d other cop%s in its range (it keeps 1.5ร— their damage through its own upgrades)", acc.n, acc.n == 1 and "y" or "ies") Match.mergeBefore = { own = acc.fresh.dps, near = acc.near, lvl = acc.fresh.lvl, top = acc.top.lvl } else Match.spellWant = Match.spellWant or { cost = 1000, why = "Merge on " .. Units.name(acc.id) } end elseif jcc == "sacrifice" and Qbc >= 1000 and not Rbc and not (acc and acc.fresh) and Match.farmMergeOk() and Match.farmMergePair() then local qcc, rcc, scc = Match.farmMergePair() kcc = qcc lcc = string.format("the %d other %s in its reach (does a farm keep their income?)", scc, Units.name(qcc._stats.id.Value)) Match.farmMergeBefore = { own = tonumber(qcc._stats.farm_amount.Value) or 0, inc = rcc } elseif jcc == "clone" then local tcc = not Rbc and acc and acc.fresh and acc.top.lvl < acc.top.max Match.cloneWaitAt = tcc and (Match.cloneWaitAt or Match.wave()) or nil local ucc = tcc and Match.wave() - Match.cloneWaitAt < 5 local vcc, wcc if not Rbc and acc and acc.fresh and acc.top.lvl >= acc.top.max then vcc, wcc = acc.top.m, "a maxed copy for the Merge to take in" elseif not ucc and Sbc then vcc, wcc = Sbc, Rbc and "a copy of your strongest unit: instant damage with enemies near the base" or "a copy of your strongest unit (two copies make a Merge)" end if ucc then Match.cloneNote = string.format("waits for %s to max (%d/%d, up to %d more waves)", Units.name(acc.id), acc.top.lvl, acc.top.max, 5 - (Match.wave() - Match.cloneWaitAt)) end if vcc and Qbc >= 5000 then kcc, lcc = vcc, wcc elseif vcc then local xcc, ycc = pcall(Match.liveDps, vcc, vcc._stats.id.Value) ycc = xcc and tonumber(ycc) or 0 if ycc >= (Match.bestRate or 0) * 5000 * 0.5 then Match.spellWant = Match.spellWant or { cost = 5000, why = "Clone of " .. Units.name(vcc._stats.id.Value), rate = ycc / 5000 } Match.cloneNote = "saving for a copy of " .. Units.name(vcc._stats.id.Value) else Match.cloneNote = string.format("a copy of %s (%s dps) is worth less than ยฅ5k of upgrades (%s)", Units.name(vcc._stats.id.Value), Utils.fmt(ycc), Utils.fmt((Match.bestRate or 0) * 5000)) end end elseif jcc == "ritual" and not Rbc and Match.wave() >= 8 and not Match.hasGeneral() then local zcc, Acc = Match.ritualTarget() if zcc and Qbc >= 10000 then kcc, lcc = zcc, Acc elseif zcc then Match.spellWant = Match.spellWant or { cost = 10000, why = "a Ritual (" .. Acc .. ")" } end elseif jcc == "summon" and Qbc >= 10000 and not Rbc and Match.summonWorth() then kcc, lcc = false, "a unit from your own team" elseif jcc == "gate" and Qbc >= 2500 and not Rbc and Match.summonWorth() then kcc, lcc = false, "3ร— the odds of Astral Summon" end if lcc then Match.spellBusy = true local Bcc = { } for Ccc, Dcc in ipairs(workspace._UNITS:GetChildren()) do Bcc [Dcc] = true end local Ecc if kcc then local Fcc = kcc._stats local Gcc, Hcc = pcall(Match.liveDps, kcc, Fcc.id.Value) Ecc = { id = Fcc.id.Value, pos = kcc:GetPivot().Position, dps = Gcc and tonumber(Hcc) or 0, lvl = Fcc.upgrade.Value, spent = Fcc:FindFirstChild("total_spent") and tonumber(Fcc.total_spent.Value) or 0 } end task.spawn(function() local Icc if kcc then Icc = App.request("spell", "use_ingame_spell", icc, kcc) else Icc = App.request("spell", "use_ingame_spell", icc) end task.wait(1.5) local Jcc = kcc and Units.name(kcc._stats.id.Value) or "" if Icc and (Core.session().ingame_spells or { }) [icc] ~= jcc then App.log(1, "SPELL", string.format("%s%s โ€” %s", Match.SPELL_NAMES [jcc] or tostring(jcc), kcc and (" on " .. Jcc) or "", lcc)) App.status("Abilities", string.format("spell %s%s โ€” %s", Match.SPELL_NAMES [jcc] or tostring(jcc), kcc and (" on " .. Jcc) or "", lcc)) if jcc == "sacrifice" then if Ecc and Units.isFarm(Ecc.id) then pcall(Match.farmMergeMeasure, kcc) else pcall(Match.mergeMeasure, kcc) end end if jcc == "random_buff" and kcc then Match.buffCount [kcc] = (Match.buffCount [kcc] or 0) + 1 end if (jcc == "clone" or jcc == "ritual") and Ecc then pcall(Match.newUnitMeasure, jcc, kcc, Jcc, Ecc, Bcc) end else App.backoff("spell", 15) App.log(2, "SPELL", tostring(jcc) .. " wasn't taken" .. (kcc and (" on " .. Jcc) or "")) end Match.spellBusy = false end) return end end end function Match.trainCount() local Kcc = Brain.train.on if not (Kcc and Match.trainee) then return end local Lcc = 0 for Mcc, Ncc in ipairs(Match.mine) do local Occ = Ncc._stats local Pcc = Occ:FindFirstChild("uuid") and Occ.uuid.Value local Qcc = Pcc == Match.trainee and Occ:FindFirstChild(Kcc.what == "kills" and "kill_count" or "takedown_count") if Qcc then Lcc = Lcc + (tonumber(Qcc.Value) or 0) end end Match.trainTk = math.max(Match.trainTk or 0, Lcc) App.status("Training", string.format("%s: %d %s this match ยท %s/%s", Units.name(Kcc.id), Match.trainTk, Kcc.what, Utils.fmt(Kcc.have + (Kcc.got or 0) + Match.trainTk), Utils.fmt(Kcc.need))) end function Match.trainDone(Rcc, Scc) local Tcc = Brain.train.on if not (Tcc and Match.trainee) then return end Match.trainCount() local Ucc = Match.trainTk or 0 Tcc.got = (Tcc.got or 0) + Ucc local Vcc = Brain.trainSim [Rcc] and tonumber(Brain.trainSim [Rcc].tk) if not Match.joinedMid then local Wcc = Brain.train.real [Rcc] or { n = 0, sum = 0 } Wcc.n, Wcc.sum = Wcc.n + 1, Wcc.sum + Ucc Brain.train.real [Rcc] = Wcc if Vcc and Vcc > 0 then local Xcc = Brain.train.cal Xcc.real, Xcc.sim, Xcc.n = (Xcc.real or 0) + Ucc, (Xcc.sim or 0) + Vcc, (Xcc.n or 0) + 1 end end table.insert(Brain.train.log, 1, { key = Rcc, got = Ucc, won = Scc, at = os.time() }) while # Brain.train.log > 20 do table.remove(Brain.train.log) end App.log(1, "TRAIN", string.format("%s: +%d %s this run (%s%s) ยท %s/%s", Units.name(Tcc.id), Ucc, Tcc.what, Scc and "won" or "lost", Vcc and string.format(", the simulator said โ‰ˆ%d", Vcc) or "", Utils.fmt(Training.realHave(Tcc)), Utils.fmt(Tcc.need))) if not Scc then Brain.train.bad [Rcc] = os.time() + 6 * 3600 App.log(1, "TRAIN", "the training team lost here: no training in this mode for 6 h") end end function Match.finish() if Match.finished then return end Match.finished = true local Ycc local Zcc = Utils.now() while Utils.now() - Zcc < 8 do Ycc = Match.result or (Core.GUIS.results_ui and Core.GUIS.results_ui.settings) if Ycc then break end task.wait(0.25) end local _cc = Ycc and Ycc.victory or false local adc = Match.infinite and Match.wave() or nil if adc then _cc = true end local bdc = Match.key local cdc = Modes.rec(bdc) if adc and not Match.joinedMid and not Match.stopping then cdc.waveSum, cdc.waveN = (tonumber(cdc.waveSum) or 0) + adc, (tonumber(cdc.waveN) or 0) + 1 cdc.waveBest = math.max(tonumber(cdc.waveBest) or 0, adc) end if adc and (tonumber(Brain.infResults) or 0) < 3 and type(Ycc) == "table" then Brain.infResults = (tonumber(Brain.infResults) or 0) + 1 local ddc = { } for edc, fdc in pairs(Ycc) do table.insert(ddc, type(fdc) == "table" and (edc .. "{โ€ฆ}") or (edc .. "=" .. tostring(fdc))) end table.sort(ddc) App.log(1, "MATCH", "Infinite result fields: " .. table.concat(ddc, ", ")) end local gdc = Ycc and Ycc.stats and tonumber(Ycc.stats.time_taken) or (os.clock() - (Match.startClock or os.clock())) local hdc = Ycc and ((tonumber(Ycc.gem_reward) or 0) + (tonumber(Ycc.first_completion_gems) or 0)) or 0 local idc = Core.GS.CURRENT_EVENT_RESOURCE or "AprilCoins" local jdc = Ycc and Ycc.resource_reward and tonumber(Ycc.resource_reward [idc]) or 0 if not Match.joinedMid then cdc.runs = cdc.runs + 1 if _cc then cdc.wins = cdc.wins + 1 cdc.streak = 0 elseif Match.kind == "event" and jdc > 0 then cdc.streak = 0 else cdc.streak = (cdc.streak or 0) + 1 end cdc.sec = cdc.sec + gdc cdc.timed = (tonumber(cdc.timed) or 0) + 1 end if _cc then if Match.ld._gamemode == "story" or Match.kind == "raid" then Brain.cleared [Match.ld.id .. ":" .. tostring(Match.ld._difficulty or "Normal")] = os.time() end if bdc == "ch" and Brain.live.normal then Brain.live.normal.doneUuid = Brain.live.normal.uuid end if bdc == "dc" and Brain.live.daily then Brain.live.daily.doneUuid = Brain.live.daily.uuid end end for kdc, ldc in pairs(Brain.cleared) do if os.time() - ldc > 86400 then Brain.cleared [kdc] = nil end end cdc.gems = cdc.gems + hdc cdc.coins = cdc.coins + jdc cdc.last = os.time() local mdc = (cdc.streak or 0) >= Config.restAfterLosses and Match.kind ~= "dungeon" and Match.kind ~= "portal" and not Match.trialId and not (Config.dailyLoop and bdc == "dc") if mdc then cdc.restUntil, cdc.restWhy = os.time() + 6 * 3600, "losses" App.log(1, "MODE", Match.label() .. ": lost " .. cdc.streak .. " in a row โ€” resting it for 6 h") end if not _cc and (bdc == "ch" or bdc == "dc") then local ndc = bdc == "ch" and Brain.live.normal or Brain.live.daily if ndc then ndc.lost = (ndc.lost or 0) + 1 end end Brain.totals.matches = (Brain.totals.matches or 0) + 1 if not Match.joinedMid then if _cc then Brain.totals.wins = Brain.totals.wins + 1 else Brain.totals.losses = Brain.totals.losses + 1 end end Brain.totals.gems = (Brain.totals.gems or 0) + hdc Brain.totals.coins = (Brain.totals.coins or 0) + jdc local odc = { } for pdc, qdc in pairs(Ycc and Ycc.new_items or { }) do local rdc = type(qdc) == "table" and (qdc.item_id or qdc.id) or tostring(qdc) odc [rdc] = (odc [rdc] or 0) + (type(qdc) == "table" and tonumber(qdc.amount) or 1) end local sdc = Match.label() if Match.kind == "dungeon" and not Match.joinedMid then local tdc, udc = pcall(Dungeon.roomDone, _cc) if not tdc then App.log(1, "ERROR", "dungeon: " .. tostring(udc)) end end if not Match.joinedMid and not Match.random and not Match.stopping and not Match.trialId then pcall(Match.spotResult, _cc) end Brain.lastResult = { won = _cc, text = string.format("%s ยท %s ยท wave %s ยท %dm%02ds", sdc, _cc and "won" or "lost", tostring(Ycc and Ycc.stats and Ycc.stats.waves_completed or Match.wave()), math.floor(gdc / 60), math.floor(gdc % 60)), gems = hdc, coins = jdc, items = odc, at = os.time() } table.insert(Brain.history, 1, { key = bdc, won = _cc, sec = gdc, gems = hdc, coins = jdc, at = os.time() }) while # Brain.history > 30 do table.remove(Brain.history) end Brain.lastMatch = { label = sdc, spent = Match.spent, actions = # Match.actions, refusals = Match.refusals or 0, at = os.time() } if not Match.trialId then pcall(Match.learn) end local vdc, wdc = pcall(Match.trainDone, bdc, _cc) if not vdc then App.log(1, "ERROR", "training: " .. tostring(wdc)) end local xdc, ydc = true, nil if Match.hunt then local zdc, Adc = pcall(Match.huntRecord, Ycc, jdc, _cc, gdc) if not zdc then App.log(1, "ERROR", "hunt record: " .. tostring(Adc)) end end if Match.random then pcall(Match.fhRecord, jdc, _cc) elseif not Match.stopping and not Match.trialId then xdc, ydc = pcall(Match.calMerge, _cc) end if not xdc then App.log(1, "ERROR", "sim calibration: " .. tostring(ydc)) end Reports.record({ mode = bdc, won = _cc, sec = math.floor(gdc), wave = Match.wave(), gems = hdc, coins = jdc, mid = Match.joinedMid or nil }) App.log(1, "RESULT", string.format("%s โ€” %s, +%d gems%s, ยฅ%s spent, %d refused spots", sdc, adc and ("reached wave " .. adc) or (_cc and "WON" or "LOST"), hdc, jdc > 0 and (", +" .. jdc .. " " .. idc) or "", Utils.fmt(Match.spent), Match.refusals or 0)) if Brain.watch and Brain.watch.key == bdc then Brain.watch.won = _cc and true or false App.saveNow() end local Bdc = Brain.testTeam local Cdc = Bdc and (Bdc.left or 0) > 0 and Bdc.key == bdc and type(Bdc.uuids) == "table" and Training.same(Training.team(), Bdc.uuids) if Bdc and (Bdc.left or 0) > 0 and Bdc.key == bdc then Bdc.left = Bdc.left - 1 Bdc.runs = Bdc.runs or { } local Ddc = Match.damageLine() or "" table.insert(Bdc.runs, { won = _cc, wave = Match.wave(), sec = gdc, at = os.time(), dmg = Ddc, other = not Cdc or nil }) App.log(1, "TEST", string.format("%s: run %d of %d %s at wave %d in %.1f min%s ยท %s", tostring(Bdc.label), # Bdc.runs, tonumber(Bdc.n) or # Bdc.runs, _cc and "WON" or "LOST", Match.wave(), (tonumber(gdc) or 0) / 60, Cdc and "" or " (the test team wasn't on: not counted)", Ddc)) if Bdc.left <= 0 then App.log(1, "TEST", tostring(Bdc.label) .. ": done, the usual team picking is back") end App.saveSoon() end pcall(function() if Cdc then return end local Edc = Modes.rec(bdc) if _cc and not adc then local Fdc = { } for Gdc, Hdc in ipairs(Units.equipped()) do table.insert(Fdc, Hdc.uuid) end table.sort(Fdc) local Idc = table.concat(Fdc, ",") local function Jdc(Kdc) local Ldc, Mdc = Core.session(), 0 for Ndc, Odc in ipairs(Kdc or { }) do local Pdc = Ldc.collection:get_unit_by_uuid(Odc) local Qdc, Rdc = pcall(Units.score, Pdc or { }) Mdc = Mdc + (Qdc and Rdc or 0) end return Mdc end if # Fdc > 0 then if Edc.winSig == Idc then Edc.winTeamN = (tonumber(Edc.winTeamN) or 0) + 1 elseif not Edc.winTeam or Jdc(Fdc) >= Jdc(Edc.winTeam) then Edc.winTeam, Edc.winSig, Edc.winTeamN = Fdc, Idc, 1 end end elseif not _cc then Edc.winTeam, Edc.winSig, Edc.winTeamN = nil, nil, nil end end) local Sdc, Tdc = pcall(Reports.match, { key = bdc, won = _cc, sec = gdc, gems = hdc, coins = jdc, items = odc }) if not Sdc then App.log(1, "ERROR", "local learning: " .. tostring(Tdc)) end if Match.kind == "portal" then pcall(function() local Udc = { } for Vdc, Wdc in ipairs(Units.equipped()) do table.insert(Udc, Units.name(Wdc.id)) end local Xdc = Match.pred FarmHub.portalLine(string.format("%s ยท %s at wave %d of %d ยท predicted %s ยท team: %s ยท style %s ยท modifiers: %s ยท %s", os.date("%m-%d %H:%M"), _cc and "WON" or "LOST", Match.wave(), Match.total or 0, Xdc and ((Xdc.won and "a win at " or "a loss at ") .. tostring(Xdc.wave)) or "nothing", table.concat(Udc, ", "), tostring(Match.style), tostring(Brain.portalMods or "?"):sub(1, 300), tostring(Match.damageLine() or ""))) end) end for Ydc, Zdc in pairs(Match.buffSeen) do local _dc = 0 for aec, bec in ipairs(Zdc.levels) do _dc = math.max(_dc, bec) end App.log(1, "BUFF", string.format("%s cast %d times: your units read %s%% after the casts (highest +%d%%)", Units.name(Ydc), Zdc.casts, table.concat(Zdc.levels, ", "), _dc)) local cec = Units.allyBuff(Ydc) if cec and cec.attack then local dec = 0 for eec, fec in pairs(cec.types or { }) do dec = math.max(dec, tonumber(fec) or 0) end local gec = Brain.stack [cec.attack] if dec > 0 and _dc / 100 > dec + 0.05 and not (gec and gec.stacks and (tonumber(gec.cap) or 0) >= _dc / 100) then Brain.stack [cec.attack] = { stacks = true, cap = math.max(gec and gec.stacks and tonumber(gec.cap) or 0, _dc / 100), before = dec, after = _dc / 100, at = os.time(), seen = true } App.log(1, "BUFF", string.format("%s's buffs stack: +%d%% with one, +%d%% seen with more. Copies now chain up to that", Units.name(Ydc), math.floor(dec * 100 + 0.5), _dc)) end end end local hec, iec = pcall(Match.damageLine) if hec and iec then App.log(1, "DAMAGE", iec) end if not Match.random then local jec, kec, lec = pcall(Match.damageShares) if jec and kec and not Match.trialId then pcall(Strategy.learn, kec, Match.team) end local mec, nec = pcall(Match.summonSplit) if mec and nec and jec and (lec or 0) > 0 then Brain.str.summon = type(Brain.str.summon) == "table" and Brain.str.summon or { } for oec, pec in pairs(nec) do if pec.total >= 0.01 * lec then local qec = Brain.str.summon [oec] local tec = (type(qec) == "table" and tonumber(qec.n) or 0) + 1 local uec = math.max(0.2, 1 / tec) Brain.str.summon [oec] = { s = type(qec) == "table" and (qec.s + (pec.share - qec.s) * uec) or pec.share, n = tec } end end end Brain.lastRun = { key = bdc, at = os.time(), xp0 = Match.xp0 } end if Match.trialId then local vec, wec = pcall(Match.trialDone, _cc, gdc) if not vec then App.log(1, "ERROR", "trial: " .. tostring(wec)) Brain.trial = nil end end local xec if _cc and Match.ld._is_actual_storymode then local yec, zec = pcall(Core.PSC.get_next_story_level, Match.ld.id) if yec and zec then xec = zec local Aec = Utils.now() while App.alive and Utils.now() - Aec < 2 do if Modes.open(zec) then break end task.wait(0.5) end end end local Bec = nil pcall(function() Bec = Modes.pick() end) if Bec and Bec.kind == "story" and Bec.first and xec and Bec.level ~= xec and not Brain.nextMode then for Cec, Dec in ipairs(Modes.lastScored or { }) do if Dec.kind == "story" and Dec.first and Dec.level == xec and Dec.diff == Match.ld._difficulty and (Dec.baseScore or Dec.score or 0) >= (Bec.baseScore or Bec.score or 0) * Modes.CHAIN_MIN then Bec = Dec break end end end if not Bec and xec and Config.onlyStory and (Config.pickStory == "" or Config.pickStory == "progress") and Match.ld._difficulty == "Normal" and not Modes.done((Core.prof() or { }).level_data or { }, xec, "Normal") then local Eec = "st:" .. xec .. ":Normal" Bec = { key = Eec, kind = "story", first = true, level = xec, diff = "Normal", label = Modes.levelName(xec) .. " (Normal)", rec = Modes.rec(Eec) } end local Fec, Gec = "lobby", "back to the lobby" local Hec = (Match.ld._gamemode == "story" or Match.ld._gamemode == "infinite" or (Match.kind == "event" and Match.ld._OVERRIDE_CAN_REPLAY)) and _cc if not Config.play or not Config.master then Fec, Gec = "stay", "Auto Play is off" elseif Bec and Bec.key == bdc and Hec then Fec, Gec = "replay", "replaying: still the best mode" elseif Bec and Bec.kind == "story" and Match.ld._is_actual_storymode and _cc then local Iec, Jec = pcall(Core.PSC.get_next_story_level, Match.ld.id) if Iec and Jec == Bec.level and Bec.diff == Match.ld._difficulty then Fec, Gec = "next_story", "next act: " .. Bec.label end end if Fec ~= "replay" and Fec ~= "next_story" and Bec then Gec = "lobby for: " .. Bec.label end local Kec = Brain.train.on if Kec and Match.trainee and (Fec == "replay" or Fec == "next_story") and Training.realHave(Kec) >= Kec.need then Fec, Gec = "lobby", "lobby: " .. Units.name(Kec.id) .. " has its " .. tostring(Kec.what) .. ", your own team goes back in" end if Brain.holdLobby and Fec ~= "stay" then Fec, Gec = "lobby", "lobby: held there: " .. tostring(Brain.holdLobby.why) end if (Fec == "replay" or Fec == "next_story") and Bec then local Lec, Mec = pcall(FarmHub.matchOn, Bec) if Lec and Mec then Fec, Gec = "lobby", "lobby: matchmaking again for " .. tostring(Bec.label) end end if (Fec == "replay" or Fec == "next_story") and Config.smartCharm and Bec then local Nec, Oec = pcall(Hunt.charmWant, Bec) if Nec and Oec then Fec, Gec = "lobby", "lobby: putting on the Summer Charm (" .. Hunt.charmWords(Oec) .. ")" end end if Match.trialId and Fec ~= "stay" then Fec, Gec = "lobby", Brain.trial and "lobby: the next trial run" or "lobby: your own team goes back in" end if Brain.buyStorage and Fec ~= "stay" then Fec, Gec = "lobby", "lobby: buying the unit storage you agreed" end if Brain.buyOnce and Fec ~= "stay" then Fec, Gec = "lobby", "lobby: buying the " .. tostring((Core.Items [Brain.buyOnce.item] or { }).name or Brain.buyOnce.item) .. " you agreed" end local Pec if ((Match.kind == "dungeon" and _cc) or Fec == "next_story" or Fec == "replay") and Fec ~= "stay" then local Qec, Rec = pcall(Strategy.hasWork) if Qec then Pec = Rec else App.log(1, "ERROR", "team work: " .. tostring(Rec)) end if Pec then Fec, Gec = "lobby", "lobby: the Units โ†’ Upgrades can " .. tostring(Pec) end end if Match.kind == "dungeon" and Fec ~= "stay" and Bec and Bec.key == "dg" and _cc and # Units.equipped() >= 6 and not Brain.holdLobby and not Brain.buyStorage and not Pec then Brain.pending = nil App.saveNow() App.status("Match", "next dungeon room") local Sec, Tec = pcall(Dungeon.chain) if not Sec then App.log(1, "ERROR", "dungeon next room: " .. tostring(Tec)) end if Sec and Tec then return end Brain.startTeam = nil Fec, Gec = "lobby", "lobby: the next dungeon room didn't start from here" end Brain.pending = (Fec == "replay" or Fec == "next_story") and { key = Bec.key, kind = Bec.kind, level = Bec.level, diff = Bec.diff, label = Bec.label, at = os.time() } or nil if Fec == "next_story" or Fec == "replay" then Brain.startTeam = { train = { key = Bec.key, kind = Bec.kind, first = Bec.first, level = Bec.level, diff = Bec.diff, label = Bec.label }, why = (Fec == "replay" and "replay: " or "next act: ") .. tostring(Bec.label) } end App.saveNow() App.status("Match", Gec) App.log(1, "MODE", Gec) if Fec == "stay" then return end task.wait(2.5) if Fec == "replay" or Fec == "next_story" then local Uec = ReplicatedStorage.endpoints.server_to_client local Vec, Wec = false, false local Xec = Uec.match_rematch_started.OnClientEvent:Connect(function() Vec = true end) local Yec = Uec.match_rematch_ready.OnClientEvent:Connect(function() Wec = true end) table.insert(App.conns, Xec) table.insert(App.conns, Yec) App.request("vote", "set_game_finished_vote", Fec) local Zec = Utils.now() while App.alive and Utils.now() - Zec < 30 and not Vec do task.wait(0.25) end if Vec then local _ec = Utils.now() while App.alive and Utils.now() - _ec < 60 and not Wec do task.wait(0.25) end if App.alive then App.log(1, "MODE", string.format("the match restarted in place in %.0f s (%s)", Utils.now() - Zec, Fec)) local afc, bfc = pcall(App.restart) if afc then return end App.log(1, "ERROR", "couldn't start again in place: " .. tostring(bfc)) else return end end end if App.alive then App.request("lobby", "teleport_back_to_lobby") end end function Match.watchMods() local cfc = workspace:FindFirstChild("ignore") if not cfc then return end local dfc = { } local efc, ffc = - 9, false local Jfc = cfc.ChildAdded:Connect(function(gfc) task.defer(function() local hfc = gfc:FindFirstChild("Gui") if not (hfc and gfc:IsA("BasePart")) then return end local ifc, jfc for kfc, lfc in ipairs(workspace._UNITS:GetChildren()) do local mfc = lfc:FindFirstChild("HumanoidRootPart") if mfc then local nfc = (mfc.Position - gfc.Position).Magnitude if nfc < 4 and (not jfc or nfc < jfc) then ifc, jfc = lfc, nfc end end end local ofc = ifc and ifc:FindFirstChild("_stats") if not (ofc and ofc:FindFirstChild("Armor")) then return end local pfc = ofc:FindFirstChild("player") if pfc and pfc.Value ~= nil then return end local qfc = { } for rfc, sfc in ipairs({ "Armor", "Ice", "Flying", "Soaring" }) do local tfc = ofc:FindFirstChild(sfc) qfc [sfc] = tfc and (tonumber(tfc.Value) or 0) > 0 end if qfc.Flying or qfc.Soaring then return end local ufc = (qfc.Armor and not qfc.Ice and "Armor") or (qfc.Ice and not qfc.Armor and "Ice") or ((not qfc.Armor and not qfc.Ice) and "plain") or nil if not ufc then return end if ufc == "Ice" then if os.clock() - efc > 2 then efc, ffc = os.clock(), Hunt.is(Match.ld and Match.ld.id) and Hunt.weatherNow() == "snow" end if ffc then ufc = "Ice@snow" end end local vfc = os.clock() for wfc, xfc in ipairs(hfc:GetChildren()) do local yfc = tonumber(xfc:GetAttribute("final_damage")) if yfc and yfc > 1 then local zfc, Afc = false, xfc:FindFirstChild("text") for Bfc, Cfc in ipairs(Afc and Afc:GetChildren() or { }) do if Cfc:IsA("ImageLabel") then zfc = true end end local Dfc = xfc.Name local Efc = { at = vfc, t = Dfc, dmg = yfc, crit = xfc:GetAttribute("is_crit") and true or false, kind = ufc, icon = zfc, res = tonumber(ofc:GetAttribute("_dmg_res_" .. Dfc)) or 0, weak = (tonumber(ofc:GetAttribute("_dmg_weak_" .. Dfc)) or 0) + (tonumber(ofc:GetAttribute("_dmg_amp_" .. Dfc)) or 0) } if not (ufc ~= "Armor" and ufc ~= "plain" and Dfc:find("fire")) then for Ffc, Gfc in ipairs(dfc) do if vfc - Gfc.at < 0.05 and Gfc.t == Dfc and Gfc.crit == Efc.crit and Gfc.res == Efc.res and Gfc.weak == Efc.weak then local Hfc = (Efc.kind ~= "plain" and Efc.icon and Efc) or (Gfc.kind ~= "plain" and Gfc.icon and Gfc) or nil local Ifc = (Efc.kind == "plain" and not Efc.icon and Efc) or (Gfc.kind == "plain" and not Gfc.icon and Gfc) or nil if Hfc and Ifc then Match.modPair(Hfc.kind, Hfc.dmg / Ifc.dmg) end end end end table.insert(dfc, Efc) end end while dfc [1] and vfc - dfc [1].at > 0.2 do table.remove(dfc, 1) end end) end) table.insert(App.conns, Jfc) end function Match.modPair(Kfc, Lfc) if Lfc <= 0.02 or Lfc >= 1.2 then return end local Mfc = Brain.enemyMods [Kfc] or { r = { } } Brain.enemyMods [Kfc] = Mfc table.insert(Mfc.r, Lfc) while # Mfc.r > 80 do table.remove(Mfc.r, 1) end local Nfc = Simulator.modRead(Mfc) local Ofc = Mfc.said and Nfc and math.abs(Nfc - Mfc.said) <= Mfc.said * 0.1 if Nfc and not Ofc then Mfc.said = Nfc end local Pfc, Qfc = Kfc == "Armor" and "Armored" or (Kfc == "Ice@snow" and "Ice (Blizzard)" or "Ice"), Kfc == "Armor" and "from full-area attacks" or "from everything but fire" if Nfc and not Ofc then App.log(1, "MATCH", string.format("%s enemies measured from %d real hits: they take ร—%.2f %s", Pfc, # Mfc.r, Nfc, Qfc)) end if Nfc == false and not Mfc.saidLoose then Mfc.saidLoose = true local Rfc = table.clone(Mfc.r) table.sort(Rfc) App.log(1, "MATCH", string.format("%s enemies: %d real hits so far disagree (ร—%.2f to ร—%.2f), so the simulator doesn't count them until they agree", Pfc, # Rfc, Rfc [1], Rfc [# Rfc])) end end function Match.startTeam() local Sfc = Brain.startTeam if type(Sfc) ~= "table" then return end Brain.startTeam = nil App.saveSoon() App.listenGame() if Sfc.train and Sfc.train.key ~= Match.key then return end if Sfc.seed and tostring(Brain.dg.room and Brain.dg.room.seed) ~= tostring(Sfc.seed) then return end local function Tfc(Ufc) Match.noBuy = true Brain.pending = nil App.log(1, "TEAM", string.format("%s: no wave started, back to the lobby (%s)", tostring(Ufc), tostring(Sfc.why))) App.saveNow() for Vfc = 1, 3 do App.request("lobby", "teleport_back_to_lobby") local Wfc = Utils.now() while App.alive and Utils.now() - Wfc < 5 do task.wait(0.5) end end end if workspace._DATA.GameStarted.Value then App.log(1, "TEAM", "too late for a team change before the first wave: " .. tostring(Sfc.why)) if Sfc.must and not Training.same(Training.team(), Sfc.uuids) then Tfc("the first wave had already started") end return end local Xfc = Utils.now() local Yfc = Training.team() local Zfc, _fc = pcall(function() if Sfc.train then Sfc.train.rec = Modes.rec(Sfc.train.key) Sfc.train.ldOverride = Simulator.slim(Match.ld) if Config.autoTeam then FarmHub.teamFor(Sfc.train, FarmHub.QUICK_SEARCH) end Training.apply(Sfc.train) elseif Sfc.home then Training.home() elseif Sfc.own then FarmHub.restoreTeam() else FarmHub.equipTeam(Sfc.uuids, tostring(Sfc.why)) end end) local agc = Training.team() local bgc = not Training.same(Yfc, agc) local cgc = App.gameSaid(Xfc) if bgc or not Zfc or cgc then App.log(1, "TEAM", string.format("team change before the first wave (%s): %s in %.1f s%s%s", tostring(Sfc.why), bgc and "the game took it" or "nothing changed", Utils.now() - Xfc, cgc and (" ยท the game said: " .. tostring(cgc)) or "", Zfc and "" or (" ยท error: " .. tostring(_fc)))) end if Sfc.must then if not Zfc or # agc < 6 or not Training.same(agc, Sfc.uuids) then Tfc(string.format("the team for this room didn't go on (%d of 6 units right)", (function() local dgc = 0 for egc, fgc in ipairs(agc) do if table.find(Sfc.uuids, fgc) then dgc = dgc + 1 end end return dgc end)())) return false end local ggc, hgc = pcall(Strategy.relicsApply) if not ggc then App.log(1, "ERROR", "relics: " .. tostring(hgc)) end end return bgc end function Match.run() App.status("Match", "loading the map") local igc = Utils.now() while App.alive and not (Core.Loader.LevelData and workspace:FindFirstChild("_MAP_CONFIG") and workspace._MAP_CONFIG:FindFirstChild("MapLoaded") and workspace._MAP_CONFIG.MapLoaded.Value) do if Utils.now() - igc > 60 then break end task.wait(0.5) end Match.ld = Core.Loader.LevelData or { } Match.key, Match.kind = Modes.fromLevelData(Match.ld) Match.key = Match.key or "?" if Brain.holdLobby and os.time() - (tonumber(Brain.holdLobby.at) or 0) < 90 then App.log(1, "MODE", "this match started just after you pressed Go To Lobby: going back") pcall(App.flush) App.request("lobby", "teleport_back_to_lobby") end if Brain.watch and Brain.watch.key == Match.key and os.time() - (tonumber(Brain.watch.at) or 0) > 120 then pcall(FarmHub.watchDone) for jgc, kgc in pairs(Modes.PORTALS) do if Match.key == "inf:" .. kgc.from then Brain.watch = { key = Match.key, portal = jgc, item = kgc.item, have = Adventure.have(kgc.item), held = # Modes.portalsHeld(jgc), used = false, at = os.time() } end end end Match.noSpot, Match.refusedRun, Match.unitRefusals = { }, { }, { } Match.style, Match.styleSig = nil, nil Match.refusals = 0 Match.startClock = os.clock() Match.startT = Utils.now() Match.placeLog, Match.styleWhy = { }, nil Match.starRun, Match.stars = nil, nil Match.joinedMid = (workspace._DATA.GameStarted.Value and Match.wave() > 2) or workspace._DATA.GameFinished.Value local lgc = { weak = { }, strong = { } } for mgc, ngc in pairs(Match.ld._weak_against or Match.ld.weak_against or { }) do if type(ngc) == "table" then lgc.weak [ngc.damage_type] = Modes.weakAmp(ngc.amplification) else lgc.weak [ngc] = 1.5 end end for ogc, pgc in pairs(Match.ld._strong_against or Match.ld.strong_against or { }) do lgc.strong [type(pgc) == "table" and pgc.damage_type or pgc] = true end if Match.key == "dc" and (Match.ld._daily_challenge_weak_against or Match.ld._daily_challenge_strong_against) then lgc = Modes.dailyMod(Match.ld._challenge_uuid, Match.ld._daily_challenge_weak_against, Match.ld._daily_challenge_strong_against) elseif Match.key == "dc" and Brain.live.daily and Brain.live.daily.mod then lgc = Brain.live.daily.mod end Match.mod = (next(lgc.weak) or next(lgc.strong) or next(lgc.res or { })) and lgc or Modes.levelMod(Match.ld.id) if Match.mod and Match.mod.res then local qgc = { } for rgc, sgc in pairs(Match.mod.weak) do table.insert(qgc, string.format("%s ร—%.2f", (rgc:gsub("_damage", "")), sgc)) end for tgc, ugc in pairs(Match.mod.res) do table.insert(qgc, string.format("%s ร—%.2f", (tgc:gsub("_damage", "")), ugc)) end table.sort(qgc) App.log(1, "MATCH", (Match.key == "dc" and "the Daily's damage taken by type, as every enemy carries it: " or "this level's weak points, weighted by enemy HP: ") .. table.concat(qgc, ", ")) end Match.buildPath() Match.buildWaves() Match.setupChecks() Match.buildCandidates() Match.random = Match.ld._randomequippedmode ~= nil or Core.session().override_equipped_units ~= nil Match.hunt = Hunt.is(Match.ld.id) if not Match.random then local vgc, wgc = pcall(Match.startTeam) if not vgc then App.log(1, "ERROR", "team change at the start: " .. tostring(wgc)) end end Match.team = Match.random and Match.randomTeam() or Units.equipped() if Match.random then App.log(1, "MATCH", "this mode deals its own team: " .. # Match.team .. " units in hand") end pcall(Match.watchHelpers) Match.trialId = nil do local xgc = Brain.trial local ygc = type(xgc) == "table" and type(xgc.list) == "table" and xgc.key == Match.key and not Match.joinedMid and xgc.list [tonumber(xgc.i) or 1] for zgc, Agc in ipairs(ygc and Match.team or { }) do if Agc.id == ygc then Match.trialId = ygc end end local Bgc = Match.trialId and xgc.study and Brain.study and Brain.study.id == Match.trialId and Brain.study.role or nil Match.studyTeam, Match.studyEntry = Bgc and Bgc.team or nil, Bgc and Bgc.entry or nil if Match.trialId then Match.style = "balanced" App.log(1, Bgc and "STUDY" or "TRIAL", string.format("%s %d of %d: %s%s", Bgc and "study run" or "trial", tonumber(xgc.i) or 1, # xgc.list, Units.name(ygc), Match.studyTeam and " plays with the whole team fighting" or " gets the yen, the rest of the team stands by") .. (Match.studyEntry and ", at the road's entrance" or "")) end end Match.xp0 = { } for Cgc, Dgc in ipairs(Match.team) do if Dgc.u and Dgc.uuid then Match.xp0 [Dgc.uuid] = tonumber(Dgc.u.xp) or 0 end end pcall(Units.capture) task.delay(15, function() local Egc, Fgc = pcall(Units.capture) if not Egc then App.log(1, "ERROR", "unit stats: " .. tostring(Fgc)) end end) Match.trainee, Match.trainTk = nil, 0 for Ggc, Hgc in ipairs(Match.team) do if Brain.train.on and Hgc.uuid == Brain.train.on.uuid then Match.trainee = Hgc.uuid end end local Igc = Match.ld._challenge or (Match.key == "dc" and Brain.live.daily and Brain.live.daily.challenge) or nil Match.calRun = { waveSec = { }, walk = { }, hp = { }, baseRel = { }, alive = { }, spawn = { }, spawnLast = { }, dpsAt = { }, dens = { }, pay = { }, skip = { }, timer = { }, votes = { } } Match.track = setmetatable({ }, { __mode = "k" }) local Jgc, Kgc = pcall(Simulator.info, Simulator.slim(Match.ld), Match.key, Igc, Match.mod) Match.info = Jgc and Kgc or nil if not Jgc then App.log(1, "ERROR", "sim info: " .. tostring(Kgc)) end if Match.info and Match.info.mapMods then local Lgc, Mgc = pcall(Match.watchMods) if not Lgc then App.log(1, "ERROR", "enemy types: " .. tostring(Mgc)) end end if Match.info and Match.kind == "dungeon" then local Ngc, Ogc = pcall(Dungeon.fx, Dungeon.data()) if Ngc then Simulator.applyFx(Match.info, Ogc) local Pgc = { } for Qgc, Rgc in pairs(Match.mod and Match.mod.weak or { }) do table.insert(Pgc, string.format("%s ร—%.2f", tostring(Qgc):gsub("_damage", ""), Rgc)) end App.log(1, "DUNGEON", string.format("this room's effects: %s ยท the run's items and curses: %s", # Pgc > 0 and table.concat(Pgc, ", ") or "none", Dungeon.fxText(Ogc))) end end if Match.info and Match.trialId then task.spawn(function() local Sgc = { } for Tgc, Ugc in ipairs(Match.team) do if Ugc.id == Match.trialId or Units.isFarm(Ugc.id) or Units.allyBuff(Ugc.id) then table.insert(Sgc, Ugc.u) end end local Vgc = { } local Wgc, Xgc = pcall(Simulator.run, Match.info, Simulator.teamFrom(Sgc, Match.info.map), { hpMul = 1, dealt = Vgc }) if Wgc and Xgc then Match.trialSim = { won = Xgc.won, wave = Xgc.wave, dealt = (Vgc [Units.name(Match.trialId)] or 0) + (Vgc.drones or 0), lvl = Xgc.levels and Xgc.levels [Match.trialId] } end end) end if Match.info and Config.simSpots and not Match.joinedMid and not Match.random and not Match.infinite and not Match.trialId and not Match.laneWant then Match.planning = Utils.now() task.spawn(function() local Ygc, Zgc = pcall(Match.planSpots) Match.planning = nil if not Ygc then App.log(1, "ERROR", "spot search: " .. tostring(Zgc)) end end) end Match.lastRes = Match.money() table.insert(App.conns, LocalPlayer._stats.resource.Changed:Connect(function(_gc) _gc = tonumber(_gc) or 0 local ahc = _gc - (Match.lastRes or _gc) Match.lastRes = _gc local bhc = Match.calRun if ahc <= 0 or not (bhc and Match.info) then return end local chc = Simulator.waveYen(Match.info, Match.wave(), Match.info.payBase) local dhc = Match.farmNow or 0 if chc > 0 and ahc >= 0.5 * (chc + dhc) then table.insert(bhc.pay, (ahc - dhc) / chc) bhc.paidT = Utils.now() end end)) task.spawn(function() local ehc, fhc = pcall(Match.recordMap) if not ehc then App.log(1, "ERROR", "map record: " .. tostring(fhc)) end end) if Match.info and not Match.joinedMid and not Match.random then task.spawn(function() task.wait(5) local ghc = { } for hhc, ihc in ipairs(Match.team) do table.insert(ghc, ihc.u) end local jhc = Utils.now() while Match.planning and Utils.now() - jhc < 20 do task.wait(0.5) end local khc, lhc = pcall(Simulator.run, Match.info, Simulator.teamFrom(ghc, Match.info.map), { hpMul = 1, train = Match.trainee, layout = Match.layoutSim }) if khc then Match.pred = lhc App.log(1, "SIM", Match.infinite and string.format("prediction for this run: the base falls at wave %d%s (%.0f min)", lhc.wave, lhc.won and "+" or "", lhc.t / 60) or string.format("prediction for this run: %s at wave %d", lhc.won and "win" or "loss", lhc.wave)) end end) end local mhc = ReplicatedStorage.endpoints.server_to_client table.insert(App.conns, mhc.game_finished.OnClientEvent:Connect(function(nhc) Match.result = nhc end)) table.insert(App.conns, mhc.show_roguelike_options.OnClientEvent:Connect(function(ohc, phc) Match.rogueOptions = phc end)) if mhc:FindFirstChild("show_roguelike_vote_result") then table.insert(App.conns, mhc.show_roguelike_vote_result.OnClientEvent:Connect(function(qhc, rhc) if type(rhc) == "table" then Match.fhMult = 1 + (tonumber(rhc.candy_multiplier) or 0) local shc, thc = { }, { } for uhc, vhc in pairs(rhc.persistent_effects or { }) do if type(vhc) == "table" then table.insert(shc, Match.effectText(vhc) .. (vhc.damage_type and (" " .. tostring(vhc.damage_type)) or "")) if vhc.type == "enemy_resistance_specific" and vhc.damage_type then thc [vhc.damage_type] = (thc [vhc.damage_type] or 0) + (tonumber(vhc.amount) or 0) end end end Match.fhEffects, Match.fhResist = shc, thc end Match.fhPicks = Match.fhPicks or { } table.insert(Match.fhPicks, string.format("w%d %s", Match.wave(), tostring(type(qhc) == "table" and (qhc.title or qhc.name) or qhc))) end)) end if mhc:FindFirstChild("start_vote_wave_skip") then table.insert(App.conns, mhc.start_vote_wave_skip.OnClientEvent:Connect(function() local whc = Match.calRun if not (whc and whc.lastWT and Match.info) then return end local xhc = Match.wave() local yhc = # (Simulator.wave(Match.info, xhc)) table.insert(whc.votes, { w = xhc, t = Utils.now() - whc.lastWT, n = yhc, alive = # (Match.enemies or { }), out = (tonumber(whc.spawn [tostring(xhc)]) or 0) >= yhc }) end)) end local zhc = Match.label() App.log(1, "MATCH", string.format("%s ยท %d waves ยท %d road samples ยท %d spots to try%s", zhc, Match.total, # Match.samples, # Match.cands, Match.joinedMid and " ยท joined mid-way" or "")) App.status("Match", zhc .. (Brain.pending and Brain.pending.key == Match.key and "" or " (not launched by the farm)")) local Ahc, Bhc, Chc = false, - 1, Utils.now() local Dhc, Ehc = nil, nil while App.alive do if Config.master and Config.battle then local gic, hic = pcall(function() Match.scan() local Fhc, Ghc = pcall(Match.measure) if not Fhc and FarmHub.every("measErr", 30) then App.log(1, "ERROR", "measure: " .. tostring(Ghc)) end if workspace._DATA.GameFinished.Value then Match.finish() return end if Config.autoStart and not Match.noBuy and not workspace._DATA.GameStarted.Value and (# Match.mine > 0 or Utils.now() - igc > 6) and (not Ahc or (Dhc and Utils.now() - Ahc > 6 and Utils.now() - Ehc < 60)) then Ehc = Ehc or Utils.now() local Hhc, Ihc = App.request("vstart", "vote_start") if Hhc and Dhc then App.log(1, "MATCH", string.format("the start vote went through %.0f s after the first one failed", Utils.now() - Ehc)) end Ahc, Dhc = Utils.now(), (not Hhc) and ((Ihc == "waiting" or Ihc == "slowing down") and Dhc or tostring(Ihc)) or nil if Dhc and Utils.now() - Ehc >= 54 then App.log(1, "MATCH", "the start vote kept failing (" .. Dhc .. "): the game starts on its own timer") end end local Jhc = workspace._DATA:FindFirstChild("Roguelike") if Jhc and Jhc:FindFirstChild("WaitingForRoguelikeChoice") and Jhc.WaitingForRoguelikeChoice.Value and FarmHub.every("rogue", 3) then Match.pickRogue() end if not Match.infinite and FarmHub.every("lastCash", 2) then local Khc, Lhc = pcall(Match.lastWaveCash) if not Khc and FarmHub.every("lastCashErr", 30) then App.log(1, "ERROR", "last-wave cash-in: " .. tostring(Lhc)) end end if Match.random and FarmHub.every("adapt", 2) then local Mhc, Nhc = pcall(Match.adapt) if not Mhc and FarmHub.every("adaptErr", 60) then App.log(1, "ERROR", "adaptation: " .. tostring(Nhc)) end end if Match.random and FarmHub.every("watch", 45) then local Ohc, Phc = pcall(Match.watchLine) if not Ohc and FarmHub.every("watchErr", 60) then App.log(1, "ERROR", "watch line: " .. tostring(Phc)) end end if Match.random and FarmHub.every("merge", 1) then local Qhc, Rhc = pcall(Match.mergeScan) if not Qhc and FarmHub.every("mergeErr", 30) then App.log(1, "ERROR", "merge plan: " .. tostring(Rhc)) end end if FarmHub.every("bossStep", 1) then local Shc, Thc = pcall(Match.bossStep) if not Shc and FarmHub.every("bossErr", 30) then App.log(1, "ERROR", "boss step: " .. tostring(Thc)) end end if not Match.stopping then Match.econ() end if FarmHub.every("act", 0.5) then Match.actives() end if FarmHub.every("prio", 2) then local Uhc, Vhc = pcall(Match.targeting) if not Uhc and FarmHub.every("prioErr", 30) then App.log(1, "ERROR", "targeting: " .. tostring(Vhc)) end end if Match.trainee and FarmHub.every("train", 2) then pcall(Match.trainCount) end if Match.random and FarmHub.every("dealt", 2) then Match.team = Match.randomTeam() end if Match.random and FarmHub.every("dealtStats", 30) then pcall(Units.capture) end if Match.random and FarmHub.every("room", 3) then local Whc, Xhc = pcall(Match.makeRoom) if not Whc and FarmHub.every("roomErr", 30) then App.log(1, "ERROR", "make room: " .. tostring(Xhc)) end local Yhc, Zhc = pcall(Match.refund) if not Yhc and FarmHub.every("refundErr", 30) then App.log(1, "ERROR", "refund: " .. tostring(Zhc)) end local _hc, aic = pcall(Match.fish) if not _hc and FarmHub.every("fishErr", 30) then App.log(1, "ERROR", "card fishing: " .. tostring(aic)) end end if Config.actives and FarmHub.every("spells", 1) then local bic, cic = pcall(Match.spells) if not bic and FarmHub.every("spellErr", 30) then App.log(1, "ERROR", "spells: " .. tostring(cic)) end end local dic = Match.wave() if Match.random and dic ~= Match.fhWaveSeen then Match.fhWaveSeen = dic pcall(Match.fhSnap) end if dic ~= Bhc then Bhc, Chc = dic, Utils.now() if Match.infinite then pcall(Match.infWave, dic) end end local eic = # (Match.enemies or { }) if eic > 0 then Match.emptyAt = nil elseif not Match.emptyAt then Match.emptyAt = Utils.now() end local fic = Match.total and dic >= Match.total and (eic > 0 or Utils.now() - Match.emptyAt < 30) if workspace._DATA.GameStarted.Value and Utils.now() - Chc > (fic and 1800 or 360) then App.log(1, "MATCH", string.format("no progress for %d min โ€” back to the lobby", fic and 30 or 6)) App.flush() Chc = Utils.now() App.request("lobby", "teleport_back_to_lobby") end end) if not gic then App.log(1, "ERROR", "match loop: " .. tostring(hic)) task.wait(1) end end if Match.finished then break end task.wait(0.3) end end function Reports.record(iic) iic.v = VERSION iic.at = os.time() table.insert(Brain.hiveQueue, iic) while # Brain.hiveQueue > 50 do table.remove(Brain.hiveQueue, 1) end end Reports.SETTINGS_SKIP = { seenVersion = true, shareNoticed = true, shareChosen = true, autoTeamChosen = true, studiedSeeded = true } function Reports.settingsOf() local jic, kic = { }, 0 for lic, mic in pairs(Core.DEFAULTS) do local nic = Config [lic] local oic = type(nic) local pic = oic == "boolean" or oic == "number" or (oic == "string" and # nic <= 24 and not nic:find("[{}%-]") and not tostring(lic):lower():find("unit")) if nic ~= mic and pic and not Reports.SETTINGS_SKIP [lic] and not tostring(lic):find("At$") and not tostring(lic):find("Sig$") then jic [lic] = oic == "number" and math.floor(nic * 100 + 0.5) / 100 or nic kic = kic + 1 end end return kic > 0 and jic or nil end function Reports.searchOf(qic) local ric = Brain.simTeam [qic] if type(ric) ~= "table" or type(ric.ids) ~= "table" or os.time() - (tonumber(ric.at) or 0) > 7200 then return nil end local sic = { } for tic, uic in ipairs(Match.team or { }) do sic [uic.id] = true end local vic = # ric.ids > 0 for wic, xic in ipairs(ric.ids) do if not sic [xic] then vic = false end end return { ids = ric.ids, won = ric.won and true or false, wave = ric.wave, cur = ric.cur, better = ric.better and true or nil, played = vic, ago = os.time() - (tonumber(ric.at) or 0) } end function Reports.modsOf() local function yic(zic) local Aic for Bic, Cic in pairs(type(zic) == "table" and zic or { }) do Aic = Aic or { } Aic [Bic] = type(Cic) == "number" and math.floor(Cic * 1000 + 0.5) / 1000 or Cic end return Aic end local Dic = { } if Match.mod then Dic.weak, Dic.res, Dic.strong = yic(Match.mod.weak), yic(Match.mod.res), yic(Match.mod.strong) end Dic.seed = Match.ld._is_daily_challenge and tonumber(Match.ld._challenge_uuid) or nil local Eic = { } for Fic, Gic in pairs(Match.info and Match.info.configs or { }) do local Hic = type(Gic) == "table" and Core.Units [Gic.unit_id] if type(Hic) == "table" then local Iic = type(Hic.enemy_modifiers) == "table" and Hic.enemy_modifiers or { } for Jic, Kic in pairs(Iic) do if Kic then Eic [Jic] = (Eic [Jic] or 0) + 1 end end if not Iic.Flying and Simulator.flies(Hic) then Eic.Flying = (Eic.Flying or 0) + 1 end end end if next(Eic) then Dic.enemy = Eic end for Lic, Mic in ipairs({ "Armor", "Ice", "Ice@snow" }) do local Nic = Brain.enemyMods [Mic] local Oic = Simulator.modRead(Nic) if Oic then Dic.measured = Dic.measured or { } Dic.measured [Mic] = { f = math.floor(Oic * 1000 + 0.5) / 1000, n = # Nic.r } end end return next(Dic) and Dic or nil end function Reports.mapOf(Pic) local Qic = Match.mapKey() local Ric = Brain.maps [Qic] or { } local Sic = Brain.simCal [Pic] or { } local function Tic(Uic) Uic = tonumber(Uic) return Uic and math.floor(Uic * 10000 + 0.5) / 10000 or nil end local Vic if type(Ric.dens) == "table" then Vic = { } for Wic = 1, Simulator.BINS do Vic [Wic] = math.floor((tonumber(Ric.dens [Wic]) or 0) * 100 + 0.5) / 100 end end return { key = Qic, walk = Tic(Simulator.mapWalk(Qic)), spots = tonumber(Ric.spots), hill = Ric.hill, lanes = type(Ric.lanes) == "table" and # Ric.lanes or nil, dens = Vic, cal = { waveSec = Tic(Sic.waveSec), spawnGap = Tic(Sic.spawnGap), yenCal = Tic(Sic.yenCal), bountyCal = Tic(Sic.bountyCal), baseRel = Tic(Sic.baseRel), countCal = Tic(Sic.countCal), skipDelay = Tic(Sic.skipDelay) } } end function Reports.match(Xic) local Yic, Zic = # game:GetService("Players"):GetPlayers() - 1, 0 for _ic, ajc in ipairs(workspace._UNITS:GetChildren()) do local bjc = ajc:FindFirstChild("_stats") local cjc = bjc and bjc:FindFirstChild("player") local djc = bjc and bjc:FindFirstChild("parent_unit") if cjc and cjc.Value ~= nil and cjc.Value ~= LocalPlayer and not (djc and djc.Value) then Zic = Zic + 1 end end local ejc = { } local function fjc(gjc) ejc [gjc] = ejc [gjc] or { dealt = 0, copies = 0, top = 0 } return ejc [gjc] end for hjc, ijc in pairs(Match.ownDealt or { }) do local jjc = fjc(ijc.id) jjc.dealt = jjc.dealt + (ijc.v or 0) jjc.copies = jjc.copies + 1 jjc.top = math.max(jjc.top, ijc.lvl or 0) end for kjc, ljc in pairs(Match.helperDealt or { }) do if ljc.owner then local mjc = fjc(ljc.owner) mjc.helpers = (mjc.helpers or 0) + (ljc.v or 0) end end for njc, ojc in ipairs(Match.mine or { }) do local pjc = ojc:FindFirstChild("_stats") local qjc = pjc and pjc:FindFirstChild("takedown_count") if pjc and pjc:FindFirstChild("id") and qjc then local rjc = fjc(pjc.id.Value) rjc.tk = (rjc.tk or 0) + (tonumber(qjc.Value) or 0) end end local sjc = { } for tjc, ujc in ipairs(Match.team or { }) do local vjc = ejc [ujc.id] or { } table.insert(sjc, { id = ujc.id, lvl = Strategy.level(ujc.u), trait = (Units.trait(ujc.u)), relic = Units.relicName(ujc.u), skin = ujc.u.skin and ujc.u.skin._skin_id or nil, copies = vjc.copies or 0, top = vjc.top or 0, dealt = math.floor(vjc.dealt or 0), helpers = vjc.helpers and math.floor(vjc.helpers) or nil, tk = vjc.tk, eff = Match.lastEff and Match.lastEff [ujc.id] or nil }) end local wjc = { } local xjc, yjc = Match.calRun or { }, Match.pred or { } local function zjc(Ajc, Bjc) return math.floor(tonumber(Ajc and (Ajc [Bjc] or Ajc [tostring(Bjc)])) or 0) end for Cjc = 1, Match.wave() do table.insert(wjc, { Cjc, zjc(xjc.alive, Cjc), zjc(xjc.dpsAt, Cjc), zjc(yjc.aliveAt, Cjc), zjc(yjc.dpsAt, Cjc) }) end local Djc = { } for Ejc, Fjc in pairs(Match.layout or { }) do if Fjc [1] then Djc [Ejc] = { math.floor(Fjc [1].X), math.floor(Fjc [1].Z) } end end local Gjc = Brain.simRes [Xic.key] or { } local Kjc = { v = VERSION, at = os.time(), mode = Xic.key, kind = Match.kind, level = Match.ld.id, map = Match.ld.map, diff = Match.ld._difficulty, challenge = Match.ld._challenge or (Match.key == "dc" and Brain.live.daily and Brain.live.daily.challenge) or nil, portal = Match.kind == "portal" and Brain.portalMods or nil, weather = Hunt.is(Match.ld.id) and Hunt.weatherNow() or nil, players = Yic, otherUnits = Zic, joinedMid = Match.joinedMid or nil, lanes = Match.laneCount and Match.laneCount.n or 1, result = { won = Xic.won and true or false, wave = Match.wave(), total = Match.total, sec = math.floor(tonumber(Xic.sec) or 0), gems = Xic.gems, coins = Xic.coins, spent = math.floor(Match.spent or 0), refused = Match.refusals or 0, items = (type(Xic.items) == "table" and next(Xic.items)) and Xic.items or nil }, sim = { pred = Match.pred and { won = Match.pred.won, wave = Match.pred.wave, min = math.floor((Match.pred.t or 0) / 6) / 10 } or nil, p = Gjc.p, margin = Gjc.playing or Gjc.margin, trust = Gjc.trust and Gjc.trust.ratio or nil, style = Match.style, why = Match.styleWhy, hpCal = Match.info and Match.info.cal and Match.info.cal.hpCal or nil, hpRun = Match.hpRun }, team = sjc, placed = Match.placeLog, plan = Djc, waves = wjc, trainee = Match.trainee and Brain.train.on and Brain.train.on.id or nil, trial = Match.trialId, hunt = Match.hunt and { picks = Match.fhPicks, how = Config.huntCards, strength = Match.fhP and math.floor(Match.fhP * 100 + 0.5) / 100 or nil, mult = Match.fhMult and math.floor(Match.fhMult * 100 + 0.5) / 100 or nil, starfall = Match.starSummary, floor = select(2, pcall(Hunt.floor)) } or nil, settings = select(2, pcall(Reports.settingsOf)), toggles = (function() local Hjc for Ijc in pairs(Match.togglesSeen or { }) do local Jjc = Brain.toggles [Ijc] if Jjc then Hjc = Hjc or { } Hjc [Ijc] = { from = Jjc.from, fill = math.floor((Jjc.fill or 0) * 100 + 0.5) / 100, drain = math.floor((Jjc.drain or 0) * 100 + 0.5) / 100 } end end return Hjc end)(), traineeTk = Match.trainee and Brain.train.on and Match.trainTk or nil, trainWhat = Match.trainee and Brain.train.on and Brain.train.on.what or nil, search = select(2, pcall(Reports.searchOf, Xic.key)), mods = select(2, pcall(Reports.modsOf)), mapData = select(2, pcall(Reports.mapOf, Xic.key)) } for Ljc, Mjc in ipairs({ "search", "mods", "mapData" }) do if type(Kjc [Mjc]) ~= "table" then Kjc [Mjc] = nil end end Brain.hiveN = (tonumber(Brain.hiveN) or 0) + 1 local Njc = Core.FOLDER .. "/hive" pcall(makefolder, Njc) local Ojc = string.format("%s/matches_%d.jsonl", Njc, math.floor((Brain.hiveN - 1) / 500) + 1) local Pjc = Utils.json(Kjc) .. "\n" if appendfile and isfile and isfile(Ojc) then pcall(appendfile, Ojc, Pjc) else local Qjc, Rjc = pcall(readfile, Ojc) pcall(writefile, Ojc, (Qjc and Rjc or "") .. Pjc) end if Config.shareStats and Core.WEBHOOK_URL ~= "" then Kjc.id = Brain.anonId table.insert(Brain.aaHiveQ, Reports.fit(Kjc)) while # Brain.aaHiveQ > 30 do table.remove(Brain.aaHiveQ, 1) end task.spawn(function() pcall(Reports.sendHive) end) end App.saveSoon() end function Reports.fit(Sjc) local Tjc = Utils.json(Sjc) if # Tjc <= 3900 then return Tjc end for Ujc, Vjc in ipairs({ 2, 5, 10 }) do local Wjc = { } for Xjc, Yjc in ipairs(Sjc.waves or { }) do if Xjc % Vjc == 0 or Xjc == # Sjc.waves then table.insert(Wjc, Yjc) end end Sjc.waves = Wjc Tjc = Utils.json(Sjc) if # Tjc <= 3900 then return Tjc end end for Zjc, _jc in ipairs(Sjc.placed or { }) do _jc.why = nil end Tjc = Utils.json(Sjc) if # Tjc <= 3900 then return Tjc end if Sjc.mapData then Sjc.mapData.dens = nil end Tjc = Utils.json(Sjc) if # Tjc <= 3900 then return Tjc end Sjc.placed, Sjc.plan = nil, nil return Utils.json(Sjc):sub(1, 3900) end function Reports.postEmbed(akc, bkc) local ckc = (syn and syn.request) or (http and http.request) or http_request or request if not ckc or Core.WEBHOOK_URL == "" then return false end local dkc, ekc = pcall(ckc, { Url = Core.WEBHOOK_URL, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = Utils.json({ username = "RobScript Team", allowed_mentions = { parse = { } }, embeds = { { title = akc, description = bkc, color = 15418782 } } }) }) local fkc = dkc and type(ekc) == "table" and tonumber(ekc.StatusCode or ekc.Status) or nil return dkc and (not fkc or fkc < 300) end function Reports.sendHive() if not (Config.shareStats and Core.WEBHOOK_URL ~= "") or # Brain.aaHiveQ == 0 or Reports.sending then return end Reports.sending = true local gkc, hkc = pcall(Reports.postEmbed, string.format("๐ŸŽŒ AA ยท match ยท %s ยท v%s", tostring(Brain.anonId), VERSION), "```json\n" .. Brain.aaHiveQ [1] .. "\n```") Reports.sending = nil if gkc and hkc then table.remove(Brain.aaHiveQ, 1) Brain.hiveSent = (tonumber(Brain.hiveSent) or 0) + 1 pcall(App.saveNow) end end function Reports.sendAll() if not (Config.shareStats and Core.WEBHOOK_URL ~= "") then return 0, # Brain.aaHiveQ end local ikc = 0 for jkc = 1, 10 do local kkc = # Brain.aaHiveQ if kkc == 0 then break end Reports.sendHive() if # Brain.aaHiveQ >= kkc then break end ikc = ikc + 1 task.wait(1.5) end return ikc, # Brain.aaHiveQ end function Reports.post(lkc) if Core.WEBHOOK_URL == "" then return false, "not connected" end local mkc = (syn and syn.request) or (http and http.request) or http_request or request if not mkc then return false, "no http" end local nkc, okc = pcall(mkc, { Url = Core.WEBHOOK_URL, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = Utils.json({ content = lkc, allowed_mentions = { parse = { } } }) }) return nkc and okc and (okc.StatusCode == 200 or okc.StatusCode == 204), okc end function Reports.flush() if Core.WEBHOOK_URL == "" or not Config.shareStats or # Brain.hiveQueue == 0 then return false, "not connected" end local pkc = Reports.post("๐Ÿ“Š AA " .. Utils.json({ id = Brain.anonId, v = VERSION, runs = Brain.hiveQueue })) if pkc then table.clear(Brain.hiveQueue) App.saveSoon() end return pkc end Reports.HIVE_FILE = Core.FOLDER .. "/hive_shared.json" function Reports.hive() if not Config.useHive then return nil end if Reports.shared == nil then local qkc = Utils.readJson(Reports.HIVE_FILE) Reports.shared = type(qkc) == "table" and type(qkc.units) == "table" and qkc or false end return Reports.shared or nil end function Reports.hiveUnit(rkc) local skc = Reports.hive() local tkc = skc and skc.units [rkc] if type(tkc) == "table" and (tonumber(tkc.eff) or 0) > 0 and ((tonumber(tkc.matches) or 0) >= 5 or (tonumber(tkc.players) or 0) >= 2) then return tkc end end function Reports.hiveTrain(ukc, vkc) local wkc = Reports.hive() local xkc = wkc and type(wkc.train) == "table" and wkc.train [ukc] if type(xkc) ~= "table" then return nil end local ykc = vkc and type(xkc.units) == "table" and xkc.units [vkc] if type(ykc) == "table" and (tonumber(ykc.runs) or 0) >= 3 then return tonumber(ykc.perRun) end if (tonumber(xkc.runs) or 0) >= 3 then return tonumber(xkc.perRun) end end function Reports.hiveToggle(zkc) local Akc = Reports.hive() local Bkc = Akc and type(Akc.toggles) == "table" and Akc.toggles [zkc] return type(Bkc) == "table" and Bkc or nil end function Reports.hiveStat(Ckc) local Dkc = Reports.hive() local Ekc = Dkc and type(Dkc.stats) == "table" and Dkc.stats [Ckc] return type(Ekc) == "table" and Ekc or nil end function Reports.hiveHp(Fkc) local Gkc = Reports.hive() local Hkc = Gkc and type(Gkc.maps) == "table" and Gkc.maps [Fkc] return type(Hkc) == "table" and (tonumber(Hkc.runs) or 0) >= 3 and tonumber(Hkc.hpCal) or nil end function Reports.hiveDrops(Ikc) local Jkc = Reports.hive() local Kkc = Ikc and Jkc and type(Jkc.drops) == "table" and Jkc.drops [Ikc] if type(Kkc) ~= "table" or (tonumber(Kkc.runs) or 0) < 3 or type(Kkc.items) ~= "table" then return nil end return Kkc.items end function Reports.sendUnit() if not (Config.shareStats and Core.WEBHOOK_URL ~= "") or Reports.sending then return end local Lkc = Reports.hive() local Mkc = Lkc and type(Lkc.stats) == "table" and Lkc.stats or { } for Nkc, Okc in pairs(Brain.unitData) do if not Brain.unitSent [Nkc] and not Mkc [Nkc] and type(Okc) == "table" and Okc.upgrade then local Pkc = { } for Qkc, Rkc in pairs(Okc) do if Qkc ~= "passives" then Pkc [Qkc] = Rkc end end local Skc, Tkc = pcall(Utils.json, { id = Nkc, data = Pkc }) if Skc and # Tkc <= 3900 then Reports.sending = true local Ukc, Vkc = pcall(Reports.postEmbed, string.format("๐ŸŽŒ AA ยท unit ยท %s ยท v%s", tostring(Brain.anonId), VERSION), "```json\n" .. Tkc .. "\n```") Reports.sending = nil if Ukc and Vkc then Brain.unitSent [Nkc] = os.time() App.saveSoon() end else Brain.unitSent [Nkc] = - 1 App.saveSoon() end return end end end function Reports.hiveTrainRate(Wkc) local Xkc = Reports.hive() local Ykc = Xkc and type(Xkc.train) == "table" and Xkc.train [Wkc] return type(Ykc) == "table" and (tonumber(Ykc.runs) or 0) >= 3 and tonumber(Ykc.perMin) or nil end function Reports.hiveFetch() if Core.HIVE_URL == "" or not Config.useHive then return end local Zkc, _kc = pcall(game.HttpGet, game, Core.HIVE_URL .. "?t=" .. os.time()) if not Zkc or type(_kc) ~= "string" or _kc:sub(1, 1) ~= "{" then return end local alc, blc = pcall(HttpService.JSONDecode, HttpService, _kc) if not (alc and type(blc) == "table" and tonumber(blc.v) == 1 and type(blc.units) == "table") then return end local clc = Reports.hive() if clc and clc.updated == blc.updated then return end pcall(Utils.writeJson, Reports.HIVE_FILE, blc) Reports.shared = blc table.clear(Units._rows) table.clear(Simulator._holds) Adventure._src = nil local dlc, elc = 0, 0 for flc in pairs(blc.units) do dlc = dlc + 1 end for glc in pairs(type(blc.train) == "table" and blc.train or { }) do elc = elc + 1 end App.log(1, "HIVE", string.format("learned from %d players' %d matches: readings for %d units, takedown rates on %d maps", tonumber(blc.players) or 0, tonumber(blc.matchRecords) or 0, dlc, elc)) local hlc = { } for ilc, jlc in ipairs(type(blc.under) == "table" and blc.under or { }) do if Units.owns(jlc) and not (Brain.learn [jlc] and (Brain.learn [jlc].n or 0) >= 3) then table.insert(hlc, string.format("%s (ร—%.1f)", Units.name(jlc), tonumber(blc.units [jlc] and blc.units [jlc].eff) or 0)) end end if # hlc > 0 then App.log(1, "HIVE", "other players' runs say the simulator reads these of yours far too low: " .. table.concat(hlc, ", ") .. ". A trial run would check them") if App.notify then App.notify("Local learning", "Units the simulator underrates: " .. table.concat(hlc, ", "), "info") end end end function Reports.hiveText() if not Config.useHive then return "Off: the farm learns only from your own runs." end local klc = Reports.hive() if not klc then return "Nothing shared yet. Once other players' farms send their matches, the farm reads what they learned here every 6 hours." end local llc, mlc, nlc, olc, plc, qlc = 0, 0, 0, 0, 0, 0 for rlc in pairs(klc.units) do if Reports.hiveUnit(rlc) and not (Brain.learn [rlc] and (Brain.learn [rlc].n or 0) >= 3) then llc = llc + 1 end end for slc in pairs(type(klc.train) == "table" and klc.train or { }) do mlc = mlc + 1 end for tlc in pairs(type(klc.stats) == "table" and klc.stats or { }) do if not Brain.unitData [tlc] then nlc = nlc + 1 end end for ulc in pairs(type(klc.maps) == "table" and klc.maps or { }) do if Reports.hiveHp(ulc) and not (Brain.simCal [ulc] and Brain.simCal [ulc].hpCal) then olc = olc + 1 end end for vlc in pairs(type(klc.drops) == "table" and klc.drops or { }) do if Reports.hiveDrops(vlc) then plc = plc + 1 end end for wlc in pairs(type(klc.toggles) == "table" and klc.toggles or { }) do if not Brain.toggles [wlc] then qlc = qlc + 1 end end local xlc = { string.format("From %d players' %d matches, updated %s.", tonumber(klc.players) or 0, tonumber(klc.matchRecords) or 0, os.date("%b %d %H:%M", tonumber(klc.updated) or 0)), string.format("What it fills in that your own runs haven't: %d units' readings, %d hidden units' real stats, %d switch abilities, how tough %d maps really are, what %d levels really drop, and takedown rates on %d maps.", llc, nlc, qlc, olc, plc, mlc) } local ylc = { } for zlc, Alc in ipairs(type(klc.under) == "table" and klc.under or { }) do table.insert(ylc, Units.name(Alc) .. (Units.owns(Alc) and " (yours)" or "")) end if # ylc > 0 then table.insert(xlc, "The simulator reads these far too low: " .. table.concat(ylc, ", ") .. ".") end return table.concat(xlc, "\n") end function Reports.suggest(Blc) Blc = tostring(Blc or ""):sub(1, 500):gsub("https?://%S+", "[link]"):gsub("@", "(at)") if Blc:gsub("%s", "") == "" then return false, "empty" end if Core.WEBHOOK_URL == "" then return false, "suggestions aren't connected yet" end if os.time() - (Brain.lastSuggest or 0) < 600 then return false, "one suggestion per 10 minutes" end local Clc = Reports.postEmbed(string.format("๐ŸŽŒ AA ยท suggestion ยท %s ยท v%s", tostring(Brain.anonId), VERSION), Blc) if Clc then Brain.lastSuggest = os.time() end return Clc, Clc and nil or "send failed" end -- Airflow UI integration. Game logic keeps its existing handles; every widget is -- constructed by Robly Airflow using the documented one-table options API. function GUI.load() if GUI.Airflow then return GUI.Airflow end local ok, result = pcall(function() local source = game:HttpGet(Core.AIRFLOW_URL) local chunk, err = loadstring(source, "=Robly_Airflow") assert(chunk, err) return chunk() end) if not ok or type(result) ~= "table" or type(result.CreateWindow) ~= "function" then App.log(0, "ERROR", "Robly Airflow could not be loaded: " .. tostring(result)) return nil end GUI.Airflow = result return result end function GUI.escape(value) return (tostring(value or ""):gsub("&", "&"):gsub("<", "<"):gsub(">", ">")) end function GUI.theme(Airflow) local theme = { Background = Color3.fromHex("#0A0A0C"), Surface = Color3.fromHex("#141418"), Surface2 = Color3.fromHex("#1A1A20"), Surface3 = Color3.fromHex("#2A2A33"), Stroke = Color3.fromHex("#26262E"), StrokeHover = Color3.fromHex("#3C3C49"), Accent = Color3.fromHex("#FF3B7F"), AccentDark = Color3.fromHex("#1A0710"), Text = Color3.fromHex("#F5F5F7"), Muted = Color3.fromHex("#8D8D99"), } Airflow.ThemePresets.Robscript = theme Airflow:SetDefaultTheme(theme) GUI.THEME = theme -- Status colours are looked up live, so the native theme manager works. local aliases = { bg = "Background", panel = "Surface", card = "Surface2", border = "Stroke", accent = "Accent", accentD = "AccentDark", accentBg = "Surface3", text = "Text", sub = "Muted", green = "Success", red = "Error", gold = "Warning", orange = "Warning", purple = "Accent", } GUI.COL = setmetatable({}, { __index = function(_, key) local current = Airflow.Theme or GUI.THEME return current[aliases[key] or key] end }) end -- Navigation describes player tasks. Legacy names below identify saved flags; -- changing a page or group label must never change a player's profile keys. GUI.NAV_SCHEMA = { { Name = "Home", Icon = "house" }, { Name = "Farm", Icon = "gamepad-2", Pages = { { "Stages", "map" }, { "Challenges", "trophy" }, { "Events", "sun" }, { "Party", "users" }, { "Rules", "sliders-horizontal" }, } }, { Name = "Units", Icon = "users", Pages = { { "Goals", "target" }, { "Upgrades", "trending-up" }, { "Summon", "sparkles" }, { "Fusion", "flame" }, { "Study", "microscope" }, } }, { Name = "Battle", Icon = "swords", Pages = { { "Controls", "crosshair" }, { "Planner", "brain" }, { "Economy", "coins" }, } }, { Name = "Activity", Icon = "scroll-text", Pages = { { "Log", "scroll-text" }, { "Spending", "wallet" }, { "History", "history" }, { "Systems", "activity" }, } }, { Name = "Settings", Icon = "settings", Pages = { { "General", "settings-2" }, { "Profiles", "save" }, { "Theme", "palette" }, { "Updates", "newspaper" }, } }, } -- root, subpage, visible group, column, collapsed GUI.LAYOUT = { Home = { Overview = { "Home", "", "Farm control", "Left" }, ["Right now"] = { "Home", "", "Current run", "Right" }, ["At a glance"] = { "Home", "", "Session", "Right" }, ["Every part"] = { "Activity", "Systems", "Automation status", "Right" }, }, Play = { Overview = { "Farm", "Stages", "Mode selection", "Left" }, ["Story and Legend Stages"] = { "Farm", "Stages", "Story & Legend", "Right" }, ["Infinite and Portals"] = { "Farm", "Stages", "Infinite & Portals", "Left" }, ["Raids and Dungeon"] = { "Farm", "Stages", "Raids & Dungeon", "Right" }, Challenges = { "Farm", "Challenges", "Challenge rules", "Left" }, Matchmaking = { "Farm", "Party", "Find players", "Left" }, ["Summer Skins and Stars"] = { "Farm", "Events", "Skins & stars", "Right" }, ["Fool's Hunt"] = { "Farm", "Events", "Event progress", "Right" }, ["What it plays next (gems a minute)"] = { "Battle", "Planner", "Mode ranking", "Left" }, ["Run records"] = { "Activity", "History", "Completed runs", "Left" }, Advanced = { "Farm", "Rules", "Mode priorities", "Left", true }, }, Goals = { Overview = { "Units", "Goals", "Current goal", "Left" }, Focus = { "Units", "Goals", "Current goal", "Left" }, ["Ready now"] = { "Units", "Goals", "Ready to evolve", "Right" }, ["Evolutions in reach (strongest first)"] = { "Units", "Goals", "Evolution paths", "Right", true }, ["Takedown training"] = { "Units", "Goals", "Takedown training", "Left" }, ["How it chases goals"] = { "Units", "Goals", "Goal priorities", "Left", true }, Notes = { "Units", "Goals", "Recommendations", "Right", true }, }, Team = { Overview = { "Units", "Upgrades", "Team upgrades", "Left" }, Next = { "Units", "Upgrades", "Team upgrades", "Left" }, ["Core units"] = { "Units", "Upgrades", "Core units", "Right", true }, Levels = { "Units", "Upgrades", "XP food", "Left" }, Gear = { "Units", "Upgrades", "Relics & limit breaks", "Right" }, Advanced = { "Units", "Upgrades", "Upgrade rules", "Left", true }, }, Summon = { Overview = { "Units", "Summon", "Banner snipe", "Left" }, ["Banner Snipe"] = { "Units", "Summon", "Banner snipe", "Left" }, ["Auto Summon"] = { "Units", "Summon", "Repeat summons", "Right" }, ["Smart pulls"] = { "Units", "Summon", "Goal summons", "Right", true }, Display = { "Units", "Summon", "Animations", "Left", true }, }, Fuse = { Overview = { "Units", "Fusion", "Limit break", "Left" }, ["Limit Break"] = { "Units", "Fusion", "Limit break", "Left" }, ["Fuse For XP"] = { "Units", "Fusion", "Fuse for XP", "Right" }, }, Battle = { Overview = { "Battle", "Controls", "Battle automation", "Left" }, ["In a match"] = { "Battle", "Controls", "Battle automation", "Left" }, ["Your team at full upgrade"] = { "Battle", "Controls", "Equipped units", "Right" }, ["Simulator and teams"] = { "Battle", "Planner", "Team planning", "Right" }, ["Advanced: farm or damage"] = { "Battle", "Economy", "Farm income", "Left" }, }, Activity = { Overview = { "Activity", "Log", "Live log", "Left" }, ["What each part is doing"] = { "Activity", "Systems", "Subsystems", "Left" }, ["Where your gold, gems and coins went"] = { "Activity", "Spending", "Spending summary", "Left" }, Log = { "Activity", "Log", "Live log", "Left" }, }, Settings = { Overview = { "Settings", "General", "General", "Left" }, General = { "Settings", "General", "General", "Left" }, ["Gold shop"] = { "Settings", "General", "Gold shop", "Right" }, ["Study A Unit"] = { "Units", "Study", "Study a unit", "Left" }, Controls = { "Settings", "General", "Script", "Right" }, }, } GUI.CONTROL_LAYOUT = { ["Keep Playing Daily"] = { "Farm", "Challenges", "Daily challenge", "Right" }, ["Daily In A Kill Box"] = { "Farm", "Challenges", "Daily challenge", "Right" }, ["Gems To Keep"] = { "Units", "Summon", "Snipe budget", "Left" }, ["Pearls To Keep"] = { "Units", "Summon", "Snipe budget", "Left" }, ["Legacy Gems To Keep"] = { "Units", "Summon", "Snipe budget", "Left" }, ["Safe Road"] = { "Battle", "Economy", "Defense thresholds", "Right", true }, ["Safe Kill Time"] = { "Battle", "Economy", "Defense thresholds", "Right", true }, ["Danger Road"] = { "Battle", "Economy", "Defense thresholds", "Right", true }, ["Save Threshold"] = { "Battle", "Economy", "Defense thresholds", "Right", true }, ["Reset Mode Stats"] = { "Activity", "History", "History tools", "Right", true }, ["Reset Unit Learning"] = { "Battle", "Planner", "Planner tools", "Right", true }, ["Go To Lobby"] = { "Home", "", "Farm control", "Left" }, ["Start Farming Again"] = { "Home", "", "Farm control", "Left" }, ["Log Level"] = { "Activity", "Log", "Logging", "Right" }, ["Print To Console"] = { "Activity", "Log", "Logging", "Right" }, } GUI.SHORT_TEXT = { ["Auto Farm"] = { "Auto Farm", "Choose modes, start runs and play automatically." }, ["Auto Play"] = { "Chain runs", "Start the next eligible stage after a run." }, ["Story Map"] = { "Story map", "Smart clears new acts before replaying stages." }, ["Legend Stage"] = { "Legend stage", "Smart picks stages that advance your goals." }, ["Infinite Map"] = { "Infinite map", "Smart weighs daily gems and takedowns." }, ["End Infinite Early"] = { "Leave after daily gems", "Finish when another mode becomes more rewarding." }, ["Play Portals You Can't Beat"] = { "Ignore portal win estimate", "Allow your portals even if the simulator predicts a loss." }, ["Start New Dungeon Runs"] = { "Start new dungeon runs", "Begin another dungeon after the current run ends." }, ["Only Ones You Can Beat"] = { "Require win estimate", "Skip challenges below the win chance set here." }, ["Win Chance Needed"] = { "Minimum win chance", "Required estimate when the win filter is enabled." }, ["Skip These Challenges"] = { "Excluded challenges", "Never queue challenges with these modifiers." }, ["Keep Playing Daily"] = { "Repeat until daily win", "Prioritize the daily challenge until it is won." }, ["Daily In A Kill Box"] = { "Daily kill box", "Cluster units near the exit during repeated daily runs." }, ["Matchmake The Daily"] = { "Daily challenge", "Look for players before starting the daily." }, ["Matchmake Raids"] = { "Raids", "Find a party for raids." }, ["Matchmake Infinite"] = { "Infinite", "Find a party for Infinite." }, ["Matchmake Legend Stages"] = { "Legend stages", "Find a party for Legend Stages." }, ["Matchmake The Summer Hunt"] = { "Summer Hunt", "Find a party for the current Summer Hunt." }, ["Matchmaking Wait"] = { "Party wait", "After this time, start alone if no party joined." }, ["Focus On"] = { "Target evolution", "Choose one unit, or let Smart pick your goal." }, ["Takedown Training"] = { "Train takedowns", "Bring a trainee when the rest of the team can win." }, ["Leave When Trained"] = { "Return when trained", "Leave Infinite once training and nearby daily gems are done." }, ["Make My Team Stronger"] = { "Automatic upgrades", "Improve your core units using available resources." }, ["Level Up With XP Food"] = { "Feed XP food", "Give food to the core unit that benefits most." }, ["Buy XP Food"] = { "Buy XP food", "Buy efficient XP food while preserving your gold floor." }, ["Limit Break Core Units"] = { "Auto limit break core", "Use spare units after showing the planned fodder." }, ["Dry Run"] = { "Preview upgrades only", "Show the plan without spending resources." }, ["Unit To Snipe"] = { "Target unit", "Pull from the banner featuring this unit." }, ["Banner Snipe"] = { "Snipe target", "Keep pulling until the target drops or a budget is reached." }, ["Gems To Keep"] = { "Gems reserve", "Snipe never spends below this reserve." }, ["Pearls To Keep"] = { "Pearls reserve", "Snipe never spends below this reserve." }, ["Legacy Gems To Keep"] = { "Legacy gems reserve", "Snipe never spends below this reserve." }, ["Keep At Least"] = { "Currency reserve", "Repeat summons keep this amount of the selected currency." }, ["Unit To Limit Break"] = { "Target unit", "Choose a unit whose level cap you want to raise." }, ["Auto Limit Break"] = { "Enable limit break", "Uses the selected fodder after the preview countdown." }, ["Unit To Feed"] = { "Unit to level", "Feed eligible duplicates into this unit for XP." }, ["Use Units Up To"] = { "Maximum fodder rarity", "Mythic and Secret units are excluded from XP feeding." }, ["Auto Fuse For XP"] = { "Enable XP fusion", "Consume eligible units up to the target's level cap." }, ["Auto-Equip Best Team"] = { "Equip planned team", "Use the stronger team before a run, then restore yours." }, ["Simulate Now"] = { "Recalculate plan", "Run the simulator and team search in the lobby." }, ["Equip Best Team"] = { "Equip planned team", "Equip the best team found in the lobby." }, ["Restore My Team"] = { "Restore my team", "Put your original team back in the lobby." }, ["Go To Lobby"] = { "Return to lobby", "Leave this run and hold future match starts." }, ["Start Farming Again"] = { "Resume after lobby hold", "Clear the lobby hold and allow match starts." }, ["Battle AI"] = { "Battle automation", "Control placement and upgrades inside a match." }, ["Ability Hold"] = { "Ability hold", "Wait this long for a boss or a crowd." }, ["Smart Charm Equip"] = { "Choose charm automatically", "Prefer rewards when strong enough, damage when needed." }, ["Smart Skins"] = { "Manage event skins", "Equip useful skins and sell spare ones listed here." }, ["Open Sunny Stars"] = { "Open Sunny Stars", "Use pearls above the reserve for the planned target." }, ["Study Runs"] = { "Study runs", "Number of real matches to measure the selected unit." }, } GUI.PAGE_HELP = { Home = "", Play = "Enabled modes compete by rewards. Only this mode restricts the farm; Disabled skips a mode.", Goals = "Choose an evolution target. Its items and takedowns take priority.", Team = "Upgrade core units. Preview mode shows spending before you enable it.", Summon = "Choose a target or banner, set reserves, then enable summons.", Fuse = "Select a target and review its fodder before enabling fusion. Used units are consumed.", Battle = "Control how the farm fights. Team predictions are under Planner.", Activity = "", Settings = "", } function GUI.prepareNavigation(window) GUI.Nav, GUI.Blocks = {}, {} for _, section in ipairs(GUI.NAV_SCHEMA) do local root = window:CreateTab({ Name = section.Name, Icon = section.Icon }) local entry = { Root = root, Pages = {} } GUI.Nav[section.Name] = entry if section.Pages then for _, spec in ipairs(section.Pages) do entry.Pages[spec[1]] = root:CreateSubTab({ Name = spec[1], Icon = spec[2], EmptyText = section.Name == "Farm" and spec[1] == "Events" and "No supported event is currently available." or "Waiting for game data.", }) end else entry.Pages[""] = root end end GUI.Nav.Settings.Pages.Profiles:CreateConfigManager({ Name = "Saved profiles", Side = "Left" }) GUI.Nav.Settings.Pages.Theme:CreateThemeManager({ Name = "Appearance", Side = "Left" }) end function GUI.block(rootName, pageName, title, side, collapsed) local id = rootName .. "/" .. pageName .. "/" .. title if GUI.Blocks[id] then return GUI.Blocks[id] end local entry = assert(GUI.Nav[rootName], "Unknown navigation section: " .. rootName) local host = assert(entry.Pages[pageName], "Unknown page: " .. pageName) local group = host:CreateGroupbox({ Name = title, Side = side or "Left", Collapsed = collapsed == true }) GUI.Blocks[id] = group return group end function GUI.sectionSpec(page) local route = GUI.LAYOUT[page.Name] local spec = route and route[page.GroupName] if spec then return spec end if page.Name == "Play" then return { "Farm", "Events", page.GroupName, "Left" } end if page.Name == "Updates" then return { "Settings", "Updates", page.GroupName, "Left", page.SectionCount > 1 } end error("Unmapped UI section: " .. page.Name .. "/" .. page.GroupName) end function GUI.controlGroup(page, name) local spec = GUI.CONTROL_LAYOUT[name] or GUI.sectionSpec(page) return GUI.block(table.unpack(spec)) end function GUI.controlText(name, description) local entry = GUI.SHORT_TEXT[name] local short = entry and entry[2] or description or "" if #short > 82 then short = "" end return entry and entry[1] or name, short end function GUI.controlDependency(name) if name == "Win Chance Needed" then return function() return Config.chSmart == true end end if name == "Daily In A Kill Box" then return function() return Config.dailyLoop == true end end if name == "Ability Hold" then return function() return Config.actives == true end end if name == "Leave When Trained" then return function() return Config.trainTakedowns == true end end end function GUI.go(rootName, pageName) local entry = assert(GUI.Nav[rootName], "Unknown navigation target") GUI.NativeWindow:SelectTab(entry.Root) if pageName and pageName ~= "" then entry.Root:SelectSubTab(entry.Pages[pageName]) end end function GUI.shortcuts() local box = GUI.block("Home", "", "Go to", "Left") local links = { { "Modes", "Farm", "Stages" }, { "Goals", "Units", "Goals" }, { "Summon", "Units", "Summon" }, { "Fusion", "Units", "Fusion" }, { "Planner", "Battle", "Planner" }, { "Logs", "Activity", "Log" }, } for first = 1, #links, 3 do local row = {} for index = first, math.min(first + 2, #links) do local target = links[index] table.insert(row, { Name = target[1], Callback = function() GUI.go(target[2], target[3]) end }) end box:CreateButtonRow(row) end end function GUI.flag(page, name) local base = "AA/" .. page.Name .. "/" .. page.GroupName .. "/" .. name GUI.flagCounts = GUI.flagCounts or {} local n = (GUI.flagCounts[base] or 0) + 1 GUI.flagCounts[base] = n return n == 1 and base or (base .. "/" .. n) end -- The farm uses dot calls for live updates. Keep that contract at its boundary, -- while every Airflow handle itself is always called with colon syntax. function GUI.valueHandle(native, scale) local handle = { Native = native, _isProxy = true } function handle.Set(a, b, c) if a == handle then return native:Set(scale and b / scale or b, c) end return native:Set(scale and a / scale or a, b) end function handle.Get() local value = native:Get() return scale and value * scale or value end return handle end function GUI.paragraph(group, text, title) local native if title and title ~= "" then native = group:CreateParagraph({ Title = GUI.escape(title), Content = GUI.escape(text) }) else native = group:CreateLabel({ Text = GUI.escape(text), Visible = text ~= nil and text ~= "" }) end local handle = { Native = native } function handle.Set(a, b) local value = a == handle and b or a native:Set(GUI.escape(value)) native:SetVisible(value ~= nil and value ~= "") end return handle end function GUI.shortLine(value, limit) local line = tostring(value or ""):match("[^\n]+") or "" local at = utf8.offset(line, (limit or 155) + 1) if at then line = line:sub(1, at - 1):gsub("%s+%S*$", "") .. "โ€ฆ" end return line end function GUI.statusCard(group, options, compact) local heading = group:CreateLabel({ Text = "" }) local status = group:CreateStatus({ Name = "State", Value = "", Style = "Badge", Visible = false }) local body = GUI.paragraph(group, "") local progress = group:CreateProgress({ Name = "Wave progress", CurrentValue = 0, Visible = false }) local handle = {} function handle.Set(a, b) local data = a == handle and b or a data = data or {} local title = tostring(data.title or "Status"):gsub("^Up next: ", "") heading:Set("" .. GUI.escape((data.icon and (data.icon .. " ") or "") .. title) .. "") status:SetVisible(data.tag ~= nil and data.tag ~= "") status:Set(GUI.escape(data.tag or "")) status:SetTone(data.tagColor or data.color or "Accent") local full = table.concat({ tostring(data.text or ""), tostring(data.sub or "") }, "\n") heading:SetTooltip(GUI.escape(full)) local lines = {} if data.text and data.text ~= "" then local value = compact and GUI.shortLine(data.text) or data.text table.insert(lines, tostring(value)) end if data.sub and data.sub ~= "" then local value = compact and GUI.shortLine(tostring(data.sub):gsub(" ยท front at.*$", ""), 110) or data.sub table.insert(lines, tostring(value)) end body.Set(table.concat(lines, "\n")) body.Native:SetTooltip(GUI.escape(full)) progress:SetVisible(type(data.progress) == "number") if type(data.progress) == "number" then progress:Set(math.clamp(data.progress, 0, 1), true) progress:SetColor(data.color or "Accent") end end handle.Set(options) return handle end function GUI.modeSummary(nextMode, skipped) if GUI.skipInfo then GUI.skipInfo.Set(#skipped > 0 and table.concat(skipped, "\n") or "Every enabled mode is available.") end local selection = Modes.onlyOn() and ("Only " .. Modes.onlyText()) or "Smart ยท enabled modes" return "Selection: " .. selection .. "\nNext: " .. (nextMode and nextMode.label or "waiting for an eligible stage") .. (#skipped > 0 and ("\n" .. #skipped .. " unavailable ยท details in Farm โ†’ Rules") or "") end -- String-only Airflow dropdowns are backed by a hidden native Input flag. -- Profiles save raw UUIDs/map IDs, rather than labels containing changing levels. -- This uses only documented Input/Dropdown APIs, including silent SetOptions. function GUI.dropdown(page, name, desc, options, initial, callback, multiple) local group = GUI.controlGroup(page, name) local visibleName, shortDesc = GUI.controlText(name, desc) local rows, labels, byLabel, byValue = {}, {}, {}, {} local rawValue, native, storage local ready = false local handle = {} local function rebuild(newOptions) rows, labels, byLabel, byValue = {}, {}, {}, {} for index, option in ipairs(newOptions or {}) do local row = type(option) == "table" and option or { text = tostring(option), value = option } local originalText = tostring(row.text or row.value or "") local label = originalText local modeNames = { On = "Enabled", Off = "Disabled", ["Only this"] = "Only this mode" } if modeNames[label] then label = modeNames[label] end if label:find("Smart:", 1, true) then label = "Smart" end if byLabel[label] then label = label .. " [" .. index .. "]" end while byLabel[label] do label = label .. "ยท" end rows[index] = { text = label, originalText = originalText, value = row.value, index = index } labels[index], byLabel[label], byValue[row.value] = label, rows[index], label end end local function pending(value) if value ~= nil and byValue[value] == nil then local label = "Saved selection ยท loading" while byLabel[label] do label = label .. "ยท" end local row = { text = label, value = value, index = #rows + 1 } table.insert(rows, row) table.insert(labels, label) byLabel[label], byValue[value] = row, label end end local function selection(value) if multiple then local result = {} for _, item in ipairs(type(value) == "table" and value or {}) do pending(item) table.insert(result, byValue[item]) end return result end pending(value) return byValue[value] end local function persist() if storage then storage:Set(Utils.json({ value = rawValue }), true) end end local function emit() if not callback then return end if multiple then local indexes = {} for _, value in ipairs(rawValue or {}) do local row = byLabel[byValue[value]] if row then table.insert(indexes, row.index) end end callback(indexes) else local row = byLabel[byValue[rawValue]] if row then callback(row.originalText or row.text, row.value, row.index) end end end rebuild(options) if multiple then rawValue = {} for _, index in ipairs(initial or {}) do if rows[index] then table.insert(rawValue, rows[index].value) end end else rawValue = rows[initial or 1] and rows[initial or 1].value or "" end local selected = selection(rawValue) native = group:CreateDropdown({ Name = visibleName, Desc = shortDesc, Tooltip = desc, Options = labels, CurrentOption = selected, MultipleOptions = multiple == true, AllowNone = multiple == true, Searchable = true, MaxRows = 8, Callback = function(picked) if not ready then return end if multiple then rawValue = {} for _, label in ipairs(picked or {}) do if byLabel[label] then table.insert(rawValue, byLabel[label].value) end end else if not byLabel[picked] then return end rawValue = byLabel[picked].value end persist() emit() end, }) storage = group:CreateInput({ Name = name .. " saved value", Visible = false, Flag = GUI.flag(page, name), CurrentValue = Utils.json({ value = rawValue }), Callback = function(text) if not ready then return end local data = Utils.unjson(text) if type(data) ~= "table" or data.value == nil then return end if multiple and type(data.value) ~= "table" then return end if not multiple and type(data.value) ~= "string" and type(data.value) ~= "number" then return end rawValue = data.value local value = selection(rawValue) native:SetOptions(labels, { KeepSelection = false, Silent = true }) native:Set(value, true) emit() end, }) ready = true function handle.Refresh(a, b, c) local newOptions, value = a, b if a == handle then newOptions, value = b, c end if value ~= nil then rawValue = value end rebuild(newOptions) local selectedValue = selection(rawValue) native:SetOptions(labels, { KeepSelection = false, Silent = true }) native:Set(selectedValue, true) persist() end function handle.Set(a, b, c) local value, silent = a, b if a == handle then value, silent = b, c end rawValue = value local selectedValue = selection(rawValue) native:SetOptions(labels, { KeepSelection = false, Silent = true }) native:Set(selectedValue, true) persist() if not silent then emit() end end function handle.Get() return rawValue end handle.Native = native return handle end function GUI.page(window, name, icon) local page = { Name = name, GroupName = "Overview", SectionCount = 0 } function page:Group() return GUI.block(table.unpack(GUI.sectionSpec(self))) end function page:Section(title) self.SectionCount = self.SectionCount + 1 self.GroupName = title ~= "" and title or "Details" return self:Group() end function page:Toggle(title, desc, value, callback) local ready = false local displayName, shortDesc = GUI.controlText(title, desc) local native = GUI.controlGroup(self, title):CreateToggle({ Name = displayName, Desc = shortDesc, Tooltip = desc, CurrentValue = value == true, DependsOn = GUI.controlDependency(title), DependsMode = "Disable", Flag = GUI.flag(self, title), Callback = function(on) if ready and callback then callback(on) end end, }) ready = true GUI.nativeToggles = GUI.nativeToggles or {} table.insert(GUI.nativeToggles, native) return GUI.valueHandle(native) end function page:Slider(title, desc, options, callback) local ready = false local scale, suffix = 1, "" if options.format then local ok, example = pcall(options.format, options.default or options.min or 0) if ok and type(example) == "string" then if example:match("%%$") then scale, suffix = 0.01, "%" elseif example:match("^[%d.%-]+s$") then suffix = "s" elseif example:match("^[%d.%-]+ gems$") then suffix = " gems" end end end local displayName, shortDesc = GUI.controlText(title, desc) local native = GUI.controlGroup(self, title):CreateSlider({ Name = displayName, Desc = shortDesc, Tooltip = desc, DependsOn = GUI.controlDependency(title), DependsMode = "Disable", Range = { (options.min or 0) / scale, (options.max or 100) / scale }, Increment = (options.step or 1) / scale, CurrentValue = (options.default or options.min or 0) / scale, Suffix = suffix, Flag = GUI.flag(self, title), Callback = function(value) if ready and callback then callback(value * scale) end end, }) ready = true return GUI.valueHandle(native, scale) end function page:Dropdown(title, desc, options, index, callback) return GUI.dropdown(self, title, desc, options, index, callback, false) end function page:MultiSelect(title, desc, options, indexes, callback) return GUI.dropdown(self, title, desc, options, indexes, callback, true) end function page:Input(title, desc, placeholder, value, callback) local ready = false local displayName, shortDesc = GUI.controlText(title, desc) local native = GUI.controlGroup(self, title):CreateInput({ Name = displayName, Desc = shortDesc, Tooltip = desc, PlaceholderText = placeholder, CurrentValue = value, Flag = GUI.flag(self, title), Callback = function(text) if ready and callback then callback(text) end end, }) ready = true return GUI.valueHandle(native) end function page:Button(icon, title, desc, colorOrCallback, finalCallback) local callback = finalCallback or colorOrCallback local icons = { ["Reset Mode Stats"] = "rotate-ccw", ["Pick The Core Units Again"] = "refresh-cw", ["Simulate Now"] = "flask-conical", ["Equip Best Team"] = "users", ["Restore My Team"] = "undo-2", ["Reset Unit Learning"] = "rotate-ccw", ["Start Study"] = "microscope", ["Stop Study"] = "square", ["Go To Lobby"] = "house", ["Start Farming Again"] = "play", ["Save Settings"] = "save", ["Unload"] = "power", } local displayName, shortDesc = GUI.controlText(title, desc) return GUI.controlGroup(self, title):CreateButton({ Name = displayName, Desc = shortDesc, Tooltip = desc, Icon = icons[title] or "chevron-right", Callback = function() task.spawn(function() if not App.alive then return end local ok, err = pcall(callback) if not ok then App.log(0, "ERROR", title .. ": " .. tostring(err)) window:Notify({ Title = title, Content = GUI.escape(err), Type = "Error" }) end end) end, }) end function page:Paragraph(text) return GUI.paragraph(self:Group(), text) end function page:Label(text) local native = self:Group():CreateLabel({ Text = GUI.escape(text) }) local handle = {} function handle.Set(a, b) native:Set(GUI.escape(a == handle and b or a)) end return handle end function page:StatusCard(options) local group, compact = self:Group(), self.Name == "Home" if self.Name == "Home" and options.title == "Simulator" then group, compact = GUI.block("Battle", "Planner", "Prediction", "Left"), false elseif self.Name == "Home" and options.title == "Up next" then group = GUI.block("Home", "", "Next run", "Right") elseif self.Name == "Fuse" then group = GUI.block("Units", "Fusion", "Requirements", "Right") end return GUI.statusCard(group, options, compact) end function page:KeyValue(keys) local group, cells, values = self:Group(), {}, {} local empty = group:CreateLabel({ Text = self.GroupName == "Run records" and "No completed runs yet." or "Waiting for game data." }) for _, key in ipairs(keys) do cells[key] = group:CreateStatus({ Name = GUI.escape(key), Value = "", Style = "Plain", Wrap = true, Visible = false }) end local handle = {} function handle.Set(a, b, c, d, e) local key, value, color, title = a, b, c, d if a == handle then key, value, color, title = b, c, d, e end local native = cells[key] if not native then return end local text = tostring(value or "") local visible = text ~= "" and text ~= "โ€”" and text ~= "โ€ฆ" values[key] = visible native:SetVisible(visible) if visible then native:SetName(GUI.escape(title ~= nil and title ~= "" and title or key)) native:Set(GUI.escape(text)) native:SetColor(color) elseif title and title ~= "" and not tonumber(title) then empty:Set(GUI.escape(title)) end local any = false for _, on in pairs(values) do if on then any = true; break end end empty:SetVisible(not any) end return handle end function page:StatGrid(keys) local group, cells = self:Group(), {} local names = { ["First Clears Left"] = "New clears", ["Summer Pearls"] = "Pearls", Record = "Wins / losses" } local gold = group:CreateStatus({ Name = "Gold", Value = "โ€ฆ", Style = "Row" }) for _, key in ipairs(keys) do cells[key] = group:CreateStatus({ Name = names[key] or key, Value = "โ€ฆ", Style = "Row" }) end local handle = {} function handle.Set(key, value, sub, color) local native = cells[key] if native then native:Set(GUI.escape(value)) native:SetColor(key == "First Clears Left" and "Text" or color) native:SetTooltip(GUI.escape(sub or "")) if key == "Gems" and sub then gold:Set(GUI.escape(tostring(sub):gsub("^gold ", ""))) end end end return handle end return page end function GUI.window(Airflow) local window = Airflow:CreateWindow({ Name = GUI.TITLE, LoadingSubtitle = GUI.TITLE .. " ยท v" .. VERSION, ToggleUIKeybind = "RightControl", Size = UDim2.fromOffset(900, 620), AdaptiveSize = true, Density = "Default", Backdrop = { Weather = "Fireflies", Tint = 0.35 }, Theme = GUI.THEME, ConfigurationSaving = { Enabled = true, FolderName = Core.FOLDER .. "/Airflow" }, Home = false, }) GUI.NativeWindow = window GUI.prepareNavigation(window) GUI.skipInfo = GUI.paragraph(GUI.block("Farm", "Rules", "Unavailable modes", "Right"), "Waiting for the first plan.") local controller = { Native = window } function controller:Tab(_, name) local page = GUI.page(window, name) if name == "Home" then self.State = page:Group():CreateStatus({ Name = "Farm", Value = "Startingโ€ฆ", Style = "Dot", Pin = true, Tone = "Accent", }) GUI.shortcuts() end return page end function controller:Status(text, color) if self.State then self.State:Set(GUI.escape(text)) self.State:SetColor(color) end end function controller:Notify(title, text, duration, tone) local tones = { success = "Success", warning = "Warning", error = "Error", default = "Info" } return window:Notify({ Title = GUI.escape(title), Content = GUI.escape(text), Duration = duration or 4, Type = tones[tone] or "Info" }) end function controller:Destroy() -- Airflow turns live toggles off during Destroy. Silence that UI teardown -- first, so unloading never overwrites the farm's autosaved preferences. for _, toggle in ipairs(GUI.nativeToggles or {}) do toggle:Set(false, true) end window:Destroy() end return controller end function GUI.banner(page, text) local brief = GUI.PAGE_HELP[page.Name] or "" if brief == "" then return end local handle = GUI.paragraph(page:Group(), brief) handle.Native:SetTooltip(GUI.escape(text)) return handle end function GUI.activity(page) GUI.activityLog = page:Group():CreateLog({ Name = "Activity", MaxEntries = 250, MaxRows = 14, Timestamps = false, EmptyText = "Nothing logged yet", }) for _, line in ipairs(LogBuffer.lines) do GUI.activityLog:Push(GUI.escape(line)) end end GUI.SYSTEMS = { "Mode", "Match", "Economy", "Placement", "Upgrade", "Abilities", "Merge", "Targeting", "Rewards", "Gold Shop", "Summon", "Evolve", "Training", "Codes" } GUI.lists, GUI.listSig, GUI.listEmpty = { }, { }, { } function GUI.sigOf(Plc) local Qlc = { } for Rlc, Slc in ipairs(Plc or { }) do table.insert(Qlc, tostring(Slc.text)) end return table.concat(Qlc, "|") end function GUI.listBuilt(Tlc, Ulc) GUI.listSig [Tlc] = GUI.sigOf(Ulc) GUI.listEmpty [Tlc] = # (Ulc or { }) <= 1 or nil GUI.listAt = GUI.listAt or Utils.now() end function GUI.bannerOptions() local Vlc = { { text = "Pick a banner", value = "" } } local Wlc = { } for Xlc, Ylc in pairs(Core.Banners or { }) do if type(Ylc) == "table" and Ylc.active then table.insert(Wlc, Xlc) end end table.sort(Wlc) for Zlc, _lc in ipairs(Wlc) do local amc = FarmHub.bannerCost(_lc) table.insert(Vlc, { text = string.format("%s (%s)", FarmHub.BANNER_NAME [_lc] or _lc, amc and amc.name or "?"), value = _lc }) end return Vlc end function GUI.snipeOptions() local bmc = { { text = "Pick a unit", value = "" } } local cmc = Brain.snipe or { } if not GUI.evolvedIds then GUI.evolvedIds = { } for dmc, emc in pairs(Core.Units) do if type(emc) == "table" and type(emc.evolve) == "table" and emc.evolve.evolve_unit then GUI.evolvedIds [emc.evolve.evolve_unit] = true end end end local fmc, gmc = { }, { } local function hmc(imc, jmc, kmc) if gmc [imc] or not Core.Units [imc] then return end gmc [imc] = true local lmc = FarmHub.snipeHas(imc) table.insert(fmc, { id = imc, text = Units.name(imc) .. jmc .. (lmc and " ยท owned" or ""), key = kmc * 10 + (lmc and 0 or 5), name = Units.name(imc) }) end if Config.snipeUnit ~= "" then hmc(Config.snipeUnit, "", 9) end for mmc, nmc in pairs(cmc.feat or { }) do for omc, pmc in ipairs(nmc) do hmc(pmc, " ยท featured on " .. (FarmHub.BANNER_NAME [mmc] or mmc), 3) end end for qmc, rmc in pairs(cmc.seen or { }) do if type(rmc) == "table" then hmc(qmc, string.format(" ยท seen on %s %s", FarmHub.BANNER_NAME [rmc.banner] or tostring(rmc.banner), os.date("%b %d", tonumber(rmc.at) or os.time())), 2) end end for smc, tmc in pairs(Core.Units) do if type(smc) == "string" and type(tmc) == "table" and tmc.rarity == "Mythic" and not tmc.hide_from_banner and not tmc.limited and not GUI.evolvedIds [smc] and not smc:find(":") then hmc(smc, "", 1) end end table.sort(fmc, function(umc, vmc) if umc.key ~= vmc.key then return umc.key > vmc.key end return umc.name < vmc.name end) for wmc, xmc in ipairs(fmc) do table.insert(bmc, { text = xmc.text, value = xmc.id }) end return bmc end function GUI.unitOptions(ymc, zmc) local Amc = { { text = "Pick a unit", value = "" } } local Bmc = Core.session() if not Bmc then return Amc end local Cmc = { } for Dmc, Emc in ipairs(Units.equipped()) do Cmc [Emc.uuid] = true end local Fmc = { } for Gmc, Hmc in pairs(Bmc.profile_data.collection.owned_units) do local Imc = Core.Units [Hmc.unit_id] if Imc and (Cmc [Gmc] or Hmc._locked or Imc.rarity == "Secret" or Imc.rarity == "Mythic" or Gmc == ymc) then local Jmc, Kmc = pcall(Core.USC.calculate_level, Hmc.unit_id, Hmc.xp) Kmc = Jmc and tonumber(Kmc) or 0 table.insert(Fmc, { uuid = Gmc, u = Hmc, lvl = Kmc, team = Cmc [Gmc], key = (Cmc [Gmc] and 4000 or 0) + (Hmc._locked and 2000 or 0) + (Imc.rarity == "Secret" and 1000 or 0) + Kmc }) end end table.sort(Fmc, function(Lmc, Mmc) if Lmc.key ~= Mmc.key then return Lmc.key > Mmc.key end return Lmc.uuid < Mmc.uuid end) for Nmc, Omc in ipairs(Fmc) do local Pmc, Qmc = true, "" if zmc then Pmc, Qmc = pcall(zmc, Omc.u) end if Pmc and (Qmc ~= false or Omc.uuid == ymc) and (# Amc <= 60 or Omc.uuid == ymc) then table.insert(Amc, { text = string.format("%s ยท lvl %d%s%s", Units.name(Omc.u.unit_id), Omc.lvl, Omc.team and " ยท team" or "", type(Qmc) == "string" and Qmc or ""), value = Omc.uuid }) end end return Amc end function GUI.focusNow() return (Config.autoEvolve and Config.evolveUnit ~= "") and Config.evolveUnit or "" end function GUI.focusOptions() local Rmc = { { text = "๐Ÿค– Smart Play (the farm chooses)", value = "" } } local Smc = { } local Tmc = Core.session() for Umc, Vmc in ipairs(Brain.goalsTop or { }) do Smc [Vmc.from] = true local Wmc = Tmc and Tmc.collection:get_unit_by_uuid(Vmc.uuid) local Xmc, Ymc = pcall(Core.USC.calculate_level, Vmc.from, Wmc and Wmc.xp or 0) table.insert(Rmc, { text = string.format("%s โ†’ %s ยท lvl %d%s", Units.name(Vmc.from), Units.name(Vmc.to), Xmc and tonumber(Ymc) or 0, Vmc.ready and " ยท ready" or ""), value = Vmc.uuid }) end local Zmc = Core.session() if Zmc then for _mc, anc in ipairs(GUI.evolveOptions(GUI.focusNow())) do local bnc = anc.value ~= "" and Zmc.collection:get_unit_by_uuid(anc.value) if bnc and not Smc [bnc.unit_id] then table.insert(Rmc, anc) end end end return Rmc end function FarmHub.setFocus(cnc) Config.evolveUnit = cnc or "" Config.autoEvolve = Config.evolveUnit ~= "" Config.focusNext = "" FarmHub.pickNote.autoEvolve = nil FarmHub.last.goals, FarmHub.last.trainScan, Training._at, Modes.lastScored = nil, nil, nil, nil local dnc, enc = pcall(Adventure.refresh) if not dnc then App.log(1, "ERROR", "focus goals: " .. tostring(enc)) end local fnc, gnc = pcall(Strategy.core, true) if not fnc then App.log(1, "ERROR", "focus core: " .. tostring(gnc)) end App.saveSoon() end function FarmHub.focusText() if GUI.focusNow() ~= "" then return "Your focus. " .. FarmHub.evolveText() .. (Config.focusNext ~= "" and ("\nNext up: " .. Units.name(Config.focusNext) .. ", once this one has evolved.") or "") end local hnc = Brain.train.on local jnc = hnc or Training.target() if jnc then return string.format("Smart Play is working on %s โ†’ %s (%s %s/%s)", Units.name(jnc.id), Units.name(jnc.to), tostring(jnc.what), Utils.fmt((tonumber(jnc.have) or 0) + (tonumber(jnc.got) or 0)), Utils.fmt(tonumber(jnc.need) or 0)) end local knc = (Brain.goals or { }) [1] if knc then return "Smart Play is working on " .. Units.name(knc.from) .. " โ†’ " .. Units.name(knc.to) end return "Smart Play: goals are worked out in the lobby" end function GUI.evolveOptions(lnc) local mnc = { { text = "Pick a unit", value = "" } } local nnc = Core.session() if not nnc then return mnc end local onc = { } for pnc, qnc in ipairs(Units.equipped()) do onc [qnc.uuid] = true end local rnc = { } for snc, tnc in pairs(nnc.profile_data.collection.owned_units) do local unc = Core.Units [tnc.unit_id] if unc and unc.evolve and unc.evolve.evolve_unit and not unc.evolve.hide_from_ui and Core.Units [unc.evolve.evolve_unit] then local vnc = (snc == lnc and 1e9 or 0) + (onc [snc] and 1e6 or 0) + (tonumber(tnc.xp) or 0) if not rnc [tnc.unit_id] or vnc > rnc [tnc.unit_id].key then rnc [tnc.unit_id] = { uuid = snc, id = tnc.unit_id, to = unc.evolve.evolve_unit, key = vnc } end end end local wnc = { } for xnc, ync in pairs(rnc) do table.insert(wnc, ync) end table.sort(wnc, function(znc, Anc) if znc.key ~= Anc.key then return znc.key > Anc.key end return znc.uuid < Anc.uuid end) for Bnc, Cnc in ipairs(wnc) do local Dnc = nnc.collection:get_unit_by_uuid(Cnc.uuid) local Enc, Fnc = pcall(Core.USC.calculate_level, Cnc.id, Dnc and Dnc.xp or 0) table.insert(mnc, { text = string.format("%s โ†’ %s ยท lvl %d%s", Units.name(Cnc.id), Units.name(Cnc.to), Enc and tonumber(Fnc) or 0, onc [Cnc.uuid] and " ยท team" or ""), value = Cnc.uuid }) end return mnc end function GUI.modeOptions(Gnc) local Hnc = { } local Inc = ({ raid = "pickRaid", story = "pickStory", legend = "pickLegend", infinite = "pickInfinite", portal = "pickPortal" }) [Gnc] local function Jnc(Knc, Lnc) for Mnc, Nnc in ipairs(Utils.list(Knc)) do local Onc = Core.Worlds [Nnc] for Pnc, Qnc in ipairs(Utils.list(Onc and Onc.levels)) do if Modes.open(Qnc.id) then Lnc(Qnc.id, Onc) end end end end if Gnc == "story" then Hnc = { { text = "๐Ÿค– Smart: first clears, then farm runs", value = "" }, { text = "๐Ÿ“ˆ Progress: the next map you haven't beaten", value = "progress" } } Jnc(Core.WLO.WORLD_ORDER, function(Rnc, Snc) if not Snc.legend_stage and not Snc.raid_world then for Tnc, Unc in ipairs({ "Normal", "Hard" }) do if Modes.diffOpen(Rnc, Unc) then table.insert(Hnc, { text = Modes.levelName(Rnc) .. " (" .. Unc .. ")", value = "st:" .. Rnc .. ":" .. Unc }) end end end end) elseif Gnc == "legend" then Hnc = { { text = "๐Ÿค– Smart: when it drops what a goal needs", value = "" }, { text = "๐Ÿ“ˆ Progress: the next legend stage you haven't beaten", value = "progress" } } Jnc(Core.WLO.LEGEND_WORLD_ORDER, function(Vnc) table.insert(Hnc, { text = Modes.levelName(Vnc), value = "st:" .. Vnc .. ":Hard" }) end) elseif Gnc == "raid" then Hnc = { { text = "๐Ÿค– Smart: the raid worth the most", value = "" } } Jnc(Core.WLO.RAID_WORLD_ORDER, function(Wnc) table.insert(Hnc, { text = Modes.levelName(Wnc), value = "rd:" .. Wnc }) end) elseif Gnc == "infinite" then Hnc = { { text = "๐Ÿค– Smart: daily gems and takedowns", value = "" } } for Xnc, Ync in ipairs(Modes.infIds()) do if Modes.open(Ync) then table.insert(Hnc, { text = Modes.levelName(Ync), value = "inf:" .. Ync }) end end elseif Gnc == "portal" then Hnc = { { text = "๐Ÿค– Smart: portals whose map drops what your evolutions need", value = "smart" } } local Znc = { } for _nc, aoc in ipairs(Modes.portalKinds()) do local boc = # Modes.portalsHeld(aoc.pid) local coc = { text = string.format("%s (%s)%s", aoc.name, tostring(aoc.rarity or "?"), boc > 0 and (" ยท you hold " .. boc) or ""), value = aoc.pid } table.insert(boc > 0 and Hnc or Znc, coc) end for doc, eoc in ipairs(Znc) do table.insert(Hnc, eoc) end end local foc = Inc and Config [Inc] or "" local goc = false for hoc, ioc in ipairs(Hnc) do if ioc.value == foc then goc = true end end if not goc then table.insert(Hnc, { text = "your pick (not open right now)", value = foc }) end return Hnc end GUI.TITLE = "RobScript Team - Anime Adventures" function GUI.lbTargetOptions() local Zoc = { { text = "Pick a unit", value = "" } } local _oc = Core.session() if not _oc then return Zoc end local apc = { } for bpc, cpc in ipairs(Units.equipped()) do apc [cpc.uuid] = true end local dpc = { } for epc, fpc in pairs(_oc.profile_data.collection.owned_units) do local gpc = _oc.collection:get_unit_by_uuid(epc) or fpc local hpc = Core.Units [gpc.unit_id] if hpc and (hpc.rarity == "Mythic" or hpc.rarity == "Secret" or apc [epc] or epc == Config.fuseUnit) then local ipc, jpc = pcall(Core.USC.get_max_limit_break, gpc) local kpc = gpc.further_modifiers and gpc.further_modifiers.limit_break and tonumber(gpc.further_modifiers.limit_break.current_limit_break) or 0 if (ipc and (tonumber(jpc) or 0) > kpc) or epc == Config.fuseUnit then local lpc, mpc = pcall(Units.score, gpc) local npc, opc = Strategy.level(gpc), Strategy.cap(gpc) table.insert(dpc, { uuid = epc, id = gpc.unit_id, lvl = npc, cap = opc, team = apc [epc], sc = lpc and mpc or 0, key = (epc == Config.fuseUnit and 4 or 0) + (apc [epc] and 2 or 0) + (npc >= opc and 1 or 0) }) end end end table.sort(dpc, function(ppc, qpc) if ppc.key ~= qpc.key then return ppc.key > qpc.key end if ppc.sc ~= qpc.sc then return ppc.sc > qpc.sc end return ppc.uuid < qpc.uuid end) for rpc, spc in ipairs(dpc) do if rpc > 60 then break end table.insert(Zoc, { text = string.format("%s ยท Lv %d/%d%s%s", Units.name(spc.id), spc.lvl, spc.cap, spc.team and " ยท team" or "", spc.lvl >= spc.cap and " ยท at cap" or ""), value = spc.uuid }) end return Zoc end function GUI.fodderTable(page) local group = GUI.block("Units", "Fusion", "Fodder units", "Left") local handle = { data = {} } local board, selectionFlag local summary = group:CreateStatus({ Name = "Fodder", Value = "Farm picks", Style = "Plain" }) GUI.paragraph(group, "Click a row to select a unit. โ˜… marks the farm's safe picks; โš  marks something worth keeping. Empty selection uses the farm's safe rule. Locked and equipped units never show.") local function selected() local result = {} for _, uuid in ipairs(Config.fuseFodder or {}) do result[uuid] = true end return result end function handle.points() local picked, total = selected(), 0 for _, unit in ipairs(handle.data) do if picked[unit.uuid] then total = total + unit.pts end end return total end function handle.summary() local requirement = Brain.fuseInfo and Brain.fuseInfo.uuid == Config.fuseUnit and tonumber(Brain.fuseInfo.requirement) local count, points = #(Config.fuseFodder or {}), handle.points() summary:Set(count == 0 and "Farm picks" or string.format("%d selected ยท %d%s points", count, points, requirement and (" / " .. requirement) or "")) summary:SetColor(requirement and points >= requirement and "Success" or "Accent") end local function render() local picked, rows = selected(), {} for _, unit in ipairs(handle.data) do local details = { unit.rarity, "Lv " .. tostring(unit.lvl) } if unit.evolved then table.insert(details, "evolved") end if #(unit.flags or {}) > 0 then table.insert(details, "โš  " .. table.concat(unit.flags, ", ")) end table.insert(rows, { picked[unit.uuid] and "โœ“" or "", GUI.escape(unit.name .. (unit.safe and " โ˜…" or "")), GUI.escape(table.concat(details, " ยท ")), unit.pts, UUID = unit.uuid, Highlight = picked[unit.uuid] == true, }) end board:SetRows(rows) handle.summary() end local function persist() Config.fuseArmSig = "" App.saveSoon() if selectionFlag then selectionFlag:Set(Utils.json(Config.fuseFodder or {}), true) end render() end board = group:CreateTable({ Name = "Available fodder", MaxRows = 6, Search = true, Columns = { { Name = "Pick", Width = 40 }, { Name = "Unit", Width = 0.32 }, { Name = "Details" }, { Name = "Points", Width = 55, Align = "Right" }, }, Rows = {}, Callback = function(row) local picked, result = selected(), {} for _, uuid in ipairs(Config.fuseFodder or {}) do if uuid ~= row.UUID then table.insert(result, uuid) end end if not picked[row.UUID] then table.insert(result, row.UUID) end Config.fuseFodder = result persist() end, }) local ready, generation = false, 0 selectionFlag = group:CreateInput({ Name = "Fodder selection", Visible = false, Flag = "AA/Fuse/FodderUUIDs", CurrentValue = Utils.json(Config.fuseFodder or {}), Callback = function(text) if not ready then return end local values = Utils.unjson(text) if type(values) ~= "table" then return end local ids = {} for _, uuid in ipairs(values) do if type(uuid) == "string" then table.insert(ids, uuid) end end generation = generation + 1 local revision = generation -- Profile flags load in arbitrary order. Restore the fodder after the -- target-unit callback, which intentionally clears a previous pick. task.defer(function() if not App.alive or revision ~= generation then return end Config.fuseFodder = ids persist() end) end, }) ready = true function handle.Refresh(data, emptyText) handle.data = data or {} summary:SetName(#handle.data == 0 and GUI.escape(emptyText or "No eligible units") or "Fodder") selectionFlag:Set(Utils.json(Config.fuseFodder or {}), true) render() end group:CreateButtonRow({ { Name = "Use farm picks", Callback = function() task.spawn(function() local session = Core.session() local unit = session and session.collection:get_unit_by_uuid(Config.fuseUnit) if not unit then return end local need = Brain.fuseInfo and Brain.fuseInfo.uuid == unit.uuid and tonumber(Brain.fuseInfo.requirement) or 12 local fuel, ids = FarmHub.fuseFuel(unit, need), {} for _, entry in ipairs(fuel) do table.insert(ids, entry.uuid) end Config.fuseFodder = ids persist() end) end }, { Name = "Clear", Callback = function() Config.fuseFodder = {}; persist() end }, { Name = "Refresh", Callback = function() GUI.fodderSig = nil end }, }) return handle end function GUI.lbStatus() local Rqc = table.concat({ tostring(Config.fuseUnit), table.concat(Config.fuseFodder or { }, ","), tostring(Config.autoFuse), tostring(FarmHub.pickNote.autoFuse), tostring(Brain.fuseInfo and Brain.fuseInfo.uuid), tostring(App.where) }, "|") local Sqc = GUI._lb if Sqc and Sqc.key == Rqc and Utils.now() - Sqc.at < 15 then return Sqc.v end local Tqc = GUI.lbStatusNow() GUI._lb = { key = Rqc, at = Utils.now(), v = Tqc } return Tqc end function GUI.lbStatusNow() local Uqc = GUI.COL or { } if Config.fuseUnit == "" then return { icon = "๐Ÿ”ฅ", title = "Pick a unit to limit break", text = "A limit break raises a unit's level cap (100 โ†’ 110). It takes a Divine Wish and fodder units, which are destroyed.", sub = "", tag = "", progress = false, color = Uqc.accent } end local Vqc = Core.session() local Wqc = Vqc and Vqc.collection:get_unit_by_uuid(Config.fuseUnit) if not Wqc then return { icon = "๐Ÿ”ฅ", title = "That unit is gone", text = "Pick another one.", sub = "", tag = "", progress = false, color = Uqc.red } end if App.where == "lobby" and not (Brain.fuseInfo and Brain.fuseInfo.uuid == Wqc.uuid) and FarmHub.every("lbInfo", 10) then task.spawn(function() local Xqc, Yqc = App.request("fuseInfo", "request_fusion_information", Wqc.uuid) if Xqc and type(Yqc) == "table" then Brain.fuseInfo = { requirement = Yqc.requirement, divine_wish = Yqc.divine_wish, allowed = Yqc.allowed, uuid = Wqc.uuid } GUI._lb = nil end end) end local Zqc = Brain.fuseInfo and Brain.fuseInfo.uuid == Wqc.uuid local _qc = Zqc and tonumber(Brain.fuseInfo.requirement) or 12 local arc = Zqc and tonumber(Brain.fuseInfo.divine_wish) or 1 local brc, crc = Strategy.level(Wqc), Strategy.cap(Wqc) local drc if # (Config.fuseFodder or { }) > 0 then _, drc = FarmHub.fodderPicked(Wqc) else _, drc = FarmHub.fuseFuel(Wqc, _qc) end local erc = Adventure.have("divine_wish") local frc = brc >= crc and erc >= arc and drc >= _qc and not (Zqc and Brain.fuseInfo.allowed == false) local hrc = function(grc) return grc and "โœ“" or "โœ—" end return { icon = "๐Ÿ”ฅ", title = Units.name(Wqc.unit_id) .. (Zqc and Brain.fuseInfo.allowed == false and " ยท can't be limit broken" or ""), text = string.format("Level %d/%d %s ยท Divine Wish %d/%d %s ยท Fodder %d/%d points %s%s", brc, crc, hrc(brc >= crc), erc, arc, hrc(erc >= arc), drc, _qc, hrc(drc >= _qc), Zqc and "" or " (requirement checked in the lobby)"), sub = FarmHub.pickNote.autoFuse or (Config.autoFuse and "Auto Limit Break is on: it fuses once everything is โœ“, after showing what it uses for 30 s." or "Limit break is off. Review the fodder, then enable it in the target panel."), tag = frc and "READY" or "WAITING", tagColor = frc and Uqc.green or Uqc.orange, progress = math.min(1, drc / math.max(1, _qc)), color = frc and Uqc.green or Uqc.accent } end function GUI.homeRows() local function irc(jrc) local krc = tostring(jrc or ""):match("^[^\n]+") or "" if # krc > 130 then krc = krc:sub(1, 127):gsub("%s+%S*$", "") .. "โ€ฆ" end return krc end local function lrc(mrc) local nrc, orc = pcall(mrc) return nrc and irc(orc) or "โ€ฆ" end local prc = { } prc.Playing = Modes.onlyOn() and ("Only " .. Modes.onlyText()) or "Smart: whatever pays best (Farm โ†’ Stages)" prc.Focus = lrc(FarmHub.focusText) prc.Training = Config.trainTakedowns and lrc(Training.text) or "off" prc.Snipe = Config.snipeOn and Config.snipeUnit ~= "" and lrc(FarmHub.snipeText) or "off" if Config.fuseUnit ~= "" then local qrc, rrc = pcall(GUI.lbStatus) prc ["Limit Break"] = qrc and irc(rrc.title .. " ยท " .. tostring(rrc.tag):lower() .. " ยท " .. (tostring(rrc.text):gsub("%s+ยท%s+", " ยท "))) or "โ€ฆ" else prc ["Limit Break"] = "Choose a target in Units โ†’ Fusion" end prc.Team = Config.team and lrc(Strategy.text) or "off" return prc end GUI.HOME_KEYS = { "Playing", "Focus", "Training", "Snipe", "Limit Break", "Team" } function GUI.build() local trc = GUI.load() if not trc then warn("[AA] Robly Airflow failed to load โ€” running without a window") return end GUI.theme(trc) local urc = GUI.window(trc) GUI.Win = urc local vrc, wrc = GUI.UI, GUI.LIVE local function xrc(yrc, zrc) Config [yrc] = zrc App.saveSoon() end local function Arc(Brc, Crc, Drc, Erc) vrc [Erc] = Brc:Toggle(Crc, Drc, Config [Erc], function(Frc) xrc(Erc, Frc) end) end local function Grc(Hrc, Irc, Jrc, Krc, Lrc) Lrc.default = Config [Krc] vrc [Krc] = Hrc:Slider(Irc, Jrc, Lrc, function(Mrc) xrc(Krc, Mrc) end) end local function Nrc(Orc) return tostring(math.floor(Orc + 0.5)) end local function Prc(Qrc) return Utils.fmt(Qrc) end local Src = function(Rrc) return string.format("%d%%", math.floor(Rrc * 100 + 0.5)) end local function Trc(Urc, Vrc, Wrc) pcall(function() urc:Notify(Urc, Vrc, 4, Wrc or "default") end) end local function Yrc(Zrc, _rc) local asc = pcall(GUI.banner, Zrc, _rc) if not asc then Zrc:Label(_rc) end end local bsc = urc:Tab("๐Ÿ ", "Home") Yrc(bsc, "Turn it on and watch it work. Every other tab is for when you want to choose something yourself.") vrc.master = bsc:Toggle("Auto Farm", "Picks the modes, plays them and runs your picks. Off: it plays nothing, but the summons, fuses and stars you switched on still run in the lobby", Config.master, function(csc) xrc("master", csc) urc:Status(csc and "โ–ถ Farming" or "โธ Paused") end) bsc:Section("Right now") GUI.nowCard = bsc:StatusCard({ icon = "โณ", title = "Startingโ€ฆ", text = "", sub = "" }) GUI.simCard = bsc:StatusCard({ icon = "๐Ÿ”ฎ", title = "Simulator", text = "", sub = "" }) GUI.nextCard = bsc:StatusCard({ icon = "โญ", title = "Up next", text = "", sub = "" }) bsc:Section("At a glance") GUI.goals = bsc:StatGrid({ "Gems", "Daily Challenge", "Challenge", "First Clears Left", "Summer Pearls", "Record" }, 3) bsc:Section("Every part") GUI.homeKv = bsc:KeyValue(GUI.HOME_KEYS, 90) bsc = urc:Tab("๐ŸŽฎ", "Play") Yrc(bsc, "What it plays. Each kind has one control:\n" .. "โ€ข On: the farm plays this kind when it pays best. It also plays every other kind that's On, so with Infinite, Raids and Challenges all On, it moves between them, picking whichever pays most right now.\n" .. "โ€ข Only this: it plays just this kind, and nothing that's merely On. Set Infinite to Only this and it plays Infinite and nothing else. Set two kinds to Only this and it plays just those two.\n" .. "โ€ข Off: it never plays that kind.\n" .. "The pick beside each kind chooses the map.") wrc.only = bsc:Paragraph("โ€ฆ") Arc(bsc, "Auto Play", "Starts the best mode and chains runs", "play") local function dsc(esc, fsc, gsc, hsc) local isc = gsc and { "Off", "On", "Only this" } or { "On", "Only this" } local jsc = 1 if Config [hsc] then jsc = # isc elseif gsc then for ksc, lsc in ipairs(gsc) do if Config [lsc] then jsc = 2 end end end vrc [hsc .. "Mode"] = bsc:Dropdown(esc, fsc, isc, jsc, function(msc) xrc(hsc, msc == "Only this") for nsc, osc in ipairs(gsc or { }) do xrc(osc, msc ~= "Off") end Modes.lastScored = nil FarmHub.last.pick = nil end) end local function psc(qsc, rsc, ssc, tsc) local usc, vsc = GUI.modeOptions(ssc), 1 if GUI.lists [tsc] then GUI.listBuilt(tsc, usc) end for wsc, xsc in ipairs(usc) do if xsc.value == Config [tsc] then vsc = wsc end end vrc [tsc .. "Pick"] = bsc:Dropdown(qsc, rsc, usc, vsc, function(ysc, zsc) xrc(tsc, zsc) Modes.lastScored = nil FarmHub.last.pick = nil end) end bsc:Section("Story and Legend Stages") dsc("Story", "First clears of every act (Normal then Hard, 60 gems each the first time), and replays of cleared acts when nothing better is open", { "firstClears", "farm" }, "onlyStory") psc("Story Map", "Smart, Progress (the next map you haven't beaten), or one act again and again", "story", "pickStory") dsc("Legend Stages", "On: played when one drops what a goal needs. Only this: just legend stages", nil, "onlyLegend") psc("Legend Stage", "Smart, Progress (the next one you haven't beaten), or one stage", "legend", "pickLegend") bsc:Section("Infinite and Portals") dsc("Infinite", "Daily gems at waves 10, 25 and 50, and lots of takedowns", { "infinite" }, "onlyInfinite") psc("Infinite Map", "Smart goes by daily gems and takedowns", "infinite", "pickInfinite") Arc(bsc, "End Infinite Early", "Ends the run when the daily gems are in and another mode pays more", "infiniteStop") dsc("Portals", "Plays the portals you hold, ahead of other kinds: on Smart, the ones whose map drops what your evolutions need; or the kind you pick below whenever you hold one. Only this: just portals", { "portals" }, "onlyPortals") GUI.lists.pickPortal = function() return GUI.modeOptions("portal") end psc("Portal", "Smart, or one kind to play whenever you hold one", "portal", "pickPortal") Arc(bsc, "Play Portals You Can't Beat", "Your portals go first even when the simulator says your team will lose there. Off: only the ones your team can win, or has won, go first", "portalsAny") bsc:Section("Raids and Dungeon") dsc("Raids", "While tries are left today: gems and raid items", { "raids" }, "onlyRaids") psc("Raid", "Smart plays the raid worth the most", "raid", "pickRaid") dsc("Artifact Stage", "The stage of the moment (a new one every 14 days), played alone ahead of other kinds while your team has a chance there, until its 10 clears are in: 30 gems a clear, 15 Limited Star Remnants and 250 gems from its milestones. Only this: just the Artifact Stage", { "artifact" }, "onlyArtifact") dsc("Dungeon", "20 rooms in a row, with Rare Chests kept for floor 20", { "dungeon" }, "onlyDungeons") wrc.dungeon = bsc:Paragraph("โ€ฆ") Arc(bsc, "Start New Dungeon Runs", "Starts a new run when the last one is over", "dungeonStart") bsc:Section("Challenges") dsc("Challenges", "The Daily Challenge (gems, a Divine Wish, Star Remnants, Stat Cubes) and the 30-minute challenge", { "dailyChallenge", "challenge" }, "onlyChallenges") do local Asc, Bsc, Csc = { }, { }, { } for Dsc in pairs(Core.CAR.challenges or { }) do table.insert(Asc, Dsc) end table.sort(Asc, function(Esc, Fsc) return Modes.chName(Esc) < Modes.chName(Fsc) end) for Gsc, Hsc in ipairs(Asc) do table.insert(Bsc, Modes.chName(Hsc)) if table.find(Config.chSkip or { }, Hsc) then table.insert(Csc, Gsc) end end vrc.chSkipPick = bsc:MultiSelect("Skip These Challenges", "The Daily and the 30-minute challenge with these never play", Bsc, Csc, function(Isc) local Jsc = { } for Ksc, Lsc in ipairs(Isc or { }) do if Asc [Lsc] then table.insert(Jsc, Asc [Lsc]) end end xrc("chSkip", Jsc) Modes.lastScored = nil FarmHub.last.pick = nil end) Arc(bsc, "Only Ones You Can Beat", "The Daily and challenges play only when the simulator gives your team the chance below, or the best team it found wins", "chSmart") Grc(bsc, "Win Chance Needed", "For Only Ones You Can Beat", "chMinP", { min = 0.1, max = 0.95, step = 0.05, format = Src }) Arc(bsc, "Keep Playing Daily", "The Daily Challenge again and again, ahead of everything, until it's won or you turn this off", "dailyLoop") Arc(bsc, "Daily In A Kill Box", "Keep Playing Daily stacks your units on the road's last stretch, where a control unit works best", "dailyBox") end bsc:Section("Matchmaking") Arc(bsc, "Matchmake The Daily", "Finds other players for the Daily Challenge, whose enemies get no tougher with more players. Plays alone if nobody joins in time", "dailyMatch") Arc(bsc, "Matchmake Raids", "Finds other players for raids (up to 6). Their units help, and the enemies get tougher with more players. Plays alone if nobody joins in time", "mmRaid") Arc(bsc, "Matchmake Infinite", "Finds other players for Infinite (up to 4). Their units help, and the enemies get tougher with more players. Plays alone if nobody joins in time", "mmInfinite") Arc(bsc, "Matchmake Legend Stages", "Finds other players for Legend Stages (up to 4). Their units help, and the enemies get tougher with more players. Plays alone if nobody joins in time", "mmLegend") Arc(bsc, "Matchmake The Summer Hunt", "Finds other players for the Summer Hunt (up to 6). Their units help, and the enemies get 20% tougher for each player. Plays alone if nobody joins in time", "mmEvent") Grc(bsc, "Matchmaking Wait", "Seconds to wait for other players before playing alone, in every mode above", "dailyMatchWait", { min = 30, max = 600, step = 30, format = Nrc }) do local Msc = Modes.eventNow() if Msc then bsc:Section(Msc.hunt and "Summer Hunt" or Msc.name) dsc(Msc.name, Msc.about, { "event" }, "onlyCoins") end if Msc and Msc.hunt then wrc.hunt = bsc:Paragraph("โ€ฆ") vrc.huntCardsPick = bsc:Dropdown("Card Pick", "Which of the 3 cards to take each time. None that risks losing the run", Hunt.CARD_PICKS, table.find(Hunt.CARD_PICKS, Config.huntCards) or 1, function(Nsc) xrc("huntCards", Nsc) end) Arc(bsc, "Smart Charm Equip", "Wears the Summer Charm that helps most before a Summer Hunt or a Sky portal: more rewards where your team wins with room to spare, more damage where it might lose", "smartCharm") bsc:Section("Summer Skins and Stars") wrc.skins = bsc:Paragraph("โ€ฆ") Arc(bsc, "Smart Skins", "Puts Summer skins on your Hunt team and sells the spares listed above. Selling can't be undone", "skinSmart") Arc(bsc, "Open Sunny Stars", "Spends Summer Pearls on stars for the need listed above, never below the pearls you keep", "starOpen") Grc(bsc, "Summer Pearls To Keep", "Stars never go below this. 105k pays for all the Merchant's stock", "pearlFloor", { min = 0, max = 300000, step = 5000, format = Prc }) end if Msc and Msc.dealt then bsc:Section("Fool's Hunt") wrc.fh = bsc:Paragraph("โ€ฆ") end end bsc:Section("What it plays next (gems a minute)") GUI.rank = bsc:KeyValue({ "#1", "#2", "#3", "#4", "#5", "#6" }, 26) bsc:Section("Run records") GUI.records = bsc:KeyValue({ "1", "2", "3", "4", "5", "6", "7", "8" }, 190) bsc:Section("Advanced") Grc(bsc, "Dungeon Room Value", "Gems one dungeon room is worth to you", "dungeonValue", { min = 0, max = 300, step = 10, format = Nrc }) if Modes.eventNow() and Modes.eventNow().dealt then Grc(bsc, "AprilCoin Value", "Gems one AprilCoin is worth to you", "aprilValue", { min = 0, max = 1, step = 0.05, format = function(Osc) return string.format("%.2f gems", Osc) end }) end if Modes.eventNow() and Modes.eventNow().hunt then Grc(bsc, "Summer Pearl Value", "Gems one Summer Pearl is worth to you, for ranking the Summer Hunt against other modes", "pearlValue", { min = 0, max = 1, step = 0.05, format = function(Psc) return string.format("%.2f gems", Psc) end }) end Grc(bsc, "Unlock Value", "Gems that opening the next act is worth, on top of its first-clear gems", "unlockValue", { min = 0, max = 200, step = 5, format = Nrc }) Grc(bsc, "Rest After Losses", "Rests a mode for 6 hours after this many losses in a row", "restAfterLosses", { min = 1, max = 5, step = 1, format = Nrc }) bsc:Button("๐Ÿงฎ", "Reset Mode Stats", "Forget win rates and run times", function() table.clear(Brain.modes) App.saveNow() Trc("Modes", "stats reset", "success") end) bsc = urc:Tab("๐ŸŽฏ", "Goals") Yrc(bsc, "Which unit the farm is working toward and what it still needs. Pick a Focus to choose it yourself.") bsc:Section("Focus") wrc.focus = bsc:Paragraph("โ€ฆ") if # (Brain.goalsTop or { }) == 0 then pcall(Adventure.refresh) end do local Qsc, Rsc = GUI.focusOptions(), 1 for Ssc, Tsc in ipairs(Qsc) do if Tsc.value == GUI.focusNow() then Rsc = Ssc end end vrc.focusPick = bsc:Dropdown("Focus On", "Its items and takedowns come first, then it evolves, which can't be undone", Qsc, Rsc, function(Usc, Vsc) FarmHub.setFocus(Vsc) GUI.focusSig = nil Trc("Focus", Vsc ~= "" and "focusing on this unit" or "Smart Play chooses", "success") end) end bsc:Section("Ready now") GUI.readyLabel = bsc:Label("โ€ฆ") bsc:Section("Evolutions in reach (strongest first)") GUI.path = bsc:KeyValue({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11" }, 150) bsc:Section("Takedown training") wrc.train = bsc:Paragraph("โ€ฆ") Arc(bsc, "Takedown Training", "A unit that needs takedowns joins runs the simulator says you still win", "trainTakedowns") Arc(bsc, "Leave When Trained", "In Infinite, once the unit in training has all its takedowns, the run ends so it can evolve. A daily-gem wave still ahead is played to first", "trainLeave") bsc:Section("How it chases goals") Arc(bsc, "Smart Evolve", "Evolves a ready unit when it gets stronger, using only spare items and copies", "smartEvolve") Arc(bsc, "Farm Toward Goals", "Items your best goals need get a gem value, so the farm goes after them", "goals") Arc(bsc, "Raid Shop For Goals", "Buys a goal's item in the raid shop once you have enough raid items", "raidShopGoals") Grc(bsc, "Goal Value", "Gems that finishing the top goal is worth (higher: it chases goals harder)", "goalValue", { min = 0, max = 10000, step = 250, format = Nrc }) bsc:Section("Notes") wrc.lb = bsc:Paragraph("checked in the lobbyโ€ฆ") wrc.community = bsc:Paragraph("โ€ฆ") bsc = urc:Tab("๐Ÿ’ช", "Team") Yrc(bsc, "What the farm does by itself to make your 8 core units stronger, biggest gain first. Your own picks on Summon and Fuse come first.") vrc.team = bsc:Toggle("Make My Team Stronger", "Levels, stats, traits, relics and limit breaks for your 8 core units, biggest gain first", Config.team, function(Wsc) xrc("team", Wsc) FarmHub.last.teamPlan = nil Brain.str.next = nil end) bsc:Section("Next") wrc.teamNext = bsc:Paragraph("โ€ฆ") bsc:Section("Core units") GUI.core = bsc:KeyValue({ "1", "2", "3", "4", "5", "6", "7", "8" }, 230) bsc:Button("๐Ÿ”", "Pick The Core Units Again", "Your team, your picks, then the attackers the planner used most this week", function() local Xsc, Ysc = pcall(Strategy.core, true) if Xsc then FarmHub.last.teamPlan = nil Trc("Team", "Core units picked again") else Trc("Team", "Couldn't pick: " .. tostring(Ysc), "error") end end) bsc:Section("Levels") vrc.teamXp = bsc:Toggle("Level Up With XP Food", "Feeds food to the core unit it helps most, never past its level cap", Config.teamXp, function(Zsc) xrc("teamXp", Zsc) FarmHub.last.teamPlan = nil end) vrc.teamBuyXp = bsc:Toggle("Buy XP Food", "The most XP per gold first, only while your units can still take it", Config.teamBuyXp, function(_sc) xrc("teamBuyXp", _sc) end) Grc(bsc, "Gold To Keep", "XP food never takes your gold below this", "xpGoldFloor", { min = 0, max = 2000000, step = 10000, format = Prc }) bsc:Section("Gear") vrc.teamRelics = bsc:Toggle("Smart Relics", "Moves relics to where they add the most damage for the team that's playing", Config.teamRelics, function(atc) xrc("teamRelics", atc) FarmHub.last.teamPlan = nil end) vrc.teamFuse = bsc:Toggle("Limit Break Core Units", "Once a core unit is capped and you hold a Divine Wish, by the farm's safe fodder rule. Lists the units it uses for 30 s first. To choose yourself, use the Units โ†’ Fusion", Config.teamFuse, function(btc) xrc("teamFuse", btc) xrc("fuseArmSig", "") FarmHub.last.teamPlan = nil end) bsc:Section("Advanced") vrc.strDry = bsc:Toggle("Dry Run", "Only says what it would do, and spends nothing", Config.strDry, function(ctc) xrc("strDry", ctc) FarmHub.last.teamPlan = nil end) Grc(bsc, "Gems Per 1% Team Damage", "Higher: it plays XP-rich modes and the Daily Challenge sooner", "strengthValue", { min = 0, max = 2000, step = 50, format = Nrc }) bsc = urc:Tab("๐ŸŽฐ", "Summon") Yrc(bsc, "Summons you choose. Each stays off until you switch it on, and never goes below the Keep lines you set.") bsc:Section("Banner Snipe") wrc.snipe = bsc:Paragraph("โ€ฆ") do GUI.lists.snipeUnit = GUI.snipeOptions local dtc, etc = GUI.snipeOptions(), 1 GUI.listBuilt("snipeUnit", dtc) for ftc, gtc in ipairs(dtc) do if gtc.value == Config.snipeUnit then etc = ftc end end vrc.snipeUnitPick = bsc:Dropdown("Unit To Snipe", "Featured units first, then every Mythic a banner can give", dtc, etc, function(htc, itc) xrc("snipeUnit", itc) FarmHub.last.snipe = nil end) end vrc.snipeOn = bsc:Toggle("Banner Snipe", "Pulls your pick on whichever banner features it, until it drops or a Keep line below. Checks again when the banners turn over, even from a match", Config.snipeOn, function(jtc) xrc("snipeOn", jtc) FarmHub.last.snipe = nil end) Grc(bsc, "Gems To Keep", "Banner Snipe never takes your gems below this", "snipeGemFloor", { min = 0, max = 300000, step = 1000, format = Prc }) Grc(bsc, "Pearls To Keep", "... or your Summer Pearls", "snipePearlFloor", { min = 0, max = 300000, step = 1000, format = Prc }) Grc(bsc, "Legacy Gems To Keep", "... or your Legacy Gems", "snipeLegacyFloor", { min = 0, max = 100000, step = 500, format = Prc }) bsc:Section("Auto Summon") wrc.summonPick = bsc:Paragraph("โ€ฆ") local ktc = GUI.bannerOptions() local ltc = 1 for mtc, ntc in ipairs(ktc) do if ntc.value == Config.summonBanner then ltc = mtc end end vrc.summonBannerPick = bsc:Dropdown("Banner", "The banner to summon on", ktc, ltc, function(otc, ptc) xrc("summonBanner", ptc) local qtc = FarmHub.bannerCost(ptc) if qtc and vrc.summonKeep then pcall(vrc.summonKeep.Set, Config [qtc.floor]) end end) vrc.autoSummon = bsc:Toggle("Auto Summon", "10 at a time in the lobby until the line below", Config.autoSummon, function(rtc) xrc("autoSummon", rtc) FarmHub.last.summon = nil end) local function stc() local ttc = FarmHub.bannerCost(Config.summonBanner) return ttc and ttc.floor or "gemFloor" end vrc.summonKeep = bsc:Slider("Keep At Least", "Never summons below this much of that banner's currency", { min = 0, max = 300000, step = 500, format = Prc, default = Config [stc()] }, function(utc) xrc(stc(), utc) end) Grc(bsc, "Summons Per Visit", "At most this many per banner on each lobby visit", "summonsPerVisit", { min = 10, max = 200, step = 10, format = Nrc }) bsc:Section("Smart pulls") if Core.Banners.April2025 then Arc(bsc, "April Banner For Your Main Goal", "Pulls until your main goal drops, keeping its evolve item's price", "summonApril") Grc(bsc, "AprilCoins To Keep", "April pulls never go below this", "aprilFloor", { min = 0, max = 30000, step = 500, format = Prc }) end Arc(bsc, "Gem Banner When It Helps", "Only while a featured unit would beat your weakest team unit. Spends gems", "summonGems") bsc:Section("Display") vrc.skipSummonFx = bsc:Toggle("Skip Summon Animations", "Units, stars and skins land with no reveal, and item pop-ups stay hidden. The game's own auto-sell still runs", Config.skipSummonFx, function(vtc) xrc("skipSummonFx", vtc) pcall(FarmHub.quietFx) end) bsc = urc:Tab("๐Ÿ”ฅ", "Fuse") Yrc(bsc, "Fusing destroys the units it uses and can't be undone. Nothing fuses until you switch it on, and it shows what it will use for 30 s first. Lock a unit in the game to keep it out of every list.") bsc:Section("Limit Break") GUI.lbCard = bsc:StatusCard({ icon = "๐Ÿ”ฅ", title = "Limit Break", text = "", sub = "" }) do GUI.lists.fuseUnit = GUI.lbTargetOptions local wtc, xtc = GUI.lbTargetOptions(), 1 GUI.listBuilt("fuseUnit", wtc) for ytc, ztc in ipairs(wtc) do if ztc.value == Config.fuseUnit then xtc = ytc end end vrc.fuseUnitPick = bsc:Dropdown("Unit To Limit Break", "Units that can still be limit broken: your team first, then the ones at their level cap", wtc, xtc, function(Atc, Btc) if Btc ~= Config.fuseUnit then Config.fuseFodder = { } end xrc("fuseUnit", Btc) Config.fuseArmSig = "" FarmHub.pickNote.autoFuse = nil GUI.fodderSig = nil end) end GUI.fodder = GUI.fodderTable(bsc) wrc.fusePick = bsc:Paragraph("โ€ฆ") vrc.autoFuse = bsc:Toggle("Auto Limit Break", "Once everything above is โœ“. Destroys the fodder, and can't be undone", Config.autoFuse, function(Ctc) xrc("autoFuse", Ctc) xrc("fuseArmSig", "") FarmHub.last.picks = nil end) local function Dtc(Etc, Ftc, Gtc, Htc) GUI.lists [Gtc] = Htc local Itc = Htc() GUI.listBuilt(Gtc, Itc) local Jtc = 1 for Ktc, Ltc in ipairs(Itc) do if Ltc.value == Config [Gtc] then Jtc = Ktc end end vrc [Gtc .. "Pick"] = bsc:Dropdown(Etc, Ftc, Itc, Jtc, function(Mtc, Ntc) xrc(Gtc, Ntc) FarmHub.pickNote = { } FarmHub.last.goals, FarmHub.last.trainScan, Training._at = nil, nil, nil Config.fuseArmSig = "" if Gtc == "fuseXpUnit" then Config.fuseXpOnAt = os.time() end end) end bsc:Section("Fuse For XP") wrc.fuseXpPick = bsc:Paragraph("โ€ฆ") Dtc("Unit To Feed", "Your team first, then locked units. Units at their cap don't show", "fuseXpUnit", function() return GUI.unitOptions(Config.fuseXpUnit, function(Otc) return Strategy.level(Otc) < Strategy.cap(Otc) and "" end) end) vrc.fuseXpRarityPick = bsc:Dropdown("Use Units Up To", "Mythic and better are never used: a limit break needs those", { "Rare", "Epic", "Legendary" }, table.find({ "Rare", "Epic", "Legendary" }, Config.fuseXpRarity) or 2, function(Ptc) xrc("fuseXpRarity", Ptc) Config.fuseXpOnAt = os.time() end) vrc.autoFuseXp = bsc:Toggle("Auto Fuse For XP", "In the lobby, up to its level cap. Destroys the units listed above, and can't be undone", Config.autoFuseXp, function(Qtc) xrc("autoFuseXp", Qtc) Config.fuseXpOnAt = os.time() FarmHub.pickNote.autoFuseXp = nil FarmHub.last.picks = nil end) bsc = urc:Tab("โš”๏ธ", "Battle") Yrc(bsc, "How it plays inside a match. The defaults work, so you rarely need this tab.") bsc:Section("In a match") Arc(bsc, "Battle AI", "Runs placement and upgrades", "battle") Arc(bsc, "Auto Place", "Where enemies spend the most time in range", "place") Arc(bsc, "Auto Upgrade", "The most damage per yen", "upgrade") Arc(bsc, "Auto Abilities", "Buffs while the team fights, damage abilities for a boss or a crowd", "actives") Grc(bsc, "Ability Hold", "How long a damage ability waits for a boss or a crowd", "abilityHold", { min = 0, max = 60, step = 5, format = function(Rtc) return math.floor(Rtc) .. "s" end }) Arc(bsc, "Auto Start", "Votes to start once the first unit is down", "autoStart") Arc(bsc, "Smart Targeting", "Crowd control holds the front, heavy hitters take the boss", "smartTarget") bsc:Section("Your team at full upgrade") GUI.team = bsc:KeyValue({ "1", "2", "3", "4", "5", "6" }, 150) bsc:Section("Simulator and teams") GUI.sim = bsc:KeyValue({ "Next mode", "Your team", "Best team", "Evolutions", "Accuracy" }, 90) Arc(bsc, "Use The Simulator", "Simulates each mode before playing it, and ranks by the odds", "sim") Arc(bsc, "Search For Best Spots", "Tries spots for each attacker at the start of a match", "simSpots") Arc(bsc, "Plan Best Teams", "Works out the best team from all your units for the top modes", "simTeams") vrc.autoTeam = bsc:Toggle("Auto-Equip Best Team", "Searches fresh before every run and equips the team that does better (on by default). Your own team goes back after", Config.autoTeam, function(Stc) if Stc ~= Config.autoTeam then Config.autoTeamChosen = true end xrc("autoTeam", Stc) end) bsc:Button("๐Ÿงช", "Simulate Now", "In the lobby: re-runs the simulator and team planner", function() if App.where ~= "lobby" then return Trc("Simulator", "works in the lobby", "warning") end table.clear(Brain.simRes) FarmHub.last.simTeam = nil FarmHub.planNow = true Trc("Simulator", "re-planning โ€” results show here in a minute", "success") end) bsc:Button("๐Ÿ‘ฅ", "Equip Best Team", "In the lobby: equips the team it found", function() if App.where ~= "lobby" then return Trc("Team", "works in the lobby", "warning") end local Ttc for Utc, Vtc in pairs(Brain.simTeam) do if Vtc.better and (not Ttc or (Vtc.at or 0) > (Ttc.at or 0)) then Ttc = Vtc end end if not Ttc then return Trc("Team", "no better team found yet", "warning") end task.spawn(function() FarmHub.equipTeam(Ttc.uuids, "best team for " .. tostring(Ttc.label) .. " (button)") Trc("Team", "equipped: " .. table.concat(Ttc.names or { }, ", "), "success") end) end) bsc:Button("โ†ฉ๏ธ", "Restore My Team", "In the lobby: puts your own team back", function() if App.where ~= "lobby" then return Trc("Team", "works in the lobby", "warning") end task.spawn(function() Trc("Team", FarmHub.restoreTeam() and "your team is back" or "your own team is already equipped", "success") end) end) bsc:Section("Advanced: farm or damage") GUI.econCard = bsc:StatusCard({ icon = "๐Ÿ’ฐ", title = "Economy", text = "shows during a match", sub = "" }) Arc(bsc, "Farm Units", "Buys farm units while they pay back and the defense holds", "farmUnits") Grc(bsc, "Farm Waves To Spare", "A farm buy must pay back with this many waves left over", "farmSpare", { min = 0, max = 6, step = 1, format = Nrc }) Grc(bsc, "Safe Road", "Farming is allowed while enemies die before this share of the road", "safeReach", { min = 0.15, max = 0.8, step = 0.05, format = Src }) Grc(bsc, "Safe Kill Time", "...and while your units would clear the field in this long", "clearSec", { min = 5, max = 60, step = 5, format = function(Wtc) return math.floor(Wtc) .. "s" end }) Grc(bsc, "Danger Road", "Past this share of the road: damage only, and buy right away", "dangerReach", { min = 0.4, max = 0.95, step = 0.05, format = Src }) Grc(bsc, "Save Threshold", "Buys now if it's worth this share of the best option, instead of saving", "saveRatio", { min = 0, max = 1, step = 0.05, format = Src }) bsc:Button("๐Ÿงฎ", "Reset Unit Learning", "Forget measured unit damage", function() table.clear(Brain.learn) App.saveNow() Trc("Battle", "learning reset", "success") end) bsc = urc:Tab("๐Ÿ“œ", "Activity") Yrc(bsc, "What it has been doing, and where your gold, gems and coins went.") bsc:Section("What each part is doing") GUI.systems = bsc:KeyValue(GUI.SYSTEMS, 84) bsc:Section("Where your gold, gems and coins went") wrc.spend = bsc:Paragraph("โ€ฆ") wrc.lastMatch = bsc:Label("โ€ฆ") bsc:Section("Log") GUI.activity(bsc) wrc.totals = bsc:Label("...") vrc.logLevel = bsc:Dropdown("Log Level", "Quiet ยท Normal ยท Verbose", { "Quiet", "Normal", "Verbose" }, (Config.logLevel or 1) + 1, function(Xtc, Ytc, Ztc) xrc("logLevel", math.clamp((tonumber(Ztc) or 2) - 1, 0, 2)) end) Arc(bsc, "Print To Console", "Mirrors log lines to the executor console", "logConsole") bsc = urc:Tab("โš™๏ธ", "Settings") Yrc(bsc, "Everything else. Settings save by themselves.") bsc:Section("General") Arc(bsc, "Anti-AFK", "Sends an input when Roblox flags you idle", "antiAfk") Arc(bsc, "Auto Rewards", "Daily login, finished quests, battle pass, milestones", "rewards") Arc(bsc, "Redeem Codes", "Promo codes from the guides, at most once a day", "codes") Arc(bsc, "Community Tips", "Tier ratings for units the game hides, banner picks, buff chaining", "community") bsc:Section("Gold shop") Arc(bsc, "Star Fruits For Gold", "Buys evolve material from the daily gold shop", "goldShop") Grc(bsc, "Gold Floor", "Star Fruits never take your gold below this", "goldFloor", { min = 0, max = 2000000, step = 50000, format = Prc }) bsc:Section("Study A Unit") wrc.study = bsc:Paragraph("โ€ฆ") do GUI.lists.studyUnit = function() pcall(FarmHub.studySeed) local guc = GUI.unitOptions(Config.studyUnit ~= "smart" and Config.studyUnit or "", function(euc) local fuc = Brain.studied [euc.unit_id] if type(fuc) ~= "table" then return "" end if (tonumber(fuc.runs) or 0) > 0 then return string.format(" ยท โœ“ studied ร—%d", fuc.runs) end return string.format(" ยท โœ“ trial run ร—%d", tonumber(fuc.trials) or 1) end) table.insert(guc, 2, { text = "๐Ÿค– Smart: strongest not studied yet", value = "smart" }) return guc end local huc = GUI.lists.studyUnit() GUI.listBuilt("studyUnit", huc) local iuc = 1 for juc, kuc in ipairs(huc) do if kuc.value == Config.studyUnit then iuc = juc end end vrc.studyUnitPick = bsc:Dropdown("Unit To Study", "Smart, or one of yours: your team first, then locked, Secret and Mythic. โœ“ = studied", huc, iuc, function(luc, muc) xrc("studyUnit", muc) FarmHub.studyCache = nil end) end Grc(bsc, "Study Runs", "Real matches with it, on the easy map you've won most", "studyRuns", { min = 1, max = 5, step = 1, format = Nrc }) bsc:Button("๐Ÿ”ฌ", "Start Study", "Reads its passives and ability, then plays the runs (the Auto Farm has to be on)", function() local nuc, ouc = FarmHub.studyStart() Trc("Study", ouc, nuc and "success" or "warning") end) bsc:Button("โน๏ธ", "Stop Study", "No more study runs after the one going now. Your own team goes back in", function() Trc("Study", FarmHub.studyStop() and "stopped: your own team goes back in" or "no study going", "default") end) bsc:Section("Controls") bsc:Button("๐Ÿ ", "Go To Lobby", "Leaves the match now and starts no other until Start Farming Again. Summons and fuses you switched on still run", function() Brain.holdLobby = { why = "you pressed Go To Lobby", at = os.time() } App.saveNow() if App.where ~= "match" then App.log(1, "MODE", "held in the lobby (you pressed Go To Lobby): no matches until you press Start Farming Again") Trc("Lobby", "you're in the lobby: the farm starts no matches until Start Farming Again", "success") return end App.log(1, "MODE", "back to the lobby (you pressed Go To Lobby): the farm waits there until you press Start Farming Again") pcall(App.flush) task.spawn(function() local puc, quc = App.request("lobby", "teleport_back_to_lobby") Trc("Lobby", puc and "on the way to the lobby" or ("the game said no: " .. tostring(quc)), puc and "success" or "warning") end) end) bsc:Button("โ–ถ๏ธ", "Start Farming Again", "After Go To Lobby: the farm picks and plays modes again", function() if not Brain.holdLobby then Trc("Lobby", "the farm isn't waiting: it's playing as usual", "default") return end Brain.holdLobby = nil App.saveNow() App.log(1, "MODE", "farming again (you pressed Start Farming Again)") Trc("Lobby", "farming again", "success") end) bsc:Button("๐Ÿ’พ", "Save Settings", "Write " .. Core.SETTINGS, function() App.saveNow() Trc("Saved", "settings written", "success") end) bsc:Button("โŒ", "Unload", "Stop and remove the window", Color3.fromRGB(239, 68, 68), function() App.unload() end) bsc = urc:Tab("๐Ÿ“ฐ", "Updates") local yuc = { NEW = trc.Theme.Success, IMPROVED = trc.Theme.Accent, FIX = trc.Theme.Warning } for zuc, Auc in ipairs(CHANGELOG) do bsc:Section(string.format("v%s ยท %s ยท %s", Auc.v, Auc.title, Auc.date)) for Buc, Cuc in ipairs(Auc.items) do bsc:StatusCard({ icon = Cuc [1], title = Cuc [2], text = Cuc [3], tag = Cuc [4], tagColor = yuc [Cuc [4]], color = yuc [Cuc [4]] }) end end urc:Status(Config.master and "โ–ถ Farming" or "โธ Paused") -- Every element exists before profiles are applied. Existing farm autosaves -- supply defaults; an explicitly selected Airflow autoload profile wins. GUI.NativeWindow:LoadAutoload() urc:Status(Config.master and "โ–ถ Farming" or "โธ Paused") if Config.seenVersion ~= VERSION then local Fuc = Config.seenVersion == nil Config.seenVersion = VERSION App.saveSoon() task.delay(Fuc and 8 or 5, function() pcall(function() urc:Notify("Updated to v" .. VERSION, CHANGELOG [1].title, 10, "default") end) end) end end GUI.ICONS = { daily = "๐Ÿ“…", challenge = "๐Ÿ†", story = "โš”๏ธ", raid = "๐Ÿ‘น", event = "๐Ÿƒ", infinite = "โ™พ๏ธ", dungeon = "๐Ÿฐ" } function GUI.keyName(Guc) if Guc == "dc" then return "๐Ÿ“… Daily Challenge" end if Guc == "ch" then return "๐Ÿ† Challenge" end if Guc == "dg" then return "๐Ÿฐ Dungeon" end local Huc, Iuc, Juc = tostring(Guc):match("^(%a+):([^:]+):?(%a*)$") if not Iuc then return tostring(Guc) end return (({ st = "โš”๏ธ ", rd = "๐Ÿ‘น ", ev = "๐Ÿƒ ", inf = "โ™พ๏ธ " }) [Huc] or "") .. Modes.levelName(Iuc) .. ((Juc and Juc ~= "") and (" " .. Juc) or "") end function GUI.plainMode(Kuc) return string.format("%s ยท %.0f%% win ยท โ‰ˆ%.0f gems/min", Kuc.label, (Kuc.p or 0) * 100, Kuc.score or 0) end function GUI.upNext(Luc, Muc) if not Luc then return "decidingโ€ฆ", "" end if App.where == "match" and Luc.kind == "story" and Luc.first and Luc.key == Match.key then local Nuc = "once this act is won. If it's lost, this act again" local Ouc, Puc = pcall(Core.PSC.get_next_story_level, Luc.level) if Config.onlyStory and Ouc and Puc then return Modes.levelName(Puc) .. " (" .. tostring(Luc.diff) .. ")", "The next act, " .. Nuc end if Muc then return GUI.plainMode(Muc), "The next best mode, " .. Nuc end end return GUI.plainMode(Luc), Luc.why or "" end GUI.STYLE_NAMES = { fast = "fast clear, at the road's start", balanced = "spread out along the road", box = "kill box, on the road's last stretch" } function GUI.simView(Quc) if App.where == "match" and Match.ld then local Ruc = Match.pred local Suc if not Ruc then Suc = (Match.planning or (Match.laneWant and not Match.laneCut)) and "Simulator: choosing spotsโ€ฆ" or "Simulator: no prediction for this run" elseif Match.infinite then Suc = string.format("Simulator: the base falls at wave %d (%.0f min)", Ruc.wave or 0, (Ruc.t or 0) / 60) else Suc = string.format("Simulator: %s at wave %d (%.0f min)", Ruc.won and "a win" or "a loss", Ruc.wave or 0, (Ruc.t or 0) / 60) end local Tuc = { } if Match.style then table.insert(Tuc, "Placement: " .. (GUI.STYLE_NAMES [Match.style] or Match.style) .. (Match.styleWhy and (" (" .. tostring(Match.styleWhy) .. ")") or "")) end if Match.laneWant then table.insert(Tuc, Match.laneCut and string.format("lane %s, the one this portal opened (%d of %d)", Match.laneCut, Match.laneWant, Match.laneTotal or 0) or string.format("waiting for the first enemies to show which lane this portal opened (%d of %d)", Match.laneWant, Match.laneTotal or 0)) end if Match.trainee and Brain.train.on then table.insert(Tuc, "training " .. Units.name(Brain.train.on.id)) end local Uuc, Vuc, Wuc = "", nil, "no wave measured yet" local Xuc = Match.calRun and Match.calRun.alive or { } for Yuc = Match.wave(), 1, - 1 do local Zuc, _uc = tonumber(Xuc [tostring(Yuc)]), Ruc and Ruc.aliveAt and tonumber(Ruc.aliveAt [Yuc]) if Zuc and _uc then local avc = 1000 if Zuc < avc and _uc < avc then Uuc, Vuc, Wuc = "ON TRACK", Quc.green, string.format("wave %d started with every enemy before it cleared, as the simulator expected", Yuc) elseif _uc < avc then Uuc, Vuc, Wuc = "BEHIND", Quc.red, string.format("wave %d started with %s HP still alive where the simulator expected none", Yuc, Utils.fmt(Zuc)) elseif Zuc < avc then Uuc, Vuc, Wuc = "AHEAD", Quc.green, string.format("wave %d started with nothing left where the simulator expected %s HP", Yuc, Utils.fmt(_uc)) else local bvc = Zuc / _uc Uuc = bvc <= 0.8 and "AHEAD" or (bvc <= 1.25 and "ON TRACK" or (bvc <= 2 and "BEHIND" or "FAR BEHIND")) Vuc = bvc <= 1.25 and Quc.green or (bvc <= 2 and Quc.orange or Quc.red) Wuc = string.format("wave %d started with %s HP alive, the simulator expected %s (%s)", Yuc, Utils.fmt(Zuc), Utils.fmt(_uc), bvc <= 0.8 and string.format("%.1fร— easier", 1 / bvc) or (bvc <= 1.25 and "close" or string.format("%.1fร— harder", bvc))) end break end end if FarmHub.every("simCardShares", 5) then local cvc, dvc = pcall(Match.damageShares) local evc = { } for fvc, gvc in pairs(cvc and type(dvc) == "table" and dvc or { }) do if gvc >= 0.01 then table.insert(evc, { n = Units.name(fvc):gsub(" %(.+%)", ""), v = gvc }) end end table.sort(evc, function(hvc, ivc) return hvc.v > ivc.v end) local jvc = { } for kvc = 1, math.min(4, # evc) do table.insert(jvc, string.format("%s %d%%", evc [kvc].n, math.floor(evc [kvc].v * 100 + 0.5))) end GUI.simShares = # jvc > 0 and ("damage: " .. table.concat(jvc, ", ")) or nil end return { icon = "๐Ÿ”ฎ", title = Suc, text = # Tuc > 0 and table.concat(Tuc, " ยท ") or "", sub = Wuc .. (GUI.simShares and (" ยท " .. GUI.simShares) or ""), tag = Uuc, tagColor = Vuc } end local lvc = Modes.lastScored and Modes.lastScored [1] local mvc = lvc and Brain.simRes [lvc.key] local nvc = lvc and Brain.simTeam and Brain.simTeam [lvc.key] if not lvc then return { icon = "๐Ÿ”ฎ", title = "Simulator", text = "waiting for the next pick", sub = "", tag = "" } end return { icon = "๐Ÿ”ฎ", title = "Simulator: " .. tostring(lvc.label), text = mvc and (tostring(mvc.margin) .. (mvc.playing and (" ยท " .. tostring(mvc.playing)) or "")) or "no reading yet for this mode", sub = nvc and type(nvc.names) == "table" and ("team: " .. table.concat(nvc.names, ", ") .. (nvc.why and (" ยท " .. tostring(nvc.why)) or "")) or "", tag = mvc and (mvc.hopeless and "HOPELESS" or string.format("%d%%", math.floor((tonumber(lvc.p) or 0) * 100 + 0.5))) or "", tagColor = mvc and (mvc.hopeless and Quc.red or ((tonumber(lvc.p) or 0) >= 0.7 and Quc.green or Quc.orange)) or nil } end function GUI.update() local ovc = GUI.COL or { } local pvc = Core.prof() or { } if GUI.nowCard then local qvc if not Config.master then qvc = { icon = "โธ", title = "Paused", text = "Turn Auto Farm on to resume", sub = "", tag = "", progress = false, color = ovc.sub } elseif App.where == "match" and Match.ld then local rvc = Modes.levelName(Match.ld.id or "?") .. (Match.ld._difficulty and (" (" .. Match.ld._difficulty .. ")") or "") local tvc = Match.pressure or 0 local uvc = (Brain.pending and Brain.pending.key == Match.key and Brain.pending.label) and "launched by the farm" or "playing this match" local vvc = Match.depth or { } local wvc = (vvc.units or 0) > 0 and Utils.clamp(vvc.covEnd or 0.6, 0.1, 1) or 0.6 local xvc = vvc.leak or tvc >= math.min(Config.dangerReach, wvc * 0.9) local yvc = tvc >= math.min(Config.safeReach, wvc * 0.75) qvc = { icon = GUI.ICONS [Match.kind or ""] or "โš”๏ธ", title = rvc, text = tostring(Status.Economy or uvc), sub = string.format("Wave %d/%d ยท ยฅ%s ยท %d enemies ยท front at %d%% of the road, your units reach %d%% ยท kills all in %s", Match.wave(), Match.total or 0, Utils.fmt(Match.money()), # (Match.enemies or { }), math.floor(tvc * 100), math.floor(((vvc.units or 0) > 0 and vvc.covEnd or 0) * 100), (Match.clearSec or 0) >= 999 and "never" or (math.floor(Match.clearSec or 0) .. "s")), tag = vvc.leak and "LEAK" or (xvc and "DANGER" or (yvc and "PRESSURE" or "SAFE")), tagColor = xvc and ovc.red or (yvc and ovc.orange or ovc.green), progress = (Match.total or 0) > 0 and Match.wave() / Match.total or false, color = ovc.accent } else qvc = { icon = "๐Ÿฐ", title = "In the lobby", text = tostring(Status.Mode or "planning the next run"), sub = tostring(Status.Rewards or ""), tag = "", progress = false, color = ovc.accent } end pcall(GUI.nowCard.Set, qvc) pcall(function() GUI.Win:Status((qvc.icon or "") .. " " .. tostring(qvc.title), qvc.tagColor or qvc.color) end) end if GUI.simCard then local zvc, Avc = pcall(GUI.simView, ovc) if zvc and Avc then pcall(GUI.simCard.Set, Avc) end end if GUI.nextCard then local Bvc = Modes.lastScored and Modes.lastScored [1] if not Bvc and Utils.now() - (GUI.pickAt or 0) > 15 then GUI.pickAt = Utils.now() pcall(Modes.pick) Bvc = Modes.lastScored and Modes.lastScored [1] end local Cvc = Brain.lastResult local Dvc = { } if Cvc then if (Cvc.gems or 0) > 0 then table.insert(Dvc, Cvc.gems .. " gems") end if (Cvc.coins or 0) > 0 then table.insert(Dvc, Cvc.coins .. " " .. Modes.coinName()) end for Evc, Fvc in pairs(Cvc.items or { }) do table.insert(Dvc, string.format("%sร—%s", (Core.Items [Evc] and Core.Items [Evc].name) or Evc, tostring(Fvc))) end end local Gvc, Hvc = GUI.upNext(Bvc, Modes.lastScored and Modes.lastScored [2]) pcall(GUI.nextCard.Set, { icon = "โญ", title = "Up next: " .. Gvc, text = Hvc, sub = Cvc and ("Last run: " .. Cvc.text .. (# Dvc > 0 and (" ยท " .. table.concat(Dvc, ", ")) or "")) or "", tag = Cvc and (Cvc.won and "WON" or "LOST") or "", tagColor = Cvc and (Cvc.won and ovc.green or ovc.red) or nil }) end if GUI.goals then local Ivc = GUI.goals pcall(Ivc.Set, "Gems", Utils.fmt(pvc.gem_amount), string.format("gold %s", Utils.fmt(pvc.gold_amount)), nil, nil) local Jvc = Brain.live.daily local Kvc = Jvc and Jvc.uuid and (Jvc.uuid == pvc.last_completed_daily_challenge_uuid or Jvc.uuid == Jvc.doneUuid) pcall(Ivc.Set, "Daily Challenge", Kvc and "done โœ“" or (Jvc and "ready" or "โ€ฆ"), Jvc and string.format("worth โ‰ˆ%s gems", Utils.fmt(Jvc.value or 0)) or "", Kvc and ovc.green or ovc.gold, nil) local Lvc = Brain.live.normal local Mvc = Lvc and Lvc.uuid and (Lvc.uuid == pvc.last_completed_challenge_uuid or Lvc.uuid == Lvc.doneUuid) local Nvc = Lvc and Lvc.expires and math.max(0, math.floor((Lvc.expires - os.time()) / 60)) or 0 pcall(Ivc.Set, "Challenge", Mvc and "done โœ“" or (Lvc and "open" or "โ€ฆ"), Lvc and string.format("new one in %d min", Nvc) or "", Mvc and ovc.green or ovc.gold, nil) local Ovc = 0 for Pvc, Qvc in ipairs(Modes.lastScored or { }) do if Qvc.first then Ovc = Ovc + 1 end end pcall(Ivc.Set, "First Clears Left", tostring(Ovc), "acts you can clear for 60 gems now", Ovc > 0 and ovc.purple or ovc.green, nil) pcall(Ivc.Set, "Summer Pearls", Utils.fmt(pvc.resources and pvc.resources.SummerPearls or 0), "a Sunny Star costs 150", nil, nil) pcall(Ivc.Set, "Record", string.format("%d W ยท %d L", Brain.totals.wins or 0, Brain.totals.losses or 0), string.format("+%s gems by the farm", Utils.fmt(Brain.totals.gems or 0)), nil, nil) end if GUI.systems then for Rvc, Svc in ipairs(GUI.SYSTEMS) do local Tvc = Status [Svc] local Uvc = tostring(Tvc or "โ€”") pcall(GUI.systems.Set, Svc, Uvc, (Uvc:find("refused") or Uvc:find("error")) and ovc.red or (Tvc and ovc.text or ovc.sub)) end end if GUI.rank then local Vvc = Modes.lastScored or { } for Wvc = 1, 6 do local Xvc = Vvc [Wvc] if Xvc then pcall(GUI.rank.Set, "#" .. Wvc, GUI.plainMode(Xvc) .. "\n" .. (Xvc.why or ""), Wvc == 1 and ovc.green or ovc.text) else pcall(GUI.rank.Set, "#" .. Wvc, Wvc == 1 and "no ranking yet" or "", ovc.sub, Wvc == 1 and "#1" or "") end end end if GUI.records then local Yvc = { } for Zvc, _vc in pairs(Brain.modes) do if _vc.runs > 0 then table.insert(Yvc, { key = Zvc, r = _vc }) end end table.sort(Yvc, function(awc, bwc) return (awc.r.last or 0) > (bwc.r.last or 0) end) for cwc = 1, 8 do local dwc = Yvc [cwc] if dwc then local ewc = dwc.key == "dc" and "๐Ÿ“… Daily Challenge" or dwc.key == "ch" and "๐Ÿ† Challenge" or Modes.levelName((dwc.key:match("^%a+:([^:]+)") or dwc.key)) .. (dwc.key:match(":(%a+)$") and (" " .. dwc.key:match(":(%a+)$")) or "") pcall(GUI.records.Set, tostring(cwc), string.format("%d/%d won ยท %.1f min avg ยท %d gems", dwc.r.wins, dwc.r.runs, dwc.r.sec / math.max(1, dwc.r.runs) / 60, dwc.r.gems), nil, ewc) else pcall(GUI.records.Set, tostring(cwc), "", nil, cwc == 1 and "no runs yet" or "") end end end if GUI.team then local fwc = Units.equipped() for gwc = 1, 6 do local hwc = fwc [gwc] if hwc then local iwc, jwc = Units.trait(hwc.u) local kwc = iwc and (Core.traitInfo(iwc) or { }).name or "no trait" local lwc = Units.relicName(hwc.u) local mwc if Units.isFarm(hwc.id) then mwc = string.format("farm unit ยท ยฅ%s a wave at full upgrade ยท %s%s", Utils.fmt(Units.farm(hwc.u, Units.rows(hwc.id).max)), kwc, jwc and jwc ~= "1" and (" " .. jwc) or "") else local nwc, owc = Units.types(hwc.id) local pwc = { } for qwc in pairs(nwc) do if qwc ~= owc then table.insert(pwc, (qwc:gsub("_damage", ""))) end end mwc = string.format("score %s ยท %s ยท up to %d on the field ยท %s%s%s", Utils.fmt(Units.score(hwc.u, Match.mod)), kwc .. (jwc and jwc ~= "1" and (" " .. jwc) or ""), Units.cap(hwc.u), owc, # pwc > 0 and ("+" .. table.concat(pwc, "/")) or "", lwc and (" ยท relic: " .. lwc) or "") end pcall(GUI.team.Set, tostring(gwc), mwc, nil, Units.name(hwc.id)) else pcall(GUI.team.Set, tostring(gwc), "", nil, "") end end end if GUI.sim then local rwc = Modes.lastScored and Modes.lastScored [1] local swc = rwc and Brain.simRes [rwc.key] pcall(GUI.sim.Set, "Next mode", rwc and (rwc.label .. ": " .. (swc and (swc.playing or swc.margin) or (swc and swc.none and "can't simulate this one yet") or "not simulated yet")) or "โ€”") local twc, uwc for vwc, wwc in pairs(Brain.simTeam) do if not uwc or (wwc.at or 0) > (uwc.at or 0) then twc, uwc = vwc, wwc end end local xwc = uwc and Brain.simRes [twc] local ywc = App.where == "match" and Match.pred pcall(GUI.sim.Set, "Your team", ywc and string.format("this run: %s wave %d ยท now wave %d", Match.infinite and (ywc.won and "lasts past" or "the base falls at") or (ywc.won and "a win at" or "a loss at"), ywc.wave, Match.wave()) or uwc and string.format("%s: %s%s", uwc.label or twc, xwc and xwc.margin or (uwc.cur.won and "wins" or ("reaches wave " .. tostring(uwc.cur.wave))), uwc.better and " (before the best team went in)" or "") or "the planner runs in the lobby") pcall(GUI.sim.Set, "Best team", uwc and (table.concat(uwc.names or { }, ", ") .. (uwc.better and (" โ†’ " .. (uwc.won and "wins" or ("reaches wave " .. tostring(uwc.wave)))) or " (your team is already the best the sim found)")) or "โ€”") local zwc = { } for Awc, Bwc in ipairs(Brain.simEvo or { }) do table.insert(zwc, string.format("%s โ†’ %s: %s%s", Bwc.from, Bwc.to, Bwc.gain, Bwc.ready and " ยท ready" or (Bwc.missing and (" ยท needs " .. Bwc.missing) or ""))) end pcall(GUI.sim.Set, "Evolutions", # zwc > 0 and table.concat(zwc, "\n") or "none ready (planner runs in the lobby)") local Cwc = { } for Dwc, Ewc in pairs(Brain.simCal) do local Fwc = Ewc.lastPred if Fwc then table.insert(Cwc, string.format("%s: predicted %s (wave %d) ยท real %s (wave %d)", GUI.keyName(Dwc), Fwc.won and "win" or "loss", Fwc.wave or 0, Fwc.realWon and "win" or "loss", Fwc.realWave or 0)) end end pcall(GUI.sim.Set, "Accuracy", # Cwc > 0 and table.concat(Cwc, "\n") or "no predicted run yet") end if GUI.path then if App.where == "match" and FarmHub.every("goalsMatch", 300) then task.spawn(function() pcall(Adventure.refresh) end) end local Gwc = GUI.focusNow() local Hwc = Gwc == "" and ((Brain.train.on and Brain.train.on.uuid) or ((Brain.goals or { }) [1] and Brain.goals [1].uuid)) or nil for Iwc = 1, 11 do local Jwc = (Brain.goalsTop or { }) [Iwc] if Jwc then local Kwc = Jwc.uuid == Gwc and "๐ŸŽฏ Your focus ยท " or (Jwc.uuid == Hwc and "๐Ÿค– Smart Play's pick ยท " or "") pcall(GUI.path.Set, tostring(Iwc), Kwc .. tostring(Jwc.text), Jwc.uuid == Gwc and ovc.gold or (Jwc.ready and not Jwc.hidden and ovc.green or (Jwc.intoTeam and ovc.purple or ((Jwc.unknown or Jwc.hidden) and ovc.sub or ovc.text))), Units.name(Jwc.to)) else pcall(GUI.path.Set, tostring(Iwc), Iwc == 1 and "goals are worked out in the lobby" or "", ovc.sub, Iwc == 1 and "โ€”" or "") end end if GUI.UI.focusPick and FarmHub.every("focusList", 3) then local Lwc, Mwc = GUI.focusOptions(), { Gwc } for Nwc, Owc in ipairs(Lwc) do table.insert(Mwc, tostring(Owc.text) .. "=" .. tostring(Owc.value)) end Mwc = table.concat(Mwc, "|") if GUI.focusSig and GUI.focusSig ~= Mwc then pcall(GUI.UI.focusPick.Refresh, Lwc, Gwc) end GUI.focusSig = Mwc end local Pwc = { } for Qwc, Rwc in ipairs(Brain.goals) do if Rwc.ready then table.insert(Pwc, Units.name(Rwc.from) .. " โ†’ " .. Units.name(Rwc.to)) end end if GUI.readyLabel then pcall(GUI.readyLabel.Set, # Pwc > 0 and ("Ready: " .. table.concat(Pwc, " ยท ")) or "Nothing ready yet โ€” the farm is collecting the items") end end local Swc = GUI.LIVE if Swc.only and FarmHub.every("onlyText", 2) then local Twc = Modes.lastScored and Modes.lastScored [1] local Uwc = { } for Vwc, Wwc in pairs(Modes.skipped) do table.insert(Uwc, "Skipped: " .. Wwc) end table.sort(Uwc) pcall(Swc.only.Set, GUI.modeSummary(Twc, Uwc)) end if Swc.summonPick and FarmHub.every("summonText", 3) then pcall(function() Swc.summonPick.Set(FarmHub.summonText()) end) end if Swc.snipe and FarmHub.every("snipeText", 5) then pcall(function() Swc.snipe.Set(FarmHub.snipeText()) end) end if Swc.study and FarmHub.every("studyText", 5) then pcall(function() Swc.study.Set(FarmHub.studyText()) end) end if Swc.hive and FarmHub.every("hiveText", 15) then pcall(function() Swc.hive.Set(Reports.hiveText()) end) end if GUI.homeKv and FarmHub.every("homeRows", 3) then local Xwc, Ywc = pcall(GUI.homeRows) for Zwc, _wc in ipairs(GUI.HOME_KEYS) do pcall(GUI.homeKv.Set, _wc, Xwc and Ywc [_wc] or "โ€ฆ", (Xwc and tostring(Ywc [_wc]):sub(1, 3) == "off") and ovc.sub or ovc.text) end end if GUI.lbCard and FarmHub.every("lbCard", 3) then local axc, bxc = pcall(GUI.lbStatus) if axc then pcall(GUI.lbCard.Set, bxc) end end if GUI.fodder and Core.session() and (GUI.fodderSig == nil or (App.where == "lobby" and FarmHub.every("fodderRows", 15))) then local cxc = Core.session() local dxc = cxc and Config.fuseUnit ~= "" and cxc.collection:get_unit_by_uuid(Config.fuseUnit) or nil local exc, fxc = pcall(FarmHub.fodderRows, dxc) fxc = exc and fxc or { } local gxc = { tostring(Config.fuseUnit) } for hxc, ixc in ipairs(fxc) do table.insert(gxc, ixc.uuid .. ":" .. ixc.pts .. ":" .. # ixc.flags .. (ixc.safe and "s" or "")) end gxc = table.concat(gxc, ",") if GUI.fodderSig ~= gxc then GUI.fodderSig = gxc pcall(GUI.fodder.Refresh, fxc, dxc and "Nothing the game would take for this unit. Locked and equipped units never show." or "Pick a unit to limit break above, and the units it could use show here.") else pcall(GUI.fodder.summary) end end if Swc.hunt and FarmHub.every("huntText", 5) then pcall(function() Swc.hunt.Set(Hunt.text()) end) end if Swc.skins and FarmHub.every("skinText", 8) then pcall(function() Swc.skins.Set(Hunt.skinText()) end) end if Swc.fusePick and FarmHub.every("fuseText", 8) then pcall(function() Swc.fusePick.Set(FarmHub.fuseText()) end) end if Swc.fuseXpPick and FarmHub.every("fuseXpText", 8) then pcall(function() Swc.fuseXpPick.Set(FarmHub.fuseXpText()) end) end if Swc.focus and FarmHub.every("focusText", 3) then pcall(function() Swc.focus.Set(FarmHub.focusText()) end) end if Swc.teamNext and FarmHub.every("teamText", 3) then pcall(function() Swc.teamNext.Set(Strategy.text()) end) end if GUI.core and FarmHub.every("coreRows", 8) then local jxc, kxc = pcall(Strategy.rows) for lxc = 1, 8 do local mxc = jxc and kxc [lxc] if mxc then pcall(GUI.core.Set, tostring(lxc), mxc.text, mxc.text:find("next:") and ovc.text or ovc.green, mxc.name) else pcall(GUI.core.Set, tostring(lxc), "", nil, lxc == 1 and (jxc and "no core units yet" or ("error: " .. tostring(kxc))) or "") end end end local nxc = false for oxc in pairs(GUI.lists) do if GUI.listEmpty [oxc] then nxc = true break end end if (App.where == "lobby" and FarmHub.every("pickLists", 30)) or (nxc and Core.session() and FarmHub.every("pickListsEarly", 5)) then for pxc, qxc in pairs(GUI.lists) do if App.where == "lobby" or GUI.listEmpty [pxc] then local rxc, sxc = pcall(qxc) if rxc then local txc = GUI.sigOf(sxc) if GUI.listSig [pxc] ~= txc then pcall(GUI.UI [pxc .. "Pick"].Refresh, sxc, Config [pxc]) if GUI.listEmpty [pxc] and # sxc > 1 then App.log(1, "INFO", string.format("the window opened before the game had your units: %s's list filled in %d s later", pxc, math.floor(Utils.now() - (GUI.listAt or Utils.now())))) end end GUI.listSig [pxc] = txc if Core.session() then GUI.listEmpty [pxc] = nil end end end end end if Swc.train then pcall(Swc.train.Set, Training.text()) end if Swc.community and FarmHub.every("communityText", 30) then pcall(Swc.community.Set, Adventure.communityText()) end if Swc.goal and FarmHub.every("goalText", 5) then pcall(Swc.goal.Set, Adventure.goalText()) end if Swc.fh and FarmHub.every("fhText", 10) then pcall(Swc.fh.Set, Match.fhText()) end if Swc.dungeon and FarmHub.every("dgText", 5) then pcall(Swc.dungeon.Set, Dungeon.text()) end if Swc.lb then pcall(Swc.lb.Set, Adventure.lbText()) end if GUI.econCard and App.where == "match" then pcall(GUI.econCard.Set, { icon = "๐Ÿ’ฐ", title = string.format("ยฅ%s now ยท farm ยฅ%s a wave", Utils.fmt(Match.money()), Utils.fmt(Match.farmNow or 0)), text = tostring(Status.Economy or ""), sub = string.format("coming yen โ‰ˆ%s ยท still to buy โ‰ˆ%s", Utils.fmt(Match.future or 0), Utils.fmt(Match.wantLeft or 0)) }) end local uxc = GUI.LIVE if uxc.spend then pcall(uxc.spend.Set, Spending.text()) end if uxc.lastMatch then local vxc = Brain.lastMatch pcall(uxc.lastMatch.Set, vxc and string.format("Last match (%s): ยฅ%s spent on %d buys ยท %d refused spots", vxc.label, Utils.fmt(vxc.spent), vxc.actions or 0, vxc.refusals or 0) or "no match yet") end if uxc.totals then pcall(uxc.totals.Set, string.format("matches %d ยท wins %d ยท losses %d ยท gems %s ยท event coins %s", Brain.totals.matches or 0, Brain.totals.wins or 0, Brain.totals.losses or 0, Utils.fmt(Brain.totals.gems or 0), Utils.fmt(Brain.totals.coins or 0))) end end function GUI.loop() while App.alive do pcall(GUI.update) task.wait(1) end end table.insert(App.conns, LocalPlayer.Idled:Connect(function() if not Config.antiAfk then return end pcall(function() local wxc = game:GetService("VirtualUser") wxc:CaptureController() wxc:ClickButton2(Vector2.new()) end) end)) function App.cleanup() for xxc, yxc in ipairs(App.conns) do pcall(function() yxc:Disconnect() end) end pcall(function() GUI.Win:Destroy() end) pcall(function() if Match.probe then Match.probe:Destroy() end end) pcall(function() if Match.bounds then Match.bounds:Destroy() end end) end function App.unload() App.alive = false App.saveNow() pcall(FarmHub.quietFx, true) App.cleanup() _G.AA_SF = nil end function App.restart() local zxc, Axc = loadstring(readfile(Core.SELF), "=RobScript_Team_Anime_Adventures") assert(zxc, Axc) App.unload() zxc() end task.spawn(function() while App.alive do task.wait(1) if _G.AA_SF ~= App then App.alive = false App.cleanup() end end end) pcall(function() local queue = queue_on_teleport or queueonteleport or (syn and syn.queue_on_teleport) if queue and not _G.AA_SF_QUEUED then local queued = string.format([[ task.wait(0.5) local ok, source = pcall(function() return isfile and isfile(%q) and readfile(%q) end) if ok and type(source) == "string" and source ~= "" then local chunk, err = loadstring(source, "=RobScript_Team_Anime_Adventures") if chunk then chunk() else warn("[RobScript Team] " .. tostring(err)) end else warn("[RobScript Team] Saved script unavailable; run the Airflow file again.") end ]], Core.SELF, Core.SELF) queue(queued) _G.AA_SF_QUEUED = true end end) App.C, App.B, App.CFG, App.MT, App.HUB, App.ST, App.UNIT, App.MODE = Core, Brain, Config, Match, FarmHub, Status, Units, Modes App.SIM, App.ECON, App.GUI, App.TRAIN, App.ADV, App.DG, App.STR, App.HUNT = Simulator, Economy, GUI, Training, Adventure, Dungeon, Strategy, Hunt App.REPORT = Reports App.version = VERSION task.spawn(GUI.build) task.spawn(function() task.wait(2) GUI.loop() end) App.log(1, "INFO", "RobScript Team - Anime Adventures v" .. VERSION .. " started in the " .. App.where .. " (copy " .. App.loadId .. ")") print("[AA SF] v" .. VERSION .. " loaded in the " .. App.where .. " ยท copy " .. App.loadId) App.status("Mode", "starting in the " .. App.where) task.spawn(function() pcall(FarmHub.quietFx) local Bxc, Cxc = pcall(App.where == "match" and Match.run or FarmHub.run) if not Bxc then App.log(0, "FATAL", tostring(Cxc)) warn("[AA] " .. tostring(Cxc)) end end) ]====] local cached, cacheError = pcall(function() assert(type(writefile) == "function", "writefile unavailable") writefile("RobScript_Team_Anime_Adventures.lua", AIRFLOW_SOURCE) end) if not cached then warn("[RobScript Team] Teleport cache could not be written: " .. tostring(cacheError)) end local run, compileError = loadstring(AIRFLOW_SOURCE, "=RobScript_Team_Anime_Adventures") assert(run, compileError) return run()