--!name Pathfinder 2E Character Sheet
--!icon sword
--!category Games
local aw=6 local aK=8000 local k,C={},{}local G=0 local function x(z)local f=k[z]if f==nil and C[z]then f=C[z].d end return f end local function o(z)return tonumber(x(z))or 0 end local function S(q)return q>=0 and"+"..q or tostring(q)end local function F(a)return tonumber(k[a[1].."_"])or a[4]or 0 end local function I(a)local ap=F(a)local f=tonumber(k[a[1]])or ap return f>ap and ap or f end local function Y(a)local e=k[a[1]]if type(e)~="table"then e={};k[a[1]]=e end return e end local aJ,ax="Pathfinder Characters","nm"local al={"Strength","Dexterity","Constitution","Intelligence","Wisdom","Charisma"}local av={"STR","DEX","CON","INT","WIS","CHA"}local am={"st","dx","co","it","wi","ch"}local ae={"U","T","E","M","L"}local function ag()return math.max(1,o("lv"))end local function ab(b)return o(am[b])end local function ah(m)m=m%5;return m>0 and 2*m+ag()or 0 end local ay={}for b=1,6 do local a={am[b],"n",al[b],-5,10,0}a.s=function(f)return al[b].."   "..S(f)end ay[b]=a end local function Q(z,_,r,aC,l)local a={z,"k",_,ae,l}a.s=function(f)return ae[f%5+1].."   "..(aC and aC()or _).." ("..av[r]..")   "..S(ab(r)+ah(f))end return a end local function aF(z,_,l)local a={z,"k",_,ae,l}a.s=function(f)return ae[f%5+1].."   ".._ end return a end local a9={Q("pe","Perception",5,nil,1),Q("fo","Fortitude",3,nil,1),Q("re","Reflex",2,nil,1),Q("wl","Will",5,nil,1),}local w={}local aP="Acrobatics2Arcana4Athletics1Crafting4Deception6Diplomacy6Intimidation6Medicine5Nature5".."Occultism4Performance6Religion5Society4Stealth2Survival5Thievery2"for a3,r in aP:gmatch("(%a+)(%d)")do w[#w+1]=Q("k"..(#w+1),a3,tonumber(r))end for b=1,2 do local e="t"..b w[#w+1]=Q("o"..b,"Lore "..b,4,function()local d=x(e)return d~=""and d.." Lore"or"Lore "..b end)end w[#w+1]={"t1","t","Lore 1 topic",24}w[#w+1]={"t2","t","Lore 2 topic",24}local K={{"tr","t","Tradition",20},{"sa","c","Spellcasting attribute",{"none","Intelligence","Wisdom","Charisma"}},aF("sr","Spellcasting proficiency",1),{"fp","p","Focus points",0},}for b=1,10 do K[#K+1]={"r"..b,"p","Rank "..b.." slots",0}end K[#K+1]={"ca","l","Cantrips",20,"cantrip",50}K[#K+1]={"sl","l","Spells",40,"spell",60}local function a7()local a=C.hp local aB=math.max(1,ab(3))*ag()k.hp=math.min(F(a),I(a)+aB)k.fp=nil for b=1,10 do k["r"..b]=nil end return"Rested: +"..aB.." HP, slots and focus are back."end local function aS()return"Level up at 1000 XP ("..math.max(0,1000-o("xp")).." to go)"end local function aT()return(x("cl")~=""and x("cl").." "or"Level ")..ag()end local function aQ()local e={}for b=1,6 do e[b]=av[b]..S(o(am[b]))end return table.concat(e," ")end local function ba()return"Ranks U T E M L: +2 each, plus your level"end local function bc()return"Name your Lore skills at the bottom of the list"end local function aU()local r=o("ka")%6+1 return"Class DC "..(10+ab(r)+ah(o("kr"))).."   Perception "..S(ab(5)+ah(o("pe")))end local function aV()local a=C.hp return"HP "..I(a).."/"..F(a)..(o("th")>0 and" +"..o("th")or"").."  AC "..o("ac")end local function be()local r=o("sa")if r==0 then return"Choose a spellcasting attribute for your spell DC and attack."end local U=ab(r+3)+ah(o("sr"))return"Spell DC "..(10+U).."   Spell attack "..S(U)end local function aZ()return o("gp").." gp"end local D={{"Basics",{{"nm","t","Name",30},{"an","t","Ancestry",30},{"he","t","Heritage",30},{"bg","t","Background",30},{"cl","t","Class",30},{"lv","n","Level",1,20,1},{"xp","n","Experience points",0,9999,0},{"hr","n","Hero points",0,3,1},{"de","t","Deity",30},{"sz","t","Size",12},{"la","t","Languages",80},{"ts","t","Traits",60},},h=aS,s=aT},{"Attributes",ay,s=aQ},{"Perception and saves",a9,h=ba},{"Skills",w,h=bc},{"Combat",{{"hp","p","Hit points",10},{"th","n","Temporary hit points",0,999,0},{"ac","n","Armor class",0,60,10},{"sh","p","Shield hit points",0},{"sd","n","Speed (ft)",0,300,25},{"ka","c","Key attribute",al},aF("kr","Class DC proficiency",1),{"dy","n","Dying",0,4,0},{"wd","n","Wounded",0,3,0},{"dm","n","Doomed",0,3,0},{"cn","t","Conditions",60},{"rw","t","Resistances and weaknesses",60},{"lr","a","Rest for the night",a7},},h=aU,s=aV},{"Strikes",{{"at","l","Strikes",20,"strike",70}}},{"Spells",K,h=be},{"Feats and features",{{"ft","l","Feats and features",40,"feat",80}}},{"Inventory",{{"iv","l","Items",40,"item",60},{"bk","t","Bulk carried",20},{"gp","n","Gold (gp)",0,9999999,0},{"sv","n","Silver (sp)",0,9999999,0},{"cp","n","Copper (cp)",0,9999999,0},{"pp","n","Platinum (pp)",0,9999999,0},},s=aZ},{"Notes",{{"no","l","Notes",40,"note",90}}},}local function an()local j,h=x("nm"),x("cl")return(j~=""and j or"Unnamed")..(h~=""and" - "..h.." "..ag()or"")end for M,d in ipairs(D)do for M,a in ipairs(d[2])do assert(not C[a[1]],"two fields are called "..a[1])C[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 p={}local n,R,t,N,u="list",1,nil,nil,nil local g={list=1,sheet=1,items=1}local function bb()local s={}for c,f in pairs(k)do if type(f)=="table"then if#f>0 then s[#s+1]=c.."="for b,q in ipairs(f)do if b>1 then s[#s+1]="|"end s[#s+1]=q end s[#s+1]="\n"end elseif f~=""then s[#s+1]=c.."="..f.."\n"end end return table.concat(s)end local function aX(d)k={}for c,f in(d or""):gmatch("([%w_]+)=([^\n]*)")do local a=C[c]if a and a[2]=="l"then local e={}for a_ in f:gmatch("[^|]+")do e[#e+1]=a_ end k[c]=e elseif a and a[2]=="t"then k[c]=f else k[c]=tonumber(f)end end end local function as()local s={"S1"}for M,m in ipairs(p)do s[#s+1]=m.slot.."|"..m.sum end snail.save(table.concat(s,"\n"))end local function T()if G==0 then return end snail.cache("c"..G,bb())local ai=an():sub(1,60)for M,m in ipairs(p)do if m.slot==G and m.sum~=ai then m.sum=ai;as()end end end local function ac()local j=x(ax);return j~=""and j or"Unnamed"end local aL="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function bf(b)return aL:sub(b+1,b+1)end local aN={"1234567890<","QWERTYUIOP'","ASDFGHJKL-+","^ZXCVBNM,./","()[_____]?>"}local aO="'-+,./()?"local i local A,v,B=1,2,1 local function H(q,L)return aN[L]:sub(q,q)end local function aj()local j=0 for M,f in pairs(k)do if type(f)=="table"then for M,q in ipairs(f)do j=j+#q+1 end elseif type(f)=="string"then j=j+#f end end return j end local function ao(a,b,aR)local ad=a[2]=="n"or a[2]=="p"local d=(a[2]=="t"and x(a[1]))or(b and Y(a)[b])or""local aq=ad and 9 or(a[2]=="t"and(a[4]or 40))or a[6]or 80 local aG=ad and aq or math.max(#d,aK-aj()+#d)i={f=a,i=b,s=d,at=#d,back=aR,num=ad,max=math.min(aq,aG),full=aG<aq}B=(d==""and not ad)and 1 or 0 if ad then A,v=1,1 elseif v==1 then A,v=1,2 end n="kb"end local function aA()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=Y(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 g.items=math.min(#e+1,a[4])end elseif d~=""then local r,U=d:match("^(%d*)/(%d+)$")local at,j=d:match("^([+-]?)(%d+)$")if c=="p"and U then k[a[1].."_"]=tonumber(U)if r~=""then k[a[1]]=tonumber(r)end k[a[1]]=I(a)elseif j then j=tonumber(j)local V=c=="p"and I(a)or o(a[1])if c=="n"and a[4]<0 then if at=="-"then j=-j end elseif at=="+"then j=V+j elseif at=="-"then j=V-j end if c=="p"then k[a[1]]=math.max(0,math.min(F(a),j))else k[a[1]]=math.max(a[4],math.min(a[5],j))end else u="That isn't a number: "..d end end n,i=i.back,nil T()end local function a6()local h=H(A,v)local d,y=i.s,i.at if h=="<"then if y>0 then i.s,i.at=d:sub(1,y-1)..d:sub(y+1),y-1 end elseif h=="^"then B=(B+1)%3 elseif h=="["then i.at=math.max(0,y-1)elseif h=="]"then i.at=math.min(#d,y+1)elseif h==">"then aA()elseif#d>=i.max then u=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 B==0 then h=h:lower()end i.s,i.at=d:sub(1,y)..h..d:sub(y+1),y+1 if B==1 and h:match("%a")then B=0 end end end local function a0(c)if c=="top"then aA()elseif c=="ok"then a6()elseif c=="left"or c=="right"then local l,h=c=="left"and-1 or 1,H(A,v)repeat A=(A-1+l)%11+1 until h~="_"or H(A,v)~="_"elseif c=="up"or c=="down"then v=(v-1+(c=="up"and-1 or 1))%5+1 end end local function a1()local J,a4={},H(A,v)=="_"for L=1,5 do local e={}for q=1,11 do local h=H(q,L)local af=L==v and(q==A or(a4 and h=="_"))local O,aE,b=af and 1 or 0,aO:find(h,1,true)if h:match("%u")then b=af and h:byte()-65 elseif h:match("%d")then b=af and 26+h:byte()-48 elseif aE then b=(af and 35+aE)or(h=="/"and 45)elseif h=="<"then b=46+O elseif h=="^"then b=48+B*2+O elseif h=="["then b=54+O elseif h=="]"then b=56+O elseif h=="_"then b=58+O*3+(H(q-1,L)~="_"and 0 or H(q+1,L)~="_"and 2 or 1)else b=64+O end e[q]=b and bf(b)or("{"..h.."}")end J[L]=table.concat(e)end return table.concat(J,"/")end local function a8(a)local c,f=a[2],x(a[1])if N==a then return"Press OK again: "..a[3]end if a.s then return a.s(f)end if c=="t"then return a[3]..": "..(f~=""and f or"-")end if c=="n"then return a[3]..": "..f end if c=="p"then return a[3]..": "..(F(a)>0 and I(a).." / "..F(a)or"none")end if c=="k"then return a[4][f%#a[4]+1].."  "..a[3]end if c=="c"then return a[3]..": "..a[4][f%#a[4]+1]end if c=="l"then return a[3].." ("..#Y(a)..")"end return a[3]end local function az(a,l)local c=a[2]if c=="n"then k[a[1]]=math.max(a[4],math.min(a[5],o(a[1])+l))elseif c=="p"then k[a[1]]=math.max(0,math.min(F(a),I(a)+l))elseif c=="k"or c=="c"then k[a[1]]=(o(a[1])+l)%#a[4]else return end T()end local function J(E,V,ar)local aI=math.max(1,math.min(V-ar//2,#E-ar+1))for b=aI,math.min(#E,aI+ar-1)do snail.row(E[b]:sub(1,90),b==V)end end local function ak(b,l,j)return(b-1+l)%j+1 end local function aH(b)local W=D[b][2]return#W==1 and W[1][2]=="l"end local function aD(b)R=b if aH(b)then t,g.items,n=D[b][2][1],1,"items"else g["s"..b]=g["s"..b]or 1 n="sect"end end local function a5(d)G=d aX((snail.cache("c"..d)))g.sheet,n=1,"sheet"end local function a2()local aj={}for M,m in ipairs(p)do aj[m.slot]=true end local d=1 while aj[d]do d=d+1 end G,k=d,{}p[#p+1]={slot=d,sum=an()}g.list,g.sheet=#p,1 as()T()aD(1)g.s1=1 ao(C[ax],nil,"sect")end local function aW()snail.cache("c"..G,nil)for b,m in ipairs(p)do if m.slot==G then table.remove(p,b);break end end as()u=ac().." was deleted."G,k,n=0,{},"list"g.list=math.max(1,math.min(g.list,#p))end function key(c)if n=="kb"then u=nil;a0(c);return end if n=="list"and c=="top"then return end u=nil local r=N N=nil local l=(c=="up"and-1)or(c=="down"and 1)local aa=(c=="left"and-1)or(c=="right"and 1)if n=="list"then local j=#p+(#p<aw and 1 or 0)if l then g.list=ak(g.list,l,j)elseif c=="ok"then local m=p[g.list]if m then a5(m.slot)else a2()end end elseif n=="sheet"then if l then g.sheet=ak(g.sheet,l,#D+1)elseif c=="top"then T();n="list"elseif c=="ok"then if g.sheet<=#D then aD(g.sheet)elseif r=="del"then aW()else N="del"end end elseif n=="sect"then local W,au=D[R][2],"s"..R local a=W[g[au]]if l then g[au]=ak(g[au],l,#W)elseif c=="top"then n="sheet"elseif aa then az(a,aa)elseif c=="ok"then local P=a[2]if P=="t"or P=="n"or P=="p"then ao(a,nil,"sect")elseif P=="k"or P=="c"then az(a,1)elseif P=="l"then t,g.items,n=a,1,"items"elseif r==a then u=a[4]()or a[5]T()else N=a end end elseif n=="items"then local e=Y(t)if l then g.items=ak(g.items,l,#e+(#e<t[4]and 1 or 0))elseif c=="top"then n=aH(R)and"sheet"or"sect"elseif c=="ok"then ao(t,e[g.items]and g.items,"items")elseif aa and e[g.items]and e[g.items+aa]then local Z=g.items+aa e[Z],e[g.items]=e[g.items],e[Z]g.items=Z T()end end end local aM={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 aY()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 X if a[2]=="p"then X="Now "..I(a).." / "..F(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 X="Now "..S(o(a[1]))..". Type the new value, like 3 or -1."elseif i.num then X="Now "..o(a[1])..". Type 12 to set it, or +2 / -2 to change it."else X=#i.s.." of "..i.max.." letters"end snail.status(u or(B==2 and"CAPS"or B==1 and"Shift"or""))snail.text(i.s:sub(1,i.at).."|"..i.s:sub(i.at+1))snail.small(X)snail.board(a1())snail.hint("BACK done  OK press  arrows move")end function draw()if n=="kb"then return aY()end if n=="list"then snail.title(aJ)if u then snail.status(u)end local e={}for b,m in ipairs(p)do e[b]=m.sum end if#p<aw then e[#e+1]="+ New character"end J(e,g.list,13)if#p==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 n=="sheet"then snail.title(ac())snail.status(u or an())local e={}for b,d in ipairs(D)do e[b]=d[1]..(d.s and("  "..d.s())or"")end e[#e+1]=N=="del"and"Press OK again to delete "..ac()or"Delete this character"J(e,g.sheet,13)snail.hint("BACK characters  OK open  UP/DN move")return end if n=="sect"then local d=D[R]local b=g["s"..R]snail.title(d[1])snail.status(u or ac())if d.h then snail.small(d.h())end local e={}for Z,a in ipairs(d[2])do e[Z]=a8(a)end J(e,b,12)snail.hint(aM[d[2][b][2]])return end local e,E=Y(t),{}snail.title(t[3])snail.status(u or(ac().." - "..#e.." of "..t[4]))if t.h then snail.small(t.h())end for b,f in ipairs(e)do E[b]=f end if#e<t[4]then E[#E+1]="+ Add "..t[5]end J(E,g.items,12)snail.hint(e[g.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 bd,ai in d:gmatch("\n(%d)|([^\n]*)")do p[#p+1]={slot=tonumber(bd),sum=ai}end end end
