--!name Shadowdark Character Sheet
--!icon sword
--!category Games
local aq=6 local aH=8000 local k,z={},{}local F=0 local function B(W)local g=k[W]if g==nil and z[W]then g=z[W].d end return g end local function n(W)return tonumber(B(W))or 0 end local function R(p)return p>=0 and"+"..p or tostring(p)end local function I(a)return tonumber(k[a[1].."_"])or a[4]or 0 end local function P(a)local E=I(a)local g=tonumber(k[a[1]])or E return g>E and E or g end local function N(a)local e=k[a[1]]if type(e)~="table"then e={};k[a[1]]=e end return e end local aF,ar="Shadowdark Characters","nm"local ap={"Strength","Dexterity","Constitution","Intelligence","Wisdom","Charisma"}local aG={"STR","DEX","CON","INT","WIS","CHA"}local ag={"st","dx","co","it","wi","ch"}local function ab()return math.max(1,n("lv"))end local function ac(b)return(n(ag[b])-10)//2 end local as={}for b=1,6 do local a={ag[b],"n",ap[b],1,20,10}a.s=function(g)return ap[b].."  "..g.."   ("..R((g-10)//2)..")"end as[b]=a end local function aD()local v=0 for G,aj in ipairs(N(z.iv))do v=v+(tonumber(aj:match("%((%d+) slots?%)"))or 1)end local av=n("gp")+n("sv")+n("cp")if av>100 then v=v+(av-1)//100 end return v,math.max(n("st"),10)+n("bs")end local function aC()local ae,E=aD()return"Slots "..ae.."/"..E..(ae>E and" - over!"or"").."   Mark big items (2 slots)"end local function a1()k.hp=nil end local function aO()return"Level up at "..ab()*10 .." XP ("..math.max(0,ab()*10-n("xp")).." to go)"end local function aP()return(B("cl")~=""and B("cl").." "or"Level ")..ab()end local function aM()local e={}for b=1,6 do e[b]=aG[b]..n(ag[b])end return table.concat(e," ")end local function aQ()return"Melee "..R(ac(1)).." (STR)   Ranged "..R(ac(2)).." (DEX)   Init "..R(ac(2))end local function aR()local a=z.hp return"HP "..P(a).."/"..I(a).."  AC "..n("ac")end local function a5()local C=n("sa")if C==0 then return"Choose your spellcasting stat to see your check bonus."end return"Spellcasting check: d20 "..R(ac(C+3)).." against 10 + the spell's tier"end local function aV()local ae,E=aD();return ae.."/"..E.." slots"end local ax={"iv","l","Gear",40,"item",60}ax.h=aC local A={{"Basics",{{"nm","t","Name",30},{"an","t","Ancestry",30},{"cl","t","Class",30},{"lv","n","Level",1,10,1},{"xp","n","XP",0,999,0},{"ti","t","Title",30},{"al","t","Alignment",20},{"bg","t","Background",30},{"de","t","Deity",30},{"la","t","Languages",80},},h=aO,s=aP},{"Stats",as,s=aM},{"Combat",{{"hp","p","Hit points",4},{"ac","n","Armor class",0,30,10},{"lk","k","Luck token",{"[  ]","[x]"}},{"cn","t","Conditions",60},{"lr","a","Rest",a1,"Rested: hit points are back to full."},},h=aQ,s=aR},{"Attacks",{{"at","l","Attacks",20,"attack",70}}},{"Talents",{{"ta","l","Talents and features",30,"talent",80}}},{"Spells",{{"sa","c","Spellcasting stat",{"none","Intelligence","Wisdom","Charisma"}},{"sl","l","Spells",30,"spell",60},},h=a5},{"Gear",{ax,{"gp","n","Gold (gp)",0,999999,0},{"sv","n","Silver (sp)",0,999999,0},{"cp","n","Copper (cp)",0,999999,0},{"bs","n","Extra gear slots",0,20,0},},h=aC,s=aV},{"Notes",{{"no","l","Notes",40,"note",90}}},}local function ah()local j,h=B("nm"),B("cl")return(j~=""and j or"Unnamed")..(h~=""and" - "..h.." "..ab()or"")end for G,d in ipairs(A)do for G,a in ipairs(d[2])do assert(not z[a[1]],"two fields are called "..a[1])z[a[1]]=a local c=a[2]a.d=(c=="t"and"")or(c=="n"and(a[6]or a[4]))or((c=="k"or c=="c")and(a[5]or 0))or nil end end local m={}local l,Q,s,L,t="list",1,nil,nil,nil local f={list=1,sheet=1,items=1}local function a3()local q={}for c,g in pairs(k)do if type(g)=="table"then if#g>0 then q[#q+1]=c.."="for b,p in ipairs(g)do if b>1 then q[#q+1]="|"end q[#q+1]=p end q[#q+1]="\n"end elseif g~=""then q[#q+1]=c.."="..g.."\n"end end return table.concat(q)end local function aT(d)k={}for c,g in(d or""):gmatch("([%w_]+)=([^\n]*)")do local a=z[c]if a and a[2]=="l"then local e={}for aj in g:gmatch("[^|]+")do e[#e+1]=aj end k[c]=e elseif a and a[2]=="t"then k[c]=g else k[c]=tonumber(g)end end end local function am()local q={"S1"}for G,r in ipairs(m)do q[#q+1]=r.slot.."|"..r.sum end snail.save(table.concat(q,"\n"))end local function S()if F==0 then return end snail.cache("c"..F,a3())local ad=ah():sub(1,60)for G,r in ipairs(m)do if r.slot==F and r.sum~=ad then r.sum=ad;am()end end end local function Z()local j=B(ar);return j~=""and j or"Unnamed"end local aI="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function a6(b)return aI:sub(b+1,b+1)end local aK={"1234567890<","QWERTYUIOP'","ASDFGHJKL-+","^ZXCVBNM,./","()[_____]?>"}local aL="'-+,./()?"local i local x,u,y=1,2,1 local function H(p,K)return aK[K]:sub(p,p)end local function v()local j=0 for G,g in pairs(k)do if type(g)=="table"then for G,p in ipairs(g)do j=j+#p+1 end elseif type(g)=="string"then j=j+#g end end return j end local function ai(a,b,aN)local _=a[2]=="n"or a[2]=="p"local d=(a[2]=="t"and B(a[1]))or(b and N(a)[b])or""local ak=_ and 9 or(a[2]=="t"and(a[4]or 40))or a[6]or 80 local aA=_ and ak or math.max(#d,aH-v()+#d)i={f=a,i=b,s=d,at=#d,back=aN,num=_,max=math.min(ak,aA),full=aA<ak}y=(d==""and not _)and 1 or 0 if _ then x,u=1,1 elseif u==1 then x,u=1,2 end l="kb"end local function aw()local a=i.f local d=(i.s:gsub("^%s+",""):gsub("%s+$",""))local c=a[2]if c=="t"then k[a[1]]=d elseif c=="l"then local e=N(a)if i.i then if d==""then table.remove(e,i.i)else e[i.i]=d end elseif d~=""then e[#e+1]=d f.items=math.min(#e+1,a[4])end elseif d~=""then local C,at=d:match("^(%d*)/(%d+)$")local an,j=d:match("^([+-]?)(%d+)$")if c=="p"and at then k[a[1].."_"]=tonumber(at)if C~=""then k[a[1]]=tonumber(C)end k[a[1]]=P(a)elseif j then j=tonumber(j)local T=c=="p"and P(a)or n(a[1])if c=="n"and a[4]<0 then if an=="-"then j=-j end elseif an=="+"then j=T+j elseif an=="-"then j=T-j end if c=="p"then k[a[1]]=math.max(0,math.min(I(a),j))else k[a[1]]=math.max(a[4],math.min(a[5],j))end else t="That isn't a number: "..d end end l,i=i.back,nil S()end local function a0()local h=H(x,u)local d,w=i.s,i.at if h=="<"then if w>0 then i.s,i.at=d:sub(1,w-1)..d:sub(w+1),w-1 end elseif h=="^"then y=(y+1)%3 elseif h=="["then i.at=math.max(0,w-1)elseif h=="]"then i.at=math.min(#d,w+1)elseif h==">"then aw()elseif#d>=i.max then t=i.full and"Sheet full: shorten a list or note"or"That's as long as it can be"else if h=="_"then h=" "elseif y==0 then h=h:lower()end i.s,i.at=d:sub(1,w)..h..d:sub(w+1),w+1 if y==1 and h:match("%a")then y=0 end end end local function aW(c)if c=="top"then aw()elseif c=="ok"then a0()elseif c=="left"or c=="right"then local o,h=c=="left"and-1 or 1,H(x,u)repeat x=(x-1+o)%11+1 until h~="_"or H(x,u)~="_"elseif c=="up"or c=="down"then u=(u-1+(c=="up"and-1 or 1))%5+1 end end local function aX()local J,aZ={},H(x,u)=="_"for K=1,5 do local e={}for p=1,11 do local h=H(p,K)local aa=K==u and(p==x or(aZ and h=="_"))local M,az,b=aa and 1 or 0,aL:find(h,1,true)if h:match("%u")then b=aa and h:byte()-65 elseif h:match("%d")then b=aa and 26+h:byte()-48 elseif az then b=(aa and 35+az)or(h=="/"and 45)elseif h=="<"then b=46+M elseif h=="^"then b=48+y*2+M elseif h=="["then b=54+M elseif h=="]"then b=56+M elseif h=="_"then b=58+M*3+(H(p-1,K)~="_"and 0 or H(p+1,K)~="_"and 2 or 1)else b=64+M end e[p]=b and a6(b)or("{"..h.."}")end J[K]=table.concat(e)end return table.concat(J,"/")end local function a2(a)local c,g=a[2],B(a[1])if L==a then return"Press OK again: "..a[3]end if a.s then return a.s(g)end if c=="t"then return a[3]..": "..(g~=""and g or"-")end if c=="n"then return a[3]..": "..g end if c=="p"then return a[3]..": "..(I(a)>0 and P(a).." / "..I(a)or"none")end if c=="k"then return a[4][g%#a[4]+1].."  "..a[3]end if c=="c"then return a[3]..": "..a[4][g%#a[4]+1]end if c=="l"then return a[3].." ("..#N(a)..")"end return a[3]end local function au(a,o)local c=a[2]if c=="n"then k[a[1]]=math.max(a[4],math.min(a[5],n(a[1])+o))elseif c=="p"then k[a[1]]=math.max(0,math.min(I(a),P(a)+o))elseif c=="k"or c=="c"then k[a[1]]=(n(a[1])+o)%#a[4]else return end S()end local function J(D,T,al)local aE=math.max(1,math.min(T-al//2,#D-al+1))for b=aE,math.min(#D,aE+al-1)do snail.row(D[b]:sub(1,90),b==T)end end local function af(b,o,j)return(b-1+o)%j+1 end local function aB(b)local U=A[b][2]return#U==1 and U[1][2]=="l"end local function ay(b)Q=b if aB(b)then s,f.items,l=A[b][2][1],1,"items"else f["s"..b]=f["s"..b]or 1 l="sect"end end local function a_(d)F=d aT((snail.cache("c"..d)))f.sheet,l=1,"sheet"end local function aY()local v={}for G,r in ipairs(m)do v[r.slot]=true end local d=1 while v[d]do d=d+1 end F,k=d,{}m[#m+1]={slot=d,sum=ah()}f.list,f.sheet=#m,1 am()S()ay(1)f.s1=1 ai(z[ar],nil,"sect")end local function aS()snail.cache("c"..F,nil)for b,r in ipairs(m)do if r.slot==F then table.remove(m,b);break end end am()t=Z().." was deleted."F,k,l=0,{},"list"f.list=math.max(1,math.min(f.list,#m))end function key(c)if l=="kb"then t=nil;aW(c);return end if l=="list"and c=="top"then return end t=nil local C=L L=nil local o=(c=="up"and-1)or(c=="down"and 1)local Y=(c=="left"and-1)or(c=="right"and 1)if l=="list"then local j=#m+(#m<aq and 1 or 0)if o then f.list=af(f.list,o,j)elseif c=="ok"then local r=m[f.list]if r then a_(r.slot)else aY()end end elseif l=="sheet"then if o then f.sheet=af(f.sheet,o,#A+1)elseif c=="top"then S();l="list"elseif c=="ok"then if f.sheet<=#A then ay(f.sheet)elseif C=="del"then aS()else L="del"end end elseif l=="sect"then local U,ao=A[Q][2],"s"..Q local a=U[f[ao]]if o then f[ao]=af(f[ao],o,#U)elseif c=="top"then l="sheet"elseif Y then au(a,Y)elseif c=="ok"then local O=a[2]if O=="t"or O=="n"or O=="p"then ai(a,nil,"sect")elseif O=="k"or O=="c"then au(a,1)elseif O=="l"then s,f.items,l=a,1,"items"elseif C==a then t=a[4]()or a[5]S()else L=a end end elseif l=="items"then local e=N(s)if o then f.items=af(f.items,o,#e+(#e<s[4]and 1 or 0))elseif c=="top"then l=aB(Q)and"sheet"or"sect"elseif c=="ok"then ai(s,e[f.items]and f.items,"items")elseif Y and e[f.items]and e[f.items+Y]then local X=f.items+Y e[X],e[f.items]=e[f.items],e[X]f.items=X S()end end end local aJ={t="BACK back  OK edit  UP/DN move",n="OK type  L/R -1/+1  UP/DN move",p="OK type  L/R -1/+1  UP/DN move",k="OK or L/R change  UP/DN move",c="OK or L/R change  UP/DN move",l="BACK back  OK open  UP/DN move",a="BACK back  OK twice to do it",}local function aU()local a=i.f snail.title(i.i==nil and a[2]=="l"and"New "..a[5]or a[2]=="l"and"Edit "..a[5]or a[3])local V if a[2]=="p"then V="Now "..P(a).." / "..I(a)..". Type -8 for damage, +5 to heal, 12 to set, or 12/30 to set the most too."elseif i.num and a[4]<0 then V="Now "..R(n(a[1]))..". Type the new value, like 3 or -1."elseif i.num then V="Now "..n(a[1])..". Type 12 to set it, or +2 / -2 to change it."else V=#i.s.." of "..i.max.." letters"end snail.status(t or(y==2 and"CAPS"or y==1 and"Shift"or""))snail.text(i.s:sub(1,i.at).."|"..i.s:sub(i.at+1))snail.small(V)snail.board(aX())snail.hint("BACK done  OK press  arrows move")end function draw()if l=="kb"then return aU()end if l=="list"then snail.title(aF)if t then snail.status(t)end local e={}for b,r in ipairs(m)do e[b]=r.sum end if#m<aq then e[#e+1]="+ New character"end J(e,f.list,13)if#m==0 then snail.small("Make a character to start. Sheets are kept on this X3.")end snail.hint("BACK leave  OK open  UP/DN move")return end if l=="sheet"then snail.title(Z())snail.status(t or ah())local e={}for b,d in ipairs(A)do e[b]=d[1]..(d.s and("  "..d.s())or"")end e[#e+1]=L=="del"and"Press OK again to delete "..Z()or"Delete this character"J(e,f.sheet,13)snail.hint("BACK characters  OK open  UP/DN move")return end if l=="sect"then local d=A[Q]local b=f["s"..Q]snail.title(d[1])snail.status(t or Z())if d.h then snail.small(d.h())end local e={}for X,a in ipairs(d[2])do e[X]=a2(a)end J(e,b,12)snail.hint(aJ[d[2][b][2]])return end local e,D=N(s),{}snail.title(s[3])snail.status(t or(Z().." - "..#e.." of "..s[4]))if s.h then snail.small(s.h())end for b,g in ipairs(e)do D[b]=g end if#e<s[4]then D[#D+1]="+ Add "..s[5]end J(D,f.items,12)snail.hint(e[f.items]and"OK edit  L/R reorder  UP/DN move"or"BACK back  OK add  UP/DN move")end function start()local d=snail.load()if type(d)=="string"and d:sub(1,2)=="S1"then for a4,ad in d:gmatch("\n(%d)|([^\n]*)")do m[#m+1]={slot=tonumber(a4),sum=ad}end end end
