mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-25 12:17:22 +00:00
Add hat item
This commit is contained in:
212
dist/web/birb.embed.js
vendored
212
dist/web/birb.embed.js
vendored
@@ -629,6 +629,7 @@
|
||||
FLOWER_HAT: "flower-hat"
|
||||
};
|
||||
|
||||
/** @type {{ [hatId: string]: { name: string, description: string } }} */
|
||||
const HAT_METADATA = {
|
||||
[HAT.NONE]: {
|
||||
name: "Invisible Hat",
|
||||
@@ -684,62 +685,108 @@
|
||||
}
|
||||
const index = i - 1;
|
||||
const hatKey = HAT[hatName];
|
||||
const hatLayer = buildHatLayer(spriteSheet, hatKey, index, false);
|
||||
const downHatLayer = buildHatLayer(spriteSheet, hatKey, index, false, 1);
|
||||
const hatLayer = buildHatLayer(spriteSheet, hatKey, index);
|
||||
const downHatLayer = buildHatLayer(spriteSheet, hatKey, index, 1);
|
||||
hatLayers.base.push(hatLayer);
|
||||
hatLayers.down.push(downHatLayer);
|
||||
}
|
||||
return hatLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[][]} spriteSheet
|
||||
* @param {string} hatId
|
||||
* @returns {Anim}
|
||||
*/
|
||||
function createHatItemAnimation(hatId, spriteSheet) {
|
||||
const hatLayer = buildHatItemLayer(spriteSheet, hatId);
|
||||
const frames = [
|
||||
new Frame([hatLayer])
|
||||
];
|
||||
return new Anim(frames, [1000], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[][]} spriteSheet
|
||||
* @param {string} hatName
|
||||
* @param {number} hatIndex
|
||||
* @param {boolean} [outlineBottom=false]
|
||||
* @param {number} [yOffset=0]
|
||||
* @returns {Layer}
|
||||
*/
|
||||
function buildHatLayer(spriteSheet, hatName, hatIndex, outlineBottom = false, yOffset = 0) {
|
||||
function buildHatLayer(spriteSheet, hatName, hatIndex, yOffset = 0) {
|
||||
const LEFT_PADDING = 6;
|
||||
const RIGHT_PADDING = 14;
|
||||
const TOP_PADDING = 5 + yOffset;
|
||||
const BOTTOM_PADDING = Math.max(0, 15 - yOffset);
|
||||
|
||||
const hatPixels = getLayerPixels(spriteSheet, hatIndex, HAT_WIDTH);
|
||||
const paddedHatPixels = [];
|
||||
let hatPixels = getLayerPixels(spriteSheet, hatIndex, HAT_WIDTH);
|
||||
hatPixels = pad(hatPixels, TOP_PADDING, BOTTOM_PADDING, LEFT_PADDING, RIGHT_PADDING);
|
||||
hatPixels = drawOutline(hatPixels, false);
|
||||
|
||||
return new Layer(hatPixels, hatName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[][]} spriteSheet
|
||||
* @param {string} hatId
|
||||
* @returns {Layer}
|
||||
*/
|
||||
function buildHatItemLayer(spriteSheet, hatId) {
|
||||
if (hatId === HAT.NONE) {
|
||||
return new Layer([], TAG.DEFAULT);
|
||||
}
|
||||
const hatIndex = Object.keys(HAT).indexOf(hatId) - 1;
|
||||
let hatPixels = getLayerPixels(spriteSheet, hatIndex, HAT_WIDTH);
|
||||
hatPixels = pad(hatPixels, 1, 1, 1, 1);
|
||||
hatPixels = drawOutline(hatPixels, true);
|
||||
hatPixels = pushToBottom(hatPixels);
|
||||
return new Layer(hatPixels, TAG.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add transparent padding around the pixel array
|
||||
* @param {string[][]} pixels
|
||||
* @param {number} top
|
||||
* @param {number} bottom
|
||||
* @param {number} left
|
||||
* @param {number} right
|
||||
* @returns {string[][]}
|
||||
*/
|
||||
function pad(pixels, top, bottom, left, right) {
|
||||
const paddedPixels = [];
|
||||
const rowLength = pixels[0].length + left + right;
|
||||
// Top padding
|
||||
for (let y = 0; y < TOP_PADDING; y++) {
|
||||
paddedHatPixels.push(Array(hatPixels[0].length + LEFT_PADDING + RIGHT_PADDING)
|
||||
.fill(PALETTE.TRANSPARENT)
|
||||
);
|
||||
for (let y = 0; y < top; y++) {
|
||||
paddedPixels.push(Array(rowLength).fill(PALETTE.TRANSPARENT));
|
||||
}
|
||||
// Left and right padding
|
||||
for (let y = 0; y < hatPixels.length; y++) {
|
||||
for (let y = 0; y < pixels.length; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < LEFT_PADDING; x++) {
|
||||
for (let x = 0; x < left; x++) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
}
|
||||
|
||||
for (let x = 0; x < hatPixels[y].length; x++) {
|
||||
row.push(hatPixels[y][x]);
|
||||
for (let x = 0; x < pixels[y].length; x++) {
|
||||
row.push(pixels[y][x]);
|
||||
}
|
||||
|
||||
for (let x = 0; x < RIGHT_PADDING; x++) {
|
||||
for (let x = 0; x < right; x++) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
}
|
||||
|
||||
paddedHatPixels.push(row);
|
||||
paddedPixels.push(row);
|
||||
}
|
||||
// Bottom padding
|
||||
for (let y = 0; y < BOTTOM_PADDING; y++) {
|
||||
paddedHatPixels.push(Array(hatPixels[0].length + LEFT_PADDING + RIGHT_PADDING)
|
||||
.fill(PALETTE.TRANSPARENT)
|
||||
);
|
||||
for (let y = 0; y < bottom; y++) {
|
||||
paddedPixels.push(Array(rowLength).fill(PALETTE.TRANSPARENT));
|
||||
}
|
||||
return paddedPixels;
|
||||
}
|
||||
|
||||
// Add outline
|
||||
/**
|
||||
* Draw an outline around non-transparent pixels
|
||||
* @param {string[][]} pixels
|
||||
* @param {boolean} [outlineBottom=false]
|
||||
* @return {string[][]}
|
||||
*/
|
||||
function drawOutline(pixels, outlineBottom = false) {
|
||||
let neighborOffsets = [
|
||||
[-1, 0],
|
||||
[1, 0],
|
||||
@@ -750,21 +797,42 @@
|
||||
if (outlineBottom) {
|
||||
neighborOffsets.push([0, 1], [-1, 1], [1, 1]);
|
||||
}
|
||||
for (let y = 0; y < paddedHatPixels.length; y++) {
|
||||
for (let x = 0; x < paddedHatPixels[y].length; x++) {
|
||||
const pixel = paddedHatPixels[y][x];
|
||||
for (let y = 0; y < pixels.length; y++) {
|
||||
for (let x = 0; x < pixels[y].length; x++) {
|
||||
const pixel = pixels[y][x];
|
||||
if (pixel !== PALETTE.TRANSPARENT && pixel !== PALETTE.BORDER) {
|
||||
for (let [dx, dy] of neighborOffsets) {
|
||||
const newX = x + dx;
|
||||
const newY = y + dy;
|
||||
if (newY >= 0 && newY < paddedHatPixels.length && newX >= 0 && newX < paddedHatPixels[newY].length && paddedHatPixels[newY][newX] === PALETTE.TRANSPARENT) {
|
||||
paddedHatPixels[newY][newX] = PALETTE.BORDER;
|
||||
if (newY >= 0 && newY < pixels.length && newX >= 0 && newX < pixels[newY].length && pixels[newY][newX] === PALETTE.TRANSPARENT) {
|
||||
pixels[newY][newX] = PALETTE.BORDER;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Layer(paddedHatPixels, hatName);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim transparent rows from the bottom and push them to the top
|
||||
* @param {string[][]} pixels
|
||||
* @returns {string[][]}
|
||||
*/
|
||||
function pushToBottom(pixels) {
|
||||
let trimmedPixels = pixels.slice();
|
||||
let trimCount = 0;
|
||||
while (trimmedPixels.length > 1) {
|
||||
const firstRow = trimmedPixels[trimmedPixels.length - 1];
|
||||
if (firstRow.every(pixel => pixel === PALETTE.TRANSPARENT)) {
|
||||
trimmedPixels.pop();
|
||||
trimCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
trimmedPixels = pad(trimmedPixels, trimCount, 0, 0, 0);
|
||||
return trimmedPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1587,6 +1655,16 @@
|
||||
z-index: 2147483630 !important;
|
||||
}
|
||||
|
||||
.birb-item {
|
||||
image-rendering: pixelated;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform-origin: bottom;
|
||||
transform: scale(calc(var(--birb-scale) * 1.5)) !important;
|
||||
transform-origin: bottom;
|
||||
z-index: 2147483630 !important;
|
||||
}
|
||||
|
||||
.birb-window {
|
||||
font-family: "Monocraft", monospace !important;
|
||||
line-height: initial !important;
|
||||
@@ -1935,6 +2013,7 @@
|
||||
const FIELD_GUIDE_ID = "birb-field-guide";
|
||||
const FEATHER_ID = "birb-feather";
|
||||
const WARDROBE_ID = "birb-wardrobe";
|
||||
const HAT_ID = "birb-hat";
|
||||
|
||||
const DEFAULT_BIRD = "bluebird";
|
||||
const DEFAULT_HAT = HAT.NONE;
|
||||
@@ -2053,7 +2132,7 @@
|
||||
insertModal(`${birdBirb()} Mode`, message);
|
||||
}),
|
||||
new Separator(),
|
||||
new MenuItem("2026.1.20", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.20"); }, false),
|
||||
new MenuItem("2026.1.21", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.21"); }, false),
|
||||
];
|
||||
|
||||
const styleElement = document.createElement("style");
|
||||
@@ -2242,6 +2321,9 @@
|
||||
setInterval(update, UPDATE_INTERVAL);
|
||||
|
||||
focusOnElement(true);
|
||||
|
||||
// TODO: This is for testing
|
||||
generateHat();
|
||||
}
|
||||
|
||||
function update() {
|
||||
@@ -2422,6 +2504,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the hat as an item element in the document if possible
|
||||
*/
|
||||
function generateHat() {
|
||||
if (document.querySelector("#" + HAT_ID)) {
|
||||
return;
|
||||
}
|
||||
// Select a random hat
|
||||
const hatKeys = Object.keys(HAT);
|
||||
const hatId = hatKeys[Math.floor(Math.random() * (hatKeys.length - 1)) + 1];
|
||||
|
||||
// Find a random valid element to place the hat on
|
||||
const element = getRandomValidElement();
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create hat element
|
||||
const hatCanvas = document.createElement("canvas");
|
||||
hatCanvas.id = HAT_ID;
|
||||
hatCanvas.classList.add("birb-item");
|
||||
hatCanvas.width = 14 * CANVAS_PIXEL_SIZE;
|
||||
hatCanvas.height = 14 * CANVAS_PIXEL_SIZE;
|
||||
const hatCtx = hatCanvas.getContext("2d");
|
||||
if (!hatCtx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create hat animation
|
||||
const hatAnimation = createHatItemAnimation(hatId, HATS_SPRITE_SHEET);
|
||||
hatAnimation.draw(hatCtx, Directions.LEFT, Date.now(), CANVAS_PIXEL_SIZE, SPECIES[currentSpecies].colors, [TAG.DEFAULT]);
|
||||
|
||||
// Position hat above the element
|
||||
const rect = element.getBoundingClientRect();
|
||||
hatCanvas.style.left = (rect.left + rect.width / 2 - hatCanvas.width / 2) + "px";
|
||||
hatCanvas.style.top = (rect.top - hatCanvas.height + window.scrollY) + "px";
|
||||
|
||||
// Append to document
|
||||
document.body.appendChild(hatCanvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} birdType
|
||||
*/
|
||||
@@ -2753,14 +2876,9 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus on an element within the viewport
|
||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||
* @returns Whether an element to focus on was found
|
||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||
*/
|
||||
function focusOnElement(teleport = false) {
|
||||
if (frozen) {
|
||||
return false;
|
||||
}
|
||||
function getRandomValidElement() {
|
||||
const MIN_FOCUS_ELEMENT_TOP = getContext().getFocusElementTopMargin();
|
||||
const elements = document.querySelectorAll(getContext().getFocusableElements().join(", "));
|
||||
const inWindow = Array.from(elements).filter((img) => {
|
||||
@@ -2782,10 +2900,22 @@
|
||||
}
|
||||
});
|
||||
if (nonFixedElements.length === 0) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
||||
focusedElement = randomElement;
|
||||
return randomElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus on an element within the viewport
|
||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||
* @returns Whether an element to focus on was found
|
||||
*/
|
||||
function focusOnElement(teleport = false) {
|
||||
if (frozen) {
|
||||
return false;
|
||||
}
|
||||
focusedElement = getRandomValidElement();
|
||||
log("Focusing on element: ", focusedElement);
|
||||
updateFocusedElementBounds();
|
||||
if (teleport) {
|
||||
@@ -2793,7 +2923,7 @@
|
||||
} else {
|
||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||
}
|
||||
return randomElement !== null;
|
||||
return focusedElement !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user