mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-25 12:17:22 +00:00
Add field guide and bird selector
This commit is contained in:
177
birb.js
177
birb.js
@@ -38,6 +38,7 @@ const HOP_DISTANCE = settings.hopDistance;
|
|||||||
const AFK_TIME = 1000 * 30;
|
const AFK_TIME = 1000 * 30;
|
||||||
const SPRITE_HEIGHT = 32;
|
const SPRITE_HEIGHT = 32;
|
||||||
const START_MENU_ID = "birb-start-menu";
|
const START_MENU_ID = "birb-start-menu";
|
||||||
|
const FIELD_GUIDE_ID = "birb-field-guide";
|
||||||
|
|
||||||
const styles = `
|
const styles = `
|
||||||
#birb {
|
#birb {
|
||||||
@@ -175,6 +176,37 @@ const styles = `
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#${FIELD_GUIDE_ID} {
|
||||||
|
width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-grid-content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-grid-item {
|
||||||
|
border: var(--border-size) solid rgb(255, 207, 144);
|
||||||
|
box-shadow: 0 0 0 var(--border-size) white;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 6px;
|
||||||
|
background: rgb(255, 221, 177, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-grid-item canvas {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
transform: scale(2);
|
||||||
|
padding-bottom: var(--border-size);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class Layer {
|
class Layer {
|
||||||
@@ -234,13 +266,14 @@ class Frame {
|
|||||||
/**
|
/**
|
||||||
* @param {CanvasRenderingContext2D} ctx
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
* @param {number} direction
|
* @param {number} direction
|
||||||
|
* @param {Theme} [theme]
|
||||||
*/
|
*/
|
||||||
draw(ctx, direction) {
|
draw(ctx, direction, theme) {
|
||||||
for (let y = 0; y < this.pixels.length; y++) {
|
for (let y = 0; y < this.pixels.length; y++) {
|
||||||
const row = this.pixels[y];
|
const row = this.pixels[y];
|
||||||
for (let x = 0; x < this.pixels[y].length; x++) {
|
for (let x = 0; x < this.pixels[y].length; x++) {
|
||||||
const cell = direction === Directions.LEFT ? row[x] : row[this.pixels[y].length - x - 1];
|
const cell = direction === Directions.LEFT ? row[x] : row[this.pixels[y].length - x - 1];
|
||||||
ctx.fillStyle = bluebirdColors[cell] ?? cell;
|
ctx.fillStyle = theme?.colors[cell] ?? cell;
|
||||||
ctx.fillRect(x * CANVAS_PIXEL_SIZE, y * CANVAS_PIXEL_SIZE, CANVAS_PIXEL_SIZE, CANVAS_PIXEL_SIZE);
|
ctx.fillRect(x * CANVAS_PIXEL_SIZE, y * CANVAS_PIXEL_SIZE, CANVAS_PIXEL_SIZE, CANVAS_PIXEL_SIZE);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -267,9 +300,10 @@ class Anim {
|
|||||||
* @param {CanvasRenderingContext2D} ctx
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
* @param {number} direction
|
* @param {number} direction
|
||||||
* @param {number} timeStart The start time of the animation in milliseconds
|
* @param {number} timeStart The start time of the animation in milliseconds
|
||||||
|
* @param {Theme} [theme] The theme to use for the animation
|
||||||
* @returns {boolean} Whether the animation is complete
|
* @returns {boolean} Whether the animation is complete
|
||||||
*/
|
*/
|
||||||
draw(ctx, direction, timeStart) {
|
draw(ctx, direction, timeStart, theme) {
|
||||||
let time = Date.now() - timeStart;
|
let time = Date.now() - timeStart;
|
||||||
const duration = this.getAnimationDuration();
|
const duration = this.getAnimationDuration();
|
||||||
if (this.loop) {
|
if (this.loop) {
|
||||||
@@ -279,12 +313,12 @@ class Anim {
|
|||||||
for (let i = 0; i < this.durations.length; i++) {
|
for (let i = 0; i < this.durations.length; i++) {
|
||||||
totalDuration += this.durations[i];
|
totalDuration += this.durations[i];
|
||||||
if (time < totalDuration) {
|
if (time < totalDuration) {
|
||||||
this.frames[i].draw(ctx, direction);
|
this.frames[i].draw(ctx, direction, theme);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Draw the last frame if the animation is complete
|
// Draw the last frame if the animation is complete
|
||||||
this.frames[this.frames.length - 1].draw(ctx, direction);
|
this.frames[this.frames.length - 1].draw(ctx, direction, theme);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,23 +355,47 @@ const SPRITESHEET_COLOR_MAP = {
|
|||||||
"#ff6b6b": HEART_SHINE
|
"#ff6b6b": HEART_SHINE
|
||||||
};
|
};
|
||||||
|
|
||||||
const bluebirdColors = {
|
class Theme {
|
||||||
[TRANSPARENT]: "transparent",
|
/**
|
||||||
[OUTLINE]: "#000000",
|
* @param {Record<string, string>} colors
|
||||||
[BORDER]: "#ffffff",
|
*/
|
||||||
[BEAK]: "#000000",
|
constructor(colors) {
|
||||||
[FOOT]: "#af8e75",
|
const defaultColors = {
|
||||||
[EYE]: "#000000",
|
[TRANSPARENT]: "transparent",
|
||||||
[FACE]: "#639bff",
|
[OUTLINE]: "#000000",
|
||||||
[BELLY]: "#f8b143",
|
[BORDER]: "#ffffff",
|
||||||
[UNDERBELLY]: "#ec8637",
|
[HEART]: "#c82e2e",
|
||||||
[WING]: "#578ae6",
|
[HEART_BORDER]: "#501a1a",
|
||||||
[WING_EDGE]: "#326ed9",
|
[HEART_SHINE]: "#ff6b6b",
|
||||||
[HEART]: "#c82e2e",
|
};
|
||||||
[HEART_BORDER]: "#501a1a",
|
this.colors = { ...defaultColors, ...colors };
|
||||||
[HEART_SHINE]: "#ff6b6b",
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const themes = {
|
||||||
|
bluebird: new Theme({
|
||||||
|
[BEAK]: "#000000",
|
||||||
|
[FOOT]: "#af8e75",
|
||||||
|
[EYE]: "#000000",
|
||||||
|
[FACE]: "#639bff",
|
||||||
|
[BELLY]: "#f8b143",
|
||||||
|
[UNDERBELLY]: "#ec8637",
|
||||||
|
[WING]: "#578ae6",
|
||||||
|
[WING_EDGE]: "#326ed9",
|
||||||
|
}),
|
||||||
|
shimaEnaga: new Theme({
|
||||||
|
[BEAK]: "#000000",
|
||||||
|
[FOOT]: "#af8e75",
|
||||||
|
[EYE]: "#000000",
|
||||||
|
[FACE]: "#ffffff",
|
||||||
|
[BELLY]: "#ebe9e8",
|
||||||
|
[UNDERBELLY]: "#ebd9d0",
|
||||||
|
[WING]: "#e3cabd",
|
||||||
|
[WING_EDGE]: "#9b8b82",
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const Directions = {
|
const Directions = {
|
||||||
LEFT: -1,
|
LEFT: -1,
|
||||||
RIGHT: 1,
|
RIGHT: 1,
|
||||||
@@ -518,10 +576,9 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
|
|||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
/** @type {HTMLElement|null} */
|
/** @type {HTMLElement|null} */
|
||||||
let focusedElement = null;
|
let focusedElement = null;
|
||||||
// Time of the user's last action on the page
|
|
||||||
let timeOfLastAction = Date.now();
|
let timeOfLastAction = Date.now();
|
||||||
// Stack of timestamps for each mouseover, max length of 10
|
|
||||||
let petStack = [];
|
let petStack = [];
|
||||||
|
let currentTheme = "bluebird";
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (window !== window.top) {
|
if (window !== window.top) {
|
||||||
@@ -619,7 +676,7 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
if (currentAnimation.draw(ctx, direction, animStart)) {
|
if (currentAnimation.draw(ctx, direction, animStart, themes[currentTheme])) {
|
||||||
setAnimation(Animations.STILL);
|
setAnimation(Animations.STILL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,6 +725,76 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
|
|||||||
}
|
}
|
||||||
|
|
||||||
// insertDecoration();
|
// insertDecoration();
|
||||||
|
// insertFieldGuide();
|
||||||
|
|
||||||
|
function insertFieldGuide() {
|
||||||
|
if (document.querySelector("#" + FIELD_GUIDE_ID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let html = `
|
||||||
|
<div class="birb-window-header">
|
||||||
|
<div class="birb-window-title">Field Guide</div>
|
||||||
|
<div class="birb-window-close">x</div>
|
||||||
|
</div>
|
||||||
|
<div class="birb-window-content">
|
||||||
|
<div class="birb-grid-content">
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
<div class="birb-grid-item"></div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
const fieldGuide = makeElement("birb-window", undefined, FIELD_GUIDE_ID);
|
||||||
|
fieldGuide.innerHTML = html;
|
||||||
|
fieldGuide.style.left = `${window.innerWidth / 2 - 115}px`;
|
||||||
|
fieldGuide.style.top = `${window.innerHeight / 2 - 115}px`;
|
||||||
|
document.body.appendChild(fieldGuide);
|
||||||
|
makeDraggable(fieldGuide.querySelector(".birb-window-header"));
|
||||||
|
|
||||||
|
fieldGuide.querySelector(".birb-window-close")?.addEventListener("click", () => {
|
||||||
|
removeFieldGuide();
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = fieldGuide.querySelector(".birb-grid-content");
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
content.innerHTML = "";
|
||||||
|
for (const [name, theme] of Object.entries(themes)) {
|
||||||
|
const themeElement = makeElement("birb-grid-item");
|
||||||
|
const themeCanvas = document.createElement("canvas");
|
||||||
|
themeCanvas.width = SPRITE_WIDTH * CANVAS_PIXEL_SIZE;
|
||||||
|
themeCanvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
|
||||||
|
const themeCtx = themeCanvas.getContext("2d");
|
||||||
|
if (!themeCtx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Draw the bird with the theme
|
||||||
|
birbFrames.base.draw(themeCtx, Directions.RIGHT, theme);
|
||||||
|
themeElement.appendChild(themeCanvas);
|
||||||
|
content.appendChild(themeElement);
|
||||||
|
themeElement.addEventListener("click", () => {
|
||||||
|
switchTheme(name);
|
||||||
|
fieldGuide.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFieldGuide() {
|
||||||
|
const fieldGuide = document.querySelector("#" + FIELD_GUIDE_ID);
|
||||||
|
if (fieldGuide) {
|
||||||
|
fieldGuide.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchTheme(theme) {
|
||||||
|
currentTheme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the start menu to the page if it doesn't already exist
|
* Add the start menu to the page if it doesn't already exist
|
||||||
@@ -687,6 +814,10 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
|
|||||||
});
|
});
|
||||||
content.appendChild(petButton);
|
content.appendChild(petButton);
|
||||||
let fieldGuideButton = makeElement("birb-window-list-item", "Field Guide");
|
let fieldGuideButton = makeElement("birb-window-list-item", "Field Guide");
|
||||||
|
fieldGuideButton.addEventListener("click", () => {
|
||||||
|
removeStartMenu();
|
||||||
|
insertFieldGuide();
|
||||||
|
});
|
||||||
content.appendChild(fieldGuideButton);
|
content.appendChild(fieldGuideButton);
|
||||||
let decorationsButton = makeElement("birb-window-list-item", "Decorations");
|
let decorationsButton = makeElement("birb-window-list-item", "Decorations");
|
||||||
decorationsButton.addEventListener("click", () => {
|
decorationsButton.addEventListener("click", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user