picotron cartridge // www.picotron.net version 2 :: gfx/ :: map/ :: sfx/ :: conversation.lua --[[pod_format="raw",created="2024-04-03 02:38:22",modified="2024-04-10 14:42:28",revision=642]] default_script = { {"welcome to Atelier Hester!", talk_sprite} } Conversation = {} function Conversation:new(script, final) local c = {script=script or default_script, final=final or function() end, index=1} setmetatable(c, self) self.__index = self return c end function Conversation:before() self.behind = scene self.before = function() end -- only ddo othis the firstt time this scene is played end function Conversation:update() local now = self.script[self.index] if now.during ~= nil then now.during() end if btnp(4) or btnp(5) then if now.after ~= nil then now.after() end if self.index < #self.script then self.index += 1 else --self:after() end end end function Conversation:draw() self.behind:draw() ragged_box(30, 100, 240-30, 130) color(0) print(self.script[self.index][1], 34, 104) end function Conversation:after() self.final() --change_scene(self.behind) end :: screen_drawer.lua --[[pod_format="raw",created="2024-04-05 00:59:25",modified="2024-04-05 00:59:26",revision=1]] :: shop.lua --[[pod_format="raw",created="2024-03-29 01:14:51",modified="2024-04-05 14:54:05",revision=1117]] -- this manages the main shop interface, where you look at the cupboard and plantss -- and sstuff. include("art.lua") include("fairy.lua") include("cabinet.lua") cabinet = Cabinet:new(40, 10, 2) fairy = Fairy:new() alchemist_sprite = 192 alchemist_bob = 0 customer_sprite = 0 customer_bob = 0 customer_visible = false door_open = 0 shop_screen = {} function shop_screen.draw() cls() cabinet:draw() -- alchemist! bob(alchemist_sprite, 0, 7, 96, 128, alchemist_bob) fairy:draw() -- countertop! draw_counter(14) if customer_visible then bob(customer_sprite, 240-96, 7, 96, 128, customer_bob) end draw_door(door_open) end function draw_counter(height) rectfill(0, 135-height, 240, 135, 4) for i=1,height do rectfill(0,135-height, 0.5*i*i, 135-i, 4+16) rectfill(240,135-height, 240-0.5*i*i, 135-i, 4+16) end line(0,135-height, 240, 135-height, 4+16) end function shop_screen.update() fairy:move() cabinet:update(fairy.x, fairy.y) if btnp(4) then local drawer = cabinet:drawer_at(fairy.x, fairy.y) if drawer then change_scene(DrawerScene:new(drawer)) end end end function draw_door(open) palt(0x00, true) if open >=0.0 then fillp(0b1111111111111111) end if open > 0.1 then fillp(0b1011010111100101) end if open > 0.2 then fillp(0b1010010110100101) end if open > 0.4 then fillp(0b1010000110100100) end if open > 0.7 then fillp(0x0000) end rectfill(240-(96*open), 135-128, 240, 135, 0x00) sspr(200, 0,0,96,128, 240-(96*open), 135-128, 96*open, 128) palt() fillp() end :: art.lua --[[pod_format="raw",created="2024-03-31 01:52:08",modified="2024-04-12 14:41:51",revision=629]] function bob(s, x, y, w, h, t) sspr(s, 0, 0, w, h, x+t/2, y-t/2, w-t, h+t) end function ragged_box(x1, y1, x2, y2) ragged_box_color(x1, y1, x2, y2, 15) end function ragged_box_color(x1, y1, x2, y2, bgcolor) rectfill(x1+1, y1+1, x2, y2, bgcolor) sspr(004, 0,0, 8,2, x1+1, y1, x2-x1, 2) sspr(004, 0,0, 8,2, x1+1, y2, x2-x1, 2) sspr(004, 0,0, 2,4, x1, y1, 2, y2-y1) sspr(004, 0,0, 2,4, x2, y1+2, 2, y2-y1) end function easeTrig(t) if t<0 then return 0 end if t>1 then return 1 end return 0.5-0.5*cos(t/2) end function easeAccelerate(t) if t<0 then return 0 end if t>1 then return 1 end return t*t end function easeDecelerate(t) if t<0 then return 0 end if t>1 then return 1 end return 1-(1-t)*(1-t) end function easeSine(t) if t<0 then return 0 end if t>1 then return 1 end return sin(t/4) end -- add newlines to a string to fit it into lines. function wrap(str, line_length) -- thanks to https://stackoverflow.com/questions/17586/best-word-wrap-algorithm, i did not feel like thinking this out myself local words = split(str, " ", false) local output = "" local c = 0 for w in all(words) do while sub(w, 1, 1) == "\n" do output = output .. "\n" c = 0 w = sub(w, 2) end if c + print(w,0,-10) > line_length then if c > 0 then output = output .. "\n" c = 0 end end output = output .. w .. " " c += print(w,0,-10) + print(" ", 0, -10) end return output end function collides(position, object) return position.x < object.right and position.x > object.left and position.y > object.top and position.y < object.bottom end :: cabinet.lua --[[pod_format="raw",created="2024-04-03 19:46:51",modified="2024-04-14 02:34:28",revision=631]] Cabinet = {} function Cabinet:new(x, y, gaps) local c = { x=40, y=10, drawers = {}, shelves = {}, hover_plant = nil, height = 4*(gaps+16) } -- populate the big drawers local xoff = x for i=0,0 do for j=0,1 do add(c.drawers, Drawer:new(xoff + gaps + i*(gaps+32), y + gaps + j*(gaps+32), 009, 32)) end end xoff += gaps + (gaps + 32) -- populate ssmall drawers for i=0,3 do for j=0,3 do add(c.drawers, Drawer:new(xoff + gaps + i*(gaps+16), y + gaps + j*(gaps+16), 008, 16)) end end xoff += gaps + 4*(gaps+16) local s = Shelf:new(xoff+gaps, y + gaps, 76, 32) add(s.plants, {13, 4}) add(s.plants, {14, 14}) add(c.shelves, s) local s = Shelf:new(xoff+gaps, y + 3*gaps + 32, 76, 32) add(s.plants, {15, 10}) add(c.shelves, s) setmetatable(c, self) self.__index = self return c end function Cabinet:draw() -- cabinet! rectfill(self.x, self.y, 230, self.y + self.height, 2) -- drawers for a in all(self.drawers) do a:draw() end -- plant shelves for s in all(self.shelves) do s:draw(self.hover_plant) end end function Cabinet:update(x,y) for d in all(self.drawers) do if d:contains(x,y) then d.offset = 4 else d.offset = 0 end end local closest_distance = 1000 local closest_plant = nil for s in all(self.shelves) do for p in all(s.plants) do local dist = abs(p[2] + s.x - x+16) + abs(s.y + 24 - y) if dist < closest_distance then closest_distance = dist closest_plant = p end end end if closest_distance < 32 then self.hover_plant = closest_plant else self.hover_plant = nil end end function Cabinet:drawer_at(x, y) for d in all(self.drawers) do if d:contains(x,y) then return d end end return nil end Drawer = {} function Drawer:new(x, y, sprite, size) local c = {x=x, y=y, sprite=sprite, size=size, offset=0} setmetatable(c, self) self.__index = self return c end function Drawer:draw() rectfill(self.x+2, self.y+2, self.x+self.size-3, self.y+self.size-3, 0) spr(self.sprite, self.x, self.y+self.offset) end function Drawer:contains(x, y) return x >= self.x and x <= self.x + self.size and y >= self.y and y <= self.y + self.size end Shelf = {} function Shelf:new(x, y, width, height) local c = {x=x, y=y, width=width, height=height, plants={}} setmetatable(c, self) self.__index = self return c end function Shelf:draw(highlighted) rectfill(self.x, self.y, self.x + self.width, self.y + self.height, 0) for p in all(self.plants) do if highlighted ~= p then palt(0, true) palt(10, true) end spr(p[1], self.x+p[2], self.y + self.height - 32+2) palt(10, false) palt(0, true) end end :: fairy.lua --[[pod_format="raw",created="2024-04-03 17:32:32",modified="2024-04-10 01:58:59",revision=384]] Fairy = { velocity = 1, accel_frames = 20, min_velocity = 0, max_velocity = 3, f = false } function Fairy:new(ff) local f = ff or {x=120, y=68} setmetatable(f, self) self.__index = self return f end function Fairy:draw() if flr(30*t()) % 2 == 0 then spr(5, self.x-8, self.y-8, self.f) else spr(6, self.x-8, self.y-8, self.f) end end function Fairy:move() if self.velocity < self.max_velocity then self.velocity += (self.max_velocity - self.min_velocity) / self.accel_frames end if btn(0) and self.x > 0 then self.x -= self.velocity end if btn(1) and self.x < 240 then self.x += self.velocity end if btn(2) and self.y > 0 then self.y -= self.velocity end if btn(3) and self.y < 135 then self.y += self.velocity end if not (btn(0) or btn(1) or btn(2) or btn(3)) then self.velocity = self.min_velocity end if btn(1) then self.f = false end if btn(0) then self.f = true end end Glow = {} function Glow:new(x,y) local g = {x=x, y=y, t=0, length=0.7, radius=10, start_color=8, end_color=9} setmetatable(g, self) self.__index = self return g end function Glow:draw() for i=0,4 do local eff_radius = self.t*self.radius / self.length if eff_radius - i >= 0 then local c = self.start_color if i < (self.t/self.length)*4 then c = self.end_color end circ(self.x, self.y, eff_radius-i, c) end end end function Glow:update() self.t += 1/30.0 return self.t < self.length end :: gfx/0.gfx b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTAzLTI5IDAxOjE0OjUxIixtb2RpZmllZD0iMjAyNC0w NC0xNCAwMzoyMjo1NiIscmV2aXNpb249MTEyMF1dbHo0AJgZAABjXQAA8Rp7WzBdPXtibXA9cHh1 AEMgEBAE8PAsZmxhZ3M9MCxwYW5feD0wLjM3NQwA_wJ5PS0wLjEyNSx6b29tPTV9LDoAkCR6cAoP HU8eCgcAcD8ODx4KYBoJAP8CDRpQCh8dPw4dClAKDx1fDg0IAAT-Aw8eClAKDl8ODgpQCn4KUJrw A4sAJqUGOrAKHxMKsAoeBADwAKAaHhqQCgMOEQqQCgMPEgcAPQ4dAQYArw4KkAo_CpBa8AV9ACYQ Jn0AER1xAP8GYDoPFy46MAo9HxweHxIKMAodbxwPCAACIF8cIAD-ARodPxwODxIaQBpeGmB68ASE ACJRCAgECQACABIQCAAfGQ4AFh8J9QAm-yAQLxyQPgAuUC4AHiAuEC4gLjAOEA4wHgBeEF5ALBos YDwaPFAcEAogHIAa0ArwOWgAJccgLDAsUAwADDAMAAwIADAcEBwIABEQAgD-EVAcAAwQDAAcYCwQ LIAcGhyQHxwaHpAeCgAegB4aEB6QfwAl8EnIZATw-7X-Dw-wG47wNf8YHf4V-RL_Bv0mcP0a-hn9 D-4P-R9w-Rb_Hv0M-hr9FnD9D-4m-Qj_Je0_rXD9B-4w-QT_O51wnVf_Od3_QI1wnQf_P73_Qhdd CgDyAECd-kQHXXCd-kJtDx-_RQsAYg0PFj0vHxkAaAf_Qh8WXw0ARW8f-kMNAE9-H-5CDQBeCYIA Dw0AFAnDAA8NACETTx8BDw0AWk8-H-5GDQAqDlsADw0AwC-_QwwAT2FfH_7H-igOADAm594QAGGu R74n-iUSAHAhV94Xbgc_FgBxfjf_Ayf_IhcAcBxXfieeF54XAHFeJ-4IJ-4gFwCQGEduZx4nfheO GQBxThf_DCf_HhkAkBRHXleeJ24XfhkAcT4X-hA3-hoZAKARN15H-gIHbhduGgCRLhdu9wOuR-4V HACQCPcE-gcXXhdeGwCyHhdON-4CN65X-hAmAaABZz5X-hEnThdOzwDBHhdOF25HvjfOR-4MIACx TsdOV-4Hx04XPhe_APA_F04XXicuN75H3jf_CAddcK0uJ26n-gdXvhc_Fx4dFy4PFm8fBz4nXhd_ J_433mf_AQddcL2ON-4Jh-4IBw49TlwOB84HvkfOR-4A9wDVAPspzT49Lv0Ap-0XLh087hcerT5n nkf_DX1w-VMevQ4HHv0HV35n-gaNcP2BBz2eh86dcP2Zjt1w-bADAF-w----3z0FFBwxvAVQ-xQB 1B4CALAEuQQeBAk-Dxk9CQkAGp0GADEPFQ4IAIcMDgS5BAwO1AMAH_y4BSKfICAE-xQR9A4eAwAC wDT5BjQeNAl-Dzl9CQkAMY0ZjQgAL-0EBwALMg8VDgkAHgwIAH-5BjQMDvQOBAASPx78DtYAJXLU DxTwAdQOBAD-EsQe8AHEDvACtB7wAqQe8AOUHvAEdC7wBVQu8Adu8P--q2YAJf8J_TAg_gqA_QTQ _gDwAdnwAtnwA7nw---8UAAlAEQCH-M9ACfwCKIq8A0KAwrwDBoDCvAISgMACvAICgMgCAAjBToI AFAKAxADAAIAZBrwBAoAAwIAHwoOAApEAToAAwIA8A5KwAr-EgEKwCrOKuAKzgrwARquGvACCq4K 8AMajlUAb44K8AWqoMYAJ8AAOvAMCh8TGvAIOi7BAPAiHgAuOvAFCi4AThrwAiouJD4K8AIKFA4A DgQQHgAa4DoALgAEDxQzCvAACi4AHiQNPgoAUQAuEAMkCgDgICQQFBAq8AAKHhQANCOwAKBeJBAa 8AEKfhQeSgAhLkAIANIaYBQQSuAK7xgK4ArsBAChGswa8AAKzArwAQUAfxqsGvACypDZACfwDl1K 8AZaLxMK8AYKTgQeCvAGGh4TBBMq8AUKIA5DOgHwByAeBD4a8AMKLhAELgAOCvADCkMkEwCdABAw ugBAAgoOQ7sAsAIKDgMODxQuFA4AdQEwEB0QwgDTBAogDRAUEDrwAQrGCgUAIBqmLgAQpiYAEIZh ABCGAQIfhqABJ-1iYGAE-x4P-xUS-x4j-xUo-x4R-xU2-x4G-xU_3x7-FUZ-Hv8VSj8e-xUM8Bj_ Cg8e-gHwMP4FBT7wPD4FnhUO8EAOFZ518DZ1rvUE8Bz1BM71Qv4A9T7_BPU6-gv1MP4c9Rj_Ch3_ Th3_Ti3_TE3_Sl0DAE9N-kw9AwAEES05AAUDADAN-v8BACQ1DREABVkABTIAAmIAQW3_SH0DAPtY jf5Gnf5Grf5Ezf5C3f5C7f5A-QH_Pv0C-j79A-48-QX_Ov0H-jj9Cf42-Qv_NP0N-jL9D-4w-RH_ Lv0T-iz9Ff4q-Rj_Jv0b-iT9Hv4g-SH_Hv0k-hr9Kf4U-S7_EP00-gj9Pd79GXoBdC01LjE1ODMB AASEAVcxNC44MBcAAY4BGTOOAfB0QEAE8P9bPxjwJq7wIe7wHf4E8Bj_BCge8BL_BEhe8Ar_Bziu 8AX_B0j_APAB-gVI-gbg-gFIfni_0M44ToheGL7AfkhOSO4YrsBOKH4oPvgBvrDemL4YDhi_oM44 7kgeGM6QLgeOCB4YrjheCO6ALgd_GC4Yfih_GO6ALg8PB24IThg5ALD_AIAuBw1_CE4YHg4A8BkC gC4dB24IXjhuOP4DoB4NBw1uGF4YLlj_AgcdwB4dB27I-gMHPRfAGgDyBP4UBy03HcAuHQf_Dwc9 Nw0aFw4YAHANLUcdSi6wGQCRCB8OHUctah7QGQDwCAVsFy0nDkoO8AEeHQf_AD1cLRdOKvAEHADw Bc4tNywtF37wBy4dfgc9Vy0nfvALGABgPgctBxs3DwAQDg8AQAc9B1sNAPAdEh4tJ3snfvAVHg1H e37wGC4NJx1bXvAbPgcdFyte8B8uF47wIq7wJm7wKx6YBS--5DMDCRYxMwMfNKUBF5Bu8An_BzjO 8AOlASAC4KQB8SAIwP4BSP4OsM44zri_kH5IfpheWL6ATiieOG6oDhjOcO44HnheGC4Y3nDOeK4o PqQBAJgBUJ4oTijeDACiCF4IbhhOOM4XoKUB8A5uGB4oLkjOR6AuBw0HXjheiN53oC4dF46I-gKX sBEBsxf_EMfAHh0n-gunlwFCF-4Ht5gBhif_AycfDmctmwFUJ943XBeeAUY3jmdsnQFGN06nfJ4B KPcBnQEWx50BF4ecARlHmwEvFy2bAV7wJtlvG-AZ-gjwEv4O8A3_FPAI-hjwBv4Mem7wBO7a-gDw A35q-g7wAv4f8AD_IeAOCv4Hev4ACADxIABqXgr_AtAOCv4BGn4a-gPQCv4EGk4K-gbACv4GGh4K -giwCv4IGv4JsAoOCv4hBgD2CiKgHgr_EXqOoA4K-gPq-gGgDgquev4QoA4jACAgwAYAQBoHHgdl APAHDvcC4AoOCk73A-8PB9AKDgoe-Qb3BhMAcA73Bv0I0BoQABAH3AYg9x0GAPEK-R7gCi73Gx7w AF6H-gHXLuD_IfAA-gKA-loIT----30pASbwLxc-G-ApfvAj3vAe-gPwGP4AKl7wEv4BOo7wDv4B Ot7wCu5K-gPwB846-gnwBZ46-g3wBW4q-gAq7vAD-gVaJwFh8AH_AjpuCQDwAw4KziqeCv4D8AAO Cu46Xgr_BAoAgf4DOh4K-gXgOAHQBuAK-hYqftAK-hFKrvsAUf4LOv4BHgHwBQZK-gXAHgr_AUr_ CdAOCt46-gg3QABhrir_BqfgTQFRDPcB8AAsACD3CC8Br-4A9w7gDgqu9xVQAXEhYBBAAPsLcP8S GPAc-jDwCv488AL_QPAF-jbwF-4c8GlWAHYtMC4wMjUwAQAUNGMANDIuM2MAHzNjAAf-Be4-Hf4d 8Ar_BT3_Ez2_8AL_Lj3ebQBA8AYSDx0AHgAO4F8e8DP-EgZ-Hp8SAA4CAP8d8BZNDx5tDi0OHQ7N DlwO-QbwCo1MfQ79Al6tDh0eHQ6d8AL9QPAF-TbwF-2RADfxD6cPHvBPDiAO8EcOIA4QDvBJDnAO 8Cg_8BIO8CheIBkB-xYtXv0n8AqdPq1ufXL9DPAC-QEy-QVyXVL9AvAF-SJyvfAX-RZSkQA2ESaR APA1EA7wTw7wSA4QDxIgDvBIHg1wDvBDDUAdIA4ADcBN8DQeHQANII0u-QwQTfAVTQAOzU79F-AK nQ4NHj0SXU6Nkm0S-QGqAP8KIv0Ekk1y-QHwBZ0S-RUSHgIuEq3wF-0VYrAANvAK-1gvHvARFvAp TvBDDxJQHgc_TfA9bW79CpoA8RotMh0SjU5NQi1CbR698Ap9wl0uTTIN0h0eAg1CrfACzYJdQm2S TSIeIqIAjyL9B0Itcj4iogA78EhzHx5wHvBGDvAUJvAdLvAdJvAfDvADDvBMPvAJFvAiHlAu8EYe MC8SDg2QHvADFvAiDVAOLQ4NDk3wPc0OHSJNYl0_AE3wFaINIo0Osg1iLV4ibSAi8ATMAPMREi0O 8gJegh1C8AKNsi49cg0ukk0CXlK98AWdMl0CvXLNAH89Mk1SHYJt0gA2oZom8E0m8K4W8EYsAvFL QBbwKjIPEvAJDvApDQDSLQBSMCIOMELQMvATfaJNYh0OAh0yAB0ugCLwABJQsg4iLdIeIg3yAy49 EELwAfIBTkI-EyItEj5SPvIJ8AKN8gs8Yjw9cg4yHVI9yAD-AhKtci0iPgIOUj0eTfAXPfIKyQAz DysZK-8KPg8e4A6gDiAOsA7wBP8SEC59Qq1C-RDwAFIAeB-wOgD------------------------- ----------------------9M8CNggATw-yS-FPBA-gLwPP4G8Dk-Ff4E8Dc9-gXwNU3_B-AzTf4I 8DJN-grwME3_C-AvXQUA_ABN-gzwLl3_DfAtTf4N8C4FADMM8C8FADELDw8HABA8JgDwDAk88Cwc Tf4IPPAuHD3_BywPF-AxTf4FCxwLDAkA8KwEDAsMCwzwM02_LS4PGRwLHPAzXY49Lho88DVdTm0e KjzwNv0BSjzwN816LPA5Ck3aHPA7_gMM8DuaDIrwO2psevA6Wnx68DlaHxwMaWrwOFqpWjnwNFrJ SjnwNEoA6SpJ8DMqIJkvDCkKWfA0CkhJ_ATwMPgR8C5oDwr4DPAraBcoJ-gH8Cn4Azc4F6jwKCf4 DAe48CIH_BIniPAh_BYHiPAg_BgHePAg_BknOPAf_CHwH-gi8B34IAcYBgDxBxcY8Bz4IQco8Bv4 JfAb_CbwGigJ_CIGAPAjDgn4BPAaGAn4DhkoCfgA8BkYCfgPGRgJmAdY8BgIGfgQCSgJiAdY8BkJ _BEZKAkYCVgZAFQJ_BIpOAsA8QIUGRgZWBdY8BcJ_BUZCBloBwsAgRgZ6PAWCfgZPgBkFBn4FRkY CwBQFAk4GegJABAacgCCExn4GhmIFzgJAEb4APASCAARG4gAgREJ_BwJqAdICQAkGZgJALD4AfAQ GfgbKagHOAkAphwZuAc48A8J_B0JAKHIBxjwEAn4HCnYTgAgKchlADATqbh9APABD0l4GajwFRn4 Dhm4GYjwF0IA8gEIIBjwGin4GxkI8B8p_BwJBwAQGQYA8CMaOQjwHin4GFkI8B8Z_BdpLfAdGfgU aQgJTfAcGfgReQht8B4J_A6ZbRXwHhn4CpmNJQgAUAapGG1VCQDyQgK5GH2F8B4ZyMkIjcXwHgmY uc3V8B8JWKnd9QLwH8kIzfUFAAXwHpkVjfUN8B5prfUPAAXwHQ0ZvfUV8Bvd9RjwGY0FDfUc8Bkt 9STwGT0FDQIAovUc8Bgt9SbwFy0QAEMFDfUdEgAGJACkHvAVPfUo8BQtFRIAhiDwFC31KvASJACF IvASLfUs8BASAAEUAIUQLfUsAAXwD14AABYAiQbwDz31KgAVFwADVwCLDy31LQAF8AztA24tMjku MTC4M0gzOC43zzMP8DkAAQIE8S3--8NvFPBF7vA8PxV_KV7wOF2OKV7wNz0OSW4JbvA2PQ5Z7vA1 PQ5pbgpu8DQdPmnu8DQNTln_APA0bkkGAPCEKW6vGfA03tzwNBmO-AHwNBlu-AMQHw-wMBle-AMQ G-AxGV78AgsAK-Axbpzb8DFujNvwMl6Mmx8fK-AyXnyLKDvwMl5sSwgbKCcb8DNeXCs4Sx8OBwvw NF4sHxcbKEsIGx8OK-AzHnsfFxgLFw8OKxhb8DMOmw8XCycfDjsYS-AzHksfF0sXHw5LCEvwMz4r Lxe7FgDwJw1ODAsWuwhb8DM9HizrGCvwMz0OHAAsqyg78DMtDmyLKEsfHPA1jGsYWwYPDB8c8DSM AMsGLwoA8kkyrKsWBT8MFfAxDAB8qwYbRCXwMJzbRBUU8C_sy0QllPAnjEWLFSRFpPAkjAU0FXsl BAX0AfAifBVEJXsl9AEV8CB8BYQFayX0AwXwIGwFlAVrGvQF8CBcCgDxQAbwH1wFlBVrCgX0BvAe TAAFlBVbBAoF9AfwHiwAJXQFBBU7FBr0B-AhFQQVZAUUBUoFGqQFtPAfFQoUJUQFFBpUCqQFxPAc FQQKRCUkBaQOAPAGGhUECnQlBAW0CpQF1PAYFcQlxAqECgBQBQQKxBUMAPAK5PAWBQQK9BcF9ADw FQUK9BgV9ADwExX0GggAYBIF9BIKhAoA8wARBfQTCnQABfQC8A8F9BQLAHAOBfQVCmQQCwAQDQsA EXQLAEEMBfQWIQA0A-ALCwChBPAKBfQfABX0AwkAEBAdADIK9CAIAFDUFfQQEBsA8gEIBeQF9AgK ZCAV9ALwCPQADQAyJfQBDQCFCQpEEBUUJeQOAEEAFUQFDgBgFfQICkQAOQEwCPQBGwAfNA0AAsEV 9A0ABfQF8Aj0AwULADME8AkLAFAEKtTwCwwAgAwgGgQatPAMDADACAokEAo0GpTwDfQCYwCgJAAK VAqE8A70AlcAAA4AQBpk8A8OAJIKCiQVVApU8BANAGE0BVQaNAUOAPEEBHX0AQo0BWQKJAXwEeRV ZAX0AA8AgRQF8BL0DQXkDQBwBAXwEwX0DBwAsyQFZAoF8BQF9AsVDgDB8BUF9Awa5AokBQpUDQBw Czr0AnrwF2kAYBRaROvwGAoAUmQaFOsWCwCAdAoE2wYr8BmlAFSECusWGxcAQcsGKwYNAFEJCnQK 2w0AEhclAAANADEL8BgMAIRrPwU7BhsGCykAMUtjKw8AAVAARFQaK4MPAFHUtVrjGwwAYhoFZHXz DhgA8hIbZW3zDjsGCwMb8BqNAw3zEisGC0PwGS3zHAtj8Bk9Aw0CABDzRgVS8yXwGC0QAIADDfMe 8Bct80YFAhAAARIAMBU980YFFBMSAABGBRDzRgUCEAAwAw3zRgUQ80YFJwMNFABlEC3zLvAPXACf Aw3zJPAOPfMuJwUsD50JJw_FCv8gIP8UwTTxAXz0P-4BBP8VPQT_AQT9PA4HACA7HgcAQS30Ny4I AE8E-jUECgD-NARSASH0OGIBMR30OnEBcg30Pf4B9D_xPDH--_iUAR-9kwH-dh9cYgMUHzLlCQHy KxjfFvBALr8PHvA9Lu0e8Dse-QIO8Doe-QMe8DkO-QMfEQ4N8DUcHs18DhzwMRwAHA7dDA0cDVzw MUwKAPYGHA0MDgzwMQwgHv0BDD0MDvA2Dv0IBgBgLU4dTm0uDwCwBR4NDvA3Dl0O-QAIAEBNHv0B IQBRPR49Ds0qAFAeTQ69HgkAUA5dDp0KLQDwDR4dPi0OjQou8DgOTU6dCg7wOR79BA7wOg5NPq0H APGALQ5tDm0e8DsOHR5NHl0_8DoeHX5NHh0u8Dk_QH5NHvBBHs0ODQLwPx7NDxhi8DourQtyG-A5 Dp0bYjvwNAs_nQtiGw0r8DICCw0enRtSGz0CC-AwEgu9G1IbXQIL8CwNCyIbrQtSG30CC-AqHQsy G30rQhudAgsC8CgdC1IrPRtiC70bAvAnHQtyW2IbzQsLAPIA8gQb3RvwJi0L8gMb7QILCgCwC-0A G-AlPQvyAhsKAPAEAggX8BA9C-IBG-0CC-ACCDfwDg4A8QAL-QML8AMIN-ANPQvyABsOAEAYR-AL DgBQC-0CDg0eAGAHGDfwCU0SADFNDr0TAEwYBxgnEwBIAFfwCBIAYQQIEFfwBxIAIz0eEgByIEcA LxXwAxUAIg7NFQA5MEcpEwCwAvADCDA3KQ3wAl2UABUtFQBwQAdJHfABXbcAUB0ezQ4bPABBWa0g vREAIQ7dEQBJIEn9DBAAMCn9DhAA0hIO3Q4CC-AK-Q8L8gQMADoL-Q4MABEFCwBUDI0g-QENAPQK DW3wBBvyBc0eAgvwDk3wBQvyBs0OEgvwKAkAcB0OvfIFvR4KAPACXhBObcLNDiIL8CANAG69ct0L APEMGmVuC-0RHiIL8BYVDkVOEC4L-REOMgvwFYWeCgDwTfAWhU4AHhAL-RAeIvAXhW4tC-0PHjIA C-AV1R0Q-REOQivwFLVgC-0NLjJL8BNlECVgCxL9CS6SK-AShYALMv0EPtIb8BFVsAti3T7yAhvw EFWwC-ICPvIGK-AOCwAiHRsIAEAeG-ANCAAgHwsIAKbyIBvwDFWg8iILBwBgsPIhG-ALJABnIBvw C2WgCAA-C-AMCAABAEwAAAgAYR4L8A9VoGwAAQgAEByHAPAAoAvyGxvwEUXAC-IZG-ASCABAGBvw EwgAQBcb8BQIAEEVK-AVCAA-C-AXCAANITXQCABvFkXgC-IUCAAUIxMbCABPCxLwFQkADR8bRAAA YvAA8hTwFwcAHwPyBBUPyAgnDzoA---------------3UG09NX19 :: gfx/.info.pod b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTAzLTI5IDAxOjE0OjUxIixzdG9yZWQ9IjIwMjQtMDMt MjkgMDE6MTQ6NTEiXV1sejQABAAAAAMAAAAwbmls :: library.lua --[[pod_format="raw",created="2024-04-10 00:24:20",modified="2024-04-10 01:58:59",revision=35]] plant_descriptions = { [-1]={{"i don't know what that is. where did you find this?"}}, [14]={{"this is foxglove"}}, } function get_conversation(plant) local id = plant[1] return plant_descriptions[id] or plant_descriptions[-1] end :: main.lua --[[pod_format="raw",created="2024-04-02 02:59:37",modified="2024-04-14 03:22:56",revision=1000]] -- this is atelier hester! -- except for the one line in _init that does change_screen(shop_screen) -- this is just a file that sets up the framework we're using: -- change_scene to change the displayed sscene -- _update and _draw to run the current scene -- and some stuff for setting animations. include("art.lua") include("script.lua") include("s_title.lua") include("s_shop.lua") include("s_conversation.lua") include("s_drawer.lua") include("s_book.lua") DEBUG = true scene = { before=function() end, draw=function() end, update=function() end } previous_scene = scene animations = {} script = {} function _init() vid(3) change_scene(title_screen) end function _draw() scene:draw() for anim in all(animations) do if anim.draw ~= nil then anim:draw() end end if DEBUG then rectfill(0,0,90,10, 7) print("animations: "..#animations, 0,0,0) rectfill(0,10,90,20,7) print("script nodes: "..#script, 0,10, 0) end end function _update() if #script == 0 or not script[1].blocking then scene:update() end local new_animations = {} for anim in all(animations) do local result = anim:update() if result then add(new_animations, anim) end end animations = new_animations if #script > 0 then local result = script[1]:update() if not result then deli(script, 1) end end end function change_scene(new_scene) if new_scene.before ~= nil then new_scene:before() end scene = new_scene end function play_script(s) for action in all(s) do add(script, action) end end :: map/0.map b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTAzLTI5IDAxOjE0OjUxIixtb2RpZmllZD0iMjAyNC0w NC0wNSAxMzo1NDowNiIscmV2aXNpb249NjgzXV1sejQAaAAAAFgQAADwCHt7Ym1wPXVzZXJkYXRh KCJpMTYiLDMyAwAvIjABAP--------------------vxCCIpLGhpZGRlbj1mYWxzZSxwYW5feD0w CADSeT0wLHRpbGVfaD0xNgoAEHcKAIB6b29tPTF9fQ== :: map/.info.pod b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTAzLTI5IDAxOjE0OjUxIixzdG9yZWQ9IjIwMjQtMDMt MjkgMDE6MTQ6NTEiXV1sejQABAAAAAMAAAAwbmls :: s_book.lua --[[pod_format="raw",created="2024-04-14 02:34:41",modified="2024-04-14 03:22:56",revision=27]] BookScene = { extension = 0, behind = nil, inset = 60, } function BookScene:new(background) c = {height=0, behind=nil, background=background} setmetatable(c, self) self.__index = self return c end function BookScene:before() self.behind = scene play_script({ Script.len(function(s) self.height = 115*easeDecelerate(s.timer/s.length) end, 0.5) }) self.before = function() end end function BookScene:update() if btnp(5) then play_script({ Script.len(function(s) self.height = 115*(1-easeAccelerate(s.timer/s.length)) end, 0.5), Script.once(function(s) change_scene(self.behind) end) }) end end function BookScene:draw() self.behind:draw() spr(007, 20, 135-self.height) end :: s_conversation.lua --[[pod_format="raw",created="2024-04-05 14:55:45",modified="2024-04-14 02:34:19",revision=129]] default_script = { {"welcome to Atelier Hester!", talk_sprite} } Conversation = {} function Conversation:new(script, final) local c = {script=script or default_script, final=final or function() end, index=1} setmetatable(c, self) self.__index = self return c end function Conversation:before() self.behind = scene self.before = function() end -- only ddo othis the firstt time this scene is played end function Conversation:update() local now = self.script[self.index] if now.during ~= nil then now.during() end if btnp(4) or btnp(5) then if now.after ~= nil then now.after() end if self.index < #self.script then self.index += 1 else self:after() end end end function Conversation:draw() self.behind:draw() ragged_box(30, 100, 240-30, 130) color(0) print(wrap(self.script[self.index][1], 240-68), 34, 104) end function Conversation:after() self:final() change_scene(self.behind) end :: s_drawer.lua --[[pod_format="raw",created="2024-04-05 00:59:36",modified="2024-04-14 03:22:56",revision=281]] DrawerScene = { extension = 0, behind = nil, inset = 60, } function DrawerScene:new(drawer) c = {extension=0, behind=nil, inset=60} if drawer.size > 16 then c.inset=30 end setmetatable(c, self) self.__index = self return c end function DrawerScene:before() self.behind = scene play_script({ Script.len(function(s) self.extension = 115*easeDecelerate(s.timer/s.length) end, 0.5) }) self.before = function() end end function DrawerScene:update() if btnp(5) then play_script({ Script.len(function(s) self.extension = 115*(1-easeAccelerate(s.timer/s.length)) end, 0.5), Script.once(function(s) change_scene(self.behind) end) }) end end function DrawerScene:draw() self.behind:draw() draw_drawer(self.extension, self.inset) end function draw_drawer(distance, inset) rectfill(inset, -1, 240-inset, distance, 20) fillp(0b1010110110110101) rectfill(inset, -1, inset+40, distance, 20 | 21<<8) fillp() for i=1,4 do rect(inset-i, -i, 240-inset+i, distance+i, 4) end rectfill(inset-10, distance, 240-inset+10, distance+10, 4) rect(inset-10, distance+10, 240-inset+10, distance+10, 20) spr(10, inset-10-32, distance, true) spr(10, 240-inset+10, distance) rectfill(inset+10, distance+10+1, 240-inset-10, distance+10+2, 9) spr(11, 120-16, distance+10+1) end :: s_shop.lua --[[pod_format="raw",created="2024-04-05 14:55:27",modified="2024-04-14 03:22:56",revision=205]] -- this manages the main shop interface, where you look at the cupboard and plantss -- and sstuff. include("art.lua") include("fairy.lua") include("cabinet.lua") include("library.lua") cabinet = Cabinet:new(40, 10, 2) fairy = Fairy:new() alchemist_sprite = 192 alchemist_bob = 0 customer_sprite = 0 customer_bob = 0 customer_visible = false door_open = 0 book_1 = {left=10, right=74, top=100, bottom=164} book_2 = {left=80, right=144, top=100, bottom=164} shop_screen = {} function shop_screen:draw() cls() cabinet:draw() -- alchemist! bob(alchemist_sprite, 0, 7, 96, 128, alchemist_bob) palt(30, true) palt(0, false) spr(16, 240-110, 135-36) palt(30, false) palt(0, true) spr(21+flr(2*t()%8), 240-110, 135-39) -- countertop! draw_counter(14) if collides(fairy, book_1) then spr(18, book_1.left, book_1.top) else spr(17, book_1.left, book_1.top) end if collides(fairy, book_2) then spr(20, book_2.left, book_2.top) else spr(19, book_2.left, book_2.top) end if customer_visible then bob(customer_sprite, 240-96, 7, 96, 128, customer_bob) end fairy:draw() draw_door(door_open) end function draw_counter(height) rectfill(0, 135-height, 240, 135, 4) for i=1,height do rectfill(0,135-height, 0.5*i*i, 135-i, 4+16) rectfill(240,135-height, 240-0.5*i*i, 135-i, 4+16) end line(0,135-height, 240, 135-height, 4+16) end function shop_screen:update() fairy:move() cabinet:update(fairy.x, fairy.y) if btnp(4) then local glow = Glow:new(fairy.x, fairy.y) glow.start_color = 7 glow.end_color = 12 add(animations, glow) local drawer = cabinet:drawer_at(fairy.x, fairy.y) if drawer then change_scene(DrawerScene:new(drawer)) end if collides(fairy, book_1) then change_scene(BookScene:new(007)) end if collides(fairy, book_2) then change_scene(BookScene:new(007)) end end if btnp(5) then add(animations, Glow:new(fairy.x, fairy.y)) if cabinet.hover_plant ~= nil then change_scene(Conversation:new(get_conversation(cabinet.hover_plant))) end end end function draw_door(open) palt(0x00, true) if open >=0.0 then fillp(0b1111111111111111) end if open > 0.1 then fillp(0b1011010111100101) end if open > 0.2 then fillp(0b1010010110100101) end if open > 0.4 then fillp(0b1010000110100100) end if open > 0.7 then fillp(0x0000) end rectfill(240-(96*open), 135-128, 240, 135, 0x00) sspr(200, 0,0,96,128, 240-(96*open), 135-128, 96*open, 128) palt() fillp() end :: s_title.lua --[[pod_format="raw",created="2024-04-10 01:43:09",modified="2024-04-10 15:04:40",revision=101]] title_screen = { continue=false } function title_screen:update() if btnp(2) or btnp(3) then self.continue = not self.continue end if btnp(4) and not self.continue then change_scene(shop_screen) play_script(the_script) end end function title_screen:draw() cls() print("Atelier Hester ", 120-(68/2), 50, 7) print("Start", 120-25/2, 70, 7) print("Continue", 120-39/2, 90, 7) local y = 70 if self.continue then y=90 end rect(120-50/2, y-5, 120+50/2-1, y+7+5, 7) end :: script.lua --[[pod_format="raw",created="2024-04-04 01:01:55",modified="2024-04-14 03:09:34",revision=554]] Script = {} -- sscriptanim generates animation functions and adds the boilerplate we want -- can track time, but generally the ssystem we're working with is that -- animations are objects which repeatedly get :update() called on them -- they run at least once, every frame, *until they return false*. -- so keep returning true to keep playing, rerturn false to end -- the blocking argument determines whether update() is called on the underlying -- scene, i think. -- the ScriptAnim.len variant will take nil returnss as carte blanche to keep -- running, checking for exactly the value false to cancel out. function Script.len(func, len, blocking) local b = blocking if b == nil then b = true end return { timer = 0, length=len, blocking=b, update=function(self) -- we wrap the function we got passsed so that it'll update the timer -- and end appropriately self.timer += 1/60.0 if func(self) == false then return false end return self.timer < self.length end } end -- ScriptAnim.once is cnofusingly named, and will call func(self) every frame -- until it returns false. function Script.once(func, blocking) local b = blocking if b == nil then b = true end return { timer=0, blocking=b, update=function(self) self.timer += 1/60.0 return func(self) end } end -- stay in this script node until func(self) sets self.done to true function Script.waitTilAfter(func, blocking) local b = blocking if b == nil then b = true end return { first=true, done=false, timer=0, blocking=b, update=function(self) if self.first then func(self) self.first = not self.first end return not self.done end } end -- functions for making a cnoversation script line that bobs the alchemist or the -- custmoer respectively function A(arg) arg.during=function() alchemist_bob=abs(5*sin(3*t())) end arg.after=function() alchemist_bob=0 end return arg end function B(arg) arg.during=function() customer_bob=abs(5*sin(3*t())) end arg.after=function() customer_bob=0 end return arg end -- here's a sample script for a guy walking in and asking for flibbertygibbets the_script = { Script.len(function(s) -- open door door_open = easeTrig(s.timer/s.length) end, 0.5), Script.once(function() -- person appearss alchemist_sprite = 193 customer_visible = true customer_sprite = 201 return false end), Script.len(function(s) -- door closes door_open = easeTrig(1-(s.timer/s.length)) end, 0.5), Script.waitTilAfter(function(s) -- cnoversation starts change_scene(Conversation:new({ A{"Welcome to Atelier Hester!"}, B{"hey do yuo have flibbertygibbetss"}, A{"Fresh out, sorry."} }, function() s.done = true end)) end, false), Script.len(function(s) -- open door door_open = easeTrig(s.timer/s.length) end, 0.5), Script.once(function() -- person appearss alchemist_sprite = 192 customer_visible = false return false end), Script.len(function(s) -- door closes door_open = easeTrig(1-(s.timer/s.length)) end, 0.5), } :: sfx/0.sfx b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTAzLTI5IDAxOjE0OjUxIixtb2RpZmllZD0iMjAyNC0w NC0wNSAxMzo1NDowNiIscmV2aXNpb249NjY4XV1sejQAEAEAAFEHAADwJ3B4dQADKAAAAwAED0AQ Ag4AAaABIAKgDgAPEAAN8MoBAgMEBQYHAA--kAgJCgsPDA8NDw4PDxAA8AANDxEPEg8TDxQPFQ8W DxcTAPEBDxgPGQ8aDxsPHA8dDx4PHxQA8QAgDyEPIg8jDyQPJQ8mDycUAPEAKA8pDyoPKw8sDy0P Lg8vFADxADAPMQ8yDzMPNA81DzYPNxQA-wU4DzkPOg87DzwPPQ8_Dz8AD--w-wEA6-8nWgEQBg8g EAEgASAB8AACEAIMEAEgDyEgATAPQPDDDygP--DGD-gKD-8PgA-3Dw0B8AkBEAYMMAD-OR--AQDc v-gPQAANQP--sPBwCgD--2Qf-wEAl1D-----Hw== :: sfx/.info.pod b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTAzLTI5IDAxOjE0OjUxIixzdG9yZWQ9IjIwMjQtMDMt MjkgMDE6MTQ6NTEiXV1sejQABAAAAAMAAAAwbmls :: story.txt --[[pod_format="raw",created="2024-03-30 15:58:22",modified="2024-04-11 01:41:32",revision=208]] ATELIER HESTER welcome to atelier phoebe 2! this is my document for recording story ideas! please don't read this; it is all the spoilers! if you want to know what happens i recommend playing the game, that is the proper way to experience this :) -------------- main gameplay interface, you're shown a gridd of cubbiess which contain various ingredients you look at the things in the cubbies and label them? labeling could be hard with just o/x you have twwo books, one of recipes and one of ingredients? drag a label from the book to the drawer/plant? -------------- plot ddetails follow!!! main character amnesia from alchemy gone wrong. main character badly in debt to loan shark everyone in town has some debt and don't talk about it most people don't like main character mc has to mend relationships with everyone in order to gather everyone together to defeat loan shark player controls a fairy assistant for alchemy possibly fairy is new to the atelier? early dialog esstablishes it's common knowledge that fairies can blink white (o) to confirm things or red (x) to be uncertain / ask a question maybe other colors at other timess but you get it, those are our two verbs - ask about something and suggest something interface is an apothecary cabinet you can open the drawerss of and ask about. you have to suggest ingredients to mix together for the cauldron at the beginning MC is unsure and doesn't know about your suggestions - "i guess i can look that up?", "maybe i'll try this?" because the player is guiding them. towards the end MC responds as though teaching the player fairy, like "that's right, that'll work!" and "good job, yeah!" to communicate that the MC is more confident and getting their mlemory of alchemy back the town generally ddoesn't like the alchemisst just because she's been kindda nasty and rude at times, just petty disagreements that have boiled over. she's known to be a decent alchemist though i guesss endgame is that you rally the town together to break free of the loan shark; it's hard too convince townsfolk both because they don't like you at first and also because the loans ARE legitimate. but it turnss out that the rest of the town also has debt to the shark and if you convince everyone you can both 1. achieve independence from the supplies he sells (maybe he's the intermediary with the trader? or buys and ressells everything at a markup?) 2. convince people to just reject the loanss/debt; quesstion here of whether they do this by force, whether cops exist, idk, this is explicitly passtoral fantasy. maybe at the end the player will muse about whether it's good to have mob rule, "no one in toown liked me, what if they threatened to burn down my shop?" "yeah, that would suck. maybe we could build something. but right now rule by mob is better than rule by loan shark!" idk this feels overly didactic but you get the idea this is really getting into the weeds of how we view the justice system but maybe he threatens to say "i'll send for the king's knights!" and the crowd is like "you can do that if you want, but by the time they get here your house will be burned down. alternately, you can keep your house and try to be friendly. maybe even contribute to the town. it's up to you. burn down your storehouse or genuinely connect with people?" definitely overly preachy but i like that ------------------ script!!!!!!!!!!!! ------------------ title screen new game or load? new game -> hester with her back to cauldrron holding a little fairy doll in pieces fairy shakes and then comes to life hester: "ahhh, the synthesis wworked! ... i think" hester: "a fairy like you should be a good help around the shop." hester: "you might noot be able to speak but you can flash white for yes and red for not sure" the whole time there's more and more bubbling in the cauldron hester: "you can be my hands across the atelier!" explosion! soot-covered hester stands up "what just happened." "wait a second. who am i." "alchemy books? a cauldron?" outside view of shop, Atelier Hester on the sign "Hester..." back inside "i must be an alchemist..." loan shark arrives "hester! you got the latest payment for me, girlie?" "i'm afraid i don't knwo who i am - can you help me?" "nice try. remember you owe me 4000Z by the end of the month. have a nice day!!!" loan shark leaves (SAVE POINT) "am i... in debt?" "i don't know hwo to do alchemy... but maybe these books..." (player opens books) "it looks like this one has recipes... this one has infoormation about ingredients... my head..." (when quit uot of both books, old man comes in) "hey, hester. can i get that purified water?" "i don't knwo who i am" "sorry, i can't hear so good these days - purified water? my usual order?" (moves on when you've crafted purified water) "thank you, hester. this will help me out. here's your payment." (old man leaves) "i guess i'll just try to... be an alchemist and pay off my debts..." (SAVE POINT) at the beginning MC is unsure and doesn't know about your suggestions - "i guess i can look that up?", "maybe i'll try this?" because the player is guiding them. towards the end MC responds as though teaching the player fairy, like "that's right, that'll work!" and "good job, yeah!" to communicate that the MC is more confident and getting their mlemory of alchemy back the town generally ddoesn't like the alchemisst just because she's been kindda nasty and rude at times, just petty disagreements that have boiled over. she's known to be a decent alchemist though i guesss endgame is that you rally the town together to break free of the loan shark; it's hard too convince townsfolk both because they don't like you at first and also because the loans ARE legitimate. but it turnss out that the rest of the town also has debt to the shark and if you convince everyone you can both 1. achieve independence from the supplies he sells (maybe he's the intermediary with the trader? or buys and ressells everything at a markup?) 2. convince people to just reject the loanss/debt; quesstion here of whether they do this by force, whether cops exist, idk, this is explicitly passtoral fantasy. maybe at the end the player will muse about whether it's good to have mob rule, "no one in toown liked me, what if they threatened to burn down my shop?" "yeah, that would suck. maybe we could build something. but right now rule by mob is better than rule by loan shark!" idk this feels overly didactic but you get the idea this is really getting into the weeds of how we view the justice system but maybe he threatens to say "i'll send for the king's knights!" and the crowd is like "you can do that if you want, but by the time they get here your house will be burned down. alternately, you can keep your house and try to be friendly. maybe even contribute to the town. it's up to you. burn down your storehouse or genuinely connect with people?" definitely overly preachy but i like that :: .info.pod b64$LS1bW3BvZCxjcmVhdGVkPSIyMDI0LTA0LTEyIDE2OjM3OjU3IixydW50aW1lPTUsc3RvcmVk PSIyMDI0LTA0LTE0IDAzOjIyOjU3Iix3b3Jrc3BhY2VzPXt7bG9jYXRpb249Im1haW4ubHVhIzE1 Iix3b3Jrc3BhY2VfaW5kZXg9MX0se2xvY2F0aW9uPSJzX3Nob3AubHVhIzM4Iix3b3Jrc3BhY2Vf aW5kZXg9MX0se2xvY2F0aW9uPSJzX2RyYXdlci5sdWEjMSIsd29ya3NwYWNlX2luZGV4PTF9LHts b2NhdGlvbj0ic19ib29rLmx1YSMzOSIsd29ya3NwYWNlX2luZGV4PTF9LHtsb2NhdGlvbj0iZ2Z4 LzAuZ2Z4Iix3b3Jrc3BhY2VfaW5kZXg9Mn19XV1sejQABAAAAAMAAAAwbmls :: [eoc]