03$Z=+Š\8¹ŠUÜ®tņÜ°ØąJLk/°:.čp:JR5“œoŲÜ}jUōļā 0’’’’’’’’2Tv˜ŗÜžļĶ«‰gE#2Tv˜ŗÜž2Tv˜ŗÜžwwøwwą‹ww°øwww°»wą ޾ ī»ėpww wwwwww°pwė p‹ pwwwwwwww¾»wąwĄģŠŻŃѽ»wwwwwwww»±pwpwpwwŻ wwĶĢpwĶĶ wwwwwwwwww!wwwq·Ė'wwww !wwwwwwwwpwwwpwww{wwwqwwwrwwwwwwwwwwwwwwwww@UwąDDwąīDwDwwwwwwwwwwwpwwUpwDDwDīwīwpwwffFīfVf€ˆV€wVqwwwwwwwwwwwīīefaoˆČnuw‡ąwwwwwwwwwwwēwwwwwwwwwww~ww‡~wwwīw'wīw'ēŽw‡~ˆwwwwwwwwwwwwēwwwēwwwčw~wī‡~w.‡wwwwwwww'ww@DwDDwNDw2##w1##'333wwwwrwwwwwwDpwwDpww>ww2īqwāīwwwwwwwwwwww‡wgwwwwwwøwwwowwww‡wwwwwwwwgwww†{wwgwwwwwwwwwwwwwwąwąw° w€ww ww ww°pˆpwī w w» w°pwww ww»pw ąĄąĖ°ĄėwwwwwwwwwwwŃūw±Üp¼Ń pė»ėĖ  wwwpww ww w'w wpwwwwwwwwwwBwww1rwwr7wwrwwrwqqwwwwwwwwwwwwwwww wą3wwwwwwwwwwwpww"ww pww1p p1pppwwwwwwwwwwwwwwwxwwpwwFˆˆff%īīwwēwwPwwįewafwę ˆA6·š&°īnffwč~(wēč.ww(.w‡įw'ˆwN2w'#4wwwwāŽ{wāwwˆįxwˆ(wwrw"äw4$swwwww'333A"23D"23$23w0ī>wįww@wwww233p㣪rć¢*t3$Bq3Dw@qwqwwwwwwwwgwwwwwwwwwwwvwwvwww÷wwwwwwwwwww†wwgww·wwwhwwwwĒwwwwūwwwwwwwww } ½            ` ` ` `` ` ``  ` ` ` ` `` ` ``  ` ` ` `€ @dĘŻĄ?A"""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """’’’’’’"ņ’’/"""""ņ’’/""ņ’"""’’’’’’"ņ’’/"""""ņ’’’’’/"’’’"""’/"’’’’’’’/""""""""""""""""""""""""""""""""""""""""""""" """’’’’’’"ņ’’/"""""ņ’’/""ņ’"""’’’’’’"ņ’’/"""""ņ’’’’’/"’’’"""’/"’’’’’’’/""""""""""BDDD""""""""""""""""""""""""""""""" """’’’’’’ņ’’ """"ņ’’ "ņ’""’’’’’’ņ’’ """"ņ’’’’’ ’’’""’ ’’’’’’’ """"""""DDDDDD$""""""""""""""""""""""""""""" "ņ’’’’ņ’’ """"ņ’’ "ņ’ņ’’ņ’’ """""’’’ ’’’’/"’ ’’’ """""""BDTUUUDD""""""""""""""""""""""""""""" "ņ’’’’ņ’’ """"ņ’’ "ņ’ņ’’ņ’’ """""’’’ ’’’’/"’ ’’’ """"""BDTUUUUUDD"""""""""""""""""""""""""""" "ņ’’’’"""ņ’’ """"ņ’’ "ņ’ņ’’ """"ņ’’ """"""’’’""’’’’ ’ ’’’"""""""""""DTUUUUUUUD$""""""""""""""""""""""""""" ""’’’’/""ņ’’ """"ņ’’ "ņ’ņ’’ ’’’"ņ’’ """"""’’’""’’’’’’’ ’’’’’’""""""""BDUUUUUUUUED""""""""""""""""""""""""""" ""’’’’/""ņ’’ """"ņ’’ "ņ’ņ’’ ’’’"ņ’’ """"""’’’""’’’’’’’ ’’’’’’""""""""BTUUUUUUUUUD""""""""""""""""""""""""""" """’’’’ "ņ’’ """"ņ’’ "ņ’ņ’’ ’’’ņ’’ """"""’’’""’’’’’’’ ’’’’’’"""""""DUUUUUUUUUUE$"""""""""""""""""""""""""" """"š’’’’"ņ’’ """"ņ’’ "ņ’ņ’’ "š’ņ’’ """"""’’’""’’’š’’ ’’’""""""BDUUUUUUUUUUED"""""""""""""""""""""""""" """"š’’’’"ņ’’ """"ņ’’ "ņ’ņ’’ "š’ņ’’ """"""’’’""’’’š’’ ’’’""""""BTUUUUUUUUUUUD""""""""""""""""""33333333 """"ņ’’’’ņ’’ """"ņ’’ "ņ’ņ’’ "ņ’ņ’’ """"""’’’""’’’ņ’’ ’’’"""""3333CTUUUUUUUUUUUD33333333333333333333333333 "ņ’’’’’ņ’’’’’’’""’’’’/"’’’’’’ņ’’’’’’’"ņ’’’’’/"’’’"’ ’’’’’’’/"3333DUUUUUUUUUUUUE43333333333333333333333333 "ņ’’’’’ņ’’’’’’’""’’’’/"’’’’’’ņ’’’’’’’"ņ’’’’’/"’’’"’ ’’’’’’’/"3333DUUUUUUUUUUUUE43333333343333333333333333 "ņ’’’’’ "ņ’’’’’’’""’’’’ """’’’’’’ņ’’’’’’’ņ’’’’’ ’’’""’ ’’’’’’’ 3333DUUUUUUUUUUUUE43333333333333333333333333 "" """"" """""" """" " 3333DUUUUUUUUUUUUE43333333333333333333333333 "" """"" """""" """" " 3333DUUUUUUUUUUUUE43333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""3333DUUUUUUUUUUUUE43333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""3333DUUUUUUUUUUUUE43333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""3333CTUUUUUUUUUUUD33333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""D433CTUUUUUUUUUUUD33333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDCDUUUUUUUUUUED33333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDUUUUUUUUUE433333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDDDTUUUUUUD343333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDDDDDDUUUED333333333333333333333333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDDDDDDDDDD43333333333333333333DD333333 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDDDDDDDDDDDD433333333333333333DDDDDD33 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDDDDDDDDDDDDDDDD43333333333333DDDDDDDD """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD "bf"&"fbfbf"f"&b""b&"&"fbbbfb"bfbbbf"bfb&""&"fbfb&"bb"&"fbfbf"f"""""""""DDDDDDDTUUUUUUUUUUUUUDDDDDDDDDDDDDDDDDDDDDDU ""&bbb""&"&b"bbb""bbbbb"bbb"b""&bbb"""&bb"bbb""&bb"bbbbb""&b"b""""""""""DDDDDUUUUUUUUUUUUUUUUUUEDDDDDDDDDDDDDDDDDUUU ""&bfb""&"&b"bfb""bfbbbbbbb&b""&b&b&""&bb"bfb""&bb"bbbf"&"&b&"&"""""""""DDTUUUUUUUUUUUUUUUUUUUUUUUDDDDDDDDDDDDDUUUUU ""&bbb""&"&b"bbb""b&bbbbbbb"b""&bbb"""&bb"bbb""&bb"bfbb"b"&b""b"""""""""TUUUUUUUUUUUUUUUUUUUUUUUUUUUDDDDDDDDDUUUUUUU ""&bb"f"&bf"fbbbf"bb"&"f"fbfbfbfbbbf"bfbb"bb"fbfb&"bfbbb&"&bfb&"""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUEDDDDDDUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUEDDDDUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUDDDUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUEDUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU ""%"U"%"""U"%RURU""URURRR%RU"%"""%""%"R%"UR%"U""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU "RRR"RR""R"RRRUR""R""%RRRR"%RR""R"R%R""RRR"RR"""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU "RRRRRRRURRRURRR%""%"%RRRR"%RR""R"%"R""%RR"%RU""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU "RRRRRR""RRRRRRR"""R"%RRRR"%RR""R"%"R"R"RRR"RR""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU ""%"U"%"""URRRRRU"R%"%"UR%RU"%"""%R%%"RUR%RURU""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUWUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUˆˆUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUXUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUwwˆˆUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU…ˆˆ‡ˆˆˆˆˆˆUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUwgwwˆˆUUUUUUUUUUUUUUUUUUUUUUUU…XˆˆxwwwwwwwwwˆˆˆˆXUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUī»»ˆUUUUUUUUUUUUUUUUUUUwwwwwwXUUUUUUUUUUUUUUUUUUUUU…ˆx‡wwwwwwwwwwwwwwww‡ˆUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUī»»ˆUUUUUUUUUUUUUUUUUUUwwwwww‡ˆXUUUUUUUUUUUUUUUUU…ˆxwgwwwwwwwwwwwwwwvwwwwˆUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU»ˆ»īUUUUUUUUUUUUUUUUUUwwwwwwww‡ˆˆˆXUUUUUUUUU…ˆˆˆxwwwwwwwwwwwwwwwwwwwwwwwwˆˆUUUUUUUUUUUUUUUUUUUUUUUUUUUUUPUUUUUU»ˆ»īUUUUUUUUUUUUUUUUUˆˆwwwwwwwwww‡ˆˆˆˆˆˆUˆˆxwwwwwwwwwwwwwwxwwwwwwwwwwwwwwwˆˆUUUUUUUUUUUUUUUUUUUUUUUUUUU9“™™PUUUUUUˆ»ˆ»UUUUUUUUQUUUUUUUˆˆˆˆwwwwwwwwwwwwwwwˆwwwwwwwwwwwwww‡ˆˆˆˆˆˆˆˆˆwwwwwwwwwwwˆXUUUUUUUUUUUUUUUUUUUUUUUUU:£ŖŖPUUUUUUˆ»ˆ»UUUUUUUUUUUUUUUUUUˆˆˆˆwwwwwwwwwwvwwwwwwwwwwwww‡xˆˆˆˆˆ…ˆˆˆˆˆˆˆˆˆˆxwwwwwww‡UUUUUUUUUUUUUUUUUUUU^UUUU:£ŖŖPUUUUUUīUUUUUUUUµĮ[UUUUUˆˆxwwwwwwwwwwwwwwwwwwwww‡ˆˆˆˆˆˆˆˆˆˆˆwwwwwwwˆį9“™™ī¼ˆˆxwwwwwwwwwwwwwwwww‡ˆˆˆˆˆwwwwwww‡ˆīį9“9“ˆˆī»»»»!!ˆˆˆˆˆxwwwwwwwww‡ˆˆˆˆˆˆˆˆwwwwwww‡ˆ!īč9“9“ˆˆwwī»»»»ˆˆˆˆˆˆˆˆˆˆˆˆwˆˆˆˆˆˆˆˆˆˆwwwwgww!įŽī9“9“€ˆww»»ī»»īīwˆ$ˆˆˆˆˆˆˆˆˆˆˆˆxwwwww‡ˆˆˆ.9“™™pwww»»ī»»īīww‡!č(āŽ :£ŖŖ»»ˆ»»»īˆī įč.ā:£ŖŖ»»ˆ»»»īˆī!! (.ˆį9“™™»»īī!@UUąˆ(»»īīwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwąDDDDˆfīwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwąīDDī"äfīwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwDī44$»ˆīīī»wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww»ˆīīī»wwwppwwwwwpwppwwpwwwwwwwwwwwwwwwwwww»»»»»»ī»»»wwwwwwwwwwpwwwwwwwwwwwwwwwwwwwwww"»»»»»»ī»»»wwwppwpwpwwpwwpwppwwpwwwwwwwwwwwwwwwwwww  »ī»»»īˆwwwwpwwwwwwwpwwwwwpwwwwwwwwwwwwwwwwwwwą»ī»»»īˆwwwwppwwwpwpwpwwpwpwwwwwwwwwwwwwwwwwww31 Ģ»ˆwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww1Ģ»ˆwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwĢŻŻŻīĢ»wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwĢŻŻŻīĢ»wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwĢĢĢŻŻĢŻŻ»U»ĢĢĢŻŻĢŻŻ»U»ĢĢŻĢŻ»»»ŻŻ»»»ĢĢŻĢŻ»»»ŻŻ»»»Ģ’»ŻĢĢ’»ŻĢŻĢ’»ĢīŻĢ’»ĢīŻŻ»ĢĢ»īŻŻ»ĢĢ»īīįįīīīīįįīīīīīī»»ī»ĢĢ»įįį»»ī»ĢĢ»įīįįįīīīįī»Ģ»ī»ī»įįįī»Ģ»ī»ī»īįįīįīīįį»Ż»»Ż»§’-- title: SLUGLINE - tbs roguelike -- author: OGO-GAME STUDIO -- desc: 3v3 tactics roguelike: 25 cards, 6 rounds, rising sludge. -- script: lua GW, GH, TS = 8, 8, 16 OX, OY = 2, 6 PX = 134 EMPTY, OBST, FOREST, HILL, SLUDGE = 0, 1, 2, 3, 4 C_OUT, C_DARK, C_BROWN, C_TERRA = 0, 1, 2, 3 C_TOPO, C_SAND, C_LIGHT, C_TOX = 4, 5, 6, 7 C_FOREST, C_ENEMY, C_ENEMY2 = 8, 9, 10 C_PL_D, C_PLAYER, C_PL_L = 11, 12, 13 C_MUTED, C_UI = 14, 15 FLOOD_T1, FLOOD_T2 = 2, 6 TURRET_DMG = 2 CLS = { B = {nm="ARM", full="ARMOR MEN", hp=30, mv=2, rmin=1, rmax=1, dmg={6}, thr=3}, M = {nm="MTC", full="MOTORCYCLE STRIKER", hp=15, mv=4, rmin=1, rmax=1, dmg={4}, thr=5}, S = {nm="SNP", full="SNIPER", hp=18, mv=2, rmin=1, rmax=3, dmg={3,5,5}, thr=5}, } GRP = {C_ENEMY2, C_PL_L, C_TOX, C_UI} CARD_INFO = { fire = {"FIR",1,"burns 2 a turn, twice"}, push = {"PSH",1,"shoves the target back"}, hook = {"HOK",1,"reach 2, drags target to you"}, feint = {"FNT",1,"+1 dmg, in contact slip past"}, oxid = {"OXD",1,"2 turns: every hit on it lands"}, glitch = {"GLT",1,"target's next hit deals 2 less"}, ricochet = {"RIC",1,"half the damage bounces to a foe"}, oil = {"OIL",1,"target cannot move next turn"}, super = {"SUP",1,"skip a turn: next hit deals 2x+1"}, rpg = {"RPG",1,"stand still: 6, cone 2, cd 1"}, regen = {"RGN",2,"clean hits of 2+ restore health"}, reflect = {"RFL",2,"returns half, if you are not exposed"}, spark = {"SPK",2,"free counterblow, cooldown 3"}, shell = {"SHL",2,"once: survive a lethal blow"}, guard = {"GRD",2,"skip a turn: half dmg, heal 3-4, cd 1"}, cover = {"COV",2,"allies next to you take 1 less"}, sentry = {"SNT",3,"hits anyone stepping next to you"}, mine = {"MIN",3,"drops a mine, 5 dmg, stops them"}, turret = {"TUR",3,"leaves a turret, 2 dmg at range 2"}, pontoon = {"PON",3,"sludge is free and you can cross it"}, phase = {"PHS",3,"+1 move thru walls, parting blow"}, teleport = {"TPT",3,"+1 move, jump over anything, cd 2"}, steal = {"STL",4,"50%: take a card, two a battle"}, cascade = {"CSC",4,"a kill gives you a whole extra turn"}, blast = {"BLS",4,"6 damage all around when you die"}, } function ci(name) return CARD_INFO[name] or {"???",4,""} end function ccode(name) return ci(name)[1] end function ccol(name) return GRP[ci(name)[2]] end function cdesc(name) return ci(name)[3] end CARD_SRC = { "fire |hit |burn,2", "push |hit |push,C", "feint |atk |dmg,1|step", "reflect|hurt|back,50", "mine |move|mine,5", "super |hold|charge", "cascade|kill|refresh", "blast |die |aoe,6", "hook |hit |pull,1", "regen |hit |heal,half", "glitch |hit |shred,2", "spark |hurt|counter,1", "sentry |pass|overwatch,half", "oxid |hit |mark,2", "guard |hold|dr,50", "shell |aura|-", "cover |aura|-", "pontoon|aura|-", "phase |aura|-", "teleport|aura|-", "oil |hit |slick,1", "ricochet|hit|splash,half", "steal |hit |rob,50", "rpg |atk |heavy", "turret |move|deploy", } CARDS, CARD_LIST = {}, {} ACC = { B = {B=85, M=60, S=95}, M = {B=100,M=85, S=90}, S = {B=70, M=95, S=85}, } SPR_ARMOR, SPR_TRIKE, SPR_SNIPER, SPR_SLUDGE = 0, 2, 4, 8 SPR_TURRET, SPR_BUSH, SPR_HILL, SPR_FLOOD = 6, 10, 12, 14 G, WAL, U, CONTOUR, MINES = {}, {}, {}, {}, {} cur = {x = 2, y = 7} sel, reach, atkc = nil, {}, {} mode = "sel" turn = 0 flood, flood_p, flood_flash = 0, 20, 0 etimer, focus = 0, nil QUAKE_T, QUAKE_P, QUAKE_DMG = 16, 50, 3 SHAKE = 0 ENEMY_PACE = 150 HOLD_END = 180 hold_t, hold_stage = 0, nil pops, log = {}, {} result = nil stale = 0 info_u = nil ROUNDS = { {bodies=3, pool=0}, {bodies=3, pool=3}, {bodies=3, pool=5}, {bodies=4, pool=8}, {bodies=5, pool=11}, {bodies=6, pool=14}, } DRAFT_OFFER, DRAFT_PICKS = 4, 2 HEROES, QUEUE = {}, {} stage = "battle" round_n = 1 offer, draft_i, draft_left, draft_card = {}, 1, 0, nil function key(x, y) return y * 16 + x end function kx(k) return k % 16 end function ky(k) return math.floor(k / 16) end function dist(a, b, c, d) return math.abs(a-c) + math.abs(b-d) end function fl(v) return math.floor(v) end function inside(x, y) return x >= 0 and x < GW and y >= 0 and y < GH end function parse_cards() for _, line in ipairs(CARD_SRC) do local parts = {} for w in line:gmatch("[^|]+") do parts[#parts+1] = w:gsub("%s", "") end local fx = {} for i = 3, #parts do local op, arg = parts[i]:match("([^,]+),?(.*)") fx[#fx+1] = {op = op, arg = arg} end CARDS[parts[1]] = {trig = parts[2], fx = fx} CARD_LIST[#CARD_LIST+1] = parts[1] end end function pal_set(a, b) poke4(0x7FE0 + a, b) end function pal_reset() for i = 0, 15 do poke4(0x7FE0 + i, i) end end function logline(s) table.insert(log, 1, s) while #log > 7 do table.remove(log) end end function popup(x, y, txt, col) pops[#pops+1] = {x=x, y=y, t=70, s=txt, c=col} end function in_frame(x, y, w) if w <= 0 then return false end return x < w or x >= GW - w or y < w or y >= GH - w end function toxic(x, y) if not inside(x, y) then return false end return G[key(x,y)] == SLUDGE or in_frame(x, y, flood) end function ring_cells(w) local out = {} for y = 0, GH-1 do for x = 0, GW-1 do if in_frame(x, y, w) and not in_frame(x, y, w-1) then out[#out+1] = {x, y} end end end return out end function unit_at(x, y) for _, u in ipairs(U) do if u.hp > 0 and u.x == x and u.y == y then return u end end end function alive(side) local n = 0 for _, u in ipairs(U) do if u.hp > 0 and u.side == side and not u.turret then n = n + 1 end end return n end function passable(x, y) if not inside(x, y) then return false end if G[key(x,y)] == OBST then return false end return true end function gen_map() G, WAL, CONTOUR = {}, {}, {} for y = 0, GH-1 do for x = 0, GW-1 do G[key(x,y)] = EMPTY end end for _ = 1, 2 do local fx, fy = math.random(1, GW-2), math.random(2, 5) local put = 0 for _, gd in ipairs({{0,0},{1,0},{0,1},{1,1},{-1,0}}) do local nx, ny = fx + gd[1], fy + gd[2] if put < 3 and inside(nx, ny) and G[key(nx,ny)] == EMPTY then G[key(nx,ny)] = FOREST put = put + 1 end end end local spots = {} for y = 2, 5 do for x = 0, GW-1 do if G[key(x,y)] == EMPTY then spots[#spots+1] = {x, y} end end end for i = #spots, 2, -1 do local j = math.random(i) spots[i], spots[j] = spots[j], spots[i] end local si = 1 for _ = 1, 4 do if spots[si] then G[key(spots[si][1], spots[si][2])] = OBST WAL[key(spots[si][1], spots[si][2])] = 4 si = si + 1 end end for _ = 1, 3 do if spots[si] then G[key(spots[si][1], spots[si][2])] = HILL; si = si + 1 end end for _ = 1, math.random(3, 4) do if spots[si] then G[key(spots[si][1], spots[si][2])] = SLUDGE; si = si + 1 end end local hh = false for y = 3, 4 do for x = 3, 4 do if G[key(x,y)] == HILL then hh = true end end end if not hh then G[key(3,3)] = HILL end for c = 0, 3 do for i = 0, GW * TS - 1 do local px = OX + i local py = OY + 18 + c * 32 + fl(9 * math.sin((i + c * 37) / 18)) CONTOUR[#CONTOUR+1] = {px, py} end end end function bfs(u, mv, sx, sy) local d = {} d[key(sx, sy)] = {0, 0, nil} if (u.immune or 0) > 0 then return d end if (u.oil or 0) > 0 then return d end local q, h = {{sx, sy}}, 1 local pon = has(u, "pontoon") local ghost = has(u, "phase") and (u.cd_phase or 0) == 0 local blink = has(u, "teleport") and (u.cd_tele or 0) == 0 mv = mv + (ghost and 1 or 0) + (blink and 1 or 0) if ghost then pon = true end while h <= #q do local c = q[h]; h = h + 1 local cx, cy = c[1], c[2] local st, hpc = d[key(cx,cy)][1], d[key(cx,cy)][2] local here_tox = toxic(cx, cy) local entered = here_tox and not (cx == sx and cy == sy) if st < mv and not (entered and not pon) then for _, dd in ipairs({{1,0},{-1,0},{0,1},{0,-1}}) do local nx, ny = cx + dd[1], cy + dd[2] local ntox = toxic(nx, ny) local blocked = here_tox and ntox and not pon local ok = (passable(nx, ny) or ghost) and not unit_at(nx, ny) if blink then ok = inside(nx, ny) end if ok and not (blocked and not ghost) then local cost = (ntox and not pon) and 2 or 0 local ns, nh = st + 1, hpc + cost local old = d[key(nx,ny)] if old == nil or ns < old[1] or (ns == old[1] and nh < old[2]) then d[key(nx,ny)] = {ns, nh, key(cx,cy)} q[#q+1] = {nx, ny} end end end end end if ghost or blink then for k, v in pairs(d) do if k ~= key(sx, sy) then local bad = (G[k] == OBST) if blink then bad = bad or unit_at(kx(k), ky(k)) or toxic(kx(k), ky(k)) end v[4] = bad or nil end end end return d end function reach_of(u) local r = CLS[u.cls].thr if has(u, "hook") then r = math.max(r, CLS[u.cls].mv + 2) end return r end function has_room(cells, u) for k, _ in pairs(cells) do if landable(cells, k) and k ~= key(u.x, u.y) then return true end end return false end function landable(cells, k) local v = cells[k] return v ~= nil and not v[4] end function threat_of(u) local t = {} local r = reach_of(u) for y = 0, GH-1 do for x = 0, GW-1 do if dist(u.x, u.y, x, y) <= r then t[key(x,y)] = true end end end return t end DEATH_MSG = { sludge = " dissolves", burn = " burns down", mine = " is blown up", quake = " buried", pure = " destroyed", blast = " is caught in the blast", hit = " destroyed", back = " destroyed", } function incoming(u, dmg, kind) if kind == "sludge" or kind == "quake" then return dmg end if u.guard and u.guard > 0 then dmg = fl(dmg / 2) end for _, o in ipairs(U) do if o.hp > 0 and o.side == u.side and o ~= u and has(o, "cover") and dist(o.x, o.y, u.x, u.y) == 1 then dmg = dmg - 1 break end end return math.max(1, dmg) end function hurt(u, amount, kind, col, txt, src) if not u or u.hp <= 0 or amount <= 0 then return 0 end if u.immune and u.immune > 0 then popup(u.x, u.y, "SHELL", C_UI) return 0 end amount = incoming(u, amount, kind) if amount >= u.hp and has(u, "shell") and not u.shell_used and not is_oxid(u) then u.shell_used = true u.hp = 1 u.immune = 1 popup(u.x, u.y, "SHELL!", C_UI) logline(CLS[u.cls].nm .. " shells up") return 0 end u.hp = math.max(0, u.hp - amount) u.flash = 6 u.din = (u.din or 0) + amount if src and src ~= u then src.dout = (src.dout or 0) + amount end if u.hp == 0 and not u.turret then for _, o in ipairs(U) do if o.turret and o.hp > 0 and o.owner == u then o.hp = 0 logline("turret goes dark") end end end popup(u.x, u.y, txt or ("-" .. amount), col or C_ENEMY2) if u.hp > 0 and (u.oxid or 0) > 0 then popup(u.x, u.y + 1, "ox", C_TOX) end if u.hp == 0 then DYING[#DYING+1] = {x = u.x, y = u.y, cls = u.cls, side = u.side, flip = u.flip, turret = u.turret, ghost = true, hp = 1, cards = {}, t = 40} snd(u.turret and "blip" or "die") logline((u.turret and "turret" or CLS[u.cls].nm) .. (DEATH_MSG[kind] or " destroyed")) popup(u.x, u.y - 1, "DOWN", C_UI) on_death(u) end return amount end function env_hit(u, amount) if has(u, "pontoon") then return 0 end snd("acid") return hurt(u, amount, "sludge", C_TOX) end STATUS = { {f="burn", at="start", mode="count", dmg=2}, {f="oxid", at="start", mode="count"}, {f="guard", at="start", mode="count"}, {f="oil", at="end", mode="clear"}, {f="cd_phase", at="start", mode="count"}, {f="cd_tele", at="start", mode="count"}, {f="cd_rpg", at="start", mode="count"}, {f="cd_spark", at="start", mode="count"}, {f="cd_guard", at="start", mode="count"}, {f="shred", at="end", mode="clear"}, {f="immune", at="end", mode="count"}, } FLAGS = { {f="did_atk", scope="turn"}, {f="did_move", scope="turn"}, {f="cascade", scope="turn"}, {f="moved_trig", scope="turn"}, {f="sentry_used", scope="turn"}, {f="shell_used", scope="battle"}, {f="super", scope="battle"}, } function status_reset(u) for _, st in ipairs(STATUS) do u[st.f] = 0 end for _, fg in ipairs(FLAGS) do u[fg.f] = false end u.in_tox, u.acted = false, false u.robbed_n = 0 end function status_turn_start(u) for _, st in ipairs(STATUS) do if st.at == "start" and (u[st.f] or 0) > 0 then u[st.f] = (st.mode == "clear") and 0 or (u[st.f] - 1) if st.dmg then local n = st.dmg + (((u.oil or 0) > 0 and st.f == "burn") and 2 or 0) hurt(u, n, st.f, C_ENEMY2) end end end for _, fg in ipairs(FLAGS) do if fg.scope == "turn" then u[fg.f] = false end end end function status_turn_end(u) for _, st in ipairs(STATUS) do if st.at == "end" and (u[st.f] or 0) > 0 then u[st.f] = (st.mode == "clear") and 0 or (u[st.f] - 1) end end end function is_oxid(u) return (u.oxid or 0) > 0 or toxic(u.x, u.y) end function has(u, c) for _, n in ipairs(u.cards or {}) do if n == c then return true end end return false end function push_unit(t, src, n) if t.hp <= 0 then return end local dx, dy = t.x - src.x, t.y - src.y local sx = (dx > 0 and 1) or (dx < 0 and -1) or 0 local sy = (dy > 0 and 1) or (dy < 0 and -1) or 0 if math.abs(dx) >= math.abs(dy) then sy = 0 else sx = 0 end for _ = 1, n do local nx, ny = t.x + sx, t.y + sy if not inside(nx, ny) or unit_at(nx, ny) then break end if G[key(nx,ny)] == OBST then WAL[key(nx,ny)] = (WAL[key(nx,ny)] or 4) - 4 if WAL[key(nx,ny)] <= 0 then G[key(nx,ny)] = EMPTY end break end t.x, t.y = nx, ny if toxic(nx, ny) then env_hit(t, 2); t.in_tox = true; break end if check_mine(t) then break end end end function tint(u, c) u.tint_c, u.tint_t = c, 30 end function rob_one(src, t) local pick = {} for i, n in ipairs(t.cards) do local dup = false for _, m in ipairs(src.cards) do if m == n then dup = true end end if not dup then pick[#pick+1] = i end end if #pick == 0 then return false end local i = pick[math.random(#pick)] local name = t.cards[i] table.remove(t.cards, i) if t.side == 0 then local was = false for si = #(t.stolen or {}), 1, -1 do if t.stolen[si] == name then table.remove(t.stolen, si); was = true; break end end if not was then t.robbed = t.robbed or {} t.robbed[#t.robbed+1] = name end end src.cards[#src.cards+1] = name src.stolen = src.stolen or {} src.stolen[#src.stolen+1] = name popup(src.x, src.y - 1, "STOLE", C_UI) logline(CLS[src.cls].nm .. " rips out " .. name) return true end PRIM = { burn = function(c, a) if not c.full then return end c.tgt.burn = tonumber(a); popup(c.tgt.x, c.tgt.y, "BURN", C_ENEMY2); tint(c.tgt, C_ENEMY2) end, push = function(c, a) if (c.r or 1) == 2 and has(c.src, "hook") then return end push_unit(c.tgt, c.src, (c.src.cls == "B") and 2 or 1) end, pull = function(c, a) if (c.r or 0) ~= 2 then return end local t, src = c.tgt, c.src if t.hp <= 0 then return end local dx, dy = t.x - src.x, t.y - src.y local sx = (dx > 0 and -1) or (dx < 0 and 1) or 0 local sy = (dy > 0 and -1) or (dy < 0 and 1) or 0 if math.abs(dx) >= math.abs(dy) then sy = 0 else sx = 0 end local nx, ny = t.x + sx, t.y + sy if not inside(nx, ny) or unit_at(nx, ny) or G[key(nx,ny)] == OBST then return end t.x, t.y = nx, ny if toxic(nx, ny) then env_hit(t, 2); t.in_tox = true; return end check_mine(t) end, heal = function(c, a) if not c.full or is_oxid(c.src) or (c.dealt or 0) < 2 then return end local n = (a == "half") and math.min(2, fl(c.dealt / 2)) or tonumber(a) local before = c.src.hp c.src.hp = math.min(CLS[c.src.cls].hp, c.src.hp + n) if c.src.hp > before then popup(c.src.x, c.src.y, "+" .. (c.src.hp - before), C_PL_L) end end, shred = function(c, a) if not c.full then return end c.tgt.shred = tonumber(a) popup(c.tgt.x, c.tgt.y - 1, "GLITCH", C_MUTED); tint(c.tgt, C_MUTED) end, counter = function(c, a) local u = c.tgt if (u.cd_spark or 0) > 0 then return end local best, bd = nil, 99 for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side then local d = dist(u.x, u.y, e.x, e.y) if d >= CLS[u.cls].rmin and d <= CLS[u.cls].rmax and d < bd then bd, best = d, e end end end if best then u.cd_spark = 3 logline(CLS[u.cls].nm .. " counters") hurt(best, outgoing(u, bd), "hit", C_ENEMY2, nil, u) end end, mark = function(c, a) if not c.full then return end c.tgt.oxid = tonumber(a) popup(c.tgt.x, c.tgt.y - 1, "OXID", C_TOX); tint(c.tgt, C_TOX) end, dr = function(c, a) if is_oxid(c.src) then popup(c.src.x, c.src.y, "NO FOOTING", C_MUTED) return end if (c.src.cd_guard or 0) > 0 then return end c.src.guard, c.src.cd_guard = 1, 2 local n = math.random(3, 4) local before = c.src.hp c.src.hp = math.min(CLS[c.src.cls].hp, c.src.hp + n) popup(c.src.x, c.src.y, "GUARD +" .. (c.src.hp - before), C_PL_L) end, slick = function(c, a) if not c.full then return end c.tgt.oil = tonumber(a) popup(c.tgt.x, c.tgt.y - 1, "OIL", C_MUTED); tint(c.tgt, C_OUT) end, splash = function(c, a) local n = math.max(1, fl((c.dealt or 2) / 2)) local near = {} for _, o in ipairs(U) do if o.hp > 0 and o ~= c.tgt and o.side ~= c.src.side and not o.turret and dist(o.x, o.y, c.tgt.x, c.tgt.y) == 1 then near[#near+1] = o end end if #near == 0 then return end local o = near[math.random(#near)] logline("shot ricochets") hurt(o, n, "hit", C_ENEMY2, nil, c.src) end, rob = function(c, a) if not c.full then return end if (c.src.robbed_n or 0) >= 2 then return end local t = c.tgt if #(t.cards or {}) == 0 then return end if math.random(100) > tonumber(a) then return end if rob_one(c.src, t) then c.src.robbed_n = (c.src.robbed_n or 0) + 1 end end, deploy = function(c, a) local fx, fy = c.from[1], c.from[2] if not passable(fx, fy) or toxic(fx, fy) or unit_at(fx, fy) then return end for _, o in ipairs(U) do if o.turret and o.hp > 0 and o.side == c.src.side then o.hp = 0 end end local t = {cls = c.src.cls, side = c.src.side, x = fx, y = fy, cards = {"sentry"}, turret = true, owner = c.src} status_reset(t) t.hp = 1 t.ready = true U[#U+1] = t logline(CLS[c.src.cls].nm .. " sets up a turret") end, heavy = function(c, a) if not c.src.did_move and (c.src.cd_rpg or 0) == 0 then c.src.rpg = true end end, overwatch = function(c, a) local n = c.src.turret and TURRET_DMG or math.max(1, fl(outgoing(c.src, 1) / 2)) logline((c.src.turret and "turret" or CLS[c.src.cls].nm) .. " fires on approach") hurt(c.tgt, n, "pure", C_ENEMY2, nil, c.src) end, dmg = function(c, a) c.bonus = (c.bonus or 0) + tonumber(a) end, step = function(c, a) c.pending_step = true end, back = function(c, a) if is_oxid(c.tgt) then return end if has(c.src, "feint") or has(c.src, "push") then return end local n = math.max(1, fl(c.dealt * tonumber(a) / 100)) logline(CLS[c.tgt.cls].nm .. " reflects " .. n) hurt(c.src, n, "back", C_PL_L, nil, c.tgt) end, mine = function(c, a) local fx, fy = c.from[1], c.from[2] if unit_at(fx, fy) or has(c.src, "turret") then local ok = false for _, d in ipairs({{1,0},{-1,0},{0,1},{0,-1}}) do local nx, ny = fx + d[1], fy + d[2] if passable(nx, ny) and not toxic(nx, ny) and not unit_at(nx, ny) then fx, fy, ok = nx, ny, true break end end if not ok then return end end if toxic(fx, fy) then return end local own = {} for i, m in ipairs(MINES) do if m.side == c.src.side then own[#own+1] = i end end if #own >= 2 then table.remove(MINES, own[1]) end MINES[#MINES+1] = {x = fx, y = fy, side = c.src.side, n = tonumber(a)} end, charge = function(c, a) c.src.super = true; popup(c.src.x, c.src.y, "CHG", C_LIGHT) end, refresh = function(c, a) c.src.cascade = true popup(c.src.x, c.src.y - 1, "EXTRA TURN", C_UI) end, aoe = function(c, a) snd("blast") logline(CLS[c.src.cls].nm .. " detonates") for _, o in ipairs(U) do if o ~= c.src and dist(o.x, o.y, c.src.x, c.src.y) == 1 then hurt(o, tonumber(a), "blast", C_ENEMY2, nil, c.src) end end end, } function fire_trig(u, trig, ctx) if not u or u.hp == nil then return ctx end ctx = ctx or {} ctx.src = ctx.src or u for _, name in ipairs(u.cards or {}) do local c = CARDS[name] if c and c.trig == trig then for _, e in ipairs(c.fx) do if PRIM[e.op] then PRIM[e.op](ctx, e.arg) end end end end return ctx end blasting = false function on_death(u) if blasting then return end blasting = true fire_trig(u, "die", {src = u}) blasting = false end function wall_adj(x, y) for _, d in ipairs({{1,0},{-1,0},{0,1},{0,-1}}) do local nx, ny = x + d[1], y + d[2] if inside(nx, ny) and G[key(nx,ny)] == OBST then return true end end return false end function accuracy(a, d, ax, ay) ax, ay = ax or a.x, ay or a.y local base = ACC[a.cls][d.cls] if (d.oxid or 0) > 0 then return 100, base, "oxidised" end if toxic(d.x, d.y) then return 100, base, "stuck in sludge" end local p, why = base, "" local r = dist(ax, ay, d.x, d.y) if G[key(d.x, d.y)] == FOREST then p = p - 20; why = "forest -20" elseif r >= 2 and wall_adj(d.x, d.y) then p = p - 20; why = "cover -20" end if p < 50 then p = 50; why = why .. " min" end if p > 100 then p = 100 end return p, base, why end function outgoing(a, r, ax, ay) ax, ay = ax or a.x, ay or a.y local t = CLS[a.cls].dmg local d = t[r] or t[#t] if G[key(ax, ay)] == HILL then d = d + 1 end if r == 2 and has(a, "hook") then d = math.max(1, fl(d / 2)) end if a.shred and a.shred > 0 then d = math.max(1, d - a.shred) end return d end function glance(d) return math.max(1, fl(d / 2)) end function swing(u, r, bonus, rpg, sup) local tbl = CLS[u.cls].dmg local base = tbl[r] or tbl[#tbl] if r == 2 and has(u, "hook") then base = math.max(1, fl(base / 2)) end local flat = bonus - (u.shred or 0) if G[key(u.x, u.y)] == HILL then flat = flat + 1 end if rpg then base = 6 elseif sup then base = base * 2 + 1 end return math.max(1, base + flat) end function can_hit(a, ax, ay, d) local r = dist(ax, ay, d.x, d.y) if r == 2 and has(a, "hook") then return true end return r >= CLS[a.cls].rmin and r <= CLS[a.cls].rmax end function attack_cells(u, cells) local out = {} for k, _ in pairs(cells) do if landable(cells, k) then for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side and can_hit(u, kx(k), ky(k), e) then out[k] = true; break end end end end return out end function any_target(u) for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side and can_hit(u, u.x, u.y, e) then return true end end return false end function feint_step(a, tx, ty, d) if dist(a.x, a.y, tx, ty) ~= 1 then return end local sx = (tx > a.x and 1) or (tx < a.x and -1) or 0 local sy = (ty > a.y and 1) or (ty < a.y and -1) or 0 if sx == 0 and sy == 0 then return end local opts = (d and d.hp > 0 and d.x == tx and d.y == ty) and {{tx + sx, ty + sy}} or {{tx, ty}} local dest = nil for _, c in ipairs(opts) do if passable(c[1], c[2]) and not unit_at(c[1], c[2]) and not toxic(c[1], c[2]) then dest = c; break end end if not dest then return end local fx, fy = a.x, a.y a.x, a.y = dest[1], dest[2] leave_tile(a, fx, fy) check_mine(a) end function do_attack(a, d) local r = dist(a.x, a.y, d.x, d.y) local tx0, ty0 = d.x, d.y local ax0, ay0 = a.x, a.y if d.x > a.x then a.flip = 0 elseif d.x < a.x then a.flip = 1 end local ctx = fire_trig(a, "atk", {src = a, tgt = d, r = r}) local acc = accuracy(a, d) local fired = a.rpg or false if fired then a.rpg, a.cd_rpg = false, 2 popup(a.x, a.y, "RPG", C_ENEMY2) end local sup = (not fired) and a.super if sup then a.super = false; popup(a.x, a.y, "SUPER", C_UI) end local dmg = swing(a, r, ctx.bonus or 0, fired, sup) local hit = math.random(100) <= acc local dealt = hit and dmg or glance(dmg) a.did_atk = true stale = 0 local shown = incoming(d, dealt, "hit") logline(CLS[a.cls].nm .. (hit and " hits " or " grazes ") .. CLS[d.cls].nm .. " " .. shown) snd(hit and "hit" or "graze") hurt(d, dealt, "hit", hit and C_ENEMY2 or C_LIGHT, (hit and "-" or "~") .. shown, a) ctx.dealt = dealt ctx.full = hit fire_trig(a, "hit", ctx) if d.hp > 0 then fire_trig(d, "hurt", {src = a, tgt = d, dealt = dealt}) elseif a.hp > 0 then fire_trig(a, "kill", {src = a, tgt = d}) end if fired then snd("blast") logline("RPG detonates") local sx = (tx0 > ax0 and 1) or (tx0 < ax0 and -1) or 0 local sy = (ty0 > ay0 and 1) or (ty0 < ay0 and -1) or 0 if math.abs(tx0 - ax0) >= math.abs(ty0 - ay0) then sy = 0 else sx = 0 end for _, pt in ipairs({{tx0+sx, ty0+sy}, {tx0-sy, ty0+sx}, {tx0+sy, ty0-sx}}) do if inside(pt[1], pt[2]) then local o = unit_at(pt[1], pt[2]) if o and o.side ~= a.side then hurt(o, 2, "hit", C_ENEMY2, nil, a) end local t = G[key(pt[1], pt[2])] if (t == FOREST or t == OBST) and math.random(2) == 1 then G[key(pt[1], pt[2])] = EMPTY end end end end if ctx.pending_step and a.hp > 0 then feint_step(a, tx0, ty0, d) end end function wash_out(u) if has(u, "pontoon") then return end for _, d in ipairs({{1,0},{-1,0},{0,1},{0,-1}}) do local nx, ny = u.x + d[1], u.y + d[2] if inside(nx, ny) and passable(nx, ny) and not toxic(nx, ny) then return end end local bx, by, bd = nil, nil, 99 for y = 0, GH-1 do for x = 0, GW-1 do if passable(x, y) and not toxic(x, y) and not unit_at(x, y) then local d = dist(u.x, u.y, x, y) if d < bd then bd, bx, by = d, x, y end end end end if bx then u.x, u.y = bx, by u.in_tox = false logline(CLS[u.cls].nm .. " is washed out") end end function flood_event() if flood >= 2 then return end if turn < FLOOD_T1 then return end if flood >= 1 and turn < FLOOD_T2 then return end if math.random(100) <= flood_p then flood_p = 20 flood = flood + 1 flood_flash = 45 local n = 0 for _, u in ipairs(U) do if u.hp > 0 and in_frame(u.x, u.y, flood) then env_hit(u, 2); n = n + 1 if u.hp > 0 then wash_out(u) end end end for i = #MINES, 1, -1 do if toxic(MINES[i].x, MINES[i].y) then table.remove(MINES, i) end end for y = 0, GH-1 do for x = 0, GW-1 do if in_frame(x, y, flood) then G[key(x,y)] = EMPTY end end end snd("flood") logline("SLUDGE floods the rim, " .. n .. " caught") if flood >= 2 then logline("outer ring is now cut off") end else flood_p = math.min(100, flood_p + 20) end end function threat_at(u, x, y) local tot = 0 for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side and dist(e.x, e.y, x, y) <= reach_of(e) then local r = math.max(CLS[e.cls].rmin, math.min(CLS[e.cls].rmax, 2)) local dm = outgoing(e, r) local p = ACC[e.cls][u.cls] / 100 if G[key(x,y)] == FOREST then p = math.max(0.5, p - 0.2) end local th = p * dm + (1 - p) * glance(dm) if e.super then th = th * 2.2 end tot = tot + th end end return tot end function ai_unit(u) local caution = math.max(0.25, 1.2 - stale * 0.3) local cells = bfs(u, CLS[u.cls].mv, u.x, u.y) local best, bx, by, bt, bhp, bkill = -999, u.x, u.y, nil, 0, false for k, v in pairs(cells) do if landable(cells, k) then local cx, cy, hpc = kx(k), ky(k), v[2] for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side and can_hit(u, cx, cy, e) then local r = dist(cx, cy, e.x, e.y) local dm = outgoing(u, r, cx, cy) local p = accuracy(u, e, cx, cy) / 100 local ed = p * dm + (1 - p) * glance(dm) local sc = ed * 2 - hpc * ((u.cls == "B") and 1.5 or 3.5) local fin = e.hp <= ed * 1.1 if fin then sc = sc + 8 end if e.turret then sc = sc * 0.35 end sc = sc - caution * threat_at(u, cx, cy) if G[key(cx,cy)] == FOREST then sc = sc + 1 end if G[key(cx,cy)] == HILL then sc = sc + 0.8 end if flood < 2 and in_frame(cx, cy, flood + 1) then sc = sc - flood_p / 18 end if sc > best then best, bx, by, bt, bhp, bkill = sc, cx, cy, e, hpc, fin end end end end end if bhp > 0 and u.cls ~= "B" and best < 4 and not bkill then bt = nil end if has(u, "guard") and (u.guard or 0) == 0 and (u.cd_guard or 0) == 0 and not is_oxid(u) and u.hp < CLS[u.cls].hp * 0.6 and best < 5 then logline(CLS[u.cls].nm .. " digs in") return end if has(u, "super") and not u.super and best < 4 then logline(CLS[u.cls].nm .. " charges") return end if bt then walk_to(u, cells, key(bx, by)) if u.hp == 0 then return end if u.x ~= bx or u.y ~= by then return end do_attack(u, bt) if u.hp > 0 then local left = 0 if u.cls == "M" then left = CLS.M.mv - (cells[key(bx,by)][1] or 0) end if left > 0 and not toxic(u.x, u.y) then local c2 = bfs(u, left, u.x, u.y) local bk, bs = nil, 999 for k2, v2 in pairs(c2) do if landable(c2, k2) then local s = threat_at(u, kx(k2), ky(k2)) + v2[2] * 4 if s < bs then bs, bk = s, k2 end end end if bk then walk_to(u, c2, bk) end end end else local tgt, td = nil, 999 for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side then local d = dist(u.x, u.y, e.x, e.y) if d < td then td, tgt = d, e end end end if tgt then local bk, bv = nil, 9999 for k, v in pairs(cells) do if landable(cells, k) then local tox = (u.cls == "B") and 3 or 5.5 local val = dist(kx(k), ky(k), tgt.x, tgt.y) * 10 + v[2] * tox + caution * threat_at(u, kx(k), ky(k)) + ((flood < 2 and in_frame(kx(k), ky(k), flood + 1)) and flood_p / 12 or 0) if val < bv then bv, bk = val, k end end end if bk and bk ~= key(u.x, u.y) then if walk_to(u, cells, bk) then logline(CLS[u.cls].nm .. " advances") end end end end end function next_free() for _, u in ipairs(U) do if u.hp > 0 and u.side == 0 and not u.acted and not u.turret then return u end end end function snap_cursor() local u = next_free() if u then cur.x, cur.y = u.x, u.y end end function pick_cards(n, avoid) local bag = {} for _, c in ipairs(CARD_LIST) do local skip = false for _, a in ipairs(avoid or {}) do if a == c then skip = true end end if not skip then bag[#bag+1] = c end end for i = #bag, 2, -1 do local j = math.random(i); bag[i], bag[j] = bag[j], bag[i] end local out = {} for i = 1, math.min(n, #bag) do out[i] = bag[i] end return out end function start_run() HEROES = {} local st = pick_cards(3) for i, p in ipairs({{"B",2},{"M",4},{"S",6}}) do HEROES[i] = {cls=p[1], side=0, x=p[2], y=GH-1, hp=CLS[p[1]].hp, cards={st[i]}} status_reset(HEROES[i]) end round_n = 1 for _, h in ipairs(HEROES) do h.din, h.dout, h.tin, h.tout = 0, 0, 0, 0 end start_round() end function build_queue(cfg) local slots = {} local mirror = {"B", "M", "S"} for i = 1, cfg.bodies do local c = mirror[i] or ({"B","M","S"})[math.random(3)] slots[i] = {cls=c, cards={}} end for _, c in ipairs(pick_cards(cfg.pool)) do local sl = slots[math.random(#slots)] local dup = false for _, e in ipairs(sl.cards) do if e == c then dup = true end end if not dup then sl.cards[#sl.cards+1] = c end end QUEUE = slots end function spawn_spot() local best, bv = nil, -1 local far, fd = nil, -1 for y = 0, GH-1 do for x = 0, GW-1 do if passable(x, y) and not toxic(x, y) and not unit_at(x, y) then local d = 99 for _, h in ipairs(HEROES) do if h.hp > 0 then d = math.min(d, dist(x, y, h.x, h.y)) end end if d > fd then fd, far = d, {x, y} end local v = d * 10 - y if d >= 3 and v > bv then bv, best = v, {x, y} end end end end return best or far end function spawn_wave(initial) local ex = {1, 4, 6, 2, 5, 3} while alive(1) < 3 and #QUEUE > 0 do local sl = table.remove(QUEUE, 1) if not initial and sl.cls == "B" then local nb = 0 for _, e in ipairs(U) do if e.hp > 0 and e.side == 1 and e.cls == "B" then nb = nb + 1 end end if nb >= 2 then sl.cls = ({"M","S"})[math.random(2)] end end local u = {cls=sl.cls, side=1, x=0, y=0, cards=sl.cards} status_reset(u) u.ready = initial u.hp = math.max(1, fl(CLS[sl.cls].hp * (initial and 1 or 0.6))) u.flip = 1 if initial then u.x, u.y = ({2, 4, 6})[alive(1) + 1] or ex[alive(1) + 1], 0 else local sp = spawn_spot() if not sp then table.insert(QUEUE, 1, sl); return end u.x, u.y = sp[1], sp[2] snd("spawn") u.spawn_t = 34 logline("reinforcement drops in") end U[#U+1] = u G[key(u.x, u.y)] = EMPTY end end function start_round() gen_map() U, pops, MINES, log, DYING = {}, {}, {}, {}, {} for i, h in ipairs(HEROES) do h.x, h.y = ({2,4,6})[i], GH-1 h.hp = CLS[h.cls].hp status_reset(h) h.spawn_t = 28 h.tin, h.tout = (h.tin or 0) + (h.din or 0), (h.tout or 0) + (h.dout or 0) h.din, h.dout = 0, 0 U[#U+1] = h G[key(h.x, h.y)] = EMPTY end build_queue(ROUNDS[round_n]) local total = #QUEUE spawn_wave(true) round_mus = (#MUS_BATTLE > 0) and MUS_BATTLE[math.random(#MUS_BATTLE)] or nil cur_mus = -1 turn, stale = 1, 0 SHAKE, SHK_X, SHK_Y = 0, 0, 0 flood, flood_p, flood_flash = 0, 20, 0 mode, sel, result = "sel", nil, nil stage = "battle" snap_cursor() logline("round " .. round_n .. ": " .. total .. " hostiles") end function settle(u) status_turn_end(u) if u.hp > 0 and not u.did_move and not u.did_atk then fire_trig(u, "hold", {src = u}) end if u.hp > 0 and toxic(u.x, u.y) and not u.in_tox then if env_hit(u, 2) > 0 then logline(CLS[u.cls].nm .. " burns in the sludge") end end u.in_tox = false end function finish_unit() if sel then settle(sel) if check_over() then return end if sel.cascade and sel.hp > 0 then sel.cascade, sel.did_atk, sel.did_move, sel.moved = false, false, false, 0 sel.acted = false cur.x, cur.y = sel.x, sel.y sel, reach, atkc, mode = nil, {}, {}, "sel" snd("blip") return end sel.acted = true end sel, reach, atkc, mode = nil, {}, {}, "sel" if check_over() then return end if next_free() then snap_cursor() else begin_enemy() end end function tick_side(side) for _, u in ipairs(U) do if u.hp > 0 and u.side == side then status_turn_start(u) end end for _, t in ipairs(U) do if t.turret and t.hp > 0 and t.side == side then local best, bd = nil, 99 for _, o in ipairs(U) do if o.hp > 0 and o.side ~= side and not o.turret then local d = dist(t.x, t.y, o.x, o.y) if d <= 2 and d < bd then bd, best = d, o end end end if best then snd("shot") logline("turret opens up") hurt(best, TURRET_DMG, "pure", C_ENEMY2, nil, t.owner or t) end end end end function begin_enemy() if check_over() then return end for _, u in ipairs(U) do if u.side == 1 then u.ready = true end end tick_side(1) if check_over() then return end spawn_wave(false) mode, etimer, focus = "enemy", 0, nil end function end_turn() for i = #U, 1, -1 do if U[i].turret and U[i].hp <= 0 then table.remove(U, i) end end stale = stale + 1 flood_event() if check_over() then return end if turn >= QUAKE_T and math.random(100) <= QUAKE_P then snd("blast") SHAKE = 20 logline("QUAKE shakes the wastes") for _, u in ipairs(U) do if u.hp > 0 and not u.turret then hurt(u, QUAKE_DMG, "quake", C_TERRA, "QUAKE -" .. QUAKE_DMG) end end if check_over() then return end end turn = turn + 1 if turn == QUAKE_T - 1 then logline("the ground shifts") end for _, u in ipairs(U) do u.acted = false end tick_side(0) if check_over() then return end mode = "sel" snap_cursor() logline("turn " .. turn) end function reconcile_stolen() for _, h in ipairs(HEROES) do for _, name in ipairs(h.stolen or {}) do for j = #h.cards, 1, -1 do if h.cards[j] == name then table.remove(h.cards, j); break end end end for _, name in ipairs(h.robbed or {}) do h.cards[#h.cards+1] = name end h.stolen, h.robbed = {}, {} end end function begin_draft() offer = pick_cards(DRAFT_OFFER) draft_i, draft_left, draft_card = 1, DRAFT_PICKS, nil stage = "draft" end function check_over() if alive(0) == 0 then result, mode = "DEFEAT", "over" hold_stage, hold_t = "lost", HOLD_END return true end if alive(1) == 0 and #QUEUE == 0 then mode = "over" reconcile_stolen() local nxt if round_n >= #ROUNDS then for _, h in ipairs(HEROES) do h.tin, h.tout = (h.tin or 0) + (h.din or 0), (h.tout or 0) + (h.dout or 0) h.din, h.dout = 0, 0 end result, nxt = "RUN CLEARED", "won" elseif round_n == 1 then nxt = "title" else nxt = "cleared" end hold_stage, hold_t = nxt, HOLD_END return true end return false end function leave_tile(u, fx, fy) if fx == u.x and fy == u.y then return end u.did_move = true if u.moved_trig then return end u.moved_trig = true fire_trig(u, "move", {src = u, from = {fx, fy}}) end function check_mine(u) for i = #MINES, 1, -1 do local m = MINES[i] if m.side ~= u.side and m.x == u.x and m.y == u.y then table.remove(MINES, i) logline((u.turret and "turret" or CLS[u.cls].nm) .. " hits a mine") hurt(u, m.n, "mine", C_ENEMY2, "MINE -" .. m.n) return true end end return false end function wades(u) return has(u, "pontoon") or (has(u, "phase") and (u.cd_phase or 0) == 0) end DYING = {} SNDREG, VOICE, SND_NEXT = 0x0FF9C, {}, 2 WAVES = {} function make_waves() WAVES.sq, WAVES.saw, WAVES.noise = {}, {}, {} local seed = 20260906 for i = 0, 31 do WAVES.sq[i] = (i < 16) and 0 or 15 WAVES.saw[i] = i % 16 seed = (seed * 1103515245 + 12345) % 2147483648 WAVES.noise[i] = fl(seed / 65536) % 16 end end SND = { hit = {700, 220, 12, 0, 8, "sq"}, graze = {950, 680, 6, 0, 5, "sq"}, die = {380, 60, 14, 0, 26, "noise"}, blast = {520, 70, 15, 0, 30, "noise"}, acid = {260, 110, 9, 0, 18, "noise"}, flood = {140, 820, 11, 2, 44, "saw"}, blip = {880, 880, 5, 0, 3, "sq"}, spawn = {180, 620, 10, 0, 16, "saw"}, shot = {1100, 500, 8, 0, 6, "saw"}, } function snd(name) local d = SND[name] if not d then return end if not WAVES.sq then make_waves() end local ch = SND_NEXT SND_NEXT = (ch == 2) and 3 or 2 VOICE[ch] = {f0 = d[1], f1 = d[2], v0 = d[3], v1 = d[4], len = d[5], t = 0} local w = WAVES[d[6]] for i = 0, 31 do poke4((SNDREG + ch * 18 + 2) * 2 + i, w[i]) end end function snd_update() for ch = 2, 3 do local v = VOICE[ch] if v then local base = SNDREG + ch * 18 local p = v.t / v.len local f = fl(v.f0 + (v.f1 - v.f0) * p) local vol = fl(v.v0 + (v.v1 - v.v0) * p) poke(base, f % 256) poke(base + 1, fl(f / 256) % 16 + vol * 16) v.t = v.t + 1 if v.t > v.len then VOICE[ch] = nil; poke(base + 1, 0) end end end end MUSIC_TRACKS = true MUSIC_ON = true MUS_TITLE = 0 MUS_WIN = 1 MUS_MENU = nil MUS_BATTLE = {} cur_mus = nil function play_mus(t, loop) if not MUSIC_TRACKS then return end if t == cur_mus then return end cur_mus = t if t and MUSIC_ON then music(t, -1, -1, loop ~= false) else music() end end function mus_for_stage() if not MUSIC_ON or stage == "lost" then play_mus(nil) elseif stage == "title" then play_mus(MUS_TITLE) elseif stage == "won" then play_mus(MUS_WIN, false) elseif stage == "battle" then play_mus(round_mus) else play_mus(MUS_MENU) end end AMB_CH = 3 AMB = false function amb_update() if AMB and (AMB ~= AMB_CH or stage ~= "title") then poke(SNDREG + AMB * 18 + 1, 0) AMB = false end if AMB_CH == nil or stage ~= "title" then return end if not WAVES.sq then make_waves() end local base = SNDREG + AMB_CH * 18 if not AMB then AMB = AMB_CH for i = 0, 31 do poke4((base + 2) * 2 + i, WAVES.noise[i]) end end local w = 640 + fl(240 * math.sin(FRAME / 130)) poke(base, w % 256) poke(base + 1, fl(w / 256) % 16 + (3 + fl(2 + 2 * math.sin(FRAME / 95))) * 16) end FRAME = 0 PAD_KEY = {[4] = 26, [5] = 24, [6] = 1} INPUT_PAD = nil function note_input() for b, k in pairs(PAD_KEY) do if btnp(b) then INPUT_PAD = not keyp(k) end end end PAD_NINTENDO = true function bt(i) local names = PAD_NINTENDO and {"B","A","Y"} or {"A","B","X"} local pad, kb = names[i], ({"Z","X","A"})[i] if INPUT_PAD == nil then return pad .. "/" .. kb end return INPUT_PAD and pad or kb end function anim_step() for _, u in ipairs(U) do if u.vx == nil then u.vx, u.vy = cell_px(u.x, u.y) end if u.trail and #u.trail > 0 then local tx, ty = cell_px(kx(u.trail[1]), ky(u.trail[1])) local sp = 4 if u.vx < tx then u.vx = math.min(tx, u.vx + sp) end if u.vx > tx then u.vx = math.max(tx, u.vx - sp) end if u.vy < ty then u.vy = math.min(ty, u.vy + sp) end if u.vy > ty then u.vy = math.max(ty, u.vy - sp) end if u.vx == tx and u.vy == ty then table.remove(u.trail, 1) end else u.vx, u.vy = cell_px(u.x, u.y) end if (u.flash or 0) > 0 then u.flash = u.flash - 1 end if (u.tint_t or 0) > 0 then u.tint_t = u.tint_t - 1 end if (u.spawn_t or 0) > 0 then u.spawn_t = u.spawn_t - 1 end end for i = #DYING, 1, -1 do DYING[i].t = DYING[i].t - 1 if DYING[i].t <= 0 or unit_at(DYING[i].x, DYING[i].y) then table.remove(DYING, i) end end end function animating() if #DYING > 0 then return true end for _, u in ipairs(U) do if u.hp > 0 and u.trail and #u.trail > 0 then return true end if u.hp > 0 and (u.spawn_t or 0) > 0 then return true end end return false end function walk_to(u, cells, k) if has(u, "teleport") and (u.cd_tele or 0) == 0 and landable(cells, k) and cells[k][1] > 0 then local fx, fy = u.x, u.y u.x, u.y = kx(k), ky(k) if u.x > fx then u.flip = 0 elseif u.x < fx then u.flip = 1 end u.cd_tele = 2 u.in_tox = toxic(u.x, u.y) if u.in_tox then env_hit(u, 2) if u.hp == 0 then return false end end leave_tile(u, fx, fy) check_mine(u) return u.hp > 0 end local path, cur_k = {}, k while cur_k do table.insert(path, 1, cur_k) cur_k = cells[cur_k][3] end u.vx, u.vy = cell_px(kx(path[1]), ky(path[1])) u.trail = {} local moved = false for i = 2, #path do local ck = path[i] local fx, fy = u.x, u.y u.x, u.y = kx(ck), ky(ck) u.trail[#u.trail+1] = ck if u.x > fx then u.flip = 0 elseif u.x < fx then u.flip = 1 end moved = true if toxic(u.x, u.y) and not wades(u) then env_hit(u, 2) u.in_tox = true logline(CLS[u.cls].nm .. " wades into the sludge") if u.hp == 0 then return false end break end u.in_tox = false local hit = check_mine(u) if u.hp == 0 then return false end for _, o in ipairs(U) do if o.hp > 0 and o.side ~= u.side and has(o, "sentry") and not o.sentry_used and dist(o.x, o.y, u.x, u.y) == 1 then o.sentry_used = true fire_trig(o, "pass", {src = o, tgt = u}) if u.hp == 0 then return false end end end if hit then break end end if moved then if has(u, "phase") and (u.cd_phase or 0) == 0 then local ox, oy = kx(path[1]), ky(path[1]) local n = math.max(1, fl(outgoing(u, 1) / 2)) for _, e in ipairs(U) do if e.hp > 0 and e.side ~= u.side and dist(e.x, e.y, ox, oy) == 1 then logline(CLS[u.cls].nm .. " clips " .. CLS[e.cls].nm .. " breaking off") hurt(e, n, "hit", C_ENEMY2, nil, u) end end u.cd_phase = 2 end if has(u, "teleport") and (u.cd_tele or 0) == 0 then u.cd_tele = 2 end leave_tile(u, kx(path[1]), ky(path[1])) end return true end function step_into(u, k) return walk_to(u, reach, k) end function input() if mode == "info" then if btnp(4) or btnp(5) or btnp(6) then info_u, mode = nil, "sel" end return end if btnp(0, 24, 6) and cur.y > 0 then cur.y = cur.y - 1 end if btnp(1, 24, 6) and cur.y < GH-1 then cur.y = cur.y + 1 end if btnp(2, 24, 6) and cur.x > 0 then cur.x = cur.x - 1 end if btnp(3, 24, 6) and cur.x < GW-1 then cur.x = cur.x + 1 end local u = unit_at(cur.x, cur.y) if mode == "sel" then if btnp(4) and u and u.side == 0 and not u.acted and not u.turret then snd("blip") sel = u reach = bfs(u, CLS[u.cls].mv, u.x, u.y) atkc = attack_cells(u, reach) mode = "move" end if btnp(5) then if u then info_u, mode = u, "info" else snap_cursor() end end if btnp(6) then for _, h in ipairs(U) do if h.side == 0 and h.hp > 0 and not h.acted then settle(h) end if h.side == 0 then h.acted = true end end begin_enemy() end elseif mode == "move" then if btnp(5) then sel, mode = nil, "sel"; snap_cursor() end if btnp(4) and u and u.side == 1 and can_hit(sel, sel.x, sel.y, u) then do_attack(sel, u) local extra = (sel.cls == "M") and CLS.M.mv or 0 if alive(1) == 0 and #QUEUE == 0 then extra = 0 end if extra > 0 and sel.hp > 0 and not toxic(sel.x, sel.y) then reach = bfs(sel, extra, sel.x, sel.y) mode = has_room(reach, sel) and "retreat" or "sel" if mode == "sel" then finish_unit(); return end else finish_unit() end return end if btnp(4) then local k = key(cur.x, cur.y) local occ = unit_at(cur.x, cur.y) if landable(reach, k) and (not occ or occ == sel) then sel.moved = reach[k][1] if not step_into(sel, k) then finish_unit(); return end if any_target(sel) then mode = "act" else finish_unit() end end end elseif mode == "act" then if btnp(5) or btnp(6) then finish_unit() end if btnp(4) and u and u.side == 1 and can_hit(sel, sel.x, sel.y, u) then do_attack(sel, u) local left = 0 if sel.cls == "M" then left = CLS.M.mv - (sel.moved or 0) end if alive(1) == 0 and #QUEUE == 0 then left = 0 end if left > 0 and sel.hp > 0 and not toxic(sel.x, sel.y) then reach = bfs(sel, left, sel.x, sel.y) mode = has_room(reach, sel) and "retreat" or "sel" if mode == "sel" then finish_unit(); return end else finish_unit() end end elseif mode == "retreat" then if btnp(5) or btnp(6) then finish_unit() end if btnp(4) then local k = key(cur.x, cur.y) if landable(reach, k) and not unit_at(cur.x, cur.y) then step_into(sel, k) finish_unit() end end end end function input_title() if btnp(4) then begin_draft() end end function input_draft() local n = draft_card and #HEROES or #offer if n < 1 then return end if btnp(0, 24, 6) then draft_i = (draft_i - 2) % n + 1 end if btnp(1, 24, 6) then draft_i = draft_i % n + 1 end if btnp(5) and draft_card then offer[#offer+1] = draft_card draft_card, draft_i = nil, 1 elseif btnp(4) then if not draft_card then draft_card = offer[draft_i] table.remove(offer, draft_i) draft_i = 1 else local h = HEROES[draft_i] local dup = false for _, c in ipairs(h.cards) do if c == draft_card then dup = true end end if not dup then h.cards[#h.cards+1] = draft_card draft_card, draft_i = nil, 1 draft_left = draft_left - 1 if draft_left <= 0 then round_n = round_n + 1 start_round() end end end end end function next_enemy() for _, u in ipairs(U) do if u.hp > 0 and u.side == 1 and u.ready and not u.turret and not u.acted then return u end end end function step_enemy() local u = focus or next_enemy() if not u then focus = nil; end_turn(); return end focus = u local speed = btn(5) and 4 or 1 etimer = etimer + speed if etimer >= 34 and not focus.done then focus.done = true ai_unit(focus) settle(focus) if check_over() then return end end if etimer >= ENEMY_PACE and not animating() then focus.done = nil if focus.cascade and focus.hp > 0 then focus.cascade, focus.did_atk, focus.did_move = false, false, false else focus.acted = true end focus = nil etimer = 0 end end SHK_X, SHK_Y = 0, 0 function cell_px(x, y) return OX + SHK_X + x * TS, OY + SHK_Y + y * TS end function draw_ground() rect(OX + SHK_X, OY + SHK_Y, GW*TS, GH*TS, C_SAND) for _, p in ipairs(CONTOUR) do local gx, gy = fl((p[1]-OX)/TS), fl((p[2]-OY)/TS) if inside(gx, gy) and G[key(gx,gy)] == EMPTY and not toxic(gx, gy) then pix(p[1] + SHK_X, p[2] + SHK_Y, C_TOPO) end end end function draw_tile(x, y) local px, py = cell_px(x, y) local t = G[key(x,y)] if in_frame(x, y, flood) then spr(SPR_FLOOD, px, py, -1, 1, 0, (x * 3 + y * 5) % 4, 2, 2) local ph = (FRAME + x * 37 + y * 53) % 260 if ph < 6 then pix(px + 4 + x % 5, py + 5 + y % 4, C_LIGHT) end elseif t == SLUDGE then clip(px, py, TS, TS) spr(SPR_SLUDGE, px, py, -1, 1, 0, 0, 2, 2) clip() end if t == SLUDGE and not in_frame(x, y, flood) then rectb(px, py, TS, TS, C_OUT) end end function draw_grid() local gx, gy = OX + SHK_X, OY + SHK_Y for x = 0, GW do line(gx + x*TS, gy, gx + x*TS, gy + GH*TS, C_TOPO) end for y = 0, GH do line(gx, gy + y*TS, gx + GW*TS, gy + y*TS, C_TOPO) end rectb(gx-1, gy-1, GW*TS+2, GH*TS+2, C_OUT) end function blinking(t) return t % 8 < 4 end function draw_unit(u) local px, py = u.vx or cell_px(u.x, u.y), u.vy or select(2, cell_px(u.x, u.y)) if u.ghost then px, py = cell_px(u.x, u.y) end if (u.t and not blinking(u.t)) or ((u.spawn_t or 0) > 0 and not blinking(u.spawn_t)) then return end local c, c2 = C_PLAYER, C_PL_L if u.side == 1 then c, c2 = C_ENEMY, C_ENEMY2 end if u.acted and u.side == 0 then c, c2 = C_MUTED, C_BROWN end if u.turret then if u.side == 1 then pal_set(3, 9); pal_set(4, 10); pal_set(2, 9) end spr(SPR_TURRET, px, py - 2, 7, 1, 0, 0, 2, 2) pal_reset() return end if u.side == 1 then pal_set(11, 9); pal_set(12, 10); pal_set(13, 6); pal_set(8, 2) pal_set(2, 9); pal_set(3, 9); pal_set(4, 10); pal_set(5, 10) elseif u.acted then pal_set(11, 11); pal_set(12, 11); pal_set(13, 12); pal_set(8, 11) pal_set(3, 11); pal_set(4, 12) else pal_set(2, 11); pal_set(3, 11); pal_set(4, 12); pal_set(5, 13) end if not u.ghost then rect(px + 3, py + 11, 10, 3, C_DARK) rect(px + 2, py + 12, 12, 2, C_DARK) end if (u.flash or 0) > 0 then for i = 0, 15 do pal_set(i, C_UI) end elseif (u.tint_t or 0) > 0 and u.tint_t % 10 < 5 then local c = u.tint_c pal_set(12, c); pal_set(13, c); pal_set(4, c); pal_set(8, c); pal_set(11, c) end local id = (u.cls == "B" and SPR_ARMOR) or (u.cls == "M" and SPR_TRIKE) or SPR_SNIPER spr(id, px, py - 2, 7, 1, u.flip or 0, 0, 2, 2) pal_reset() if u.ghost then pal_reset(); return end local w = math.max(1, fl(14 * u.hp / CLS[u.cls].hp)) rect(px+1, py+14, 14, 2, C_OUT) rect(px+1, py+14, w, 2, (u.side == 0) and C_PL_L or C_ENEMY2) local b = {} local t = G[key(u.x, u.y)] if t == HILL then b[#b+1] = C_TERRA elseif t == FOREST then b[#b+1] = C_FOREST end if (u.burn or 0) > 0 then b[#b+1] = C_ENEMY2 end if (u.oil or 0) > 0 then b[#b+1] = C_OUT end if (u.oxid or 0) > 0 then b[#b+1] = C_TOX end if has(u, "spark") and (u.cd_spark or 0) == 0 then b[#b+1] = C_PL_L end for i = 1, math.min(#b, 4) do local bx = px + (i - 1) * 5 rect(bx, py - 3, 5, 5, C_LIGHT) rect(bx + 1, py - 2, 3, 3, b[i]) end if u.super then rectb(px-1, py-1, TS+2, TS+2, C_UI) end end function draw_barrel(px, py) rect(px + 3, py + 2, 10, 13, C_OUT) rect(px + 4, py + 3, 8, 11, C_ENEMY) rect(px + 4, py + 4, 8, 2, C_ENEMY2) rect(px + 4, py + 11, 8, 2, C_ENEMY2) rect(px + 5, py + 3, 2, 11, C_TERRA) rect(px + 9, py + 7, 2, 3, C_TERRA) end function draw_objects() local list = {} for y = 0, GH-1 do for x = 0, GW-1 do local t = G[key(x,y)] if (t == FOREST or t == HILL) and not in_frame(x, y, flood) then local px, py = cell_px(x, y) local dim = unit_at(x, y) ~= nil if not dim then spr(t == HILL and SPR_HILL or SPR_BUSH, px, py, 7, 1, 0, 0, 2, 2) end end end end for y = 0, GH-1 do for x = 0, GW-1 do if G[key(x,y)] == OBST and not in_frame(x, y, flood) then list[#list+1] = {y = y, x = x} end end end for _, u in ipairs(U) do if u.hp > 0 then list[#list+1] = {y = u.y, u = u} end end for _, g in ipairs(DYING) do if not unit_at(g.x, g.y) then list[#list+1] = {y = g.y, u = g} end end table.sort(list, function(a, b) if a.y ~= b.y then return a.y < b.y end return (a.u == nil) and (b.u ~= nil) end) for _, o in ipairs(list) do if o.u then draw_unit(o.u) else local px, py = cell_px(o.x, o.y) draw_barrel(px, py) end end end function draw_overlay() if mode == "move" then for _, e in ipairs(U) do if e.hp > 0 and e.side == 1 and sel and can_hit(sel, sel.x, sel.y, e) then local px, py = cell_px(e.x, e.y) rectb(px, py, TS, TS, C_ENEMY2) end end end if mode == "move" or mode == "retreat" then for k, v in pairs(reach) do if landable(reach, k) then local px, py = cell_px(kx(k), ky(k)) if atkc[k] and mode == "move" then rectb(px+1, py+1, TS-2, TS-2, C_ENEMY2) else rectb(px+1, py+1, TS-2, TS-2, C_PL_L) end if v[2] > 0 then print("-" .. v[2], px+3, py+3, C_OUT, false, 1, true) end end end elseif mode == "act" then for _, e in ipairs(U) do if e.hp > 0 and e.side == 1 and can_hit(sel, sel.x, sel.y, e) then local px, py = cell_px(e.x, e.y) rectb(px, py, TS, TS, C_ENEMY2) rectb(px+1, py+1, TS-2, TS-2, C_UI) end end elseif mode == "sel" then local u = unit_at(cur.x, cur.y) if u and u.side == 1 then for k, _ in pairs(threat_of(u)) do local px, py = cell_px(kx(k), ky(k)) if (kx(k) + ky(k)) % 2 == 0 then rectb(px+2, py+2, TS-4, TS-4, C_ENEMY) end end end end end function draw_flash() if flood_flash <= 0 then return end local c = (fl(flood_flash / 5) % 2 == 0) and C_TOX or C_UI for _, cc in ipairs(ring_cells(flood)) do local px, py = cell_px(cc[1], cc[2]) rectb(px, py, TS, TS, c) end flood_flash = flood_flash - 1 end function draw_mines() for _, m in ipairs(MINES) do local px, py = cell_px(m.x, m.y) local c = (m.side == 0) and C_PL_L or C_ENEMY2 rect(px+6, py+6, 4, 4, C_OUT) pix(px+4, py+4, C_OUT); pix(px+11, py+11, C_OUT) pix(px+11, py+4, C_OUT); pix(px+4, py+11, C_OUT) rect(px+7, py+7, 2, 2, c) pix(px+5, py+5, c); pix(px+10, py+10, c); pix(px+10, py+5, c); pix(px+5, py+10, c) end end function draw_pops() for i = #pops, 1, -1 do local p = pops[i] local px, py = cell_px(p.x, p.y) print(p.s, px + 2, py - 2 - fl((70 - p.t) / 6), C_OUT, false, 1, true) print(p.s, px + 1, py - 3 - fl((70 - p.t) / 6), p.c, false, 1, true) p.t = p.t - 1 if p.t <= 0 then table.remove(pops, i) end end end function bar(x, y, w, v, mx, c) rect(x, y, w, 3, C_OUT) rect(x, y, math.max(0, fl(w * v / mx)), 3, c) end function panel_box(x, y, w, h) rect(x+1, y, w-2, h, C_DARK) rect(x, y+1, w, h-2, C_DARK) rectb(x+1, y, w-2, 1, C_OUT) rectb(x+1, y+h-1, w-2, 1, C_OUT) end function unit_row(u, y, col) print(CLS[u.cls].nm, PX, y, col, false, 1, true) print(u.hp, PX + 15, y, col, false, 1, true) bar(PX + 25, y + 1, 12, u.hp, CLS[u.cls].hp, col) local x = PX + 39 for i, n in ipairs(u.cards or {}) do if i <= 3 then rect(x, y, 3, 3, ccol(n)) print(ccode(n), x + 4, y, C_MUTED, false, 1, true) x = x + 18 end end if #(u.cards or {}) > 3 then print("+" .. (#u.cards - 3), x, y, C_MUTED, false, 1, true) end end function draw_panel() panel_box(PX - 4, 0, 240 - PX + 4, 136) print("R" .. round_n .. " T" .. turn, PX, 3, C_UI, false, 1, true) if #QUEUE > 0 then print("+" .. #QUEUE, PX + 30, 3, C_ENEMY2, false, 1, true) end local wait = (flood == 0 and FLOOD_T1) or (flood == 1 and FLOOD_T2) or 0 local slg, sc if turn >= QUAKE_T then slg, sc = "QUAKE " .. QUAKE_P .. "%", C_ENEMY2 elseif flood >= 2 then slg, sc = "SLG MAX", C_TOX elseif turn < wait then slg, sc = "SLG T" .. wait, C_MUTED else slg, sc = "SLG " .. flood_p .. "%", (flood_p >= 60) and C_TOX or C_MUTED end print(slg, PX + 58, 3, sc, false, 1, true) local y = 13 for _, u in ipairs(U) do if u.side == 0 and not u.turret then local col = (u.hp > 0) and (u.acted and C_MUTED or C_PL_L) or C_BROWN if u == sel then rect(PX - 3, y - 1, 2, 7, C_PL_L) end unit_row(u, y, col) y = y + 9 end end y = y + 2 for _, u in ipairs(U) do if u.side == 1 and not u.turret and u.hp > 0 then if u == focus then rect(PX - 3, y - 1, 2, 7, C_ENEMY2) end unit_row(u, y, C_ENEMY2) y = y + 9 end end y = 68 local t = unit_at(cur.x, cur.y) if (mode == "act" or mode == "move") and sel and t and t.side == 1 and can_hit(sel, sel.x, sel.y, t) then local is_rpg = has(sel, "rpg") and not sel.did_move and (sel.cd_rpg or 0) == 0 local dm = swing(sel, dist(sel.x, sel.y, t.x, t.y), has(sel, "feint") and 1 or 0, is_rpg, sel.super and not is_rpg) local ac, base, why = accuracy(sel, t) print("hit " .. ac .. "% " .. dm .. "/" .. glance(dm), PX, y, C_UI, false, 1, true) if why ~= "" then print(why, PX, y + 7, C_TOX, false, 1, true) end if G[key(sel.x, sel.y)] == HILL then print("hill +1", PX + 60, y + 7, C_TERRA, false, 1, true) end elseif mode == "move" and sel then print("mv " .. CLS[sel.cls].mv .. " dmg " .. CLS[sel.cls].dmg[1] .. " rng " .. CLS[sel.cls].rmax, PX, y, C_MUTED, false, 1, true) print("yellow = can shoot", PX, y + 7, C_TERRA, false, 1, true) elseif t then local e = eff_list(t) if #e == 0 then print("no effects, " .. bt(2) .. " for info", PX, y, C_MUTED, false, 1, true) else for i, it in ipairs(e) do if i <= 4 then local ex = PX + ((i - 1) % 2) * 52 local ey = y + fl((i - 1) / 2) * 7 rect(ex, ey, 3, 5, it[1]) print(it[2], ex + 5, ey, it[1], false, 1, true) end end if #e > 4 then print("+" .. (#e - 4), PX + 92, y + 7, C_MUTED, false, 1, true) end end end y = 84 for i, l in ipairs(log) do print(l, PX, y + (i - 1) * 6, (i == 1) and C_LIGHT or C_MUTED, false, 1, true) end local h = bt(1) .. " pick " .. bt(2) .. " info " .. bt(3) .. " end" if mode == "move" then h = bt(1) .. " go or hit " .. bt(2) .. " back" elseif mode == "act" then h = bt(1) .. " fire " .. bt(2) .. " skip" elseif mode == "retreat" then h = bt(1) .. " fall back" elseif mode == "enemy" then h = "enemy... hold " .. bt(2) end print(h, PX, 130, C_MUTED, false, 1, true) end function eff_list(u) local e = {} local t = G[key(u.x, u.y)] if t == HILL then e[#e+1] = {C_TERRA, "HILL", "+1 to the damage you deal"} end if t == FOREST then e[#e+1] = {C_FOREST, "BUSHES", "-20% chance of a full hit on you"} end if t ~= FOREST and wall_adj(u.x, u.y) then e[#e+1] = {C_MUTED, "COVER", "-20% chance of a full hit at range 2+"} end if toxic(u.x, u.y) then e[#e+1] = {C_TOX, "SLUDGE", "-2 a turn, every hit lands, no guard"} end if (u.burn or 0) > 0 then e[#e+1] = {C_ENEMY2, "BURNING", "-" .. (((u.oil or 0) > 0) and 4 or 2) .. " at the start of your turn, " .. u.burn .. " left"} end if (u.oil or 0) > 0 then e[#e+1] = {C_OUT, "SLICKED", "you cannot move next turn"} end if (u.oxid or 0) > 0 then e[#e+1] = {C_TOX, "OXIDISED", "every hit lands, no guard, regen, shell"} end if (u.shred or 0) > 0 then e[#e+1] = {C_MUTED, "GLITCHED", "your next hit deals " .. u.shred .. " less"} end if (u.guard or 0) > 0 then e[#e+1] = {C_PL_L, "DUG IN", "half damage until your next turn"} end if (u.immune or 0) > 0 then e[#e+1] = {C_UI, "SHELLED", "immune this turn, rooted"} end if u.super then e[#e+1] = {C_UI, "CHARGED", "next hit doubles your damage, +1"} end if has(u, "rpg") and (u.cd_rpg or 0) > 0 then e[#e+1] = {C_BROWN, "RPG CD", u.cd_rpg .. " turn(s) to reload"} elseif has(u, "rpg") and not u.did_move then e[#e+1] = {C_ENEMY2, "RPG READY", "firing from here deals 6 and a cone"} end if has(u, "spark") and (u.cd_spark or 0) == 0 then e[#e+1] = {C_PL_L, "SPARK READY", "answers the next blow for free"} elseif has(u, "spark") then e[#e+1] = {C_BROWN, "SPARK CD", u.cd_spark .. " turn(s) until it answers again"} end if has(u, "shell") and not u.shell_used and not is_oxid(u) then e[#e+1] = {C_UI, "SHELL READY", "survives one lethal blow at 1 HP"} end if has(u, "shell") and u.shell_used then e[#e+1] = {C_BROWN, "SHELL SPENT", "no second save this battle"} end for _, o in ipairs(U) do if o.hp > 0 and o.side == u.side and o ~= u and has(o, "cover") and dist(o.x, o.y, u.x, u.y) == 1 then e[#e+1] = {C_PL_L, "COVERED", "-1 damage from an ally standing next to you"} break end end if (u.cd_guard or 0) > 0 then e[#e+1] = {C_BROWN, "GUARD CD", u.cd_guard .. " turn(s) before you can dig in"} end if (u.cd_phase or 0) > 0 then e[#e+1] = {C_BROWN, "PHASE CD", u.cd_phase .. " turn(s) until it works again"} end if (u.cd_tele or 0) > 0 then e[#e+1] = {C_BROWN, "BLINK CD", u.cd_tele .. " turn(s) until it works again"} end return e end function draw_info() cls(C_DARK) local u = info_u local c = (u.side == 0) and C_PL_L or C_ENEMY2 rect(0, 0, 240, 11, C_OUT) print(u.turret and "TURRET" or CLS[u.cls].full, 4, 3, c, false, 1, true) print((u.side == 0 and "yours" or "hostile"), 198, 3, C_MUTED, false, 1, true) local C = CLS[u.cls] if u.turret then print("hp 1/1 dmg " .. TURRET_DMG .. " range 2 move 0 threat 2", 4, 15, C_SAND, false, 1, true) else print("hp " .. u.hp .. "/" .. C.hp .. " dmg " .. table.concat(C.dmg, "/") .. " range " .. C.rmin .. "-" .. C.rmax .. " move " .. C.mv .. " threat " .. reach_of(u), 4, 15, C_SAND, false, 1, true) end local e = eff_list(u) local y = 24 if #e == 0 then print("nothing acting on it", 4, y, C_MUTED, false, 1, true) y = y + 8 end for _, it in ipairs(e) do rect(4, y, 3, 5, it[1]) print(it[2], 10, y, it[1], false, 1, true) print(it[3], 68, y, C_MUTED, false, 1, true) y = y + 7 if y > 96 then break end end line(0, y + 1, 240, y + 1, C_BROWN) y = y + 6 if #(u.cards or {}) == 0 then print("carries nothing", 4, y, C_MUTED, false, 1, true) end for _, n in ipairs(u.cards or {}) do rect(4, y, 5, 5, C_OUT) rect(5, y + 1, 3, 3, ccol(n)) print(ccode(n), 13, y, C_LIGHT, false, 1, true) print(n, 32, y, C_SAND, false, 1, true) print(cdesc(n), 78, y, C_MUTED, false, 1, true) y = y + 9 if y > 118 then break end end print(bt(2) .. " to close", 4, 128, C_MUTED, false, 1, true) end function draw_title() cls(C_DARK) for y = 0, 48 do line(0, y, 239, y, (y < 16) and C_DARK or ((y < 32) and C_BROWN or C_TERRA)) end circ(190, 36, 14, C_TOPO) circ(190, 36, 12, C_SAND) for i = 0, 239 do line(i, 44 + fl(6 * math.sin((i + 20) / 32)), i, 72, C_TOPO) line(i, 58 + fl(8 * math.sin((i + 90) / 26)), i, 90, C_SAND) local sy = 82 + fl(5 * math.sin(i / 18)) + fl(i * 0.08) line(i, sy, i, sy + 7, C_FOREST) line(i, sy + 1, i, sy + 5, C_TOX) if (i + FRAME * 2) % 29 == 0 then pix(i, sy + 2, C_LIGHT) end end rect(0, 96, 240, 40, C_DARK) line(0, 96, 240, 96, C_OUT) for i = 0, 239, 4 do pix(i + FRAME % 4, 98 + i % 7, C_BROWN) pix(i + 2, 108 + i % 5, C_TERRA) end draw_barrel(162, 85) spr(SPR_BUSH, 148, 88, 7, 1, 0, 0, 2, 2) spr(SPR_TURRET, 140, 96, 7, 1, 0, 0, 2, 2) rect(182, 116, 28, 4, C_OUT) spr(SPR_ARMOR, 180, 84, 7, 2, 1, 0, 2, 2) spr(SPR_SNIPER, 214, 86, 7, 1, 1, 0, 2, 2) rect(150, 130, 26, 4, C_OUT) spr(SPR_TRIKE, 146, 100, 7, 2, 1, 0, 2, 2) for p = 1, 14 do pix((p * 47 + FRAME * 1.5) % 244 - 4, (p * 29 + fl(math.sin((FRAME + p * 10) / 20) * 8)) % 90, (p % 3 == 0) and C_TOX or C_TOPO) end rect(16, 14, 152, 58, C_OUT) rect(19, 17, 146, 52, C_BROWN) print("SLUGLINE", 25, 23, C_OUT, false, 3) print("SLUGLINE", 23, 21, C_UI, false, 3) print("TACTICAL ROGUELIKE IN ACID WASTES", 23, 50, C_LIGHT, false, 1, true) print("OGO-GAME STUDIO (c) 2026", 23, 59, C_SAND, false, 1, true) if fl(FRAME / 30) % 2 == 0 then rect(20, 100, 106, 13, C_TOX) print("PRESS " .. bt(1) .. " TO DEPLOY", 27, 104, C_OUT, false, 1, true) else rectb(20, 100, 106, 13, C_TOX) print("PRESS " .. bt(1) .. " TO DEPLOY", 27, 104, C_LIGHT, false, 1, true) end print(bt(1) .. " ACT " .. bt(2) .. " INFO " .. bt(3) .. " END TURN", 20, 122, C_MUTED, false, 1, true) end function draw_draft() cls(C_DARK) rect(0, 0, 240, 11, C_OUT) print("SALVAGE", 4, 3, C_UI, false, 1, true) print("take " .. draft_left .. " more", 50, 3, C_SAND, false, 1, true) print("round " .. (round_n + 1) .. " next", 188, 3, C_MUTED, false, 1, true) if not draft_card then print("pick a card", 4, 16, C_LIGHT, false, 1, true) for i, n in ipairs(offer) do local y = 27 + (i - 1) * 14 local on = (i == draft_i) if on then rect(2, y - 2, 236, 13, C_BROWN) end rect(5, y + 1, 5, 5, C_OUT) rect(6, y + 2, 3, 3, ccol(n)) print(ccode(n), 14, y + 1, on and C_UI or C_LIGHT, false, 1, true) print(n, 34, y + 1, on and C_SAND or C_MUTED, false, 1, true) print(cdesc(n), 80, y + 1, on and C_SAND or C_MUTED, false, 1, true) end print(bt(1) .. " take", 4, 128, C_MUTED, false, 1, true) else print("give " .. ccode(draft_card) .. " to", 4, 16, C_LIGHT, false, 1, true) print(cdesc(draft_card), 62, 16, C_TOX, false, 1, true) for i, h in ipairs(HEROES) do local y = 30 + (i - 1) * 30 local on = (i == draft_i) if on then rect(2, y - 2, 236, 27, C_BROWN) end print(CLS[h.cls].full, 6, y, on and C_UI or C_PL_L, false, 1, true) print(#h.cards .. " cards", 190, y, C_MUTED, false, 1, true) local x, yy = 6, y + 9 for _, n in ipairs(h.cards) do rect(x, yy + 1, 4, 4, ccol(n)) print(ccode(n), x + 6, yy, C_MUTED, false, 1, true) x = x + 24 if x > 200 then x = 6; yy = yy + 8 end end if #h.cards == 0 then print("empty", 6, yy, C_MUTED, false, 1, true) end end print(bt(1) .. " assign " .. bt(2) .. " back", 4, 128, C_MUTED, false, 1, true) end end function mvp_of() local best, bv = nil, -1 for _, h in ipairs(HEROES) do local v = (h.dout or 0) * 10 + (h.din or 0) if v > bv then bv, best = v, h end end return (bv > 0) and best or nil end function stat_rows(x, y, total) local mvp = (not total) and mvp_of() or nil print("dealt", x + 46, y, C_MUTED, false, 1, true) print("taken", x + 76, y, C_MUTED, false, 1, true) y = y + 8 for _, h in ipairs(HEROES) do local col = (h.hp > 0) and C_PL_L or C_ENEMY print(CLS[h.cls].nm, x, y, col, false, 1, true) if not total then print(h.hp .. "/" .. CLS[h.cls].hp, x + 16, y, col, false, 1, true) end print(total and (h.tout or 0) or (h.dout or 0), x + 48, y, C_UI, false, 1, true) print(total and (h.tin or 0) or (h.din or 0), x + 78, y, C_SAND, false, 1, true) if h == mvp then print("MVP", x + 100, y, C_TOX, false, 1, true) end y = y + 9 end return y end function draw_cleared() cls(C_DARK) rect(28, 30, 184, 68, C_OUT) rect(29, 31, 182, 66, C_BROWN) print("ROUND " .. round_n .. " CLEARED", 84, 36, C_UI, false, 1, true) stat_rows(40, 48, false) print(bt(1) .. " to salvage", 92, 84, C_SAND, false, 1, true) end function draw_victory() cls(C_DARK) for i = 0, 239 do pix(i, 20 + fl(6 * math.sin((i + FRAME) / 24)), C_TOX) end rect(24, 28, 192, 80, C_OUT) rect(25, 29, 190, 78, C_BROWN) print("VICTORY", 92, 34, C_OUT, false, 2) print("VICTORY", 91, 33, C_TOX, false, 2) print("all six rounds survived", 76, 50, C_SAND, false, 1, true) stat_rows(36, 60, true) print(bt(1) .. " to run again", 88, 96, C_UI, false, 1, true) print("OGO-GAME STUDIO (c) 2026", 66, 122, C_MUTED, false, 1, true) end function draw_end(txt, col) cls(C_DARK) rect(50, 50, 140, 34, C_OUT) rect(51, 51, 138, 32, C_BROWN) print(txt, 74, 60, col, false, 2) print("reached round " .. round_n .. " " .. bt(1) .. " to restart", 62, 76, C_SAND, false, 1, true) print("OGO-GAME STUDIO (c) 2026", 66, 122, C_MUTED, false, 1, true) end function draw_cursor() local px, py = cell_px(cur.x, cur.y) local c = ((time() // 200) % 2 == 0) and C_UI or C_LIGHT rectb(px-1, py-1, TS+2, TS+2, C_OUT) rectb(px, py, TS, TS, c) end function BOOT() make_waves() parse_cards() math.randomseed(fl(time())) start_run() end function TIC() FRAME = FRAME + 1 note_input() if btnp(7) then MUSIC_ON = not MUSIC_ON; cur_mus = -1 end mus_for_stage() if SHAKE > 0 then SHAKE = SHAKE - 1 SHK_X, SHK_Y = (SHAKE % 3) - 1, (fl(SHAKE / 2) % 3) - 1 else SHK_X, SHK_Y = 0, 0 end if hold_t > 0 then if (btnp(4) or btnp(5)) and not animating() then hold_t = 1 end hold_t = hold_t - 1 if hold_t == 0 then stage, hold_stage = hold_stage, nil return end end amb_update() snd_update() if stage == "title" then input_title(); draw_title(); return end if stage == "cleared" then if btnp(4) then begin_draft() end draw_cleared(); return end if stage == "draft" then input_draft(); draw_draft(); return end if stage == "lost" then if btnp(4) then start_run() end draw_end("DEFEAT", C_ENEMY2); return end if stage == "won" then if btnp(4) then start_run() end draw_victory(); return end anim_step() if mode == "enemy" then step_enemy() else input() end if mode == "info" and info_u then draw_info(); return end cls(C_BROWN) draw_ground() for y = 0, GH-1 do for x = 0, GW-1 do draw_tile(x, y) end end draw_grid() draw_overlay() draw_flash() draw_objects() if sel then local px, py = cell_px(sel.x, sel.y) rectb(px-1, py-1, TS+2, TS+2, C_PL_L) end if focus and focus.hp > 0 then local px, py = cell_px(focus.x, focus.y) rectb(px-1, py-1, TS+2, TS+2, C_ENEMY2) end draw_mines() draw_pops() if mode ~= "enemy" and mode ~= "over" then draw_cursor() end draw_panel() local tip = "" if mode == "sel" and not sel then tip = "pick a unit: press " .. bt(1) elseif mode == "move" then tip = bt(1) .. " on enemy = hit from here" elseif mode == "act" then tip = "choose target, " .. bt(1) .. " to fire" elseif mode == "retreat" then tip = "trike falls back" end if tip ~= "" then print(tip, OX + 2, 1, C_OUT, false, 1, true) print(tip, OX + 1, 0, C_LIGHT, false, 1, true) end end