--!name Initiative Tracker
--!icon sword
--!category Games
local Q,aw,ay=16,16,16 local b={}local o local G,N=1,true local r,p,A,E,k="list",1,1,nil,nil local w,D local function u(a)for g,s in ipairs(b)do if s==a then return g end end end local function V(a)local g=u(a)if g then table.remove(b,g)end local t=#b+1 for f,s in ipairs(b)do if s.i<a.i then t=f;break end end table.insert(b,t,a)end local function J(af,aL,aF)return math.max(aL,math.min(aF,af))end local function H()local F={"I1|"..G.."|"..(u(o)or 0).."|"..(N and 1 or 0)}for R,a in ipairs(b)do F[#F+1]=table.concat({a.n,a.i,a.h,a.m,(a.p and"p"or"")..(a.d and"d"or""),a.t},"|")end snail.save(table.concat(F,"\n"))end local function aM()local c=snail.load()if type(c)~="string"or c:sub(1,3)~="I1|"then return end local n for aJ in c:gmatch("[^\n]+")do local e={}for af in(aJ.."|"):gmatch("([^|]*)|")do e[#e+1]=af end if e[1]=="I1"then G,n,N=math.max(1,tonumber(e[2])or 1),tonumber(e[3])or 0,e[4]~="0"elseif#b<Q and e[1]and e[1]~=""then b[#b+1]={n=e[1],i=tonumber(e[2])or 0,h=tonumber(e[3])or 0,m=tonumber(e[4])or 0,p=(e[5]or""):find("p")~=nil,d=(e[5]or""):find("d")~=nil,t=e[6]or""}end end o=b[n]or b[1]end local function ab(a)return not(N and a.d)end local function aq(x)local i=#b if i==0 then return end local g,d,X=u(o)or 1,G,false for R,a in ipairs(b)do X=X or ab(a)end repeat g=g+x if g>i then g,d=1,d+1 elseif g<1 then g,d=i,d-1 end until not X or ab(b[g])if d<1 then k="That was the first turn";return end o,G,p=b[g],d,g H()end local function aT()local g=u(o)if not g then return end for f=1,#b-1 do local a=b[(g-1+f)%#b+1]if ab(a)then return a end end end local function ar(ac,aQ)local Y=ac:gsub(" %d+$","")local C,ak=0,false for R,a in ipairs(b)do if a~=aQ then if a.n==ac then ak=true end local S,L=a.n:match("^(.-) (%d+)$")if a.n==Y then C=math.max(C,1)elseif S==Y then C=math.max(C,tonumber(L))end end end if not ak then return ac end return Y.." "..(C+1)end local function ap(a)local g=u(a)table.remove(b,g)if a==o then o=b[g]or b[1]end p=J(p,1,#b+1)end local function W(a,B)a.h=J(B,0,a.m)a.d=a.m>0 and a.h==0 end local at="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function aR(l)return at:sub(l+1,l+1)end local av={"1234567890<","QWERTYUIOP'","ASDFGHJKL-+","^ZXCVBNM,./","()[_____]?>"}local ax={"1234567890<","-+/       >"}local az="'-+,./()?"local j local m,q,z=1,2,1 local function aa()return j.num and ax or av end local function y(s,I)return aa()[I]:sub(s,s)end local function K(e)local L=e=="i"or e=="h"local c=e=="n"and w.n or e=="t"and w.t or""j={f=e,s=c,at=#c,num=L,max=L and 7 or e=="n"and aw or ay}z=(c==""and not L)and 1 or 0 if L then m,q=1,1 elseif q==1 or q>5 then m,q=1,2 end r="kb"end local function al()local e,a=j.f,w local c=(j.s:gsub("^%s+",""):gsub("%s+$",""))r,j="edit",nil if e=="n"then if c~=""then a.n=ar(c,a)elseif D then ap(a);r="list"end elseif e=="t"then a.t=c elseif e=="i"then if c:match("^[+-]?%d+$")then a.i=J(tonumber(c),-9,99)V(a)elseif c~=""then k="That isn't a number: "..c end elseif c~=""then local n,S=c:match("^(%d*)/(%d+)$")local ae,i=c:match("^([+-]?)(%d+)$")if S then a.m=J(tonumber(S),0,999)W(a,n~=""and tonumber(n)or(a.h>0 and a.h or a.m))elseif i then i=tonumber(i)if a.m==0 then if ae==""then a.m=J(i,0,999);W(a,i)else k="Set HP first, like 12 or 12/30"end else if ae=="+"then i=a.h+i elseif ae=="-"then i=a.h-i end W(a,i)end else k="That isn't a number: "..c end end if D and r=="edit"then if e=="n"then K("i")D="i"H()return end k=k or(a.n.." joins on "..a.i)A=3 end D=nil H()end local function aP()local a=y(m,q)local c,t=j.s,j.at if a=="<"then if t>0 then j.s,j.at=c:sub(1,t-1)..c:sub(t+1),t-1 end elseif a=="^"then z=(z+1)%3 elseif a=="["then j.at=math.max(0,t-1)elseif a=="]"then j.at=math.min(#c,t+1)elseif a==">"then al()elseif#c>=j.max then k="That's as long as it can be"else if a=="_"then a=" "elseif z==0 then a=a:lower()end j.s,j.at=c:sub(1,t)..a..c:sub(t+1),t+1 if z==1 and a:match("%a")then z=0 end end end local function aG(f)if f=="top"then al()elseif f=="ok"then aP()elseif f=="left"or f=="right"then local x,a=f=="left"and-1 or 1,y(m,q)local i repeat m=(m-1+x)%11+1 i=y(m,q)until i~=" "and(a~="_"or i~="_")elseif f=="up"or f=="down"then q=(q-1+(f=="up"and-1 or 1))%#aa()+1 if y(m,q)==" "then for M=1,10 do if m-M>=1 and y(m-M,q)~=" "then m=m-M;break end if m+M<=11 and y(m+M,q)~=" "then m=m+M;break end end end end end local function aH()local ah=aa()local P,aO={},y(m,q)=="_"for I=1,#ah do local h={}for s=1,11 do local a=ah[I]:sub(s,s)local T=I==q and(s==m or(aO and a=="_"))local B,ao,l=T and 1 or 0,az:find(a,1,true)if a==" "then l=nil;a="."elseif a:match("%u")then l=T and a:byte()-65 elseif a:match("%d")then l=T and 26+a:byte()-48 elseif ao then l=(T and 35+ao)or(a=="/"and 45)elseif a=="<"then l=46+B elseif a=="^"then l=48+z*2+B elseif a=="["then l=54+B elseif a=="]"then l=56+B elseif a=="_"then l=58+B*3+(y(s-1,I)~="_"and 0 or y(s+1,I)~="_"and 2 or 1)else l=64+B end h[s]=l and aR(l)or a=="."and"."or("{"..a.."}")end P[I]=table.concat(h)end return table.concat(P,"/")end local function Z(a)return a.m>0 and(a.h.." / "..a.m)or"not tracked"end local function aI(a)local c=(a==o and"* "or"")..a.i.."  "..a.n if a.p then c=c.." (PC)"end if a.m>0 then c=c.."  "..a.h.."/"..a.m end if a.d then c=c.."  DOWN"end if a.t~=""then c=c.."  "..a.t end return c end local function ai()local h={}if#b<Q then h[#h+1]="add"end h[#h+1]="skip"if#b>0 then h[#h+1]="new";h[#h+1]="clear"end return h end local as={add="+ Add a fighter",new="New encounter (keeps players)",clear="Clear everyone",}local function aA(n)if E==n then return n=="new"and"Press OK again: clear the monsters"or"Press OK again: clear everyone"end if n=="skip"then return"Skip downed fighters: "..(N and"yes"or"no")end return as[n]end local function aB()w={n="",i=10,h=0,m=0,p=false,d=false,t=""}V(w)o=o or w D=true K("n")end local function aK(f,n)local aj=ai()local i=#b+#aj if f=="up"then p=(p-2)%i+1 elseif f=="down"then p=p%i+1 elseif f=="right"then aq(1)elseif f=="left"then aq(-1)elseif f=="ok"then if b[p]then w,r,E=b[p],"edit",nil;return end local v=aj[p-#b]if v=="add"then aB()elseif v=="skip"then N=not N;H()elseif v=="new"or v=="clear"then if n~=v then E=v;return end local _={}if v=="new"then for R,a in ipairs(b)do if a.p then _[#_+1]=a end end end b,G=_,1 o=b[1]p=1 k=v=="new"and"New encounter: round 1"or"Everyone is gone"H()end end end local O={"name","init","hp","note","side","down","order","turn","copy","remove"}local function aE(d,a)if d=="name"then return"Name: "..a.n end if d=="init"then return"Initiative: "..a.i end if d=="hp"then return"HP: "..Z(a)end if d=="note"then return"Note: "..(a.t~=""and a.t or"-")end if d=="side"then return"Side: "..(a.p and"Player"or"Monster / NPC")end if d=="down"then return"Down or out: "..(a.d and"yes"or"no")end if d=="order"then return"Turn order: "..u(a).." of "..#b end if d=="turn"then return a==o and"It's their turn now"or"Give them the turn"end if d=="copy"then return"Add another like this"end return E=="remove"and"Press OK again to remove "..a.n or"Remove from the fight"end local function aN(a,x)local g=u(a)local F=b[g+x]if not F then return end a.i=F.i b[g],b[g+x]=F,a end local function aD(f,n)local a,d=w,O[A]local U=(f=="left"and-1)or(f=="right"and 1)if f=="top"then r,p="list",u(a)or p elseif f=="up"then A=(A-2)%#O+1 elseif f=="down"then A=A%#O+1 elseif U then if d=="init"then a.i=J(a.i+U,-9,99);V(a)elseif d=="hp"then if a.m==0 then k="OK sets HP, like 12"else W(a,a.h+U)end elseif d=="side"then a.p=not a.p elseif d=="down"then a.d=not a.d elseif d=="order"then aN(a,U)else return end H()elseif f=="ok"then if d=="name"then K("n")elseif d=="init"then K("i")elseif d=="hp"then K("h")elseif d=="note"then K("t")elseif d=="side"then a.p=not a.p elseif d=="down"then a.d=not a.d elseif d=="order"then k="LEFT moves them up, RIGHT down"elseif d=="turn"then o,r,p=a,"list",u(a)elseif d=="copy"then if#b>=Q then k=Q.." fighters is the most"else local x={n=ar(a.n),i=a.i,h=a.m,m=a.m,p=a.p,d=false,t=""}V(x)k="Added "..x.n end elseif n~="remove"then E="remove"else k=a.n.." left the fight"ap(a)r="list"end H()end end function key(f)if r=="kb"then k=nil;aG(f);return end if r=="list"and f=="top"then return end k=nil local n=E E=nil if r=="list"then aK(f,n)else aD(f,n)end end local function P(h,am,ad)local C=math.max(1,math.min(am-ad//2,#h-ad+1))for l=C,math.min(#h,C+ad-1)do snail.row(h[l]:sub(1,90),l==am)end end local ag={n="Up to 16 letters. A name already here gets a number: Goblin 2.",t="A condition or reminder, like Poisoned or Prone.",i="Type the roll, like 17.",}local function aC()local e,a=j.f,w local aS={n=D==true and"New fighter"or"Name",i="Initiative",h="HP",t="Note"}snail.title(aS[e]..((D==true or a.n=="")and""or" - "..a.n))snail.status(k or(j.num and""or z==2 and"CAPS"or z==1 and"Shift"or""))snail.text(j.s:sub(1,j.at).."|"..j.s:sub(j.at+1))if e=="h"then snail.small(a.m>0 and"Now "..Z(a)..". Type -8 for damage, +5 to heal, 12 to set, 12/30 for both."or"No HP yet. Type 12, or 12/30 for 12 of 30.")elseif e=="i"then snail.small("Now "..a.i..". "..ag.i)else snail.small(ag[e])end snail.board(aH())snail.hint("arrows move  BACK done  OK press")end local au={init="BACK list  OK type  L/R -1/+1",hp="BACK list  OK type  L/R -1/+1",side="BACK list  OK or L/R change",down="BACK list  OK or L/R change",order="BACK list  L/R move up/down",remove="BACK list  OK twice to remove",}function draw()if r=="kb"then return aC()end if r=="edit"then local a=w snail.title(a.n)snail.status(k or(a==o and"Their turn - round "..G or"Initiative "..a.i..(a.m>0 and"  HP "..Z(a)or"")))local h={}for g,d in ipairs(O)do h[g]=aE(d,a)end P(h,A,13)snail.hint((au[O[A]]or"BACK list  OK choose").."  UP/DN move")return end if#b==0 then snail.title("Initiative Tracker")snail.status(k or"No one in the fight yet")else snail.title("Round "..G)snail.status(k or(o.n.."'s turn"..(o.d and" (down)"or"")))local an=aT()snail.small(an and"Next up: "..an.n or"No one else is up")end local h={}for g,a in ipairs(b)do h[g]=aI(a)end for R,v in ipairs(ai())do h[#h+1]=aA(v)end P(h,p,13)if#b==0 then snail.small("Add each fighter with their initiative roll. HP and notes are optional.")end snail.hint(#b>0 and"BACK leave  OK open  L/R turn  UP/DN move"or"BACK leave  OK choose  UP/DN move")end function start()aM()p=u(o)or 1 end
