Compare commits

..

1 Commits

Author SHA1 Message Date
cece0ee870 laptop backup 2023-01-29 07:54:14 -05:00
25 changed files with 10689 additions and 2876 deletions

View File

@ -1,37 +0,0 @@
name: Build
run-name: Build
on:
push:
jobs:
build:
name: Build NPM Project
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Install Dependencies
run: npm i
- name: Build Project
run: npm run build
tag:
name: Tag Version
needs: build
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}

11
LICENSE
View File

@ -1,11 +0,0 @@
Copyright (c) 2023 Zakary Timson
All Rights Reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,72 +1,5 @@
<!-- Header --> # TSElectronTemplate
<div id="top" align="center"> This is a minimalist setup to get TypeScript & Electron working. It includes nothing extra.
<br />
<!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/c3df2b0fe775af37194771569752d82e54c81ba8a5493540bf14ec6bf1eadd9d" alt="Logo" width="200" height="200">
<!-- Title -->
### Desktop Daemon
<!-- Description -->
Virtual Desktop Pets
<!-- Repo badges -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/desktop-daemon/tags&query=$[0].name)](https://git.zakscode.com/ztimson/desktop-daemon/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/desktop-daemon&query=open_pr_counter)](https://git.zakscode.com/ztimson/desktop-daemon/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/desktop-daemon&query=open_issues_count)](https://git.zakscode.com/ztimson/desktop-daemon/issues)
<!-- Links -->
---
<div>
<a href="https://git.zakscode.com/ztimson/desktop-daemon/releases" target="_blank">Release Notes</a>
<a href="https://git.zakscode.com/ztimson/desktop-daemon/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
<a href="https://git.zakscode.com/ztimson/desktop-daemon/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div>
---
</div>
## Table of Contents ## Table of Contents
- [Template](#top) [[_TOC_]]
- [About](#about)
- [Built With](#built-with)
- [Setup](#setup)
- [Development](#development)
- [License](#license)
## About
<img alt="Screenshot" src="./screenshot.gif" width="60%" height="auto">
Desktop Daemon is an experimental game I used to learn sprites. You can create customziable pets which live on your desktop. They can largly be ignored as they play along your taskbar but need attention from time to time.
DD was writen completely froms scratch using TypeScript and Electron.
### Built With
[![Electron](https://img.shields.io/badge/Electron-47848F?style=for-the-badge&logo=electron&logoColor=white)](https://www.electronjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
## Setup
<details>
<summary>
<h3 id="development" style="display: inline">
Development
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install the dependencies: `npm install`
2. Start the Angular server: `npm run start`
</details>
## License
Copyright © 2023 Zakary Timson | All Rights Reserved
See the [license](./LICENSE) for more information.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -19,7 +19,7 @@ export class NPC {
private sprite!: any; private sprite!: any;
private text: string; private text: string;
public pos = [1000, 0]; public pos = [0, 0];
public vel = [0, 0]; public vel = [0, 0];
constructor(private readonly ctx: CanvasRenderingContext2D, constructor(private readonly ctx: CanvasRenderingContext2D,

12698
v2/package-lock.json generated

File diff suppressed because it is too large Load Diff

2
v2/src/constants.ts Normal file
View File

@ -0,0 +1,2 @@
export const FRAME_RATE = 10;
export const GRAVITY = 9.8;

View File

@ -1,12 +1,23 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Hello World!</title> <title>Desktop Daemon</title>
<link rel="icon" type="img/png" src="assets/logo.png">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
</head> </head>
<body> <body>
<h1>💖 Hello World!</h1> <canvas id="canvas"></canvas>
<p>Welcome to your Electron application.</p>
</body> </body>
</html> </html>

View File

@ -1,54 +1,45 @@
import { app, BrowserWindow } from 'electron'; import {app, BrowserWindow, screen} from 'electron';
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack import path from 'path';
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).
declare const MAIN_WINDOW_WEBPACK_ENTRY: string; declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string; declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
// Handle creating/removing shortcuts on Windows when installing/uninstalling. if(require('electron-squirrel-startup')) app.quit();
if (require('electron-squirrel-startup')) {
// eslint-disable-line global-require
app.quit();
}
const createWindow = (): void => { const createWindow = (): void => {
// Create the browser window. const primaryDisplay = screen.getPrimaryDisplay();
const height = 250;
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
height: 600, x: 0,
width: 800, y: primaryDisplay.workArea.height - height,
height: height,
width: primaryDisplay.size.width,
frame: false,
transparent: true,
roundedCorners: false,
resizable: false,
fullscreen: false,
alwaysOnTop: true,
icon: path.join(__dirname, "../assets/logo.png"),
title: 'Desktop Daemon',
webPreferences: { webPreferences: {
// nodeIntegration: true,
// contextIsolation: false,
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
}, },
}); });
// and load the index.html of the app.
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// Open the DevTools.
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
}; };
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow); app.on('ready', createWindow);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') app.quit();
app.quit();
}
}); });
app.on('activate', () => { app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the if (BrowserWindow.getAllWindows().length === 0) createWindow();
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
}); });
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

