sprites.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. import type { CompanionBones, Eye, Hat, Species } from './types.js'
  2. import {
  3. axolotl,
  4. blob,
  5. cactus,
  6. capybara,
  7. cat,
  8. chonk,
  9. dragon,
  10. duck,
  11. ghost,
  12. goose,
  13. mushroom,
  14. octopus,
  15. owl,
  16. penguin,
  17. rabbit,
  18. robot,
  19. snail,
  20. turtle,
  21. } from './types.js'
  22. // Each sprite is 5 lines tall, 12 wide (after {E}→1char substitution).
  23. // Multiple frames per species for idle fidget animation.
  24. // Line 0 is the hat slot — must be blank in frames 0-1; frame 2 may use it.
  25. const BODIES: Record<Species, string[][]> = {
  26. [duck]: [
  27. [
  28. ' ',
  29. ' __ ',
  30. ' <({E} )___ ',
  31. ' ( ._> ',
  32. ' `--´ ',
  33. ],
  34. [
  35. ' ',
  36. ' __ ',
  37. ' <({E} )___ ',
  38. ' ( ._> ',
  39. ' `--´~ ',
  40. ],
  41. [
  42. ' ',
  43. ' __ ',
  44. ' <({E} )___ ',
  45. ' ( .__> ',
  46. ' `--´ ',
  47. ],
  48. ],
  49. [goose]: [
  50. [
  51. ' ',
  52. ' ({E}> ',
  53. ' || ',
  54. ' _(__)_ ',
  55. ' ^^^^ ',
  56. ],
  57. [
  58. ' ',
  59. ' ({E}> ',
  60. ' || ',
  61. ' _(__)_ ',
  62. ' ^^^^ ',
  63. ],
  64. [
  65. ' ',
  66. ' ({E}>> ',
  67. ' || ',
  68. ' _(__)_ ',
  69. ' ^^^^ ',
  70. ],
  71. ],
  72. [blob]: [
  73. [
  74. ' ',
  75. ' .----. ',
  76. ' ( {E} {E} ) ',
  77. ' ( ) ',
  78. ' `----´ ',
  79. ],
  80. [
  81. ' ',
  82. ' .------. ',
  83. ' ( {E} {E} ) ',
  84. ' ( ) ',
  85. ' `------´ ',
  86. ],
  87. [
  88. ' ',
  89. ' .--. ',
  90. ' ({E} {E}) ',
  91. ' ( ) ',
  92. ' `--´ ',
  93. ],
  94. ],
  95. [cat]: [
  96. [
  97. ' ',
  98. ' /\\_/\\ ',
  99. ' ( {E} {E}) ',
  100. ' ( ω ) ',
  101. ' (")_(") ',
  102. ],
  103. [
  104. ' ',
  105. ' /\\_/\\ ',
  106. ' ( {E} {E}) ',
  107. ' ( ω ) ',
  108. ' (")_(")~ ',
  109. ],
  110. [
  111. ' ',
  112. ' /\\-/\\ ',
  113. ' ( {E} {E}) ',
  114. ' ( ω ) ',
  115. ' (")_(") ',
  116. ],
  117. ],
  118. [dragon]: [
  119. [
  120. ' ',
  121. ' /^\\ /^\\ ',
  122. ' < {E} {E} > ',
  123. ' ( ~~ ) ',
  124. ' `-vvvv-´ ',
  125. ],
  126. [
  127. ' ',
  128. ' /^\\ /^\\ ',
  129. ' < {E} {E} > ',
  130. ' ( ) ',
  131. ' `-vvvv-´ ',
  132. ],
  133. [
  134. ' ~ ~ ',
  135. ' /^\\ /^\\ ',
  136. ' < {E} {E} > ',
  137. ' ( ~~ ) ',
  138. ' `-vvvv-´ ',
  139. ],
  140. ],
  141. [octopus]: [
  142. [
  143. ' ',
  144. ' .----. ',
  145. ' ( {E} {E} ) ',
  146. ' (______) ',
  147. ' /\\/\\/\\/\\ ',
  148. ],
  149. [
  150. ' ',
  151. ' .----. ',
  152. ' ( {E} {E} ) ',
  153. ' (______) ',
  154. ' \\/\\/\\/\\/ ',
  155. ],
  156. [
  157. ' o ',
  158. ' .----. ',
  159. ' ( {E} {E} ) ',
  160. ' (______) ',
  161. ' /\\/\\/\\/\\ ',
  162. ],
  163. ],
  164. [owl]: [
  165. [
  166. ' ',
  167. ' /\\ /\\ ',
  168. ' (({E})({E})) ',
  169. ' ( >< ) ',
  170. ' `----´ ',
  171. ],
  172. [
  173. ' ',
  174. ' /\\ /\\ ',
  175. ' (({E})({E})) ',
  176. ' ( >< ) ',
  177. ' .----. ',
  178. ],
  179. [
  180. ' ',
  181. ' /\\ /\\ ',
  182. ' (({E})(-)) ',
  183. ' ( >< ) ',
  184. ' `----´ ',
  185. ],
  186. ],
  187. [penguin]: [
  188. [
  189. ' ',
  190. ' .---. ',
  191. ' ({E}>{E}) ',
  192. ' /( )\\ ',
  193. ' `---´ ',
  194. ],
  195. [
  196. ' ',
  197. ' .---. ',
  198. ' ({E}>{E}) ',
  199. ' |( )| ',
  200. ' `---´ ',
  201. ],
  202. [
  203. ' .---. ',
  204. ' ({E}>{E}) ',
  205. ' /( )\\ ',
  206. ' `---´ ',
  207. ' ~ ~ ',
  208. ],
  209. ],
  210. [turtle]: [
  211. [
  212. ' ',
  213. ' _,--._ ',
  214. ' ( {E} {E} ) ',
  215. ' /[______]\\ ',
  216. ' `` `` ',
  217. ],
  218. [
  219. ' ',
  220. ' _,--._ ',
  221. ' ( {E} {E} ) ',
  222. ' /[______]\\ ',
  223. ' `` `` ',
  224. ],
  225. [
  226. ' ',
  227. ' _,--._ ',
  228. ' ( {E} {E} ) ',
  229. ' /[======]\\ ',
  230. ' `` `` ',
  231. ],
  232. ],
  233. [snail]: [
  234. [
  235. ' ',
  236. ' {E} .--. ',
  237. ' \\ ( @ ) ',
  238. ' \\_`--´ ',
  239. ' ~~~~~~~ ',
  240. ],
  241. [
  242. ' ',
  243. ' {E} .--. ',
  244. ' | ( @ ) ',
  245. ' \\_`--´ ',
  246. ' ~~~~~~~ ',
  247. ],
  248. [
  249. ' ',
  250. ' {E} .--. ',
  251. ' \\ ( @ ) ',
  252. ' \\_`--´ ',
  253. ' ~~~~~~ ',
  254. ],
  255. ],
  256. [ghost]: [
  257. [
  258. ' ',
  259. ' .----. ',
  260. ' / {E} {E} \\ ',
  261. ' | | ',
  262. ' ~`~``~`~ ',
  263. ],
  264. [
  265. ' ',
  266. ' .----. ',
  267. ' / {E} {E} \\ ',
  268. ' | | ',
  269. ' `~`~~`~` ',
  270. ],
  271. [
  272. ' ~ ~ ',
  273. ' .----. ',
  274. ' / {E} {E} \\ ',
  275. ' | | ',
  276. ' ~~`~~`~~ ',
  277. ],
  278. ],
  279. [axolotl]: [
  280. [
  281. ' ',
  282. '}~(______)~{',
  283. '}~({E} .. {E})~{',
  284. ' ( .--. ) ',
  285. ' (_/ \\_) ',
  286. ],
  287. [
  288. ' ',
  289. '~}(______){~',
  290. '~}({E} .. {E}){~',
  291. ' ( .--. ) ',
  292. ' (_/ \\_) ',
  293. ],
  294. [
  295. ' ',
  296. '}~(______)~{',
  297. '}~({E} .. {E})~{',
  298. ' ( -- ) ',
  299. ' ~_/ \\_~ ',
  300. ],
  301. ],
  302. [capybara]: [
  303. [
  304. ' ',
  305. ' n______n ',
  306. ' ( {E} {E} ) ',
  307. ' ( oo ) ',
  308. ' `------´ ',
  309. ],
  310. [
  311. ' ',
  312. ' n______n ',
  313. ' ( {E} {E} ) ',
  314. ' ( Oo ) ',
  315. ' `------´ ',
  316. ],
  317. [
  318. ' ~ ~ ',
  319. ' u______n ',
  320. ' ( {E} {E} ) ',
  321. ' ( oo ) ',
  322. ' `------´ ',
  323. ],
  324. ],
  325. [cactus]: [
  326. [
  327. ' ',
  328. ' n ____ n ',
  329. ' | |{E} {E}| | ',
  330. ' |_| |_| ',
  331. ' | | ',
  332. ],
  333. [
  334. ' ',
  335. ' ____ ',
  336. ' n |{E} {E}| n ',
  337. ' |_| |_| ',
  338. ' | | ',
  339. ],
  340. [
  341. ' n n ',
  342. ' | ____ | ',
  343. ' | |{E} {E}| | ',
  344. ' |_| |_| ',
  345. ' | | ',
  346. ],
  347. ],
  348. [robot]: [
  349. [
  350. ' ',
  351. ' .[||]. ',
  352. ' [ {E} {E} ] ',
  353. ' [ ==== ] ',
  354. ' `------´ ',
  355. ],
  356. [
  357. ' ',
  358. ' .[||]. ',
  359. ' [ {E} {E} ] ',
  360. ' [ -==- ] ',
  361. ' `------´ ',
  362. ],
  363. [
  364. ' * ',
  365. ' .[||]. ',
  366. ' [ {E} {E} ] ',
  367. ' [ ==== ] ',
  368. ' `------´ ',
  369. ],
  370. ],
  371. [rabbit]: [
  372. [
  373. ' ',
  374. ' (\\__/) ',
  375. ' ( {E} {E} ) ',
  376. ' =( .. )= ',
  377. ' (")__(") ',
  378. ],
  379. [
  380. ' ',
  381. ' (|__/) ',
  382. ' ( {E} {E} ) ',
  383. ' =( .. )= ',
  384. ' (")__(") ',
  385. ],
  386. [
  387. ' ',
  388. ' (\\__/) ',
  389. ' ( {E} {E} ) ',
  390. ' =( . . )= ',
  391. ' (")__(") ',
  392. ],
  393. ],
  394. [mushroom]: [
  395. [
  396. ' ',
  397. ' .-o-OO-o-. ',
  398. '(__________)',
  399. ' |{E} {E}| ',
  400. ' |____| ',
  401. ],
  402. [
  403. ' ',
  404. ' .-O-oo-O-. ',
  405. '(__________)',
  406. ' |{E} {E}| ',
  407. ' |____| ',
  408. ],
  409. [
  410. ' . o . ',
  411. ' .-o-OO-o-. ',
  412. '(__________)',
  413. ' |{E} {E}| ',
  414. ' |____| ',
  415. ],
  416. ],
  417. [chonk]: [
  418. [
  419. ' ',
  420. ' /\\ /\\ ',
  421. ' ( {E} {E} ) ',
  422. ' ( .. ) ',
  423. ' `------´ ',
  424. ],
  425. [
  426. ' ',
  427. ' /\\ /| ',
  428. ' ( {E} {E} ) ',
  429. ' ( .. ) ',
  430. ' `------´ ',
  431. ],
  432. [
  433. ' ',
  434. ' /\\ /\\ ',
  435. ' ( {E} {E} ) ',
  436. ' ( .. ) ',
  437. ' `------´~ ',
  438. ],
  439. ],
  440. }
  441. const HAT_LINES: Record<Hat, string> = {
  442. none: '',
  443. crown: ' \\^^^/ ',
  444. tophat: ' [___] ',
  445. propeller: ' -+- ',
  446. halo: ' ( ) ',
  447. wizard: ' /^\\ ',
  448. beanie: ' (___) ',
  449. tinyduck: ' ,> ',
  450. }
  451. export function renderSprite(bones: CompanionBones, frame = 0): string[] {
  452. const frames = BODIES[bones.species]
  453. const body = frames[frame % frames.length]!.map(line =>
  454. line.replaceAll('{E}', bones.eye),
  455. )
  456. const lines = [...body]
  457. // Only replace with hat if line 0 is empty (some fidget frames use it for smoke etc)
  458. if (bones.hat !== 'none' && !lines[0]!.trim()) {
  459. lines[0] = HAT_LINES[bones.hat]
  460. }
  461. // Drop blank hat slot — wastes a row in the Card and ambient sprite when
  462. // there's no hat and the frame isn't using it for smoke/antenna/etc.
  463. // Only safe when ALL frames have blank line 0; otherwise heights oscillate.
  464. if (!lines[0]!.trim() && frames.every(f => !f[0]!.trim())) lines.shift()
  465. return lines
  466. }
  467. export function spriteFrameCount(species: Species): number {
  468. return BODIES[species].length
  469. }
  470. export function renderFace(bones: CompanionBones): string {
  471. const eye: Eye = bones.eye
  472. switch (bones.species) {
  473. case duck:
  474. case goose:
  475. return `(${eye}>`
  476. case blob:
  477. return `(${eye}${eye})`
  478. case cat:
  479. return `=${eye}ω${eye}=`
  480. case dragon:
  481. return `<${eye}~${eye}>`
  482. case octopus:
  483. return `~(${eye}${eye})~`
  484. case owl:
  485. return `(${eye})(${eye})`
  486. case penguin:
  487. return `(${eye}>)`
  488. case turtle:
  489. return `[${eye}_${eye}]`
  490. case snail:
  491. return `${eye}(@)`
  492. case ghost:
  493. return `/${eye}${eye}\\`
  494. case axolotl:
  495. return `}${eye}.${eye}{`
  496. case capybara:
  497. return `(${eye}oo${eye})`
  498. case cactus:
  499. return `|${eye} ${eye}|`
  500. case robot:
  501. return `[${eye}${eye}]`
  502. case rabbit:
  503. return `(${eye}..${eye})`
  504. case mushroom:
  505. return `|${eye} ${eye}|`
  506. case chonk:
  507. return `(${eye}.${eye})`
  508. }
  509. }