pico-8 cartridge // http://www.pico-8.com
version 41
__lua__

-- vacation (18+)
-- kistaro windrider

--------------------------------
-- copyright (C) 2024 kistaro windrider
--
-- this program is free software; you can redistribute it and/or modify
-- it under the terms of the gnu general public license as published by
-- the free software foundation; either version 2 of the license, or
-- (at your option) any later version.
--
-- this program is distributed in the hope that it will be useful,
-- but without any warranty; without even the implied warranty of
-- merchantability or fitness for a particular purpose.  see the
-- gnu general public license for more details.
--------------------------------

-- monogram font by vincius menezio
-- https://datagoblin.itch.io/monogram

-- tab 0: library
-- tab 1: p8 entry points
-- tabs 2...F: actual code

function csv(s)
 local ret = split(s, "\n")
 for i, v in ipairs(ret) do
  ret[i] = type(v) == "string" and split(v) or { v }
 end
 return ret
end
function hz(n)
 return t()*2*n&1~=0
end

function mknew(tt, more)
 local mt, oldnew={__index=tt},tt.new
 tt.new=function(ret)
  if (not ret) ret = {}
  if (more) more(ret)
  if (oldnew) oldnew(ret)
  setmetatable(ret, mt)
  return ret
 end
end

event_list = {}
mknew(event_list, function(x)
  x.next=nil
  x.tail=x
 end)

function event_list:pb(x)
 self.tail.next = x
 self.tail = x
end

function event_list:u()
 local p, n = self, self.next
 while n do
  if n:u() then
   p.next = n.next
  else
   p = n
  end
  n = n.next
 end
 self.tail = p
 return p
end

function event_list:draw()
 local n = self.next
 while n do
  n:draw()
  n = n.next
 end
end

function outer_or_each_opt(t, mname)
 local fun = t[mname]
 if fun then
  fun(t)
  return
 end
 foreach(t, function(o)
  local f = o[mname]
  if(f) f(o)
 end)
end

function nop() end

blank = {}
function blank:u() end
function blank:draw() end

view = {
 x=0,
 y=0,
 w=255,
 h=128,
}
mknew(view, function(x)
 if (not x.views) x.views = {}
end)

function view.of(subviews)
 return view.new{views=subviews}
end

function view:u()
 outer_or_each_opt(self.views, "u")
end

function view:draw()
 local oldcam = $0x5f28
 poke2(0x5f28, %0x5f28-self.x)
 poke2(0x5f2a, %0x5f2a-self.y)
 outer_or_each_opt(self.views, "draw")
 poke4(0x5f28, oldcam)
end

bg = {
 x=0,y=0,w=128,h=128,fp=0,c=0
}
mknew(bg)

function bg:draw()
 local oldfp=fillp(self.fp)
 rectfill(self.x,self.y,self.x+self.w,self.y+self.h,self.c)
 fillp(oldfp)
end

-->8
-- setup and p8 entry points

function _init()
 -- disable btnp repeat
 poke(0x5f5c, 255)

 -- complex fill API mode
 poke(0x5f34, 1)

 awakener_hold_frames = 0
 -- debug mode: skip splahes
 mainview = ao_splash.new()
 menuitem(1, "⬅️ music: on ➡️", cycle_music)
 musicmode = 0
 --arm_awakener()
 --mainview=newtitle()
end

mlbl={"on", "text", "game", "off"}

function cycle_music(b)
 if b & 1 ~= 0 then
  -- left
  musicmode -= 1
  if (musicmode < 0) musicmode = 3
 elseif b & 34 ~= 0 then
  musicmode += 1
  if (musicmode > 3) musicmode = 0
 end
 if (mainview and mainview.onmchg) mainview:onmchg(musicmode)
 menuitem(nil, "⬅️ music: "..mlbl[musicmode+1].." ➡️")
 return true -- stay open
end

function arm_awakener()
 awakener_hold_frames=0
 awakener_armed=true
 menuitem(2, "awaken now", awaken_now)
end

function awaken_now()
 exit_dither = ditherer.new{di=1}
 awakener_armed = false
 menuitem(2)
end

function _update60()
 if awakener_armed then
  if btn() & 0x30 > 0 then
   awakener_hold_frames += 1
  else
   awakener_hold_frames = 0
  end
  if awakener_hold_frames == 90 then
   awaken_now()
  end
 end
 if exit_dither and exit_dither:u() then
  exit_dither = nil
  awakener_hold_frames=0
  awakener_armed=false
  mainview = fast_awakener()
  mainview:av()
 end
 mainview:u()
end

function _draw()
 mainview:draw()
 if awakener_hold_frames >= 30 then
  local x=(awakener_hold_frames-30) * 2 + 7
  rectfill(0,0,x,9,4)
  rectfill(x+1,0,128,9,5)
  font_default()
  print("keep holding for awakener", 1, 1, 7)
 end
 if exit_dither then
  exit_dither:draw()
 end
end

function font_monogram()
 memcpy(0x5600, 0x2000, 0x800)
 poke(0x5f58, 0x81)
end

function font_compact()
 memcpy(0x5600, 0x2800, 0x800)
 poke(0x5f58, 0x81)
end

function font_default()
 poke(0x5f58, 0x80)
end

function font_special()
 poke(0x5f58, 0x81)
end

-- play sound n on channel 3 iff
-- 3 doesn't already have a
-- greater index sound going.
function psound(n, off, len)
 if (stat(49) > n) return
 sfx(n, 3, off, len)
end

-->8
-- text rendering

txtbox = {
 x=0,
 y=0,
 f=0,
 interval=4,
 mode=0x81,
}
mknew(txtbox, function(self)
 if (not self.cols) self.cols = {14,10,9,8}
 self.c = deli(self.cols)
end)

function txtbox:u()
 if #self.cols > 0 then
  self.f += 1
  if self.f >= self.interval then
   self.f = 0
   self.col = deli(self.cols)
  end
 end
end

function txtbox:draw()
 poke(0x5f58, self.mode)
 print(self.text, self.x, self.y, self.col)
end

function txtbox:xm()
 return print(self.text, self.x, -9999)
end

spring = {
 from = 128,
 to = 0,
 frames=120,
 f = 0,
}
mknew(spring)

function easeoutovershoot(t)
 t-=1
 return 1+2.7*t*t*t+1.7*t*t
end


function spring:u()
 local v = self.v
 self.v:u()
 if self.f >= self.frames then
  v.y=self.to
  return true
 end
 local t, range = self.f/self.frames, self.to - self.from
 v.y = self.from + easeoutovershoot(t)*range
 self.f += 1
end

function spring:draw()
 self.v:draw()
end

scoot = {
 from=0,
 to=-128,
 frames=60,
 f=0,
}
mknew(scoot)

function scoot:u()
 local v = self.v
 self.v:u()
 if self.f >= self.frames then
  v.y=self.to
  return true
 end
 self.f += 1
 if self.f <= 0 then
  v.y=self.from
  return
 end
 local t, range = self.f/self.frames, self.to - self.from
 v.y = self.from + range * t * t * t
end

function scoot:draw()
 self.v:draw()
end

scootbox = {}
mknew(scootbox, function(x)
 x.v = view.new{
  x=x.x,
  y=x.from or scoot.from,
 }
 x.s = scoot.new{
  from=x.from or scoot.from,
  to=x.to or scoot.to,
  frames=x.frames or scoot.frames,
  v=x.v
 }
end)

function scootbox:u()
 if self.go then
  self.s:u()
 else
  self.v:u()
 end
end

function scootbox:push(drawable)
 add(self.v.views, drawable)
end

function scootbox:done()
 return self.s.f >= self.s.frames
end

function scootbox:draw()
 return self.v:draw()
end

nrm_txt_pal = split"14,10,9,8"
sfd_txt_pal = split"15,10,9,8"
hlt_txt_pal = split"13,11,9,8"
shd_txt_pal = split"12,12,1,0"

function cparr(t)
 local ret = {}
 for i,x in ipairs(t) do
  ret[i]=x
 end
 return ret
end

function zonk_txt(s, x, y, p, md, amt, frms)
 md = md or 0x80
 frms = frms or 0
 amt = amt or 0
 local itv = (frms>>2)&0x7fff
 local t1 = txtbox.new{
  x=x+1,
  y=y+1,
  interval=itv,
  cols=cparr(shd_txt_pal),
  mode=md,
  text=s,
 }
 local v = view.of{
  t1,
  txtbox.new{
   x=x,
   y=y,
   interval=itv,
   cols=cparr(p),
   mode=md,
   text=s,
  },
 }
 itv=spring.new{
  from=amt,
  frames=frms,
  v=v,
 }
 itv.eff_w=t1:xm()-1
 return itv
end

-->8
-- zonk renderer