84
v2/src/npc.ts Normal file
View File

@ -0,0 +1,84 @@
import {GRAVITY} from './constants';
import {SpriteSheet} from './sprite-sheet';
export type NPCOptions = {
bubbleOffset?: [number, number];
noBounds?: boolean;
noGravity?: boolean;
scale?: number;
}
export class NPC {
private readonly emojis = [
0x1F600, 0x1F601, 0x1F603, 0x1F603, 0x1F604, 0x1F605, 0x1F606,
0x1F607, 0x1F609, 0x1F60A, 0x1F642, 0x1F643, 0x1F355, 0x1F354,
];
private readonly texturePack: any;
private random: number = 0;
private sprite!: any;
private text: string;
public pos = [0, 0];
public vel = [0, 0];
constructor(private readonly ctx: CanvasRenderingContext2D,
public readonly spriteSheetPath: string,
public readonly spriteDefPath: string,
public readonly options: NPCOptions = {},
) {
this.texturePack = new SpriteSheet(this.ctx,
'./assets/sprites/texture-pack/spritesheet.png',
'../assets/sprites/texture-pack/spritesheet.json');
if(this.options.bubbleOffset == null) this.options.bubbleOffset = [0, 0];
if(this.options.scale == null) this.options.scale = 1;
this.sprite = new SpriteSheet(ctx, spriteSheetPath, spriteDefPath);
setInterval(() => {
this.message(String.fromCodePoint(this.emojis[~~(Math.random() * this.emojis.length)]));
}, 10000);
}
animate(name: string, reverse = false) {
if(this.text) {
const bubble = this.texturePack.definitions.sprites['bubble'];
const x = this.pos[0] + this.options.bubbleOffset[0] * (reverse ? -1 : 1);
const y = this.pos[1] + this.options.bubbleOffset[1];
this.texturePack.drawSprite(x, y, 'bubble', reverse);
this.ctx.save();
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.font = '24px serif';
this.ctx.fillText(this.text, x + (bubble.width / 2 * (reverse ? -1 : 1)), y + (bubble.height / 2));
this.ctx.restore();
}
this.sprite.animate(this.pos[0], this.pos[1], name, reverse, this.options.scale);
}
tick() {
if(this.random <= 0) {
const move = Math.random();
if(move < 0.333) this.vel = [0, 0];
else if(move < 0.666) this.vel = [-10, 0];
else this.vel = [10, 0];
this.random = Math.random() * 50;
}
this.random--;
this.pos = [this.pos[0] + this.vel[0], this.pos[1] + this.vel[1]];
if(!this.options.noGravity) this.vel[1] -= GRAVITY;
if(this.pos[1] < 0) this.pos[1] = 0;
if(!this.options.noBounds) {
if(this.pos[0] < 0) this.pos[0] = 0;
if(this.pos[0] > this.ctx.canvas.width) this.pos[0] = this.ctx.canvas.width;
}
this.animate(this.vel[0] == 0 ? 'idle' : 'walk', this.vel[0] < 0);
}
message(text: string, ms = 5000) {
setTimeout(() => this.text = null, ms);
this.text = text;
}
}

View File

@ -1,2 +0,0 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

View File

@ -1,31 +1,28 @@
/**
* This file will automatically be loaded by webpack and run in the "renderer" context.
* To learn more about the differences between the "main" and the "renderer" context in
* Electron, visit:
*
* https://electronjs.org/docs/latest/tutorial/process-model
*
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
* in a renderer process, please be aware of potential security implications. You can read
* more about security risks here:
*
* https://electronjs.org/docs/tutorial/security
*
* To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
* flag:
*
* ```
* // Create the browser window.
* mainWindow = new BrowserWindow({
* width: 800,
* height: 600,
* webPreferences: {
* nodeIntegration: true
* }
* });
* ```
*/
import './index.css'; import './index.css';
import {FRAME_RATE} from './constants';
import {NPC} from './npc';
console.log('👋 This message is being logged by "renderer.js", included via webpack'); const canvas = <HTMLCanvasElement>document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const screenHeight = window.innerHeight;
const screenWidth = window.innerWidth;
canvas.width = screenWidth;
canvas.height = screenHeight;
function clearScreen() { ctx.clearRect(0, 0, screenWidth, screenHeight); }
const dog = new NPC(ctx, '/static/sprites/shadow-dog/spritesheet.png', '/static/sprites/shadow-dog/spritesheet.json', {
bubbleOffset: [50, 75],
scale: 0.25
});
let frame = 0, once = true;
setInterval(() => {
requestAnimationFrame(() => {
frame++;
clearScreen();
dog.tick();
})
}, 1000 / FRAME_RATE);

87
v2/src/sprite-sheet.ts Normal file
View File

