Use this if you want a copy of the source for modifying in the editor.
{"workspaces":{"Adventure":[["init",":import adventure_lib\n:name {package}:init\n\n:global int leon.adventure.maxDifficulty\n:global int turbo.register\n:global bool leon.adventure.running\n:global string leon.adventure.path\n:global string leon.adventure.room_direction\n\n:local int maxDifficultyInit\n:local double difficulty\n\nkey.r()\nkey.i()\n\nisopen(\"arcade\")\n\n#nextRoomLower if(\\\n x(adventure.roomCoords()) < 127.0 && y(adventure.roomCoords()) >= 127.0,\\\n \"R\",\\\n if(\\\n x(adventure.roomCoords()) >= 127.0 && y(adventure.roomCoords()) > 127.0,\\\n \"D\",\\\n if(\\\n x(adventure.roomCoords()) > 127.0 && y(adventure.roomCoords()) <= 127.0,\\\n \"L\",\\\n \"U\" \\\n )\\\n )\\\n)\n#rotateLeft(x) sub(\"LDRU\", index(\"ULDR\", {x}, 0), 1)\n; Conditionally rotate the direction \"x\" to the left.\n; The benefit of this macro is that it only expands \"x\" once.\n#condRotateLeft(cond, x) sub(if({cond}, \"LDRU\", \"ULDR\"), index(\"ULDR\", {x}, 0), 1)\n\n; ======== Start init code ==============\n\n; Because of how turbo exec works, we can't launch turbo on the frame the script\n; starts by calling \"TE2.2:start\", if we are being called from another script.\n; We can only do it by changing turbo.register directly, saving a layer of\n; script execution.\n; (This is because of the relative positioning of TE.turbo vs our script;\n; usually we would be before, but when we are first launched our script is after.)\nturbo.register += 1\n\n; This exists as a local variable solely to provide users an easy place to\n; customize it, after it's been imported.\nmaxDifficultyInit = 81\n\n; Initialize all our global variables.\n; This is a maze of conditional expressions, because init is called from\n; *every* script that has a key impulse, in order to handle proper variable\n; initialization first.\n\n; Check the variable hider to see if this is first execution or not.\nleon.adventure.maxDifficulty = if(\\\n gsg(\"adv---Stop hiding---\") == \"\" || contains(impulse(), \"key.i\"),\\\n maxDifficultyInit,\\\n leon.adventure.maxDifficulty\\\n)\n; This variable also begins the variable hiding block with its name.\nglobal.string.set({target_pos_var}, {target_positions})\n\n; If we are triggered by key.r or key.k (from the kill enemies script), then\n; we should toggle the running state. This expression toggles the state when\n; the second part is true, and keeps it constant when the second part is false.\nleon.adventure.running = leon.adventure.running !=\\\n contains(\"key.r|{package}:kill enemies\", impulse())\nleon.adventure.path = leon.adventure.path\nleon.adventure.room_direction = leon.adventure.room_direction\ngss(\"adv---Stop hiding---\", \"</size=0>\")\n\n; If we are invoked from \"kill enemies,\" it means we are using that script stand-alone.\n; We will execute the last executesync of the loop below, just to re-exec the\n; kill-enemies script. This is done to keep all the init and turbo start/stop logic\n; centralized here, in init.\n; In general, \"contains\" checks are more size/speed efficient than \"==\" checks\n; for strings, since they have one fewer argument.\ngoto(if(\\\n contains(impulse(), \"kill enemies\"),\\\n last_exec,\\\n if(\\\n contains(impulse(), \"key.r\") && leon.adventure.running,\\\n loop,\\\n end\\\n )\\\n))\n\n; ======== Continuous loop code ==============\n\nloop:\nexecutesync(\"{package}:farm keys\")\n\n; We check based on current difficulty plus 1, because it's more efficient\n; that way.\ndifficulty = {cur_difficulty(1.)}\n\n; This line is responsible for maxDifficulty \"creeping\" upwards. When used\n; properly, it will cause the AI to slowly make spirals that cover (nearly)\n; the entire maze. However, extra lines can \"trick\" it and cause it to jump\n; more difficulty levels than wanted.\n;\n; Note the check against current difficulty (which is current_difficulty+1 here),\n; which prevents this from triggering multiple times before the AI can travel\n; to new rooms.\n;\n; We add 2 to maxDifficulty because adding 1 would cause us to overlap\n; prior progress, and we'd immediately add another 1 anyway.\nleon.adventure.maxDifficulty = leon.adventure.maxDifficulty + if(\\\n difficulty > i2d(leon.adventure.maxDifficulty)\\\n && adventure.isCompleted(adventure.roomCoords()),\\\n 2,\\\n 0\\\n)\nleon.adventure.room_direction = if(\\\n difficulty == 1. && leon.adventure.room_direction != \"\",\\\n {rotateLeft(leon.adventure.room_direction)},\\\n {condRotateLeft({survivable(difficulty)}, {nextRoomLower})}\\\n)\n\nlast_exec:\n; We might be here to invoke \"kill enemies\", which happens iff we ourselves were\n; invoked by \"kill enemies.\" Otherwise, we need to protect against arcade being closed,\n; because executesync will stall forever in that case due to the script condition.\n; This also prevents spurious moves in case we've cancelled the script.\nexecutesync(if(\\\n contains(impulse(), \"kill enemies\"),\\\n \"{package}:kill enemies\",\\\n if(\\\n isopen(\"arcade\") && leon.adventure.running,\\\n \"{package}:find loot\",\\\n \"{package}:xNOOPx\"\\\n )\\\n))\ngotoif(loop, isopen(\"arcade\") && leon.adventure.running && contains(impulse(), \"kill enemies\") == false)\n\nend:\nexecutesync(\"TE2.2:stop\")\n; If we were invoked by a manual action (key.r or kill enemies), we shut down\n; automatically once we reach the end of the script. This might be redundant, but\n; in some cases (such as exiting due to leaving the arcade) it is needed.\n; In any other situation, the running state should be unmodified.\nleon.adventure.running = leon.adventure.running &&\\\n contains(\"key.r|{package}:kill enemies\", impulse()) == false\n"],["analyze loot",":import adventure_lib\n:name {package}:analyze loot\n\n:local string queue\n:local string idx\n:local vector curPos\n:local string targetPos\n:local string lootPositions\n\n#setField(index, value) lss(\"_f\" . {index}, {value})\n#getField(index) lsg(\"_f\" . {index})\n#setTPos(index, value) lss(\"_t\" . {index}, {value})\n#getTPos(index) lsg(\"_t\" . {index})\n\nqueue = {posToString(x(adventure.playerPos()), y(adventure.playerPos()))} . \"S:\"\n\n; On the first entry of this loop, the junk entry \"_f\" will be set to \"S\".\n; This has no effect.\n; In general, lootPositions, queue and setField are protected so that if the field\n; has already been set, we won't take action again.\nloop1:\ncurPos = {firstElementPos}\nidx = sub(queue, 0, 4)\nlootPositions = if(\\\n {getField(idx)} == \"\"\\\n && contains(adventure.entityType(curPos), \"Chest\"),\\\n {posToString(x(curPos), y(curPos))} . \":\",\\\n \"\") . lootPositions\n{setField(if({getField(idx)} != \"\", \"\", idx), {firstElementDir})}\nqueue = sub(queue, 6, 99999) . if({getField(idx)} != {firstElementDir},\\\n \"\",\\\n if(\\\n adventure.isWall(curPos + vec(0., 1.)) || y(curPos) >= 18.,\\\n \"\",\\\n {posToString(x(curPos), y(curPos) + 1.)} . \"U:\"\\\n ) . if(\\\n adventure.isWall(curPos + vec(0., -1.)) || y(curPos) < 1.,\\\n \"\",\\\n {posToString(x(curPos), y(curPos) - 1.)} . \"D:\"\\\n ) . if(\\\n adventure.isWall(curPos + vec(1., 0.)) || x(curPos) >= 18.,\\\n \"\",\\\n {posToString(x(curPos) + 1., y(curPos))} . \"R:\"\\\n ) . if(\\\n adventure.isWall(curPos + vec(-1., 0.)) || x(curPos) < 1.,\\\n \"\",\\\n {posToString(x(curPos) - 1., y(curPos))} . \"L:\"\\\n )\\\n)\ngotoif(loop1, queue != \"\")\n\ncontinue:\nlootPositions = {target_positions} . lootPositions\n\nloop2:\ntargetPos = sub(lootPositions, 0, 4)\ncurPos = {strToVec(targetPos)}\nlootPositions = sub(lootPositions, 5, 99999)\n\ninnerLoop2:\nidx = {posToString(x(curPos), y(curPos))}\nglobal.string.set({target_pos_var}, if(\\\n {getField({posToString(\\\n x(curPos) - {nextX({getField(idx)})},\\\n y(curPos) - {nextY({getField(idx)})}\\\n )})} == \"S\"\\\n && false == contains({target_positions}, targetPos),\\\n targetPos . \":\" . {target_positions},\\\n if(\\\n false == contains({target_positions}, targetPos) \\\n && {getTPos(idx)} != \"\",\\\n sub(\\\n {target_positions},\\\n 0,\\\n index({target_positions}, {getTPos(idx)}, 0)\\\n )\\\n . targetPos . \":\"\\\n . sub(\\\n {target_positions},\\\n index({target_positions}, {getTPos(idx)}, 0),\\\n 99999\\\n ),\\\n {target_positions}\\\n )\\\n))\n\n{setTPos(\\\n idx,\\\n if(\\\n false == contains({target_positions}, targetPos)\\\n || {getTPos(idx)} == sub({target_positions}, index({target_positions}, targetPos, 0) + 5, 4),\\\n targetPos,\\\n {getTPos(idx)}\\\n )\\\n)}\ncurPos -= {nextDir({getField(idx)})}\ngoto(if(\\\n {getField({posToString(x(curPos), y(curPos))})} != \"S\",\\\n innerLoop2,\\\n if(len(lootPositions) > 0, loop2, end)\\\n))\n\nend:\nwait(0.)\n"],["farm keys",":import adventure_lib\n:name {package}:farm keys\n\n:global int leon.adventure.maxDifficulty\n:global bool leon.adventure.running\n:global string leon.adventure.path\n\n:local double difficulty\n:local bool parity\n:local bool needs_something\n:local bool skipped\n\n:local string dir\n\n#needs_bombs adventure.bombs() < 99 && difficulty >= 80. && adventure.hasItem(\"leechSword\")\n#needs_health adventure.playerHealth() < 99 && adventure.hasItem(\"leechSword\")\n#needs_mana adventure.mana() < 99 && adventure.hasItem(\"manaReaver\") && adventure.hasItem(\"bookSpells\")\n#needs_keys adventure.keys() <= 10\n#num_enemies round(difficulty * 0.1 + 3.)\n#elite_dmg d2i(floor(difficulty * 0.01 + 1.)) * (d2i(ceil(ceil(difficulty * 0.39) * 1.5)) - adventure.playerArmor())\n\ngoto(start)\n\nnext:\nparity = false == parity\nleon.adventure.path = dir\nexecutesync(\"{package}:follow path\")\nstart:\ndifficulty = {cur_difficulty(0.)}\nneeds_something = {survivable(difficulty)} && ({needs_bombs} || {needs_health} || {needs_mana} || {needs_keys})\ndir = sub(\"XU D L R\", index(\" 918 90 09 189\", x(adventure.playerPos()) . y(adventure.playerPos()), 0), 1)\nskipped = contains(\"UDLR\", dir) && (\\\n (adventure.countEntities(\"Elite\") > 0) != (needs_something && {needs_bombs})\\\n && (adventure.countEntities(\"Elite\") > 0 || adventure.playerHealth() > 100 - d2i({num_enemies} * 1.8))\\\n || (adventure.countEntities(\"Elite\") > 0 && adventure.playerHealth() <= {elite_dmg})\\\n || (parity && false == needs_something)\\\n)\n; Need to protect against arcade being closed during the loop, because\n; executesync will stall forever in that case due to the script condition\nexecutesync(if(\\\n skipped || isopen(\"arcade\") == false,\\\n \"{package}:xNOOPx\",\\\n \"{package}:kill enemies\"\\\n))\n; Update needs_something after clearing\ngoto(if(\\\n contains(\"UDLR\", dir) && leon.adventure.running && (\\\n needs_something && ({needs_bombs} || {needs_health} || {needs_mana} || {needs_keys})\\\n || parity\\\n || skipped\\\n ),\\\n next,\\\n end\\\n))\nend:\nwait(0.)\n"],["find loot",":import adventure_lib\n:name {package}:find loot\n\n:global int turbo.cycles.max\n:global int turbo.cycles\n:global int leon.adventure.maxDifficulty\n:global bool leon.adventure.running\n:global string leon.adventure.path\n:global string leon.adventure.room_direction\n\n:local string market_data\n\nkey.u()\nkey.j()\n\nisopen(\"arcade\")\n\n; Initialize variables so that pressing \"u\" or \"j\" after resetting AI\n; doesn't increment from 0\nexecutesync(if(\\\n contains(impulse(), \"key.\"),\\\n \"{package}:init\",\\\n \"{package}:xNOOPx\"\\\n))\nleon.adventure.maxDifficulty +=\\\n if(contains(impulse(), \"key.j\"), -1, if(contains(impulse(), \"key.u\"), 1, 0))\ngotoif(end, contains(impulse(), \"key.\"))\n\n; Buy the first market item in our priority list that we don't have.\n; We don't need to worry about checking for the market or available emeralds;\n; if we can't buy the item, we'll simply fail to purchase and continue on.\n\n; This Lua macro constructs a data table that is used to iterate over the items\n; efficiently. The encoding is length-2 size, length-3 position, and\n; variable-length data. The size and position are for locating the *next* item.\n#data_table {lua(\\\n local acc = {}\\\n local item_str = \"impaler,hammer,bootsPhasing,leechSword,manaReaver,eodArmor,thornsArmor,holyBomb,bookSpells\"\\\n local i = 3\\\n for item in item_str:gmatch(\"%a+\") do\\\n acc[i] = item\\\n i = i + 3\\\n end\\\n i = 3\\\n pos = 0\\\n while acc[i] do\\\n pos = pos + 5 + #acc[i]\\\n acc[i-2] = string.format(\"%02d\", #(acc[i+3] or \"\") + 5)\\\n acc[i-1] = string.format(\"%03d\", pos)\\\n i = i + 3\\\n end\\\n return table.concat(acc)\\\n)}\n\nmarket_loop:\nmarket_data = sub(\\\n \"{data_table}\",\\\n s2i(sub(market_data, 2, 3), 0),\\\n s2i(sub(market_data, 0, 2), 12)\\\n)\n; Loop until we find an unbought item. By the nature of hasItem, it will\n; always return false for the empty string at the end of the loop, so we\n; don't need an explicit end check.\ngotoif(market_loop, adventure.hasItem(sub(market_data, 5, 99)))\nadventure.buyMarketItem(sub(market_data, 5, 99))\n\n; Turn our room direction into a target exit via lookup-table.\nglobal.string.set({target_pos_var}, sub(\\\n \"0918 0009 1809 0900\",\\\n index(\"U L R D\", leon.adventure.room_direction, 0),\\\n 4\\\n) . \":\")\n\nturbo.cycles.max = max(turbo.cycles.max, turbo.cycles + 4000)\nexecutesync(\"{package}:analyze loot\")\nloop:\nexecutesync(\"{package}:find path\")\nexecutesync(\"{package}:follow path\")\nglobal.string.set({target_pos_var}, sub({target_positions}, 5, 99999))\ngotoif(loop, len({target_positions}) > 0 && leon.adventure.running)\n\nleon.adventure.path = leon.adventure.room_direction\nexecutesync(\"{package}:follow path\")\nend:\nwait(0.)\n"],["find path",":import adventure_lib\n:name {package}:find path\n\n:global int turbo.cycles.max\n:global int turbo.cycles\n:global string leon.adventure.path\n\n:local vector curPos\n:local string queue\n:local string idx\n:local string dir\n:local string path\n:local bool cantPhase\n\n#setField(index, value) lss(\"_f\" . {index}, {value})\n#getField(index) lsg(\"_f\" . {index})\n\nturbo.cycles.max = max(turbo.cycles.max, turbo.cycles + 4000)\nleon.adventure.path = \"\"\ncantPhase = false == adventure.hasItem(\"bootsPhasing\")\\\n|| 0 < max(\\\n max(\\\n max(\\\n adventure.countEntities(\"Chest\"),\\\n adventure.countEntities(\"Enemy\")\\\n ),\\\n adventure.countEntities(\"Elite\")\\\n ),\\\n adventure.countEntities(\"Mimic\")\\\n)\nqueue = {posToString(x(adventure.playerPos()), y(adventure.playerPos()))} . \"S:\"\nloop1:\ncurPos = {firstElementPos}\nidx = sub(queue, 0, 4)\n{setField(if({getField(idx)} != \"\", \"\", idx), {firstElementDir})}\nqueue = sub(queue, 6, 99999) . if({getField(idx)} != {firstElementDir},\\\n \"\",\\\n if(\\\n adventure.isWall(curPos + vec(0., 1.)) && cantPhase\\\n || max(x(curPos), y(curPos)) >= 18.\\\n || x(curPos) < 1.,\\\n \"\",\\\n {posToString(x(curPos), y(curPos) + 1.)} . \"U:\"\\\n ) . if(\\\n adventure.isWall(curPos + vec(0., -1.)) && cantPhase\\\n || min(x(curPos), y(curPos)) < 1.\\\n || x(curPos) >= 18.,\\\n \"\",\\\n {posToString(x(curPos), y(curPos) - 1.)} . \"D:\"\\\n ) . if(\\\n adventure.isWall(curPos + vec(1., 0.)) && cantPhase\\\n || max(x(curPos), y(curPos)) >= 18.\\\n || y(curPos) < 1.,\\\n \"\",\\\n {posToString(x(curPos) + 1., y(curPos))} . \"R:\"\\\n ) . if(\\\n adventure.isWall(curPos + vec(-1., 0.)) && cantPhase\\\n || min(x(curPos), y(curPos)) < 1.\\\n || y(curPos) >= 18.,\\\n \"\",\\\n {posToString(x(curPos) - 1., y(curPos))} . \"L:\"\\\n )\\\n)\ngoto(if(\\\n queue == \"\" || contains(idx, sub({target_positions}, 0, 4)),\\\n l2start,\\\n loop1\\\n))\n\nloop2:\nleon.adventure.path = dir . leon.adventure.path\ncurPos -= {nextDir(dir)}\nidx = {posToString(x(curPos), y(curPos))}\nl2start:\ndir = {getField(idx)}\ngotoif(loop2, dir != \"S\")\n\nexecutesync(\"{package}:refine path\")\nwait(0.0)\n"],["follow path",":import adventure_lib\n:name {package}:follow path\n\n:global bool leon.adventure.running\n:global string leon.adventure.path\n\n:local int stepCount\n:local vector curPos\n:local bool waitChest\n:local vector nextMove\n\n#nextDirection sub(leon.adventure.path, stepCount, 1)\n#nextMove {nextDir({nextDirection})}\n#curEntity adventure.entityType(curPos + {nextDir({nextDirection})})\n\ngoto(if(\\\n isopen(\"arcade\") && leon.adventure.running,\\\n start,\\\n end\\\n))\n\nbomb:\nadventure.placeBomb()\ngoto(move)\n\nwait:\nadventure.wait()\n\nmove:\nstepCount += 1\nstart:\ncurPos = adventure.playerPos()\nnextMove = {nextMove}\nwaitChest = contains(adventure.entityType(adventure.playerPos() + nextMove), \"Chest\")\n\nmove2:\nexecutesync(\"TE2.2:stop\")\nadventure.move(nextMove) ; This is a no-op for invalid (0,0) moves\nexecutesync(\"TE2.2:start\")\n\nloop:\n; This uses the spacing of unused values in the index expression to be equivalent\n; to lines to skip *backward*. This is much cheaper than having a nested ternary.\n; index returns -1 for the fallthrough case. (UDLR -> move)\ngoto(if(\\\n stepCount < len(leon.adventure.path) && leon.adventure.running && isopen(\"arcade\"),\\\n if(\\\n contains(\"BW\", {nextDirection})\\\n || x(curPos) != x(adventure.playerPos())\\\n || y(curPos) != y(adventure.playerPos())\\\n || waitChest && adventure.entityType(adventure.playerPos() + nextMove) == \"\",\\\n (move - 1) - index(\"W B\", {nextDirection}, 0),\\\n move2\\\n ),\\\n end\\\n))\n\nend:\nwait(0.)\n"],["kill enemies",":import adventure_lib\n:name {package}:kill enemies\n\n:global bool leon.adventure.running\n\nkey.k()\n\nisopen(\"arcade\")\n\n#enemyInDir(x, y) adventure.isEnemy(adventure.playerPos() + vec({x},{y}))\n#enemyDirection if({enemyInDir(0., 1.)}, vec(0., 1.),\\\n if({enemyInDir(1., 0.)}, vec(1., 0.),\\\n if({enemyInDir(0., -1.)}, vec(0., -1.),\\\n if({enemyInDir(-1., 0.)}, vec(-1., 0.), vec(0., 0.)))))\n#anyEnemies {enemyInDir(0., 1.)} || {enemyInDir(0., -1.)} || {enemyInDir(1., 0.)} || {enemyInDir(-1., 0.)}\n\n; Initialize variables so that pressing \"k\" after resetting AI\n; toggles leon.adventure.running. We're using plain execute here\n; because we want the launching script to continue and die while\n; the copy of init we execute will keep running, and eventually\n; launch another copy of \"kill enemies\".\n; This architecture, although roundabout, keeps the starting and\n; stopping of turbo in a single place (init) and thus ultimately\n; is simpler.\nexecute(if(\\\n contains(impulse(), \"key.\"),\\\n \"{package}:init\",\\\n \"{package}:xNOOPx\"\\\n))\n goto(if(contains(impulse(), \"key.\"), end, loop))\n\nwait:\nadventure.wait()\n\nattack:\nexecutesync(\"TE2.2:stop\")\nadventure.move({enemyDirection})\nexecutesync(\"TE2.2:start\")\n\nloop:\ngotoif(\\\n if(\\\n {anyEnemies},\\\n attack,\\\n if(\\\n max(max(adventure.countEntities(\"Enemy\"), adventure.countEntities(\"Elite\")), adventure.countEntities(\"Mimic\")) > 0,\\\n wait,\\\n end\\\n )\\\n ),\\\n leon.adventure.running\\\n)\nend:\nwait(0.)\n"],["refine path",":import adventure_lib\n:name {package}:refine path\n\n:global string leon.adventure.path\n\n:local vector curPos\n:local int stepCount\n\n#curDirection sub(leon.adventure.path, stepCount, 1)\n#insert(st,i,val) sub({st},0,{i}) . ({val}) . sub({st},{i},99999)\n#escapeDirection if(\\\n adventure.entityType(curPos + vec(0., 1.)) == \"\"\\\n && adventure.entityType(curPos + vec(0., 2.)) == \"\"\\\n && false == adventure.isWall(curPos + vec(0., 1.))\\\n && y(curPos) < 17.,\\\n \"BUUWDD\",\\\n if(\\\n adventure.entityType(curPos + vec(0., -1.)) == \"\"\\\n && adventure.entityType(curPos + vec(0., -2.)) == \"\"\\\n && false == adventure.isWall(curPos + vec(0., -1.))\\\n && y(curPos) > 1.,\\\n \"BDDWUU\",\\\n if(\\\n adventure.entityType(curPos + vec(-1., 0.)) == \"\"\\\n && adventure.entityType(curPos + vec(-2., 0.)) == \"\"\\\n && false == adventure.isWall(curPos + vec(-1., 0.))\\\n && x(curPos) > 1.,\\\n \"BLLWRR\",\\\n \"BRRWLL\"\\\n )\\\n )\\\n)\n\ncurPos = adventure.playerPos()\ngoto(start)\n\nrock:\nleon.adventure.path = {insert(\\\n leon.adventure.path,\\\n stepCount,\\\n if(adventure.hasItem(\"eodArmor\"), \"BWWW\", {escapeDirection})\\\n)}\nstepCount += if(adventure.hasItem(\"eodArmor\"), 4, 6)\n\nnextTile:\ncurPos += {nextDir({curDirection})}\nstepCount += 1\n\nstart:\ngoto(if(\\\n stepCount >= len(leon.adventure.path),\\\n end,\\\n if(\\\n adventure.entityType(curPos + {nextDir({curDirection})}) != \"Rock\" || adventure.hasItem(\"hammer\"),\\\n nextTile,\\\n rock\\\n )\\\n))\nend:\nwait(0.)\n"],["adventure_lib","#package Adventure v3.3\n\n; The name of the variable that stores \"target positions\" state.\n; Doubles as the start of the variable-hiding block.\n#target_pos_var \"<size=0>adv.target_pos\"\n#target_positions global.string.get({target_pos_var})\n\n; Convert a vector that has been pre-split into its two components into\n; stringified form. This only works for non-negative numbers.\n#posToString(x, y) sub(d2s(({x}) + 200.), 1, 2) . sub(d2s(({y}) + 200.), 1, 2)\n\n; Convert a length-4 stringified vector to vector form.\n; This evaluates str twice, so that should be a small expression.\n; This *will* work with negative values, if they're small enough.\n#strToVec(str) vec(s2d(sub({str}, 0, 2), -1.), s2d(sub({str}, 2, 2), -1.))\n\n; Macros for working with BFS queues. The queue is structured as a\n; length-4 stringified position followed by a length-1 direction.\n#firstElementPos {strToVec(queue)}\n#firstElementDir sub(queue, 4, 1)\n\n; Calculate the difficulty of the room the player is in.\n; This formulation is a bit harder to follow than the traditional one\n; that uses absolute values, but it require fewer operations.\n;\n; This takes a parameter \"offset\", which is added to the difficulty.\n; This exists because it can be constant-folded in to this calculation,\n; whereas if we add it later it's a separate addition.\n#cur_difficulty(offset) (\\\n max(\\\n 254. - x(adventure.roomCoords()),\\\n x(adventure.roomCoords())\\\n ) + max(\\\n 254. - y(adventure.roomCoords()),\\\n y(adventure.roomCoords())\\\n ) + ({offset} - 254.)\\\n)\n\n; Calculate if the given difficulty (i.e. distance) is \"survivable\",\n; i.e. can be cleared automatically without taking damage. This includes\n; comparing against maxDifficulty, which technically doesn't affect your\n; ability to survive, but does determine how the AI chooses rooms.\n#survivable(difficulty) (\\\n ceil(i2d(adventure.playerAttack()) * if(adventure.hasItem(\"impaler\"), 1.1, 1.)) >=\\\n round({difficulty} * 0.38 + 1.) + round({difficulty} * 0.08)\\\n || i2d(adventure.playerArmor()) >= ceil({difficulty} * 0.39)\\\n) && i2d(leon.adventure.maxDifficulty) >= {difficulty}\\\n && (adventure.hasItem(\"lantern\") || {difficulty} < 100.)\n\n; Convert a U/D/R/L direction into a direction vector by using index\n; and lookup tables. These are arranged so that if the lookup fails\n; (i.e. if the direction is \"W\" for wait), the s2d will default to (0.,0.).\n#nextX(d) s2d(sub(\"X 0 0 1-1\", index(\" U D R L\", {d}, 0), 2), 0.)\n#nextY(d) s2d(sub(\"X 1-1 0 0\", index(\" U D R L\", {d}, 0), 2), 0.)\n#nextDir(d) vec({nextX({d})}, {nextY({d})})\n"]]}}
Bugfixes, market buying, and size reduction.
New feature: Auto-buys items from the emerald market. If you don't like the order it buys things in, tough. It's optimized for improving the speed of the AI, and it will buy all the items relatively quickly.
Because of auto-buying, the AI will now reach the default maxDifficulty on its own without cheat armor or player intervention.
Fix: Buying the mana sword before the spellbook no longer puts the AI in a room-loop.
Fix: Handling of impulses is greatly improved. It should be impossible to get into weird states by pressing keys rapidly now. You can press either "R" or "K" to cancel the AI when it's running, and it will always stop what it's doing.
Fix: Make leaving the Arcade properly shutdown in all cases.
Eliminated a script via code combining.
Bundle size: 8636 (51531 uncompressed) Scripts: 8 Max lines: 17
Efficient bomb/health/mana grinding.
Grinding only happens when you have the appropriate items for it (leech and/or mana sword). When grinding bombs, rooms will be skipped if they don't contain elites. The time ends up dominated by how much health you lose, so grinding at either d80 or d100 is best.
Bundle size: 49192 Scripts: 9 Max lines: 17
Major bugfixes. Major size reduction.
Basic mimic handling, although it takes 2x as much damage as it needs to.
Boots of Phasing are used, Voidforged Impaler effect is accounted for in determining maximum distance.
Bundle size: 43160 Scripts: 9 Max lines: 17
First release under new management!
Checks relics for EODArmor instead of assuming you have it at distance 50, and uses hammer when possible instead of bombing all rocks.
Significantly smaller.
Bundle size: 98476 Scripts: 9 Max lines: 17
Last version by xWar131x.
Bundle size: 138196 Scripts: 9 Max lines: 18