fadetable = split"0,1.5,1025.5,1029.5,1285.5,1413.5,9605.5,9637.5,-23130.5,-23066.5,-18970.5,-18954.5,-2570.5,-2568.5,-520.5,-8.5,-0.5"

ditherer = {
 i = 0,
 di = 0.25,
 c=0,
 pattern=fadetable,
}
mknew(ditherer, function(x)
 if (x.di and x.di < 0 and not x.i) x.i = (x.pattern and #x.pattern or #fadetable) + 0.99
end)

function ditherer:u()
 if (self.i > 0 and self.di < 0) self.i += self.di
 if (self.i < #self.pattern + 1 and self.di > 0) self.i += self.di
 return self.i < 0 or self.i >= #self.pattern + 1
end

function ditherer:draw()
 local idx = min(flr(self.i), #self.pattern)
 if (idx < 1) idx = 1
 fillp(self.pattern[#self.pattern+1-idx])
 rectfill(0,0,128,128,self.c)
end

fuzzy = {
 p = 0xffff,
 n = 0,
 interval = 3,
 tries = 4,
}
mknew(fuzzy, function(x)
 local p = (x.p or fuzzy.p) & 0xffff
 x.mass = 0
 while p ~= 0 do
  x.mass += p & 1
  p = (p >>> 1) & 0xffff
 end
end)

function fuzzy:u()
 self.n += 1
 if (self.n < self.interval) return
 self.n = 0
 for i=1,self.tries do
  local b = 1 << rnd(16)
  if b & self.p == 0 then
   if i == self.tries or not self.weight or self.mass <= self.weight then
    self.mass += 1
    self.p |= b
    return
   end
  else
   if i == self.tries or not self.weight or self.mass >= self.weight then
    self.mass -= 1
    self.p &= ~b
    return
   end
  end
 end
end

function fuzzy:set_fillp()
 fillp(self.p + 0x0.8)
end

fuzzy_stripey = {
 y = 0,
 dy = -0.25,
 thicc = 3,
 weight = 12,
 spacing = 7,
 gap = 32,
 colors = split"3,5,2,1",
}
mknew(fuzzy_stripey, function(f)
 f.fuzz = fuzzy.new{
  weight = f.weight or fuzzy_stripey.weight,
  interval = f.interval,
  tries = f.tries,
 }
end)

function fuzzy_stripey:u()
 local fz = self.fuzz
 fz.weight = self.weight
 fz.interval = self.interval
 fz.tries = self.tries
 self.y += self.dy
 if (self.y <= -self.gap) self.y += self.gap
 if (self.y >= 0) self.y -= self.gap
 fz:u()
end

function fuzzy_stripey:draw()
 self.fuzz:set_fillp()
 for y=self.y,128,self.gap do
  for i,c in ipairs(self.colors) do
   local y0 = y + (i-1) * self.spacing
   rectfill(0, y0, 128, y0 + self.thicc, c)
  end
 end
end


--@musurca & @felice
function cminskycirc(r,c)
 local j,k,rat=r,0,1/r
 poke(0x5f25,c) --set color
 for i=1,r*0.785 do
  k-=rat*j
  j+=rat*k
  pset(63.5+j,63.5+k)
  pset(63.5+j,63.5-k)
  pset(63.5-j,63.5+k)
  pset(63.5-j,63.5-k)
  pset(63.5+k,63.5+j)
  pset(63.5+k,63.5-j)
  pset(63.5-k,63.5+j)
  pset(63.5-k,63.5-j)
 end
 pset(63.5,63.5-r)
 pset(63.5,63.5+r)
 pset(63.5-r,63.5)
 pset(63.5+r,63.5)
end

brth_fg={2,12,4,12,2}
brth_bg={1,2,3,2,1}
breather = {
 colors = brth_fg,
 sep = 8,
 speed=300,
 f=0,
}
mknew(breather)

function breather:u()
 if (self.freeze) return
 local f = self.f + 1
 if (not self.on) f = 0
 if (f >= self.speed) f = -self.speed
 if f == 0 or f == -self.speed then
  if self.nextspeed then
   self.speed = self.nextspeed
   self.nextspeed = nil
  end
  if self.off_soon then
   self.on = false
   f=0
  end
  if self.nextcolors then
   self.colors=self.nextcolors
  end
 end
 self.f = f
end

function easeinout(t)
 if(t<.5) then
     return t*t*t*4
 else
     t-=1
     return 1-t*t*t*-4
 end
end

function breather:draw()
 fillp(0)
 local cols, f, spd, sep = self.colors, self.f, self.speed, self.sep
 local stall = sep * (#cols - 1)
 local cap = spd-stall
 if (f >= 0) f += stall
 for i,c in ipairs(cols) do
  local ef = f - (i-1)*sep
  ef = abs(ef) - stall
  if ef <= cap and ef > 0 then
   cminskycirc(easeinout(ef/cap)<<6,c)
  end
 end
end

function breather:mt(x, awm)
 if (not self.on) return true
 if (x==1) return not awm and self:starting_reverse()
 if (x==2) return not awm and self:starting_forward()
 if (x==3) return not awm and self:starting_reverse() or self:starting_forward()
 return true
end

function breather:starting_reverse()
 return self.f + self.speed < self.sep * #self.colors
end

function breather:starting_forward()
 return self.f > 0 and self.f < self.sep * #self.colors
end

def_z_pal = {
 [0]=0,129,1,130,4,5,6,7,1,5,6,140,2,12,7,7
}
def_14_fade = split"0,0,0,0,0,128,129,133,141,13,6,15,7"
def_15_fade = split"0,0,128,129,133,141,13,13,6,6,15,15,7"
def_13_fade = split"0,0,0,0,0,128,129,133,141,140,140,13,12"
def_shd_fade = split"0,0,0,128,130,141,2"

-- frames between words during
-- zonk txt
fbw=8

zonk_mode = {
 files={},
 lnh = 9,
 --space width
 spc_full=6,
 --text mode
 txmd=0x81,
 --text spring-in distance
 txd=12,
 --text spring-in frames
 txf=30,
 --exit frames
 exf=45,
 --exit magnitude
 exmg=16,
 --character wait multiplier
 cmul=1,
 p=def_z_pal,
 fd13 = def_13_fade,
 fd14 = def_14_fade,
 fd15 = def_15_fade,
 fd12 = def_shd_fade,

 twt=60,
 expect_cfg_line=true,
 txt_frame=blank,
 px=true,
 cx=0,
 cy=0,
}
mknew(zonk_mode, function(self)
 self.stripes=fuzzy_stripey.new{
  colors=self.bg_stripe_cols,
  dy=self.bg_dy,
  weight=self.bg_wgt,
 }
 self.brth=breather.new{
  colors=self.br_cols,
  sep=self.br_sep,
  speed=self.br_spd,
 }
 self.awm=true
end)

function zonk_mode:set(_, field,value)
 self[field]=value
end
function zonk_mode:unset(_, ...)
 for n in all(pack(...)) do
  self[n] = nil
 end
end
function zonk_mode:at(_, x, y)
 self.txt_frame=scootbox.new{
  x=x,
  from=y,
  to=y-self.exmg,
  frames=self.exf,
 }
end
function zonk_mode:c(_, rows, cols)
 local w,h = cols*self.spc_full, rows*self.lnh-1
 self:at(_, 64-ceil(w/2), 64-ceil(h/2)) 
end
function zonk_mode:bon()
 self.brth.on=true
 self.brth.off_soon=false
end
function zonk_mode:boff()
 self.brth.off_soon=true
end
function zonk_mode:bspd(_, spd)
 self.brth.nextspeed=spd
end
function zonk_mode:bpal(_, p)
 self.brth.nextcolors= p == 1 and brth_fg or brth_bg
end
function zonk_mode:bgdy(_, dy)
 self.stripes.dy = dy
end
function zonk_mode:bgwt(_, w)
 self.stripes.weight = w
end

function zonk_mode:av()
 clear_alt_pal_bits()
 pal()
 font_monogram()
 if (type(self.file) == "string") self.file = split(self.file, "\n")
 if (not self.file) self:nf()
 assert(self.file)
 if self.bgm and not self.xbgm and (musicmode & 2 == 0) then
  music(self.bgm,1000,7)
 else
  music(-1, 250)
 end
end

function zonk_mode:onmchg()
 if (musicmode & 2 == 0 and not self.xbgm) then
  music(self.nextbgm or self.bgm or -1, 500, 7)
  if self.nextbgm then
   self.bgm = self.nextbgm
   self.nextbgm = nil
  end
 else
  music(-1, 250)
 end
end

function zonk_mode:nf()
 if #self.files > 0 then
  self.file=split(deli(self.files,1), "\n")
  self.expect_cfg_line=true
  self.cy=0
  self.cx=0
 end
end

-- return char count, item
-- or 0, nil: end of page
-- or nil (, nil): end of file
function zonk_mode:ni()
 if not self.line then
  if (not self.file or #self.file == 0) return
  local line = deli(self.file, 1)
  if self.expect_cfg_line then
   self.expect_cfg_line = false
   for i,cmd in ipairs(split(line, " ")) do
    if #cmd > 0 then
     local frags = split(cmd,":")
     assert(type(self[frags[1]])=="function", tostr(i).." - "..line)
     self[frags[1]](self, unpack(frags))
    end
   end
   return self:ni()
  end
  if line == "-----" then
   self.line = nil
   self.expect_cfg_line = true
   self.cx = 0
   self.cy = 0
   return nil
  end
  self.line = split(line, " ")
 end
 if #self.line==0 then
  self.line = nil
  self.cx = 0
  self.cy += self.lnh
  return self:ni()
 end
 -- parse token
 local token = tostr(deli(self.line, 1))
 while #token == 0 and #self.line > 0 do
  -- emit extra spaces
  self.cx += self.spc_full
  token = tostr(deli(self.line, 1))
 end
 local pp=nrm_txt_pal,1
 if (token[1]==">") token,self.cx=sub(token,2),self.cx+self.spc_full\2
 if (token[1]=="!") token,pp=sub(token,2),sfd_txt_pal
 if (token[1]=="#") token,pp=sub(token,2),hlt_txt_pal
 local ret = zonk_txt(token,self.cx,self.cy,pp,self.txmd,self.txd,self.txf)
 self.cx = ret.eff_w+self.spc_full
 return ret, fbw * twmul(token)
end

punct = {["."]=2, ["?"]=2, ["!"]=2}
-- token wait multiplier
function twmul(s)
 if (punct[s[#s]]) return 2
 return 1
end

function zonk_mode:ey()
 if (self.file and #self.file > 0) return false
 return #self.files == 0
end

function fadetbl(col, tbl, frac)
 pal(col,tbl[1+(frac*#tbl)&0x7fff],1)
end

function zonk_mode:u()
 if (not self.brth:mt(self.bwt)) self.awm=false
 if self.px then
  self.twt -= 1
  if self.twt <= 0 then
   local item, wt = self:ni()
   self.cf = self.cf or self.ac
   if not item then
    if (not self.file or #self.file == 0) self:nf()
    self.px=false
    if (not self.cf) psound(9,8,2)
   else
    self.txt_frame:push(item)
    self.twt = wt*self.cmul
    psound(self.txf > 1 and 8 or 9,0,4)
   end
  end
 else
  --waiting to advance or exit
  if self.twt <= 0 then
   local justc=false
   if (btnp(1) and not self.cf) self.cf,justc=true,true
   if not self.nxp and self.cf and self.brth:mt(self.bwt, self.awm) then
    self.nxp = true
    self.txt_frame.go = true
    self.fpfrm = self.exf
    self.awm=true
    if (not self.ac) psound(8, justc and 16 or 18, 8)
   end
   if (justc and not self.nxp) psound(8,16,2)
  else
   self.twt -= 1
  end
 end
 if self.nxp and not self.d then
  if self.fpfrm > 0 then
   self.fpfrm -= 1
  elseif self:ey() then
   self.txt_frame=blank
   self.d = ditherer.new{di=0.5}
   music(-1,533)
  else
   self.nxp = false
   self.cf=false
   self.txt_frame=blank
   self.px=true
   self.fpfrm=nil
  end
 end
 self.stripes:u()
 self.brth:u()
 self.txt_frame:u()
 if (self.d and self.d:u()) seq:next()
 if not self.d and self.nextbgm and not self.xbgm and (musicmode & 2 == 0) and stat(56) <= 0 then
  music(self.nextbgm, 500, 7)
  self.bgm = self.nextbgm
  self.nextbgm = nil
 end
end

function zonk_mode:draw()
 cls(0)
 pal(self.p, 1)
 if (not self.hide_stripes) self.stripes:draw()
 if (not self.hide_breath) self.brth:draw()
 if self.fpfrm then
  local ffrac = self.fpfrm/self.exf
  fadetbl(12, self.fd12, ffrac)
  fadetbl(13, self.fd13, ffrac)
  fadetbl(14, self.fd14, ffrac)
  fadetbl(15, self.fd15, ffrac)
 end
  self.txt_frame:draw()
 if not self.px and not self.cf and self.twt <= 0 then
  font_default()
  print("➡️",121,121,12)
  print("➡️",120,120,self.brth:mt(self.bwt) and (hz(2) and 7 or 6) or 5)
 end
 if(self.d) self.d:draw()
 -- debug info
 -- print(tostr(stat(56)), 0, 0, 7)

end

-->8
-- awakener

count_up=[[bgdy:-0.1 bspd:240 c:3:18
>wITH EVERY COUNT
 FROM 1 !UP TO 5,
YOU !WAKE FURTHER.
-----
bgdy:0 c:5:19
 >aS YOU !WAKE !UP,
THE WORLD RETURNS.
 YOUR MIND IS !NOW
 RETURNING TO ITS
   USUAL FLOW.
-----
bpal:1 set:bwt:2 c:3:19
 !aWAKEN, BECOMING
FULLY ALERT AT THE
  >COUNT OF #5...
-----
set:exf:10 set:txf:30 set:txd:45 set:ac:1 bgwt:9 bgdy:0.1 bspd:225 c:1:1
1
-----
bspd:210 bgdy:0.15 bgwt:10 c:1:1
2
-----
bspd:180 bgdy:0.2 bgwt:11 c:1:1 set:nextbgm:21
3
-----
bgdy:0.25 bpal:2 bgwt:12 c:1:1
4
-----
bgdy:0.3 bgwt:13 c:1:1 set:nextbgm:-1
5
-----
set:hide_breath:1 unset:ac set:hide_stripes:1 boff set:txf:1 set:txd:0 set:exd:0 set:exf:1 set:bwt:0 c:1:11
wide awake!
]]

function normal_awakener()
 return zonk_mode.new{
  bgm=19,
  br_cols=brth_bg,
  file=[[c:2:15 bon
yOU'VE LEARNED
   SO WELL!
-----
c:2:14
yOU'RE SUCH A
  !GOOD !TOY!
-----
c:4:19
 tAKE A MOMENT TO
  JUST !ENJOY HOW
 >IT FEELS, TO BE
#VINYL FULL OF #AIR.
-----
c:6:20 bgdy:-0.2
bECAUSE, NEXT, IT'S
 >TIME TO #WAKE #UP.
yOU GET TO TAKE YOUR
!HAPPY MOOD AND !VIVID
  !MEMORIES OF THIS
EXPERIENCE WITH YOU,
-----
c:4:20 bgdy:-0.15 bspd:270 set:nextbgm:20
BUT !ALL THE HYPNOTIC
  !SUGGESTIONS FROM
>THIS GAME WILL !FADE
  >AS YOU #WAKE #UP.
]],
  files={count_up},
 }
end

function fast_awakener()
 seq=sequencer.new{
  {f=awakener_lock},
 }
 return zonk_mode.new{
  xbgm=true,
  br_speed=240,
  br_cols=brth_bg,
  bg_dy=-0.12,
  p={
   [0]=0,128,133,5,4,5,6,7,133,134,6,140,132,12,7,7
  },
  file=[[c:3:13
tHIS HYPNOTIC
EXPERIENCE IS
 ENDING !NOW.
-----
c:6:20
>aS YOU #WAKE #UP, ALL
HYPNOTIC SUGGESTIONS
FROM THIS GAME FADE.
  yOUR MIND IS !NOW
  !RETURNING !TO ITS
 !ORDINARY PATTERNS.
-----
c:4:19 bon
 tHE !STORY TOLD BY
 >THIS SOFTWARE !IS
 ENDED, WORDS ON A
SCREEN IN THE !PAST.
-----
c:5:17
>sUGGESTIONS FROM
>THIS FICTION ARE
LEFT BEHIND. tHEY
DO NOT AFFECT YOU
 OR YOUR MEMORY.]],
  files={count_up},
 }
end

function awakener_lock()
 arm_awakener()
 return {
  u=nop,
  av=nop,
  draw=function()
   pal()
   font_default()
   cls(0)
   print("session ended",38,7,5)
   print("to replay awakener,",26,58,6)
   print("hold ❎",45,65,6)
   print("kistaro.itch.io",34,116,5)
  end,
 }
end

-->8
-- consent screens

ao_splash = {f=180}
mknew(ao_splash, function(self)
 self.c = breather.new{
  colors={7,15,14,8,2},
  sep=3,
  f=15,
  on=true,
  speed=240,
 }
 font_monogram()
end)

function ao_splash:u()
 if self.c.f < 96 then
  self.c:u()
  return
 end
 self.f -= 1
 if (self.f < 150 and (btnp() & 0xf > 0) and self.f > 0) self.f = 0
 if self.f == 0 then
  self.d = ditherer.new{di=1}
 end
 if (self.d) self.d:u()
 if self.f <= -32 then
  mainview=consent_splash.new()
  mainview:u()
 end
end

function ao_splash:draw()
 cls()
 self.c:draw()
 if self.f < 180 then
  font_special()
  print("18+", 55, 60, 7)
  print("aDULTS ONLY", 30, 90, 15)
  if (self.f == 179) psound(10)
 end
 if (self.d) self.d:draw()
end

consent_splash = {
 f = 0,
}
mknew(consent_splash, function(self)
 self.d = ditherer.new{
  di=-1
 }
 arm_awakener()
end)

function consent_splash:u()
 local f = self.f + 1
 if (f < 0) f = 0x7fff
 if btnp(1) and f > 150 and self.d.di~=1 then
  self.d.di = 1
  psound(9,16,8)
 end
 if self.d:u() and self.d.di > 0 then
  mainview=newtitle()
  mainview:u()
 end
 if (f == 1 or f==45 or f==90) psound(9,0,4)
 self.f=f
end

function consent_splash:draw()
 self:dtx()
 self.d:draw()
end

function consent_splash:dtx()
 cls()
 font_special()
 print("\^w\^twarning", 20, 2, 10)
 font_default()
 cursor(1, 25)
 color(6)
 print("this game will \fchypnotize\f6 you.")
 if (self.f < 45) return
 print("")
 print("hypnotic suggstions include:")
 print("relaxation, trance, obedience,")
 print("loss of volition, euphoria, joy,")
 print("amnesia, identity loss, and the")
 print("experience of \fetranformation")
 print("into a pooltoy orca.\f6")
 if (self.f < 90) return
 print("")
 print("an awakener that removes all")
 print("suggestions is available at")
 print("\faany time:\f6 hold ❎ or 🅾️.")
 print("gameplay uses only arrows.")
 if(self.f < 300) return
 print("")
 print("")
 print("           consent and begin: \f7➡️")
end

-->8
-- title screen

function newtitle()
 local ret = title_screen.new()
 ret:av()
 return ret
end

title_screen = {}
mknew(title_screen, function(self)
 self.a = arcade_level.new{
  noscore=true,
  max_score=9999,
  wordwait=9999,
  extra_layer={
   draw=function()
    self:m()
   end,
   u=nop,
  },
  phin_x = 62,
 }
end)

function title_screen:av()
 self.a:av()
 font_monogram()
end
function title_screen:onmchg()
 self.a:onmchg()
end

function title_screen:draw()
 self.a:draw()
end

function title_screen:m()
 font_special()
 print("\^w\^tvACATION", 18, 24, 0)
 print("\^w\^tvACATION", 17, 23, 3)
 print("\^w\^tvACATION", 16, 22, 15)
 if hz(8) or self.a.wordremain ~= 0 then
  print("pRESS", 43, 96, 3)
  print("pRESS", 42, 95, 15)
  font_default()
  print("➡️", 79, 98, 3)
  print("➡️", 78, 97, 15)
 end
end

function title_screen:u()
 self.a.wordtimer=9999
 if btnp(1) and self.a.wordremain ~= 0 and not self.pp then
  self.a.wordremain=0
  self.pp=true
  create_game()
  psound(11,0,16)
 end
 self.a:u()
end
-->8
-->8
-- dolphin sprite renderer

phinstate_nrm = {
 --s={4, 36, 4, 9},
 s={4},
 ws=3,
 hs=2,
 idle=true,
 xo=-12,
 yo=-8,
}
phinstate_jump_full = {
 s={64},
 ws=2,
 hs=3,
 xo=-4,
 yo=-8, 
}
phinstate_jump_wane = {
 s={69},
 ws=3,
 hs=3,
 xo=-12,
 yo=-8,
}
phinstate_crest = {
 s={72},
 ws=3,
 hs=2,
 xo=-12,
 yo=-8,
}
phinstate_fall_wax = {
 s={128},
 ws=3,
 hs=3,
 xo=-12,
 yo=-16,
}
phinstate_fall_full = {
 s={77},
 ws=2,
 hs=3,
 xo=-4,
 yo=-16,
}
phinstate_dive_full = phinstate_fall_full
phinstate_dive_wane = {
 s={128},
 ws=3,
 hs=3,
 xo=-12,
 yo=-16,
}
phinstate_return = {
 s={104},
 ws=3,
 hs=2,
 xo=-12,
 yo=-8,
}
phinstate_rise_wax = phinstate_jump_wane
phinstate_rise_full = phinstate_jump_full

phinstate_error = {
 s={0},
 ws=1,
 hs=2,
}

-- coordinates are the notional
-- center point of the dolphin.
-- many states are off-center.
toyphin = {
 x=-12,
 xtarget=16,
 y=64,
 dy=0,
 state=phinstate_nrm
}
mknew(toyphin)

function toyphin:u()
 local x, y, dy, splash = self.x, self.y, self.dy, self.splasher
 -- entry mode?
 if not self.entered then
  x += 1
  self.entered = x >= self.xtarget
 elseif self.exiting then
  if x + self.state.xo > 128 then
   self.exited = true
  else
   x += 1
  end
 end

 -- button handling
 if self.entered and not self.exiting then
  if y >= 61 and y <= 67 then
   if (btn(2)) and dy < 1 then
    splash:surfacing_splash(x, 3.8, true)
    dy=-3.8
   elseif (btn(3)) and dy > -1 then
    splash:landing_splash(x, 3.8, true)
    dy=3.8
   end
  else
   dy += (btn(3) and 0.125 or 0) - (btn(2) and 0.125 or 0)
  end
 end

 if (y > 64) dy -= 0.3
 if (y < 64) dy += 0.3

 local new_y = y + dy
 if new_y  <= 64 and y > 64 then
  -- surfacing
  splash:surfacing_splash(x, -dy, btn(2) and (dy > -3.8))
  if btn(2) then
   -- maybe boost
   if dy > -3.8 then
    new_y = 64 + ((dy + y - 64)/dy * -3.8) 
    dy = -3.8
   else
    dy = (dy - 7.6) / 2.5
   end
  else
   -- brake
   if dy > -0.75 then
    --stabilize
    new_y = 64
    dy = 0
   else
    dy /= 1.75
   end
  end
 elseif new_y >= 64 and y < 64 then
  -- landing
  splash:landing_splash(x, dy, btn(3) and (dy < 3.8))
  if btn(3) then
   -- maybe boost
   if dy < 3.8 then
    new_y = 64 - ((dy - y + 64)/dy * 3.8)
    dy = 3.8
   else
    dy = (7.6 + dy) / 2.5
   end
  else
   --brake
   if dy < 0.75 then
    --stabilize
    new_y = 64
    dy = 0
   else
    dy /= 1.75
   end
  end
 end
 y=new_y

 local wet, st = y > 64, phinstate_error
 if dy < -2.15 then
  st = wet and phinstate_rise_full or phinstate_jump_full
 elseif dy <= -1.45 then
  st = wet and phinstate_rise_wax or phinstate_jump_wane
 elseif dy < 1.45 then
  if y > 50 and y < 78 then
   st = phinstate_nrm
  else
   st = wet and phinstate_return or phinstate_crest
  end
 elseif dy <= 2.15 then
  st = wet and phinstate_dive_wane or phinstate_fall_wax
 else
  st = wet and phinstate_dive_full or phinstate_fall_full
 end

 if (y > 50 and y < 78 and dy > -1.35 and dy <1.35 ) st = phinstate_nrm
 -- test mode
 --if (btn(5)) self.exiting = true
 self.x, self.y, self.dy, self.state = x, y, dy, st
end

-- hitbox for current state
function toyphin:box()
 local st = self.state
 return {self.x + st.xo, self.y + st.yo, st.ws * 8, st.hs * 8}
end

function toyphin:draw()
 local st, y = self.state, self.y
 if (st.idle) y += wave()
 spr(st.s[1+(((t()<<1)&0x0.ffff*#st.s)&0x7fff)], self.x + st.xo, y + st.yo, self.state.ws, self.state.hs)
end

-->8
-- arcade mode

wordtarget = {
 x = 129,
 y = 60,
 on_hit = nop,
}
mknew(wordtarget, function(x)
 poke(0x5f58, 0x81)
 x.w = print(x.str, 0, -9999)-1
end)

function collides(b1, b2)
 if (b1[1] > b2[1] + b2[3]) return false
 if (b1[1] + b1[3] < b2[1]) return false
 if (b1[2] > b2[2] + b2[4]) return false
 return not (b1[2] + b1[4] < b2[2])
end

function wordtarget:u()
 if collides({self.x, self.y, self.w, 6}, self.phin:box()) then
  self:on_hit()
  return true
 end
 self.x -= 1
 return self.x < -self.w
end

function wordtarget:draw()
 poke(0x5f58, 0x81)
 -- debug: show hitbox
 --rect(self.x-1, self.y-1, self.x+self.w+1, self.y + 8, 0x10F1.5a5a)
 print(self.str, self.x+1, self.y+1, 0x100b)
 print(self.str, self.x, self.y, 0x100a)
end

function setup_arcade_pal()
 -- per-line color mode
 poke(0x5f5f, 0x10)
 -- rows 72 and lower: sea
 memset(0x5f79,0xff,7)
 pal()
 --game_nrm_pal
 pal({
  [0] = 1, 0, 2, 140, 4, 5, 7, 7, 8, 9, 10, 132, 12, 12, 14, 7
 }, 1)
 --game_uw_pal
 pal({
  [0]=1, 0, 130, 140, 129, 5, 141, 13, 8, 9, 10, 132, 131, 12, 141, 7
 }, 2)
end

function wave()
 return 2.5 * sin((t())>>1)
end

function clear_alt_pal_bits()
 memset(0x5f70,0x0,16)
end

sea = {}
mknew(sea)

function sea:draw()
 local w = wave()
 rectfill(0, 66+w, 128, 77+w, 0x1000)
 poke2(0x5f78, 0xffe0.00ff <<> w)
 rectfill(0, 78+w, 128, 89+w, 0x1040.a842)
 rectfill(0, 90+w, 128, 97+(w>>1), 0x1004.e169)
 rectfill(0, 98+(w>>1), 128, 104+(w>>2), 0x1004.a842)
 rectfill(0, 104+(w>>2), 128, 110+(w>>2), 0x1004)
 rectfill(0, 111+(w>>2), 128, 118+(w>>3), 0x1041.e169)
 rectfill(0, 119+(w>>3), 128, 124+(w>>3), 0x1041.a842)
 rectfill(0, 125+(w>>3), 128, 128, 0x1001)
end

spark = {
 x=0,
 y=0,
 dx=0,
 dy=0,
 f=-1,
}
mknew(spark)
function spark:u()
 self.f += 1
 if (self.f >= 12) return true
 self.x += self.dx
 self.y += self.dy
end
function spark:draw()
 spr(48+self.f\2, self.x, self.y)
end

arcade_level = {
 score=0,
 wordwait = 90,
}
mknew(arcade_level, function(x)
 x.phin = toyphin.new{
  splasher=x,
  xtarget=x.phin_x,
 }
 x.sky = bg.new{c=13}
 x.sea = sea.new()
 x.bg = event_list.new()
 x.fg = event_list.new()
 x.words = event_list.new()
 x.t0 = t()
 x.wordremain = x.max_score or arcade_level.max_score
 x.wordtimer = x.wordwait or arcade_level.wordwait

 x.v = view.of{
  x.sky,
  x.sea,
  x.waves,
  x.noscore and blank or {
   draw=function()
    poke(0x5f58, 0)
    local s = tostr(x.score)
    print(s,1,2,3)
    print(s,2,1,3)
    print(s,0,1,3)
    print(s,1,0,3)
    print(s,1,1,15)
   end
  },
  x.bg,
  x.phin,
  x.fg,
  x.words,
  x.extra_layer or blank,
 }
 x.d = ditherer.new{
  c=1,
  di = 0.34,
 }
end)

function arcade_level:av()
 if (musicmode & 1 ==0) music(0,1000,7)
 font_compact()
end

function arcade_level:onmchg()
 music((musicmode & 0x1 == 0) and 0 or -1, 500, 7)
end

function arcade_level:u()
 if self.phin.entered then
  if self.wordtimer <= 0 and self.wordremain > 0 then
   self:spawn_word()
   self.wordtimer = self.wordwait
   self.wordremain -= 1
  end
  self.wordtimer -= 1
 end
 if self.wordremain <= 0 and self.words.next == nil then
  self.phin.exiting = true
 end
 if self.phin.x > 90 then
  if not self.mstp then
   self.mstp = true
   music(-1,800)
  end
  if (self.d:u()) seq:next()
 end
 self.v:u()
end

function arcade_level:spawn_word()
 -- pick row from pattern
 if not self.pattern then
  self.pattern=gen_pattern()
  self.pattern_idx = 0
  self.next_break = 8 + irnd(9)
 end
 local i, pbk = (self.pattern_idx&3)+1,0
 if self.next_break <= 0 then
  -- do pattern break
  local np = gen_pattern()
  i = np[1]==self.pattern[i] and 2 or 1
  self.pattern=np
  self.next_break = 8 + irnd(9)
  pbk=3
 end
 self.pattern_idx = i
 self.next_break -= 1
 local row = self.pattern[i]

 self.words:pb(wordtarget.new{
  y=32*row+irnd(13)-6,
  phin=self.phin,
  on_hit = function(word)
   self:word_hit(word)
  end,
  str=rnd(self.wordsets[row + pbk]),
 })
end

function irnd(n)
 return rnd(n) & 0x7fff
end

function gen_pattern()
 local x0 = irnd(3)
 local x1 = irnd(2)
 if (x1 == x0) x1 = 2
 local x2 = irnd(2)
 if (x2 == x1) x2 = 2
 local x3 = irnd(2)
 if (x3 == x2) x3 = 2
 while x3 == x2 or x3 == x0 do
  x3 = (x3 + 1)%3
 end
 return {x0 + 1, x1 + 1, x2 + 1, x3 + 1}
end

function arcade_level:word_hit(word)
 self.score += 1
 psound(15)
 self.bg:pb(spark.new{x=word.x-1, y=word.y-1, dx=-2})
 self.bg:pb(spark.new{x=word.x+word.w, y=word.y-1, dx=1})
end

function arcade_level:draw()
 setup_arcade_pal()
 self.v:draw()
 self.d:draw()
end

function arcade_level:draw_splash(x, force)
 if (force < 0.5) return
 local n = (force + rnd(force)) & 0x7FF
 for i=0,n do
  local d = droplet.new{x=x, force=force, y=72+wave(), f=i/n-0.5}
  if rnd() < 0.5 then
   self.bg:pb(d)
  else
   self.fg:pb(d)
  end
 end
end

function arcade_level:landing_splash(x, force, harder)
 -- TODO: sfx, vfx for surfacing from a dive
 if (force > 1) psound(harder and 13 or 12, harder and 16 or 0, 16)
 self:draw_splash(x, force)
end

function arcade_level:surfacing_splash(x, force, harder)
 -- TODO: sfx, vfx for landing from a jump
 if (force > 1) psound(harder and 13 or 12, 0, 16)
 self:draw_splash(x, force)
end
splashcols = {0x1003, 0x103d.a5a5}
droplet = {}
mknew(droplet, function(d)
 d.dx = (d.f * d.force >> 2) - 0.75
 d.x += 16 * d.f
 d.dy = -rnd(d.force*0.66)
 d.r = 1 + rnd(0.75 + (d.force >> 4))
 d.c = rnd(splashcols)
end)

function droplet:u()
 self.x += self.dx
 self.y += self.dy
 self.dy += 0.3
 return self.y >72 + wave()
end

function droplet:draw()
 circfill(self.x, self.y, self.r, self.c)
 local r2 = self.r >> 1
 pset(self.x+r2, self.y-r2, 0x100F)
end

-->8
-- end of game

ggwp = {
 frem = 60
}
mknew(ggwp)

function ggwp:av()
 font_monogram()
 if (musicmode & 1 == 0) music(0,500,7)
 local score_pct = 100
 if seq.max_score ~= 0 then
  score_pct = seq.score/seq.max_score*100\1
 end
 if (score_pct == 0 and seq.score > 0) score_pct = 1
 self.score_pct = "\^w\^t"..tostr(score_pct).."%"
 self.score_str = tostr(seq.score).." / "..tostr(seq.max_score)
 self.col = irnd(8)+8

 if score_pct == 100 then
  self.final_text=[[perfect! remarkable! you hit
   each and every target.
         good toy!]]
 elseif score_pct > 90 then
  self.final_text=[[       so close!
going to go for them all
      next time?]]
 elseif score_pct > 20 then
  self.final_text=[[     hope you had fun!
come play again some time!]]
 elseif score_pct > 5 then
  self.final_text=[[ wow! it is harder to miss the
words than to hit them. this is
a great try! think you can miss
    all of them next time?]]
 elseif score_pct > 0 then
  self.final_text=[[  you're so close! just a tiny
bit more and you will miss every
  target! this is a phenomenal
accomplishment already. will you
    miss them all next time?]]
 else
  self.final_text=[[unbelieveable! i didn't think
   this was even possible!
      spectacular work.
         good toy!!]]
 end
end

function ggwp:onmchg()
 music((musicmode & 0x1 == 0) and 0 or -1, 500, 7)
end

function ggwp:u()
 if (hz(2) ~= self.lasthz) self.col = irnd(8)+8
 self.lasthz = hz(2)
 if (self.frem <= 0 and btnp(1) and not self.d) self.d = ditherer.new{di=0.5}
 if (self.frem > 0) self.frem -= 1
 if (self.d and self.d:u()) seq:next()
end

sunset_pal=split"12,13,14,8,137,9,10,129,1,130,2,140,5,6,7"

function ggwp:draw()
 pal()
 camera()
 clear_alt_pal_bits()
 pal(sunset_pal,1)
 for i=0,5 do
  local col, y0 = 0x1000 + i+1 + (i+2 << 4),20*i
  for j,p in ipairs{0, 0x5050>>>16,0x5a5a>>>16, 0xafaf>>>16} do
   rectfill(0, y0 + 5*j, 128, y0+4+5*j,col+p)
  end
 end
 rectfill(0, 120, 128, 128, 0x1007)
 font_special()
 print("\^w\^tgreat job!",5,8,0)
 print("fINAL SCORE:", 5, 26, 0)
 --0, 8, 9, 12
 local xpos = 123-6*#self.score_str
 print(self.score_str, xpos+3, 35, 15)
 print(self.score_str, xpos+2, 34, 14)
 print(self.score_str, xpos+1, 33, 12)
 print(self.score_str, xpos, 32, 0)

 print(self.score_pct,64-print(self.score_pct,0,-9999)/2,59,self.col)

 font_default()
 local xoff = print(self.final_text, 0, -999, 0) - 1
 print(self.final_text, 64-xoff/2, 90, 0)

 if self.frem <= 0 and not self.d then
  print("➡️", 117, 120, 15)
  print("➡️", 116, 119, hz(2) and 0 or 9)
 end
 if (self.d) self.d:draw()
end

-->8
-- game sequencer

sequencer = {
 idx = 0,
 score = 0,
 max_score = 0,
}
mknew(sequencer)

basic_breathe=[[c:1:13 bon set:ac:1 set:bwt:1
bREATHE IN...
-----
c:1:14 set:bwt:2
bREATHE OUT...]]
crescendo_breathe=[[c:1:13 set:bwt:1 set:nextbgm:19
bREATHE IN...
-----
c:1:14 set:bwt:2
bREATHE OUT...]]

function sequencer:next()
 if (mainview.score) self.score += mainview.score
 if (mainview.max_score) self.max_score += mainview.max_score
 self.idx += 1
 local rec = self[self.idx]
 if rec == nil then
  mainview = newtitle()
  return
 end
 if rec.params then
  mainview = rec.f(unpack(rec.params))
 else
  mainview = rec.f()
 end
 mainview:av()
 mainview:u()
end

function create_game()
 seq = sequencer.new{
  {
   f=zonk_mode.new,
   params={{
    txd=0,
    txf=1,
    exf=30,
    cmul=0.25,
    hide_stripes=true,
    file=[[c:6:20
wELCOME TO !vACATION!

tHIS GAME USES #ONLY
#THE #ARROW #KEYS FOR
GAMEPLAY, SO PRESS
!RIGHT TO CONTINUE.
-----
c:9:20
   >vACATION IS A
#HYPNOTIC #EXPERIENCE.

iF YOU WOULD LIKE TO
 >#STOP AT ANY TIME,
FOR ANY REASON, HOLD
 #x OR #z. tHIS HALTS
>THE GAME AND STARTS
    AN AWAKENER.
-----
c:10:20
>aT THE END OF THIS
>GAME, THE HYPNOTIC
SUGGESTIONS WILL END
>AND YOU'LL BE WOKEN
      BACK UP.

>sO DURING THE GAME,
FEEL FREE TO !SINK AS
!DEEP INTO TRANCE AS
     >YOU LIKE!
-----
c:2:20
iN THIS GAME, YOU'RE
  AN #ORCA #POOLTOY.
-----
c:3:16
>tHAT'S ALSO THE
CHARACTER IN THE
  ARCADE PART.
-----
c:10:20
lET'S TRY THE ARCADE
 PART! #jUMP AND #DIVE
>TO CATCH !THE WORDS.

yOU DON'T HAVE TO
PAY ATTENTION TO THE
 >!WORDS THEMSELVES;
THEY'RE JUST HERE TO
!SHAPE THE EXPERIENCE
      FOR !YOU.
-----
c:2:10
  UP: JUMP
DOWN: !DIVE]],
   }},
  },
  {
   f = arcade_level.new,
   params = {{
    max_score=10,
    wordsets=csv[[JUMP,PLAY,AIR,SUN
DRIFT,SURF,FLOAT,WAVES
DIVE,WATER,OCEAN,SEA
DRIFT,SOAR,IMAGINE,BE
RELAX,CHILL,TOY,POOLTOY
SINK,DEEP,TRANCE,FOLLOW]],
   }},
  },
  {
   f = zonk_mode.new,
   params={{
    txd=8,
    txf=15,
    exf=30,
    bgm=16,
    br_spd = 240,
    files={[[c:3:18
hEY, WELCOME BACK!
 >sTARTING TO GET
 >THE HANG OF IT?
-----
c:3:14
nOW, THIS GAME
PROMISED TO BE
  >#HYPNOTIC.
-----
c:6:18
 aND YOU MIGHT BE
 !ZONING !OUT TO IT
ALREADY! bUT THESE
>TEXT SEGMENTS ARE
MORE TRADITIONALLY
    >#HYPNOTIC.
-----
c:8:20
 nOW, YOU MIGHT NOT
THINK !HYPNOSIS !WORKS
 ON YOU. tHAT'S ALL
>RIGHT! yOU CAN JUST
 #ZONE #OUT AND ENJOY
>THE GAME. yOU DON'T
 HAVE TO !FOLLOW THE
 TEXT TOO CLOSELY.
-----
c:9:20
>tHIS IS A #vACATION!
yOU DON'T HAVE TO DO
 >MUCH OF #ANYTHING.
 >yOU DON'T HAVE TO
>DO ANYTHING COMPLEX
>TO !RELAX. yOU DON'T
 >HAVE TO !IMAGINE A 
>STAIRCASE AND COUNT
     BACKWARDS.
-----
c:4:20
tHAT ALL SOUNDS LIKE
 TOO MUCH WORK. aLL
YOU NEED TO DO RIGHT
 !NOW IS TO #BREATHE.
-----
c:1:13 bon bpal:1 set:ac:1 set:bwt:1 set:txf:4 set:txd:0 set:exd:0 set:exf:10 set:nextbgm:17
bREATHE IN...
-----
c:1:14 set:bwt:2 bspd:270
bREATHE OUT...
-----
c:1:13 bpal:1 set:bwt:1
bREATHE IN...
-----
c:1:14 set:bwt:2 bspd:300
bREATHE OUT...]],
basic_breathe,
[[set:bwt:0 bpal:2 unset:txd:txf:exf:exd:ac c:4:19
  gREAT! iT FEELS
>NICE TO !SINK INTO
   RELAXATION SO
EASILY, DOESN'T IT?
-----
c:4:19
>iT HELPS YOUR MIND
 >FLOAT AND !DRIFT,
LIKE A #POOLTOY #ORCA
  ON THE WAVES...
-----
c:3:18
iT'S A FUN IDEA TO
JUST BE A #TOY LIKE
 >THAT, ISN'T IT?
-----
c:7:20
!iMAGINE IT! #fLOATING
>FREE ON A WARM DAY,
!COOL WATER #SPLASHING
>AT YOUR !VINYL FINS,
YOUR AIR-FILLED BODY
 >#BOBBING BUOYANTLY
   ON THE SURF...
-----
c:8:20
 nOW, !YOUR !MIND CAN
 >#DO #WHATEVER #FEELS
>#RIGHT -- IT !FOLLOWS
  ONLY !SUGGESTIONS
  THAT ARE FUN FOR
YOU, IDEAS !YOU WOULD
 >ENJOY AND !WANT TO
    >EXPERIENCE.
-----
c:4:19
 aNYTHING ELSE CAN
>JUST ROLL OFF YOUR
MIND LIKE WATER OFF
 A POOLTOY'S BACK.
-----
c:6:19
bUT WON'T IT BE !FUN
 !TO FEEL YOUR MIND
 >!REALLY #DIVE #DEEP
   INTO A FANTASY
 >WHERE YOU GET TO
   !BE A #POOLTOY?
-----
c:6:20
>wHEN WILL IT !BECOME
 >SO #VIVID THAT YOU
FEEL THE WAVES UNDER
>YOU, FEEL THE WATER
AGAINST YOUR !PLASTIC
>#EVERY #ARCADE #ROUND?
-----
c:4:20 bpal:1
 tHESE !WORDS MIGHT
>!SINK #DEEP !INTO YOUR
MIND SOONER THAN !YOU
      >EXPECT!]],
     },
   }},
  },
  {
   f = arcade_level.new,
   params = {{
    max_score=30,
    wordsets=csv[[JUMP,PLAY,AIR,SUN,SQUEAKY
DRIFT,SURF,FLOAT,WAVES,FUN
DIVE,WATER,OCEAN,SEA,SPLASH
DRIFT,SOAR,IMAGINE,POOLTOY
RELAX,CHILL,TOY,ORCA,VIVID
SINK,DEEP,TRANCE,FOLLOW,FANTASIZE]],
   }},
  },
  {
   f=zonk_mode.new,
   params={{
    bgm=17,
    files={
    [[c:4:19
>dON'T THOSE LEVELS
 >JUST #FLY BY WHEN
  >YOU'RE FEELING
    SO !RELAXED?
-----
c:7:20 bpal:1
hAVE YOU YET NOTICED
  HOW !DIFFERENT IT
  >FEELS !NOW, YOUR
>RHYTHMIC MOTIONS ON
 >THE #WAVES PULLING
YOUR MIND TO !GREATER
>DEPTHS OF #HYPNOSIS?
-----
c:4:20 bpal:2
  sTRANGE HOW !THAT
!BODY !CAN !BE !SO !HEAVY
  >WHILE YOU'RE SO
 >#LIGHT AND #FLOATY!
-----
c:4:20
oR PERHAPS !IT !CAN !BE
 >VERY !FAMILIAR, IF
 YOU'VE EXPERIENCED
  #HYPNOSIS BEFORE.
-----
c:7:20 set:bwt:2 bpal:1
 iT CAN BECOME VERY
FAMILIAR, !THAT SENSE
 OF #FOCUS AND #QUIET
 IN THAT !MIND AS IT
  >!FINDS #AUTOMATIC
 #RESPONSES TO THESE
  WORDS IN !TRANCE.]],
  basic_breathe,
  crescendo_breathe,
  [[set:bwt:0 bpal:2 unset:ac c:8:20
sO #RELAXED NOW, !THAT
!BODY BARELY WANTS TO
 >MOVE, !DOESN'T IT?
 >tHAT FEELING WHEN
  THOSE HANDS ONLY
>!RESPOND TO TURN THE
>PAGE, EVERYTHING IS
 SO #CALM AND #HEAVY.
-----
c:9:20
 >iF YOU NEEDED TO,
>YOU COULD LOOK AWAY
AND MOVE FREELY, BUT
>RIGHT NOW YOUR EYES
  ARE #FIXED TO THE
SCREEN, AREN'T THEY?
  >tHEY'RE #STAYING
>#RIGHT #HERE WHEN YOU
 TRY TO TURN AWAY.
-----
c:6:20
 bECAUSE EVERY WORD
IS TAKING YOU !DEEPER
  INTO !TRANCE !NOW,
  LET'S PLANT SOME
 >#HYPNOTIC #SAFETIES
 DEEP IN YOUR MIND.
-----
c:9:20
 tHE !SUGGESTIONS IN
>THIS GAME ARE #PLAY.
  tHEY CAN !FEEL AS
>INTENSE, !IMMERSIVE,
AND IMMEDIATE AS YOU
 LIKE, !BUT WHEN THE
PLAY IS OVER AND THE
>CURTAIN FALLS, !THEY
 >!END AND YOUR MIND
 WILL BE AS IT WAS.
-----
c:9:20
 >eVEN WHEN YOU ARE
  FULLY !ENGAGED IN
 THIS EXPERIENCE, A
 QUIET PART OF YOUR
>MIND IS MONITORING.
>iT ONLY ACCEPTS THE
SUGGESTIONS THAT ARE
#FUN FOR YOU, AND THE
 >OTHERS #DRIFT PAST
  >WITH NO EFFECT.
-----
c:9:20
>iF YOU NEED OR WANT
>TO END THIS GAME AT
 >ANY TIME, FOR ANY
REASON OR NO REASON,
HOLD #x OR #z UNTIL AN
AWAKENER STARTS. aNY
 >PART OF YOUR MIND
>KEEPING YOU SAFE IS
>INVITED TO DO THIS.
-----
c:8:20 bpal:1
 >bECAUSE ALL THESE
 SAFETIES ARE HERE,
 THE SUGGESTIONS IN
THIS GAME WILL #TWIST
>#YOUR #MIND #UP LIKE !A
BALLOON ANIMAL UNTIL
  IT'S THE !PERFECT
  SHAPE FOR A !TOY!
-----
c:9:20
>a !PERFECT MATCH FOR
 THOSE !SMOOTH PUFFY
 !VINYL #FLIPPERS NOW
>OPERATING THE KEYS!
 >tHE PARTS OF YOUR
 MIND ACCUSTOMED TO
#DREAMING GET TO TELL
  YOU STORIES WITH
  #EVERY #SENSE NOW.
-----
c:9:20
 >wHAT DOES IT !FEEL
 LIKE, TO WEAR THAT
 !CETACEAN #SHAPE, SO
>LIGHTWEIGHT DESPITE
 ALL ITS BULK? wHAT
 DOES IT FEEL LIKE,
TO HAVE INTERNAL #AIR
 #PRESSURE RESISTING
     MOVEMENTS?
-----
c:7:20
 >bECAUSE WHEN !YOUR
>PAINTED-ON EYES CAN
>SEE YOUR INFLATABLE
!TOY #ORCA SHAPE RIGHT
  IN FRONT OF YOU,
>THAT !EXPERIENCE !CAN
 ONLY !GET !STRONGER!]],
   }}},
  },
  {
   f = arcade_level.new,
   params = {{
    max_score=40,
    wordsets=csv[[JUMP,PLAY,AIR,FIN,BOUNCE,SQUEAKY
SURF,FLOAT,WAVES,TAIL,VINYL,DRIFT
DIVE,WATER,SEA,FLUKES,FLIPPERS,CETACEAN
DRIFT,IMAGINE,INFLATED,BULGING,SWELL
RELAX,TOY,SEAMS,ORCA,DROP
DEEP,TRANCE,PLASTIC,HYPNOSIS,SENSE]],
   }},
  },
  {
   f=zonk_mode.new,
   params={{
    bgm=17,
    files={
    [[c:3:19
>yOU'RE DEFINITELY
GETTING A !FEEL FOR
    >THIS !NOW!
-----
c:7:20
 iT'S NOT #JUST YOUR
>!PLASTIC SURFACE, OR
  >YOUR AIR-FILLED
 >FLIPPERS, OR YOUR
>PUFFY !INFLATED TAIL
FLUKES THAT MAKE YOU
 >A !TOY, OF COURSE.
-----
c:5:20 set:bwt:2 bpal:1
  >iT'S THE ENTIRE
>#MINDSET! oF COURSE,
#HYPNOSIS CAN BE !VERY
 !GOOD AT #SHIFTING A
 >PERSON'S !MINDSET.]],
 basic_breathe,
 crescendo_breathe,
[[unset:ac set:bwt:0 bpal:2 c:7:18
a TOY IS AN #OBJECT
TO BE #PLAYED #WITH.
 tHINK ABOUT THAT
 PAINTED-ON !HAPPY
>EXPRESSION! a TOY
>#DOESN'T #NEED #MUCH
 #GOING #ON IN THAT
 !AIR-FILLED !HEAD.
-----
c:5:20
>tHAT TOY'S SHAPE IS
SIMPLIFIED AND SOFT.
>sMOOTH VINYL YIELDS
EASILY; IT'S PLIABLE
   >AND FLEXIBLE.
-----
c:7:19
 >jUST BEING A TOY
 FEELS GREAT! yOUR
MIND CAN !BE FULL OF
GIDDY, PLAYFUL #JOY,
>BECAUSE THAT'S ALL
 IT NEEDS TO BE !AN
>!INFLATABLE !OBJECT!
-----
c:9:20
  !sMOOTH #VINYL, AN
  >#INFLATABLE !ORCA
 #BOBBING ABOUT UPON
>THE #WAVES --  THERE
 ARE SO MANY THINGS
 >ABOUT BEING A !TOY
 THAT IMMERSE IT IN
ITS #NATURAL #STATE OF
>CAREFREE #EUPHORIA.
-----
c:7:20
oF COURSE, THESE !TOY
>THOUGHTS MIGHT FEEL
>SO #NATURAL THAT THE
!ORCA TOY DOESN'T YET
REALIZE HOW MUCH ITS
>!THOUGHTS ARE SHAPED
  BY #HYPNOSIS !NOW!
-----
c:7:20    
bUT A !TOY THAT !LIKES
THE IDEA OF BEING #SO
#PLIANT #AND #SIMPLE
  MIGHT NOTICE HOW
 ITS IMAGINATION IS
PURSUING THESE !WORDS
   >SO EAGERLY...
-----
c:9:20
>a TOY THAT WANTS TO
>FEEL ITS MIND #SWIRL
#AWAY IN A GIDDY HAZE
 MIGHT DEEPLY !ENJOY
 >HOW !AUTOMATIC ITS
>OWN !RESPONSES FEEL,
  PLAYING A SIMPLE
>ARCADE GAME #WITHOUT
>#A #SINGLE #THOUGHT IN
ITS AIR-FILLED HEAD!
-----
c:10:20
iT'S SO FUN TO #DRIFT
 AND #FLOAT AND #FEEL
#YOUR #PLASTIC #BODY ON
 >THE WAVES THAT !IT
 >!CAN !FEEL MORE AND
 >MORE #AUTOMATIC TO
 PRESS THE BUTTONS,
 BECAUSE IT'S !GREAT
  >TO BE A PLAYFUL
 INFLATABLE OBJECT!]],
   }}},
  },
  {
   f = arcade_level.new,
   params = {{
    max_score=50,
    wordsets=csv[[JUMP,PLAY,AIR,FIN,GIDDY,HIGH,SQUEAKY
SURF,FLOAT,WAVES,VINYL,TAIL,JOY,HAPPY
DIVE,WATER,SEA,FLUKES,FLIPPERS,SIMPLE,ORCA
INFLATED,HYPNOTIZED,EUPHORIA,VACANT,INFLATABLE
RELAX,TOY,THING,SWIRL,HAZE
DEEP,SINK,PLIANT,AUTOMATIC,HOLLOW]]
   }},
  },
  {
   f=zonk_mode.new,
   params={{
    bgm=19,
    files={
    [[c:1:16 bon bpal:1
sUCH A #GOOD #TOY!]],
basic_breathe,
basic_breathe,
[[c:5:19 bpal:2
rEMINDING A !SQUASHY
!VINYL !THING THAT IT
IS A #"GOOD #TOY" CAN
 BE SUCH A RUSH OF
 >#EUPHORIA FOR IT!
-----
c:9:20
 tOYS DON'T HAVE TO
  !LET !THAT WAVE OF
 GOOD FEELINGS #WASH
#AWAY #EVERYTHING ELSE
IN THAT HOLLOW !HEAD,
>BUT MANY DO BECAUSE
>#IT #FEELS #SO #GOOD TO
 >!SINK !DEEPER INTO
  TOYMINDEDNESS...
-----
c:3:20
aND BECAUSE IT FEELS
  >SO !GOOD TO BE A
     >#GOOD #TOY!
-----
c:7:20
aND BECAUSE IT FEELS
  >SO GOOD TO !BE A
#SIMPLE #HOLLOW #OBJECT
 >OF !VINYL AND AIR,
 !AND LET EVERYTHING
>ELSE !DRIFT AWAY FOR
 >THE TIME BEING...
-----
c:6:20
 >dOES A !TOY NOTICE
>WHEN SO MUCH OF ITS
#IDENTITY HAS SWIRLED
AWAY INTO THAT #GIDDY
 #HAZE THAT THE ONLY
!THING LEFT IS #"TOY"?
-----
c:8:20
>yOU COULD IMAGINE A
 TOY THAT LIKES THE
 IDEA WOULD FEEL SO
  #EUPHORIC WHEN IT
>TRIES !HARD !TO !THINK
>ABOUT WHO IT IS YET
EVERY CONCEPT SWIRLS
 >AWAY BUT #"TOY"...
-----
c:7:19 bpal:1
>sOME !TOYS !LIKE THE
 IDEA SO MUCH THEY
 >#LET #GO OF THEIR
>MEMORY! #tHEY #DON'T
#HAVE #TO, BUT IT CAN
 BE SO MUCH FUN !TO
FEEL IT !DRIFT AWAY.
-----
c:10:20
>a !TOY THAT WANTS TO
LET GO OF ITS MEMORY
 FOR NOW CAN TRY TO
>RECALL ANYTHING BUT
 >WATER AND !PLASTIC
AND !AIR YET ONLY GET
A VAGUE #SWIRL, A CUP
 OF !WATER POURED IN
>THE SEA AND LOST TO
    THE WAVES...
-----
c:8:20
>iF IT FEELS !GOOD TO
FIND ONLY #AIR WITHIN
THAT !HOLLOW CETACEAN
>HEAD, THAT !MIND CAN
 GET EVEN #BETTER AT
>FINDING NOTHING BUT
>#GIDDY #EUPHORIA WHEN 
IT TRIES FOR MEMORY!
-----
c:9:20
iMAGINE A TOY'S !MIND
 #WASHED #AWAY BY THE
 >WAVES, EVERY JUMP
 AND DIVE MAKING IT
 >#SIMPLER, WAVES OF
  #EUPHORIA !LEAVING
>ONLY #"TOY", #"ORCA",
>#"INFLATED", #"PLAY",
    >#"OBJECT"...
-----
c:1:9 bpal:2
!gOOD !TOY!
-----
c:8:20
>!a TOY ONLY NEEDS TO
PLAY, AND FLOAT, AND
#ENJOY ITS OWN #SMOOTH
>#PLASTIC TEXTURE AND
ITS !HOLLOW, BULGING,
 >#INFLATED !CETACEAN
>SHAPE! a !TOY CAN BE
SUCH A #SIMPLE THING.
-----
c:7:20
 >iMAGINE HOW !EAGER
THAT !TOY IS TO #FLOAT
>AND #DRIFT AND #PLAY!
 eVERY TIME IT GOES
OUT ON THE WAVES, IT
FINDS THE !EXPERIENCE
  EVEN MORE #VIVID.
-----
c:8:20
a !TOY MIGHT BE READY
TO PLAY ON THE WAVES
 FOR #QUITE #A #WHILE,
NOW THAT ITS !MIND !IS
 >SO WELL-VERSED IN
THE !SIMPLE, EUPHORIC
  >#JOYS OF BEING A
 #HOLLOW #VINYL #ORCA!
-----
c:8:20 bpal:1
 >aFTER ALL, IT CAN
>!FEEL EVERY #JUMP AND
>#SPLASH NOW, AND !THE
>!WAVES CAN WASH AWAY
>EVERYTHING A #SIMPLE
#MINDLESS #TOY DOESN'T
 NEED, LEAVING ONLY
 GIDDY VINYL #JOY...
-----
c:4:20 set:cmul:2
sMOOTH PLASTIC...
 #COOL #WATER...
  SOLAR WARMTH...
   #SPRINGY #BUOYANCY!
-----
c:1:9
!gOOD !TOY!]],
   }}},
  },
  {
   f = arcade_level.new,
   params = {{
    max_score=100,
    wordsets=csv[[JUMP,PLAY,AIR,FIN,GIDDY,HIGH,SQUEAKY,BREEZE,SUN,WARM,PLASTIC,BUOYANT
SURF,FLOAT,WAVES,VINYL,TAIL,JOY,HAPPY,SWIM,WET,ORCA,COOL,pvc,DRIFT
DIVE,WATER,SEA,FLUKES,FLIPPERS,SIMPLE,SPLASH,DREAM,IMAGINE,POOLTOY,DREAM
INFLATED,HYPNOTIZED,EUPHORIA,VACANT,gOOD TOY!,gOOD TOY!,gOOD TOY!
RELAX,TOY,THING,SWIRL,HAZE,EMPTY,BLANK,DROP
DEEP,SINK,PLIANT,AUTOMATIC,MINDLESS,BULGING,gOOD TOY!,gOOD TOY!,gOOD TOY!]],
   }},
  },
  {f=normal_awakener},
  {f=ggwp.new},
 }
end

__gfx__
[encoded data, 11352 characters]
__label__
[encoded data, 16512 characters]

__map__
[encoded data, 8224 characters]
__sfx__
[encoded data, 6591 characters]
__music__
01 10111744
00 10111844
00 10111744
00 10111944
00 10111a44
00 10111844
00 10111b44
00 10111c44
00 10121d44
00 10131e44
00 10121d44
00 10141f44
00 10161d44
00 10151e44
00 10161d44
02 10212044
03 22636444
03 22232444
03 22252544
03 22252644
03 41252644
03 41232444

