pet-engine.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. (function () {
  2. 'use strict';
  3. const petImg = document.getElementById('pet-img');
  4. const petEl = document.getElementById('pet');
  5. const bubble = document.getElementById('bubble');
  6. let currentState = 'idle';
  7. let currentAnimation = null;
  8. let config = null;
  9. let bubbleTimer = null;
  10. const DEFAULT_IMAGE = 'data:image/svg+xml;utf8,' +
  11. encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="120" height="140">' +
  12. '<text y="100" font-size="90" text-anchor="middle">🦜</text></svg>');
  13. const BUBBLE_TEXTS = {
  14. happy: '完成了!继续保持~',
  15. busy: '有任务在等你呢~',
  16. worried: '任务要过期啦,快去看看!',
  17. excited: '打卡成功,太棒了!',
  18. remind: '该喝水啦~',
  19. tired: '有点累了呢…',
  20. sad: '要照顾好自己哦…',
  21. waitingForInteraction: '来和我互动一下吧!',
  22. miniGame: '我们一起玩吧!',
  23. sleeping: 'zZ…',
  24. custom: '自定义状态'
  25. };
  26. window.cfcPetAPI = {
  27. notifyState: function (stateJson) {
  28. const payload = typeof stateJson === 'string' ? JSON.parse(stateJson) : stateJson;
  29. setState(payload.state, payload.reason || null, payload.data || null);
  30. },
  31. setCustomState: function (stateName, dataJson) {
  32. const data = typeof dataJson === 'string' ? JSON.parse(dataJson) : dataJson;
  33. setState(stateName, 'custom', data);
  34. },
  35. loadConfig: function (configJson) {
  36. config = typeof configJson === 'string' ? JSON.parse(configJson) : configJson;
  37. applyImage(config);
  38. },
  39. showBubble: function (text, durationMs) {
  40. bubble.textContent = text;
  41. bubble.style.display = 'block';
  42. clearTimeout(bubbleTimer);
  43. if (durationMs) bubbleTimer = setTimeout(hideBubble, durationMs);
  44. },
  45. hideBubble: hideBubble,
  46. getState: function () { return currentState; }
  47. };
  48. function hideBubble() { bubble.style.display = 'none'; }
  49. function applyImage(cfg) {
  50. if (!cfg || !cfg.default_image) return;
  51. const src = cfg.image_base_url + cfg.default_image;
  52. petImg.src = src;
  53. petImg.onerror = function () { petImg.src = DEFAULT_IMAGE; };
  54. }
  55. function setState(stateName, reason, data) {
  56. currentState = stateName;
  57. if (currentAnimation) { petEl.classList.remove(currentAnimation); currentAnimation = null; }
  58. let animClass = null;
  59. if (config && config.actions && config.actions[stateName]) {
  60. animClass = 'anim-' + config.actions[stateName];
  61. } else {
  62. animClass = 'anim-' + stateName;
  63. }
  64. const styleEl = document.getElementById('pet-animations');
  65. const sheet = styleEl ? styleEl.sheet : null;
  66. const hasClass = sheet && Array.from(sheet.cssRules).some(r => r.selectorText === '.' + animClass);
  67. if (hasClass) { petEl.classList.add(animClass); currentAnimation = animClass; }
  68. if (reason === 'drink_water') {
  69. bubble.textContent = '该喝水啦~';
  70. bubble.style.display = 'block';
  71. clearTimeout(bubbleTimer);
  72. bubbleTimer = setTimeout(hideBubble, 8000);
  73. } else if (reason === 'welcome') {
  74. bubble.textContent = '我来了!';
  75. bubble.style.display = 'block';
  76. clearTimeout(bubbleTimer);
  77. bubbleTimer = setTimeout(hideBubble, 3000);
  78. } else if (BUBBLE_TEXTS[stateName]) {
  79. bubble.textContent = BUBBLE_TEXTS[stateName];
  80. bubble.style.display = 'block';
  81. clearTimeout(bubbleTimer);
  82. bubbleTimer = setTimeout(hideBubble, 5000);
  83. }
  84. }
  85. petImg.src = DEFAULT_IMAGE;
  86. setInterval(function () {
  87. if (document.getElementById('pet-img')) {
  88. petImg.classList.add('anim-blink_eye');
  89. setTimeout(() => petImg.classList.remove('anim-blink_eye'), 200);
  90. }
  91. }, 3500);
  92. })();