From 826bb0c679b6bc0a064bffe8f96240ae8d2a1a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ta=C5=84czyk?= Date: Sun, 21 Apr 2024 21:59:08 +0200 Subject: [PATCH] pointer and command controls --- games/nukes/package-lock.json | 376 +++++++++++++++++- games/nukes/package.json | 3 +- .../src/controls-render/launch-highlight.tsx | 33 ++ games/nukes/src/controls/command.ts | 46 +++ games/nukes/src/controls/pointer.tsx | 75 ++++ games/nukes/src/controls/selection.tsx | 22 +- games/nukes/src/events.ts | 21 + games/nukes/src/game-states/state-intro.tsx | 3 - .../src/game-states/state-tech-world.tsx | 21 +- .../{render => world-render}/city-render.tsx | 5 + .../explosion-render.tsx | 0 .../launch-site-render.tsx | 4 + .../missile-render.tsx | 0 .../sector-render.tsx | 0 .../{render => world-render}/state-render.tsx | 2 +- .../world-state-render.tsx | 10 +- games/nukes/src/world/world-state-create.ts | 18 +- games/nukes/src/world/world-state-types.ts | 5 + games/nukes/src/world/world-state-updates.ts | 2 +- games/nukes/vite.config.ts | 9 +- 20 files changed, 627 insertions(+), 28 deletions(-) create mode 100644 games/nukes/src/controls-render/launch-highlight.tsx create mode 100644 games/nukes/src/controls/command.ts create mode 100644 games/nukes/src/controls/pointer.tsx create mode 100644 games/nukes/src/events.ts rename games/nukes/src/{render => world-render}/city-render.tsx (74%) rename games/nukes/src/{render => world-render}/explosion-render.tsx (100%) rename games/nukes/src/{render => world-render}/launch-site-render.tsx (81%) rename games/nukes/src/{render => world-render}/missile-render.tsx (100%) rename games/nukes/src/{render => world-render}/sector-render.tsx (100%) rename games/nukes/src/{render => world-render}/state-render.tsx (73%) rename games/nukes/src/{render => world-render}/world-state-render.tsx (81%) diff --git a/games/nukes/package-lock.json b/games/nukes/package-lock.json index c82a23f1..dcba20fd 100644 --- a/games/nukes/package-lock.json +++ b/games/nukes/package-lock.json @@ -27,7 +27,8 @@ "react-router-dom": "^6.21.1", "styled-components": "^6.1.6", "typescript": "^5.2.2", - "vite": "^5.0.0" + "vite": "^5.0.0", + "vite-plugin-checker": "^0.6.4" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1482,6 +1483,33 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1503,6 +1531,19 @@ "node": ">=4" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1699,6 +1740,18 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1819,6 +1872,30 @@ "node": ">=4" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1834,6 +1911,15 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2762,6 +2848,20 @@ "is-callable": "^1.1.3" } }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2946,6 +3046,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -3138,6 +3244,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -3496,6 +3614,18 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -3672,6 +3802,27 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4087,6 +4238,18 @@ "react-dom": ">=16.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -4556,6 +4719,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4736,6 +4905,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -4830,6 +5008,202 @@ } } }, + "node_modules/vite-plugin-checker": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.4.tgz", + "integrity": "sha512-2zKHH5oxr+ye43nReRbC2fny1nyARwhxdm0uNYp/ERy4YvU9iZpNOsueoi/luXw5gnpqRSvjcEPxXbS153O2wA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "ansi-escapes": "^4.3.0", + "chalk": "^4.1.1", + "chokidar": "^3.5.1", + "commander": "^8.0.0", + "fast-glob": "^3.2.7", + "fs-extra": "^11.1.0", + "npm-run-path": "^4.0.1", + "semver": "^7.5.0", + "strip-ansi": "^6.0.0", + "tiny-invariant": "^1.1.0", + "vscode-languageclient": "^7.0.0", + "vscode-languageserver": "^7.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-uri": "^3.0.2" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "eslint": ">=7", + "meow": "^9.0.0", + "optionator": "^0.9.1", + "stylelint": ">=13", + "typescript": "*", + "vite": ">=2.0.0", + "vls": "*", + "vti": "*", + "vue-tsc": ">=1.3.9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "meow": { + "optional": true + }, + "optionator": { + "optional": true + }, + "stylelint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vls": { + "optional": true + }, + "vti": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/vite-plugin-checker/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vite-plugin-checker/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vite-plugin-checker/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vite-plugin-checker/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/vite-plugin-checker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/vite-plugin-checker/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "dev": true, + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz", + "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4", + "semver": "^7.3.4", + "vscode-languageserver-protocol": "3.16.0" + }, + "engines": { + "vscode": "^1.52.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "dev": true, + "dependencies": { + "vscode-languageserver-protocol": "3.16.0" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "dev": true, + "dependencies": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/games/nukes/package.json b/games/nukes/package.json index 92ff6b68..c1e236a8 100644 --- a/games/nukes/package.json +++ b/games/nukes/package.json @@ -29,6 +29,7 @@ "react-router-dom": "^6.21.1", "styled-components": "^6.1.6", "typescript": "^5.2.2", - "vite": "^5.0.0" + "vite": "^5.0.0", + "vite-plugin-checker": "^0.6.4" } } diff --git a/games/nukes/src/controls-render/launch-highlight.tsx b/games/nukes/src/controls-render/launch-highlight.tsx new file mode 100644 index 00000000..3cee6a6c --- /dev/null +++ b/games/nukes/src/controls-render/launch-highlight.tsx @@ -0,0 +1,33 @@ +import styled from 'styled-components'; +import { EntityType } from '../world/world-state-types'; +import { useSelectedObject } from '../controls/selection'; +import { usePointer } from '../controls/pointer'; + +export function LaunchHighlight() { + const selectedObject = useSelectedObject(); + const pointer = usePointer(); + + if (selectedObject?.type !== EntityType.LAUNCH_SITE) { + return null; + } + + return ( + + {pointer.x}, {pointer.y} + + ); +} + +const HighlightContainer = styled.div` + position: absolute; + transform: translate(calc(var(--x) * 1px), calc(var(--y) * 1px)); + pointer-events: none; + color: red; +`; diff --git a/games/nukes/src/controls/command.ts b/games/nukes/src/controls/command.ts new file mode 100644 index 00000000..fb09ea64 --- /dev/null +++ b/games/nukes/src/controls/command.ts @@ -0,0 +1,46 @@ +import { useCustomEvent } from '../events'; +import { EntityType, Explosion, Missile, WorldState } from '../world/world-state-types'; +import { usePointer } from './pointer'; +import { useSelectedObject } from './selection'; + +export function Command({ + worldState, + setWorldState, +}: { + worldState: WorldState; + setWorldState: (worldState: WorldState) => void; +}) { + const selectedObject = useSelectedObject(); + const pointer = usePointer(); + + useCustomEvent('world-click', () => { + if (selectedObject?.type !== EntityType.LAUNCH_SITE || pointer.pointingObjects.length === 0) { + return; + } + + const missile: Missile = { + id: Math.random() + '', + launch: selectedObject.position, + launchTimestamp: worldState.timestamp, + + target: pointer.pointingObjects[0].position, + targetTimestamp: worldState.timestamp + 10, + }; + + const explosion: Explosion = { + id: Math.random() + '', + startTimestamp: missile.targetTimestamp, + endTimestamp: missile.targetTimestamp + 5, + position: missile.target, + radius: 30, + }; + + setWorldState({ + ...worldState, + missiles: [...worldState.missiles, missile], + explosions: [...worldState.explosions, explosion], + }); + }); + + return null; +} diff --git a/games/nukes/src/controls/pointer.tsx b/games/nukes/src/controls/pointer.tsx new file mode 100644 index 00000000..43642691 --- /dev/null +++ b/games/nukes/src/controls/pointer.tsx @@ -0,0 +1,75 @@ +import React, { createContext, useContext, useReducer } from 'react'; +import { City, LaunchSite } from '../world/world-state-types'; + +type PointableObject = LaunchSite | City; + +type PointerDispatchAction = + | { + type: 'move'; + x: number; + y: number; + } + | { + type: 'point' | 'unpoint'; + object: PointableObject; + }; + +type Pointer = { + x: number; + y: number; + pointingObjects: PointableObject[]; +}; + +const initialPointer: Pointer = { x: 0, y: 0, pointingObjects: [] }; + +const pointerReducer: React.Reducer = ( + pointer: Pointer, + action: PointerDispatchAction, +) => { + if (action.type === 'move') { + return { x: action.x, y: action.y, pointingObjects: pointer.pointingObjects }; + } else if (action.type === 'point' && !pointer.pointingObjects.some((object) => object.id === action.object.id)) { + return { x: pointer.x, y: pointer.y, pointingObjects: [...pointer.pointingObjects, action.object] }; + } else if (action.type === 'unpoint' && pointer.pointingObjects.some((object) => object.id === action.object.id)) { + return { + x: pointer.x, + y: pointer.y, + pointingObjects: pointer.pointingObjects.filter((object) => object.id === action.object.id), + }; + } else { + return pointer; + } +}; + +const PointerContext = createContext(initialPointer); + +const PointerDispatchContext = createContext>(() => {}); + +export function PointerContextWrapper({ children }: { children: React.ReactNode }) { + const [selection, reducer] = useReducer(pointerReducer, initialPointer); + + return ( + + {children} + + ); +} + +export function usePointer() { + const pointer = useContext(PointerContext); + + return pointer; +} + +export function usePointerMove() { + const dispatch = useContext(PointerDispatchContext); + return (x: number, y: number) => dispatch({ type: 'move', x, y }); +} + +export function useObjectPointer() { + const dispatch = useContext(PointerDispatchContext); + return [ + (object: PointableObject) => dispatch({ type: 'point', object }), + (object: PointableObject) => dispatch({ type: 'unpoint', object }), + ]; +} diff --git a/games/nukes/src/controls/selection.tsx b/games/nukes/src/controls/selection.tsx index 4b945208..58a72a98 100644 --- a/games/nukes/src/controls/selection.tsx +++ b/games/nukes/src/controls/selection.tsx @@ -4,10 +4,10 @@ import { LaunchSite } from '../world/world-state-types'; type SelectionDispatchAction = | { - action: 'clear'; + type: 'clear'; } | { - action: 'set'; + type: 'set'; object: LaunchSite; }; @@ -21,9 +21,9 @@ const selectionReducer: React.Reducer = ( selection: Selection, action: SelectionDispatchAction, ) => { - if (action.action === 'clear') { + if (action.type === 'clear') { return initialSelection; - } else if (action.action === 'set') { + } else if (action.type === 'set') { return { ...selection, selectedObject: action.object }; } else { return selection; @@ -34,7 +34,7 @@ const selectionReducer: React.Reducer = ( const SelectionContext = createContext(initialSelection); // definition of dispatch function for selection context -const SelectionDispatchContext = createContext>(selectionReducer); +const SelectionDispatchContext = createContext>(() => {}); export function SelectionContextWrapper({ children }: { children: React.ReactNode }) { const [selection, reducer] = useReducer(selectionReducer, initialSelection); @@ -50,11 +50,19 @@ export function useObjectSelection(object: LaunchSite) { const dispatch = useContext(SelectionDispatchContext); const selection = useContext(SelectionContext); - return [selection.selectedObject?.id === object.id, () => dispatch({ action: 'set', object })] as const; + return [selection.selectedObject?.id === object.id, () => dispatch({ type: 'set', object })] as const; } +export function useSelectedObject() { + const selection = useContext(SelectionContext); + + return selection.selectedObject; +} + +// definition of clear selection function for selection context + export function useClearSelection() { const dispatch = useContext(SelectionDispatchContext); - return () => dispatch({ action: 'clear' }); + return () => dispatch({ type: 'clear' }); } diff --git a/games/nukes/src/events.ts b/games/nukes/src/events.ts new file mode 100644 index 00000000..ec45560b --- /dev/null +++ b/games/nukes/src/events.ts @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; + +export function dispatchCustomEvent(eventName: string, data?: T) { + const event = new CustomEvent(eventName, { + bubbles: true, + detail: data, + }); + document.dispatchEvent(event); +} + +export function useCustomEvent(eventName: string, callback: (data: T) => void) { + useEffect(() => { + const handler = (event: Event | CustomEvent) => { + callback((event as CustomEvent).detail as T); + }; + document.addEventListener(eventName, handler, false); + return () => { + document.removeEventListener(eventName, handler, false); + }; + }, [eventName, callback]); +} diff --git a/games/nukes/src/game-states/state-intro.tsx b/games/nukes/src/game-states/state-intro.tsx index 90136b58..230f8767 100644 --- a/games/nukes/src/game-states/state-intro.tsx +++ b/games/nukes/src/game-states/state-intro.tsx @@ -5,9 +5,6 @@ const Intro: GameStateComponent = ({ setGameState }) => { return ( <>

intro

- -
-
diff --git a/games/nukes/src/game-states/state-tech-world.tsx b/games/nukes/src/game-states/state-tech-world.tsx index bb54eb7b..f7ce8d14 100644 --- a/games/nukes/src/game-states/state-tech-world.tsx +++ b/games/nukes/src/game-states/state-tech-world.tsx @@ -5,11 +5,15 @@ import { createWorldState } from '../world/world-state-create'; import { updateWorldState } from '../world/world-state-updates'; import { WorldState } from '../world/world-state-types'; import { SelectionContextWrapper } from '../controls/selection'; -import { WorldStateRender } from '../render/world-state-render'; +import { WorldStateRender } from '../world-render/world-state-render'; + +import { LaunchHighlight } from '../controls-render/launch-highlight'; import { GameState, GameStateComponent } from './types'; +import { PointerContextWrapper } from '../controls/pointer'; +import { Command } from '../controls/command'; -const WorldComponent: GameStateComponent = ({ setGameState }) => { +const WorldComponent: GameStateComponent = ({}) => { const [worldState, setWorldState] = useState(() => createWorldState()); const updateWorld = useCallback( (worldState: WorldState, deltaTime: number) => setWorldState(updateWorldState(worldState, deltaTime)), @@ -18,10 +22,15 @@ const WorldComponent: GameStateComponent = ({ setGameState }) => { return ( - - - - + + + + + + + + + ); }; diff --git a/games/nukes/src/render/city-render.tsx b/games/nukes/src/world-render/city-render.tsx similarity index 74% rename from games/nukes/src/render/city-render.tsx rename to games/nukes/src/world-render/city-render.tsx index 66fff944..ca9a6bfa 100644 --- a/games/nukes/src/render/city-render.tsx +++ b/games/nukes/src/world-render/city-render.tsx @@ -1,10 +1,15 @@ import styled from 'styled-components'; import { City } from '../world/world-state-types'; +import { useObjectPointer } from '../controls/pointer'; export function CityRender({ city }: { city: City }) { + const [point, unpoint] = useObjectPointer(); + return ( point(city)} + onMouseLeave={() => unpoint(city)} style={ { '--x': city.position.x, diff --git a/games/nukes/src/render/explosion-render.tsx b/games/nukes/src/world-render/explosion-render.tsx similarity index 100% rename from games/nukes/src/render/explosion-render.tsx rename to games/nukes/src/world-render/explosion-render.tsx diff --git a/games/nukes/src/render/launch-site-render.tsx b/games/nukes/src/world-render/launch-site-render.tsx similarity index 81% rename from games/nukes/src/render/launch-site-render.tsx rename to games/nukes/src/world-render/launch-site-render.tsx index c77f2d4c..10e596a4 100644 --- a/games/nukes/src/render/launch-site-render.tsx +++ b/games/nukes/src/world-render/launch-site-render.tsx @@ -2,12 +2,16 @@ import styled from 'styled-components'; import { LaunchSite } from '../world/world-state-types'; import { useObjectSelection } from '../controls/selection'; +import { useObjectPointer } from '../controls/pointer'; export function LaunchSiteRender({ launchSite }: { launchSite: LaunchSite }) { const [isSelected, select] = useObjectSelection(launchSite); + const [point, unpoint] = useObjectPointer(); return ( point(launchSite)} + onMouseLeave={() => unpoint(launchSite)} onClick={() => select()} style={ { diff --git a/games/nukes/src/render/missile-render.tsx b/games/nukes/src/world-render/missile-render.tsx similarity index 100% rename from games/nukes/src/render/missile-render.tsx rename to games/nukes/src/world-render/missile-render.tsx diff --git a/games/nukes/src/render/sector-render.tsx b/games/nukes/src/world-render/sector-render.tsx similarity index 100% rename from games/nukes/src/render/sector-render.tsx rename to games/nukes/src/world-render/sector-render.tsx diff --git a/games/nukes/src/render/state-render.tsx b/games/nukes/src/world-render/state-render.tsx similarity index 73% rename from games/nukes/src/render/state-render.tsx rename to games/nukes/src/world-render/state-render.tsx index 32fa5f24..d5e259f5 100644 --- a/games/nukes/src/render/state-render.tsx +++ b/games/nukes/src/world-render/state-render.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; import { State } from '../world/world-state-types'; -export function StateRender({ state }: { state: State }) { +export function StateRender(_props: { state: State }) { return ; } diff --git a/games/nukes/src/render/world-state-render.tsx b/games/nukes/src/world-render/world-state-render.tsx similarity index 81% rename from games/nukes/src/render/world-state-render.tsx rename to games/nukes/src/world-render/world-state-render.tsx index b6440909..d7c434f5 100644 --- a/games/nukes/src/render/world-state-render.tsx +++ b/games/nukes/src/world-render/world-state-render.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components'; import { WorldState } from '../world/world-state-types'; +import { usePointerMove } from '../controls/pointer'; import { SectorRender } from './sector-render'; import { StateRender } from './state-render'; @@ -8,11 +9,16 @@ import { CityRender } from './city-render'; import { LaunchSiteRender } from './launch-site-render'; import { MissileRender } from './missile-render'; import { ExplosionRender } from './explosion-render'; +import { dispatchCustomEvent } from '../events'; export function WorldStateRender({ state }: { state: WorldState }) { - // wrap this into styled components globl css + const pointerMove = usePointerMove(); + return ( - + pointerMove(event.clientX, event.clientY)} + onClick={() => dispatchCustomEvent('world-click')} + > {state.sectors.map((sector) => ( ))} diff --git a/games/nukes/src/world/world-state-create.ts b/games/nukes/src/world/world-state-create.ts index 18b29389..f4a13c96 100644 --- a/games/nukes/src/world/world-state-create.ts +++ b/games/nukes/src/world/world-state-create.ts @@ -1,5 +1,5 @@ import { distance } from '../math/position-utils'; -import { SectorType, WorldState } from './world-state-types'; +import { EntityType, SectorType, WorldState } from './world-state-types'; export function createWorldState(): WorldState { const result: WorldState = { @@ -17,14 +17,28 @@ export function createWorldState(): WorldState { stateId: 'test-state', position: { x: 100, y: 100 }, }, + { + id: 'test-city2', + name: 'TestCity2', + stateId: 'test-state', + position: { x: 150, y: 100 }, + }, + { + id: 'test-city3', + name: 'TestCity3', + stateId: 'test-state', + position: { x: 150, y: 50 }, + }, ], launchSites: [ { + type: EntityType.LAUNCH_SITE, id: 'test-launch-site-1', stateId: 'test-state', position: { x: 100, y: 100 }, }, { + type: EntityType.LAUNCH_SITE, id: 'test-launch-site-2', stateId: 'test-state', position: { x: 200, y: 200 }, @@ -58,7 +72,7 @@ function generateSectors(cols: number, rows: number, sectorSize: number) { const centerColX = cols / 2; const centerRowY = rows / 2; - return Array.from({ length: cols * rows }).map((v, i) => { + return Array.from({ length: cols * rows }).map((_v, i) => { const x = i % cols; const y = Math.floor(i / rows); diff --git a/games/nukes/src/world/world-state-types.ts b/games/nukes/src/world/world-state-types.ts index f34d8f72..c70195de 100644 --- a/games/nukes/src/world/world-state-types.ts +++ b/games/nukes/src/world/world-state-types.ts @@ -26,6 +26,10 @@ export type City = { position: Position; }; +export enum EntityType { + LAUNCH_SITE = 'LAUNCH_SITE`', +} + export enum SectorType { WATER = 'WATER', GROUND = 'GROUND', @@ -38,6 +42,7 @@ export type Sector = { }; export type LaunchSite = { + type: EntityType.LAUNCH_SITE; id: LaunchSiteId; position: Position; stateId?: StateId; diff --git a/games/nukes/src/world/world-state-updates.ts b/games/nukes/src/world/world-state-updates.ts index e98ab5b4..313b14ec 100644 --- a/games/nukes/src/world/world-state-updates.ts +++ b/games/nukes/src/world/world-state-updates.ts @@ -1,4 +1,4 @@ -import { WorldState, Missile } from './world-state-types'; +import { WorldState } from './world-state-types'; export function updateWorldState(state: WorldState, deltaTime: number): WorldState { const worldTimestamp = state.timestamp + deltaTime; diff --git a/games/nukes/vite.config.ts b/games/nukes/vite.config.ts index 5a33944a..928491b4 100644 --- a/games/nukes/vite.config.ts +++ b/games/nukes/vite.config.ts @@ -1,7 +1,8 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import checker from 'vite-plugin-checker'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], -}) + plugins: [react(), checker({ typescript: true })], +});