picotron cartridge // www.picotron.net version 2 :: gfx/ :: map/ :: sfx/ :: 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 :: 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-10 01:58:59",revision=627]] 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 :: 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 :: 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 NC0xMiAxNDo0MTo1MSIscmV2aXNpb249MTA1MF1dbHo0AJQUAADWVQAA8Rp7WzBdPXtibXA9cHh1 AEMgEBAE8PAsZmxhZ3M9MCxwYW5feD0wLjM3NQwA_wJ5PS0wLjEyNSx6b29tPTV9LDoAkCR6cAoP HU8eCgcAcD8ODx4KYBoJAP8CDRpQCh8dPw4dClAKDx1fDg0IAAT-Aw8eClAKDl8ODgpQCn4KUJrw A4sAJqUGOrAKHxMKsAoeBADwAKAaHhqQCgMOEQqQCgMPEgcAPQ4dAQYArw4KkAo_CpBa8AV9ACYQ Jn0AER1xAP8GYDoPFy46MAo9HxweHxIKMAodbxwPCAACIF8cIAD-ARodPxwODxIaQBpeGmB68ASE ACJRCAgECQACABIQCAAfGQ4AFh8J9QAm-yAQLxyQPgAuUC4AHiAuEC4gLjAOEA4wHgBeEF5ALBos YDwaPFAcEAogHIAa0ArwOWgAJccgLDAsUAwADDAMAAwIADAcEBwIABEQAgD-EVAcAAwQDAAcYCwQ LIAcGhyQHxwaHpAeCgAegB4aEB6QfwAaDx4DNVD-FAHUHgIAsAS5BB4ECT8PGT0JCQAanQYAMQ8V DggAhwwOBLkEDA7UAwAf7HsAIp8gIAT-FBH0Dh4DAALANPkGNB40CX8POX0JCQAxjRmNCAAv-QQH AAsyDxUOCQAeDAgAf-kGNAwO9A4EABI-HvwO1gAlctQPFPAB1A4EAP8SxB7wAcQO8AK0HvACpB7w A5Qe8AR0LvAFVC7wB27w--_rZgAl-wn5MCD6CoD5BND6APAB2fAC2fADufD---xQACVf8P----M9 ACfwCKIq8A0KAwrwDBoDCvAISgMACvAICgMgCAAjBToIAFAKAxADAAIAZBrwBAoAAwIAHwoOAApE AToAAwIA8A5KwAr-EgEKwCrOKuAKzgrwARquGvACCq4K8AMajlUAb44K8AWqoMYAJ8AAOvAMCh8T GvAIOi7BAPAiHgAuOvAFCi4AThrwAiouJD4K8AIKFA4ADgQQHgAa4DoALgAEDxQzCvAACi4AHiQN PgoAUQAuEAMkCgDgICQQFBAq8AAKHhQANCOwAKBeJBAa8AEKfhQeSgAhLkAIANIaYBQQSuAK7xgK 4ArsBAChGswa8AAKzArwAQUAfxqsGvACypDZACfwDl1K8AZaLxMK8AYKTgQeCvAGGh4TBBMq8AUK IA5DOgHwByAeBD4a8AMKLhAELgAOCvADCkMkEwCdABAwugBAAgoOQ7sAsAIKDgMODxQuFA4AdQEw EB0QwgDTBAogDRAUEDrwAQrGCgUAIBqmLgAQpiYAEIZhABCGAQIfhqABJ-1iYGAE-x4P-xUS-x4j -xUo-x4R-xU2-x4G-xU_3x7-FUZ-Hv8VSj8e-xUM8Bj_Cg8e-gHwMP4FBT7wPD4FnhUO8EAOFZ51 8DZ1rvUE8Bz1BM71Qv4A9T7_BPU6-gv1MP4c9Rj_Ch3_Th3_Ti3_TE3_Sl0DAE9N-kw9AwAEES05 AAUDADAN-v8BACQ1DREABVkABTIAAmIAQW3_SH0DAPtYjf5Gnf5Grf5Ezf5C3f5C7f5A-QH_Pv0C -j79A-48-QX_Ov0H-jj9Cf42-Qv_NP0N-jL9D-4w-RH_Lv0T-iz9Ff4q-Rj_Jv0b-iT9Hv4g-SH_ Hv0k-hr9Kf4U-S7_EP00-gj9Pd79GXoBdC01LjE1ODMBAASEAVcxNC44MBcAAY4BGTOOAfMgQEAE 8P8HjxjwJt7wIf4D8B3_CPAY-gvwFP4Q8A-_FfAL-hnwBf4b8AT_HPAE-hsMACIbBwUAwAP_Gxfw Av4cBwbwAQYAMBcO4AcA8QIHLsD_HQYHBi7A-hwGBwY_sBUADw8ACKAGBwYHPqBOV-4SCgByLqBO Fnf_DhUA8wU_JzZH-gwGFwYHLrAuFxY3Nhf_CxoA8AE3Njc2-ggGBwYXLrA_Jkc2HAAQASoAID6w NgBBNjc27ioAQcAuJkcPADFXBi4uAELQPhc2EgAAiAARFyUAdNBORzZHRjcUAFA_0H5XRiIAEhci AEE_8AG_MwARFhEAQD7wAu4uAPAIJwYHBic_8AbuVzY3JhcGJy7wDO5HNjcUAEEQzmcmKgDwABW_ AFcGFwA_8BquADdO8NECUCLO8CdeqQUbhskBNjIuOEMDETkNAAFDAx00tQHRYl8Y8B-_APAV-gvw D60BURHwDv4RCAACEAAABAAQEhQAtBfwC-4TZ-AF-hN3BQBjEofwBP4TBQCQA-4Uh-AC-hSHxgEh FXfGASEUh8YBIhSHqQEiFHeqAQQQACIThxAAIhOXEAAhDtcQAFB_d473BZwBNE73GMoBI-cYxwEl 9xTEASb3DcMBNhb3DMEBJvcGvgE4FvcBvwEp9wK8ARrXtgECxgEPuAGQ9Cz-Fl8b8CXO8CD_AfAb -gbwFv4L8BD_EfAN-hTwBP4e8AH_IOD_IsD_JaD_JpD_J3D_KWD_KlAOCv4pQAUAsiowDgoOCv4j Hw8uCgDwKB99QC4K-hwtJx0nUB4K-hktJy03HUAeCv4VPSctNz1QPgr_EC03LTdNJ1AuCv4MPTct J10nHQ4NADAJLUcNADAtBy5DAEAFPTc9DgD0AF5gHgr_Ai1HLTddFz1_cAsAci2ukE4Kbj0MAHCe 4D4KTh1XVgByLW7wBT4KTkoAc37wCC4KThcMAFAMHgpOHQsA8QaO8A5_F10XPY7wEm5NFy0HjvAW Xh0IAPAEGl4HHRd_8B3_AfAgzvAknvAoLmMBL-_hZAEmshQfG-ArXvAnnvAkaQG1HP4F8Bj_CfAU -g1tAdIJ-hfwB-4a8AX_HPADeQHwA-AA-iHg-iOw-iQPD6D_JAcdgF0BgVcNcA4K-h1XDgBhGlct Fx1gdgFSFkctVw0LAFAURx1HbUEBUxI3LTcdhwF0ECctJw0HDYwBRQ8XHRePAUYKFy0HkgFHCS0X HZQBWAcdFy0nlgFHHQctF5gBRQ0nHReaASANJz0ABJwBVi0XLScNngE1Jx13ngE1Fy1XngEfTZ0B ZA-gDBoPOgD--------------------------------------------------6TwI2CABPD-JL8U 8ED_AvA8-gbwOT8V-gTwNz3_BfA1Tf4H8DNN-gjwMk3_CvAwTf4L8C9dBQD4AE3_DPAuXf4N8C1N -g3wLgUAMwzwLwUAMQsPDwcAEDwmAPAMCTzwLBxN-gg88C4cPf4HLA8X8DFN-gULHAsMCQDwrAQM CwwLDPAzTb4tLg8ZHAsc8DNdjj0uGjzwNV1ObR4qPPA2-QFKPPA3zXos8DkKTdoc8Dv6AwzwO5oM ivA7amx68DpafHrwOVofHAxpavA4WqlaOfA0WslKOfA0SgDpKknwMyogmS8MKQpZ8DQKSEn4BPAw _BHwLmgPCvgM8CtoFygn_AfwKfgDNzgXqPAoJ-gMB7jwIgf4EieI8CH4FgeI8CD4GAd48CD4GSc4 8B-4IfAf_CLwHfggBxgGAPEHFxjwHPghByjwG-gl8Bv4JvAaKAn4IgYA8CMOCfgE8BoYCfgOGSgJ _ADwGRgJ_A8ZGAmYB1jwGAgZ_BAJKAmIB1jwGQn4ERkoCRgJWBkAVAn4Eik4CwDxAhQZGBlYF1jw Fwn4FRkIGWgHCwCBGBno8BYJ_Bk_AGQUGfgVGRgLAFAUCTgZ6AkAEBpyAIITGfgaGYgXOAkARvgA 8BIIABEbiACBEQn4HAmoB0gJACQZmAkAsPgB8BAZ_BspqAc4CQCmHBm4BzjwDwn4HQkAocgHGPAQ CfgcKdhOACApyGUAMBOpuH0A8AEPSXgZqPAVGfgOGbgZiPAXQgDyAQggGPAaKfgbGQjwHyn4HAkH ABAZBgDwIxo5CPAeKfgYWQjwHxn4F2kt8B0Z_BRpCAlN8BwZ_BF5CG3wHgn4DpltFfAeGfgKmY0l CABQBqkYbVUJAPJCArkYfYXwHhnIyQiNxfAeCZi5zdXwHwlYqd31AvAfyQjN9QUABfAemRWN9Q3w Hmmt9Q8ABfAdDRm99RXwG931GPAZjQUN9RzwGS31JPAZPQUNAgCi9RzwGC31JvAXLRAAQwUN9R0S AAYkAKQe8BU99SjwFC0VEgCGIPAULfUq8BIkAIUi8BIt9SzwEBIAARQAhRAt9SwABfAPXgAAFgCJ BvAPPfUqABUXAANXAIsPLfUtAAXwDO0Dbi0yOS4xMC4xXDM4Ljc1LjEeMQIE8S3--8NvFPBF7vA8 PxV_KV7wOF2OKV7wNz0OSW4JbvA2PQ5Z7vA1PQ5pbgpu8DQdPmnu8DQNTln_APA0bkkGAPCEKW6v GfA03tzwNBmO-AHwNBlu-AMQHw-wMBle-AMQG-AxGV78AgsAK-Axbpzb8DFujNvwMl6Mmx8fK-Ay XnyLKDvwMl5sSwgbKCcb8DNeXCs4Sx8OBwvwNF4sHxcbKEsIGx8OK-AzHnsfFxgLFw8OKxhb8DMO mw8XCycfDjsYS-AzHksfF0sXHw5LCEvwMz4rLxe7FgDwJw1ODAsWuwhb8DM9HizrGCvwMz0OHAAs qyg78DMtDmyLKEsfHPA1jGsYWwYPDB8c8DSMAMsGLwoA8kkyrKsWBT8MFfAxDAB8qwYbRCXwMJzb RBUU8C_sy0QllPAnjEWLFSRFpPAkjAU0FXslBAX0AfAifBVEJXsl9AEV8CB8BYQFayX0AwXwIGwF lAVrGvQF8CBcCgDxQAbwH1wFlBVrCgX0BvAeTAAFlBVbBAoF9AfwHiwAJXQFBBU7FBr0B-AhFQQV ZAUUBUoFGqQFtPAfFQoUJUQFFBpUCqQFxPAcFQQKRCUkBaQOAPAGGhUECnQlBAW0CpQF1PAYFcQl xAqECgBQBQQKxBUMAPAK5PAWBQQK9BcF9ADwFQUK9BgV9ADwExX0GggAYBIF9BIKhAoA8wARBfQT CnQABfQC8A8F9BQLAHAOBfQVCmQQCwAQDQsAEXQLAEEMBfQWIQA0A-ALCwChBPAKBfQfABX0AwkA EBAdADIK9CAIAFDUFfQQEBsA8gEIBeQF9AgKZCAV9ALwCPQADQAyJfQBDQCFCQpEEBUUJeQOAEEA FUQFDgBgFfQICkQAOQEwCPQBGwAfNA0AAsEV9A0ABfQF8Aj0AwULADME8AkLAFAEKtTwCwwAgAwg GgQatPAMDADACAokEAo0GpTwDfQCYwCgJAAKVAqE8A70AlcAAA4AQBpk8A8OAJIKCiQVVApU8BAN AGE0BVQaNAUOAPEEBHX0AQo0BWQKJAXwEeRVZAX0AA8AgRQF8BL0DQXkDQBwBAXwEwX0DBwAsyQF ZAoF8BQF9AsVDgDB8BUF9Awa5AokBQpUDQBwCzr0AnrwF2kAYBRaROvwGAoAUmQaFOsWCwCAdAoE 2wYr8BmlAFSECusWGxcAQcsGKwYNAFEJCnQK2w0AEhclAAANADEL8BgMAIRrPwU7BhsGCykAMUtj Kw8AAVAARFQaK4MPAFHUtVrjGwwAYhoFZHXzDhgA8hIbZW3zDjsGCwMb8BqNAw3zEisGC0PwGS3z HAtj8Bk9Aw0CABDzRgVS8yXwGC0QAIADDfMe8Bct80YFAhAAARIAMBU980YFFBMSAABGBRDzRgUC EAAwAw3zRgUQ80YFJwMNFABlEC3zLvAPXACfAw3zJPAOPfMuJwUsD50JJw_FCv8gIP8UgjHxAXz0 P-4BBP8VPQT_AQT9PA4HACA7HgcAQS30Ny4IAE8E-jUECgD-NARSASH0OGIBMR30OnEBcg30Pf4B 9D8nOjH--_iUAR-9kwH-dh9cYgMUHzLlCQHyKxjfFvBALr8PHvA9Lu0e8Dse-QIO8Doe-QMe8DkO -QMfEQ4N8DUcHs18DhzwMRwAHA7dDA0cDVzwMUwKAPYGHA0MDgzwMQwgHv0BDD0MDvA2Dv0IBgBg LU4dTm0uDwCwBR4NDvA3Dl0O-QAIAEBNHv0BIQBRPR49Ds0qAFAeTQ69HgkAUA5dDp0KLQDwDR4d Pi0OjQou8DgOTU6dCg7wOR79BA7wOg5NPq0HAPGALQ5tDm0e8DsOHR5NHl0_8DoeHX5NHh0u8Dk_ QH5NHvBBHs0ODQLwPx7NDxhi8DourQtyG-A5Dp0bYjvwNAs_nQtiGw0r8DICCw0enRtSGz0CC-Aw Egu9G1IbXQIL8CwNCyIbrQtSG30CC-AqHQsyG30rQhudAgsC8CgdC1IrPRtiC70bAvAnHQtyW2Ib zQsLAPIA8gQb3RvwJi0L8gMb7QILCgCwC-0AG-AlPQvyAhsKAPAEAggX8BA9C-IBG-0CC-ACCDfw Dg4A8QAL-QML8AMIN-ANPQvyABsOAEAYR-ALDgBQC-0CDg0eAGAHGDfwCU0SADFNDr0TAEwYBxgn EwBIAFfwCBIAYQQIEFfwBxIAIz0eEgByIEcALxXwAxUAIg7NFQA5MEcpEwCwAvADCDA3KQ3wAl2U ABUtFQBwQAdJHfABXbcAUB0ezQ4bPABBWa0gvREAIQ7dEQBJIEn9DBAAMCn9DhAA0hIO3Q4CC-AK -Q8L8gQMADoL-Q4MABEFCwBUDI0g-QENAPQKDW3wBBvyBc0eAgvwDk3wBQvyBs0OEgvwKAkAcB0O vfIFvR4KAPACXhBObcLNDiIL8CANAG69ct0LAPEMGmVuC-0RHiIL8BYVDkVOEC4L-REOMgvwFYWe CgDwTfAWhU4AHhAL-RAeIvAXhW4tC-0PHjIAC-AV1R0Q-REOQivwFLVgC-0NLjJL8BNlECVgCxL9 CS6SK-AShYALMv0EPtIb8BFVsAti3T7yAhvwEFWwC-ICPvIGK-AOCwAiHRsIAEAeG-ANCAAgHwsI AKbyIBvwDFWg8iILBwBgsPIhG-ALJABnIBvwC2WgCAA-C-AMCAABAEwAAAgAYR4L8A9VoGwAAQgA EByHAPAAoAvyGxvwEUXAC-IZG-ASCABAGBvwEwgAQBcb8BQIAEEVK-AVCAA-C-AXCAANITXQCABv FkXgC-IUCAAUIxMbCABPCxLwFQkADR8bRAAAYvAA8hTwFwcAHwPyBBUPyAgnDzoA------------ ---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-12 14:41:51",revision=951]] -- 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") 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_conversation.lua --[[pod_format="raw",created="2024-04-05 14:55:45",modified="2024-04-12 14:41:51",revision=118]] 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-12 14:41:51",revision=273]] 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-12 14:41:51",revision=158]] -- 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 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) -- countertop! draw_counter(14) local book1 = {left=10, right=74, top=100, bottom=164} if collides(fairy, book1) then spr(18, book1.left, book1.top) else spr(17, book1.left, book1.top) end local book2 = {left=80, right=144, top=100, bottom=164} if collides(fairy, book2) then spr(20, book2.left, book2.top) else spr(19, book2.left, book2.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 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 :: screen_drawer.lua --[[pod_format="raw",created="2024-04-05 00:59:25",modified="2024-04-05 00:59:26",revision=1]] :: script.lua --[[pod_format="raw",created="2024-04-04 01:01:55",modified="2024-04-12 14:41:51",revision=538]] 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, false), Script.once(function() -- person appearss alchemist_sprite = 193 customer_visible = true customer_sprite = 201 return false end, false), Script.len(function(s) -- door closes door_open = easeTrig(1-(s.timer/s.length)) end, 0.5, false), 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 :: 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 :: [eoc]