@ -0,0 +1,87 @@
export type SpriteDefinitions = {
width?: number;
height?: number;
animations?: {[key: string]: {y: number, frames: number}};
sprites?: {[key: string]: {x: number, y: number, width?: number, height?: number}};
}
export class SpriteSheet {
private readonly definitions!: SpriteDefinitions;
private readonly spriteSheet!: HTMLImageElement;
private animation = '';
private frame = -1;
constructor(private readonly ctx: CanvasRenderingContext2D,
public readonly spriteSheetPath: string,
public readonly spriteDefPath: string,
) {
this.spriteSheet = new Image();
this.spriteSheet.src = spriteSheetPath;
this.definitions = require(spriteDefPath);
// Backfill coordinates if shorthand is used
if(this.definitions.sprites?.length) {
Object.entries(this.definitions.sprites).forEach(sprite => {
const [name, def] = sprite;
if(def.width == null) {
if(this.definitions.width == null)
throw new Error(`Sprite is missing it's width: ${name}`);
this.definitions.sprites[name].x = this.definitions.width * def.x;
this.definitions.sprites[name].width = this.definitions.width;
}
if(def.height == null) {
if(this.definitions.height == null)
throw new Error(`Sprite is missing it's height: ${name}`);
this.definitions.sprites[name].y = this.definitions.height * def.y;
this.definitions.sprites[name].height = this.definitions.height;
}
});
}
}
animate(x: number, y: number, name: string = this.animation, flip = false, scale = 1) {
const animation = this.definitions.animations[name];
if(!animation) throw new Error(`Animation doesn't exist: ${name}`);
if(name == this.animation) {
this.frame++;
if(this.frame >= animation.frames) this.frame = 0;
} else {
this.animation = name;
this.frame = 0;
}
this.drawFrame(x, y, this.animation, this.frame, flip, scale);
}
drawFrame(x: number, y: number, name: string, frame: number, flip = false, scale = 1, centerX = true, centerY = false) {
const sprite = this.definitions.animations[name];
if(!sprite) throw new Error(`Animation doesn't exist: ${name}`);
this.ctx.save();
this.ctx.translate(centerX ?
this.definitions.width * scale / 2 * (flip ? 1 : -1) :
flip ? this.definitions.width : 0,
centerY ? this.definitions.height / -2 : 0);
this.ctx.scale(flip ? -1 : 1, 1);
this.ctx.drawImage(this.spriteSheet,
frame * this.definitions.width, sprite.y * this.definitions.height, this.definitions.width, this.definitions.height,
x * (flip ? -1 : 1), this.ctx.canvas.height - (this.definitions.height * scale + y),
this.definitions.width * scale, this.definitions.height * scale);
this.ctx.restore();
}
drawSprite(x: number, y: number, name: string, flip = false, scale = 1, centerX = true, centerY = false) {
const sprite = this.definitions.sprites[name];
if(!sprite) throw new Error(`Sprite doesn't exist: ${name}`);
this.ctx.save();
this.ctx.translate(centerX ?
this.definitions.width * scale / 2 * (flip ? 1 : -1) :
flip ? this.definitions.width : 0,
centerY ? this.definitions.height / -2 : 0);
this.ctx.scale(flip ? -1 : 1, 1);
this.ctx.drawImage(this.spriteSheet,
sprite.x, sprite.y, sprite.width, sprite.height,
x * (flip ? -1 : 1), this.ctx.canvas.height - (sprite.height * scale + y),
sprite.width * scale, sprite.height * scale);
this.ctx.restore();
}
}

3
v2/src/utils.ts Normal file
View File

@ -0,0 +1,3 @@
export function sleep(ms: number) {
return new Promise(res => setTimeout(res, ms));
}

BIN
v2/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

View File

@ -0,0 +1,14 @@
{
"width": 575,
"height": 523,
"animations": {
"idle": {
"y": 0,
"frames": 6
},
"walk": {
"y": 3,
"frames": 8
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -0,0 +1,11 @@
{
"width": 50,
"height": 50,
"animations": [
{
"name": "idle",
"y": 0,
"frames": 8
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

View File

@ -0,0 +1,58 @@
{
"sprites": {
"generic": {
"x": 0,
"y": 0,
"width": 100,
"height": 100
},
"text": {
"x": 100,
"y": 0,
"width": 100,
"height": 100
},
"code": {
"x": 200,
"y": 0,
"width": 100,
"height": 100
},
"zip": {
"x": 300,
"y": 0,
"width": 100,
"height": 100
},
"image": {
"x": 400,
"y": 0,
"width": 100,
"height": 100
},
"audio": {
"x": 500,
"y": 0,
"width": 100,
"height": 100
},
"video": {
"x": 600,
"y": 0,
"width": 100,
"height": 100
},
"pdf": {
"x": 700,
"y": 0,
"width": 100,
"height": 100
},
"bubble": {
"x": 0,
"y": 100,
"width": 100,
"height": 100
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB