Buff Queen AKA "Ez egy fa?"

Because the first placeholder player model
resembled a queen that's been to the gym a
bit too much. Also, before she got her head
and hands, she looked like a tree, legit.
This commit is contained in:
2024-12-21 23:46:01 +09:00
commit 2957903cb1
15 changed files with 1509 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
.git
*Dockerfile*
*docker-compose*
node_modules
+130
View File
@@ -0,0 +1,130 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
+6
View File
@@ -0,0 +1,6 @@
FROM node:current-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD ["node", "js/index.js"]
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

+1090
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
{
"name": "instructions-clear",
"version": "1.0.0",
"main": "src/index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Thayol",
"license": "UNLICENSED",
"description": "",
"dependencies": {
"detect-collisions": "^9.24.0",
"three": "^0.171.0",
"victor": "^1.1.0",
"websocket-express": "^3.1.2"
}
}
+46
View File
@@ -0,0 +1,46 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="Thayol">
<script type="importmap">
{
"imports": {
"three": "/three/build/three.module.js",
"three/addons/": "/three/examples/jsm/"
}
}
</script>
<style>
* {
box-sizing: border-box;
}
html {
font-family: sans-serif;
}
html, body {
margin: 0;
padding: 0;
}
.debug-panel {
position: fixed;
inset: 0 0 auto auto;
border-bottom-left-radius: 10px;
padding: 10px 10px 20px 20px;
background-color: white;
border: 5px solid gray;
border-top: none;
border-right: none;
}
</style>
</head>
<body>
<div class="debug-panel">
<p>Connection: <span id="connection"></span></p>
<pre id="state"></pre>
</div>
<script type="module" src="main.js"></script>
</body>
</html>
+73
View File
@@ -0,0 +1,73 @@
import * as THREE from 'three'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
const global = (0,eval)("this")
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
const entities = {}
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animate)
document.body.appendChild(renderer.domElement)
const geometry = new THREE.BoxGeometry(1000, 1000)
const material = new THREE.MeshBasicMaterial({ color: 0x115011 })
const ground = new THREE.Mesh(geometry, material)
scene.add(ground)
// const loader = new GLTFLoader();
// loader.load('player.gltf', (gltf) => scene.add(gltf.scene), undefined, (err) => console.log(err))
global.camera = camera
global.scene = scene
camera.position.set(0, -250, 500)
camera.lookAt(0, 0, 0)
function animate() {
renderer.render(scene, camera)
}
var websocket = null
var timerId = null
function connectWebSocket() {
websocket = new WebSocket('ws://127.0.0.1:1280/ws')
global.websocket = websocket
websocket.onerror = () => websocket.close()
websocket.onopen = () => {
document.getElementById('connection').innerHTML = 'open'
clearInterval(timerId)
}
websocket.onclose = () => {
websocket = null
document.getElementById('connection').innerHTML = 'closed'
timerId = setInterval(() => {
if (websocket == null) {
connectWebSocket()
}
}, 2000)
}
websocket.onmessage = (event) => {
let state = JSON.parse(event.data)
for (const e of state.entities) {
let entity
if (e.id in entities) {
entity = entities[e.id]
}
else {
entity = new THREE.Mesh(new THREE.SphereGeometry(e.radius), new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }))
scene.add(entity)
entities[e.id] = entity
}
entity.position.set(e.pos.x, e.pos.y, e.radius)
}
document.getElementById('state').innerHTML = JSON.stringify(state, null, 2)
}
}
window.addEventListener('load', () => {
connectWebSocket()
})
File diff suppressed because one or more lines are too long
+50
View File
@@ -0,0 +1,50 @@
import { Circle } from 'detect-collisions'
import Victor from 'victor'
export default class Entity {
id = crypto.randomUUID()
pos = new Victor(0, 0)
radius = 0
#collider = null
#dest = null
#game = null
constructor(...options) {
Object.entries(options).forEach((value, key) => this[key] = value)
this.#collider = new Circle(this.pos, this.radius)
}
get collider() { return this.#collider }
get game() { return this.#game }
get system() { return this.game?.system }
set game(value) { this.#game = value }
moveAction(x, y) {
this.#dest = new Victor(x, y)
}
async takeStep() {
if (this.#dest != null) {
this.pos.add(this.#dest.clone().subtract(this.pos).normalize())
if (this.pos.clone().subtract(this.#dest).length() <= 1) {
this.pos = this.#dest
this.#dest = null
}
}
}
async updateCollider() {
if (this.pos.x != this.collider.pos.x || this.pos.y != this.collider.pos.y || this.radius != this.collider.unscaledRadius) {
this.system?.remove(this.#collider)
this.#collider = new Circle(this.pos, this.radius)
this.system?.insert(this.#collider)
}
}
async update() {
await Promise.allSettled([
this.updateCollider(),
this.takeStep(),
])
}
}
+32
View File
@@ -0,0 +1,32 @@
import { System } from 'detect-collisions'
import { EventEmitter } from 'node:events'
export default class Game {
entities = []
tickRate = 30
currentTick = 0
#eventEmitter = new EventEmitter()
#system = new System()
#tickBudget = Math.floor(1000 / this.tickRate)
get eventEmitter() { return this.#eventEmitter }
get system() { return this.#system }
spawn_entity(entity) {
this.entities.push(entity)
this.#system.insert(entity.collider)
}
async start() {
const start = performance.now()
await this.update()
setTimeout(() => this.start(), Math.max(0, this.#tickBudget - (performance.now() - start)))
}
async update() {
Promise.allSettled(this.entities.map((e) => e.update()))
this.currentTick++
this.eventEmitter.emit('tick')
}
}
+57
View File
@@ -0,0 +1,57 @@
import express from 'express'
import { WebSocketExpress } from 'websocket-express'
import Game from './game.js'
import Entity from './entity.js'
import Victor from 'victor'
const app = new WebSocketExpress()
const port = 1280
const game = new Game()
app.use('/', express.static('public'))
app.use('/three/', express.static('node_modules/three'))
app.use(express.urlencoded({ extended: true }))
app.ws('/ws', async (req, res) => {
const websocket = await res.accept()
const subscription = () => websocket.send(JSON.stringify(game))
game.eventEmitter.on('tick', subscription)
websocket.on('close', () => {
game.eventEmitter.removeListener('tick', subscription)
})
websocket.on('message', (rawData) => {
const message = JSON.parse(rawData)
console.log(message)
if (message.action == 'teleport') {
const entity = game.entities.find((e) => e.id == message.id)
entity.pos = new Victor(message.x, message.y)
}
if (message.action == 'move') {
const entity = game.entities.find((e) => e.id == message.id)
entity.moveAction(message.x, message.y)
}
})
})
app.listen(port, () => {
console.log(`Server started! Visit http://localhost:${port}`)
const entity = new Entity()
entity.id = '1'
entity.pos = new Victor(0, 0)
entity.radius = 35
game.spawn_entity(entity)
const entity2 = new Entity()
entity2.id = '2'
entity2.pos = new Victor(200, 100)
entity2.radius = 35
game.spawn_entity(entity2)
game.start()
})