Compare commits

...
Sign in to create a new pull request.

923 commits

Author SHA1 Message Date
Vitaly Turovsky
253e094c74 add zardoy/mwc-proxy repo ref 2025-10-11 02:25:14 +03:00
Vitaly Turovsky
fef94f03fb feat: add support for alt+arrows navigation to navigate between commands only 2025-10-11 02:25:06 +03:00
Vitaly Turovsky
e9f91f8ecd feat: enable music by default, add slider for controlling its volume 2025-10-11 02:24:51 +03:00
Colbster937
634df8d03d
Add WebMC & WS changes (#431)
Co-authored-by: Colbster937 <96893162+colbychittenden@users.noreply.github.com>
2025-10-11 01:52:06 +03:00
Vitaly Turovsky
a88c8b5470 possible fix for rare edgecase where skins from server were not applied. Cause: renderer due to rare circumnstances could be loaded AFTER gameLoaded which is fired only when starting rendering 3d world. classic no existing data handling issue
why not mineflayerBotCreated? because getThreeJsRendererMethods not available at that time so would make things only much complex
2025-09-30 09:38:37 +03:00
Vitaly Turovsky
f51254d97a fix: dont stop local replay server with keep alive connection error 2025-09-30 07:20:30 +03:00
Vitaly Turovsky
05cd560d6b add shadow and directional light for player in inventory (model viewer) 2025-09-29 02:01:04 +03:00
Vitaly Turovsky
b239636356 feat: add debugServerPacketNames and debugClientPacketNames for quick access of names with intellisense of packets for current protocol. Should be used with window.inspectPacket in console 2025-09-28 22:04:17 +03:00
Vitaly Turovsky
4f421ae45f respect loadPlayerSkins option for inventory skin 2025-09-28 21:59:00 +03:00
Vitaly
3b94889bed
feat: make arrows colorful and metadata (#430)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2025-09-20 02:57:59 +03:00
Vitaly
636a7fdb54
feat: improve fog a little (#427) 2025-09-19 05:42:22 +03:00
Vitaly Turovsky
c930365e32 fix sometimes inventory player should not be rendered 2025-09-18 07:49:44 +03:00
Vitaly Turovsky
852dd737ae fix: fix some UI like error screen was not visible fully (buttons were clipped behind the screen) on larger scale on large screens 2025-09-11 22:24:04 +03:00
Vitaly Turovsky
06dc3cb033 feat: Add saveLoginPassword option to control password saving behavior in browser for offline auth on servers 2025-09-08 05:38:16 +03:00
Vitaly Turovsky
c4097975bf add a way to disable sky box for old behavior (not tested) 2025-09-08 05:29:34 +03:00
Vitaly Turovsky
1525fac2a1 fix: some visual camera world view issues (visible lines between blocks) 2025-09-08 05:22:24 +03:00
Vitaly Turovsky
f24cb49a87 up lockfile 2025-09-08 04:55:43 +03:00
Vitaly Turovsky
0b1183f541 up minecraft-data 2025-09-08 04:36:09 +03:00
Vitaly Turovsky
739a6fad24 fix lockfile 2025-09-08 04:34:17 +03:00
Vitaly Turovsky
7f7a14ac65 feat: Add overlay model viewer. Already integrated into inventory to display player! 2025-09-08 04:19:38 +03:00
Vitaly
265d02d18d up protocol for 1.21.8 2025-09-07 18:23:13 +00:00
Vitaly
b2e36840b9
feat: brand new default skybox with fog, better daycycle and colors (#425)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-05 05:02:54 +03:00
Vitaly
7043bf49f3
fix: adding support for newer skin profile data structure in player heads (#424) 2025-09-04 21:55:34 +03:00
Vitaly
528d8f516b
Update worldrendererThree.ts 2025-09-04 21:55:02 +03:00
Kesuaheli
70534d8b5a
fix: adding support for newer skin profile data structure in player heads 2025-09-04 12:51:56 +02:00
Vitaly Turovsky
9d54c70fb7 use node 22 2025-09-02 19:05:18 +03:00
Vitaly Turovsky
7e3ba8bece up integrated server for the latest fixes and better stability 2025-09-02 19:02:30 +03:00
Vitaly
513201be87 up browserify 2025-09-01 08:56:08 +03:00
Vitaly Turovsky
cb82188272 add addPing query param for testing 2025-08-31 19:31:26 +03:00
Vitaly Turovsky
d0d5234ba4 fix: stop right click emulation once window got opened eg chest 2025-08-31 18:31:49 +03:00
Vitaly Turovsky
e81d608554 fix cname 2025-08-27 19:52:09 +03:00
Vitaly Turovsky
1f240d8c20 up mouse allowing to disable positive break block 2025-08-27 19:50:53 +03:00
Vitaly Turovsky
2a1746eb7a [skip ci] fix repository name 2025-08-27 13:33:39 +03:00
Vitaly Turovsky
9718610131 ci: add deployment step for mcw-mcraft-page repository in GitHub Actions 2025-08-27 12:08:20 +03:00
Vitaly Turovsky
8f62fbd4da fix: window title sometimes was not showing up on old versions 2025-08-26 13:50:36 +03:00
Vitaly Turovsky
bc2972fe99 fix registering custom channels too late (a few ms diff) 2025-08-24 19:53:44 +03:00
Vitaly
a12c61bc6c
add simple monaco (#418) 2025-08-21 13:21:02 +03:00
Vitaly Turovsky
6e0d54ea17 up mc-protocol patch 2025-08-20 20:45:02 +03:00
Vitaly Turovsky
72e9e656cc new! helpful errors on custom channels payloads! 2025-08-20 20:42:40 +03:00
Vitaly Turovsky
4a5f2e799c disable experimentalClientSelfReload by default until it's reworked with more fine-tuned checks against server connection 2025-08-20 20:02:57 +03:00
Vitaly
a8fa3d47d1 up protocol & mineflayer for 1.21.6 2025-08-19 12:49:33 +03:00
Vitaly Turovsky
9a84a7acfb do less annoying logging 2025-08-18 11:37:20 +03:00
Vitaly Turovsky
d6eb1601e9 disable remote sounds by default 2025-08-16 09:16:29 +03:00
Vitaly Turovsky
e1293b6cb3 - Introduced a patchAssets script to apply custom textures to the blocks and items atlases.
- Enhanced the ThreeJsSound class to support sound playback timeout and volume adjustments.
- Added a custom sound system to handle named sound effects with metadata.
2025-08-16 09:15:37 +03:00
Vitaly Turovsky
cc4f705aea feat: new experimental chunk loading logic by forcing into spiral queue
feat: add highly experimental logic to try to self-restore any issues with chunks loading by automating f3+a action. write debug info into chat for now. can be disabled
feat: rework chunks debug screen showing actually useful information now
2025-08-15 07:46:52 +03:00
Vitaly Turovsky
54c114a702 feat(big): items are now rendered in 3d not in 2d and it makes insanely huge difference on the game visuals 2025-08-15 05:26:11 +03:00
Vitaly Turovsky
65575e2665 rm faulty mineflayer ver, fix kickin in singleplayer 2025-08-15 01:33:37 +03:00
Vitaly
1ddaa79162
rm not tested pathfinder (#415) 2025-08-15 01:12:24 +03:00
Vitaly Turovsky
e2b141cca0 fix a few packet specific errors 2025-08-14 04:51:37 +03:00
Vitaly
15e3325971 add param for testing for immediate reconnect after kick or error (warning: will cause infinite reload loop) 2025-08-14 01:25:24 +03:00
Vitaly Turovsky
60fc5ef315 feat: add skybox renderer: test it by dragging an image window into window, fix waypoint block pos 2025-08-13 19:19:46 +03:00
Vitaly
8827aab981 dont add test waypoints on dev 2025-08-12 06:27:42 +03:00
Vitaly
0a474e6780 feat: add custom experimental waypints impl 2025-08-12 06:27:06 +03:00
Vitaly Turovsky
cdd8c31a0e fix: fix player colored username rendering, fix sometimes skin was overriden 2025-08-11 21:21:44 +03:00
Vitaly Turovsky
e7c358d3fc feat: add minecraft-web-client:block-interactions-customization 2025-08-11 03:12:05 +03:00
Vitaly Turovsky
fb395041b9 fix: fix on 1.18.2 many blocks like mushrom blocks, fence gates, deepslate, basalt, copper stuff like ore, infested stone, cakes and tinted glass was resulting in instant breaking on the client
dev: add debugTestPing
2025-08-11 01:39:08 +03:00
Vitaly Turovsky
353ba2ecb3 fix: some blocks textures were not update in hotbar after texturepack change 2025-08-08 21:52:55 +03:00
Vitaly Turovsky
53cbff7699 dont conflict fire with chat 2025-08-08 18:37:10 +03:00
Vitaly Turovsky
caf4695637 feat: silly player on fire renderer effect 2025-08-08 18:33:20 +03:00
Vitaly
167b49da08
fix: fix cannot write after stream was destroyed message (#413) 2025-08-08 02:07:52 +03:00
Vitaly Turovsky
d7bd26b6b5 up protocol patch 2025-08-06 01:47:09 +03:00
Vitaly Turovsky
d41527edc8 manually fix lockfile because of silly pnpm dep resolution 2025-08-03 03:33:37 +03:00
Vitaly Turovsky
24ab260e8e fix: up protocol to support 1.21.5 2025-08-03 03:20:38 +03:00
Vitaly
c4b284b9b7 fix: fix supported versions display in server menu 2025-08-02 21:34:33 +03:00
Kesu
67855ae25a
fix: fix some window titles (#401) 2025-07-27 15:24:26 +02:00
Vitaly Turovsky
b9c8ade9bf fix: fix chat was crashing sometimes 2025-07-20 10:06:57 +03:00
Max Lee
4d7e3df859
feat: Item projectiles support (#395) 2025-07-18 14:18:05 +03:00
Vitaly Turovsky
a498778703 always wait for config load so autoConnect works on remote config 2025-07-18 09:56:50 +03:00
Vitaly Turovsky
b6d4728c44 display disconnect always last 2025-07-18 09:44:50 +03:00
Vitaly Turovsky
0dca8bbbe5 fix(important): F3 actions didn't work on mobile at all like chunks reload 2025-07-18 08:32:32 +03:00
Vitaly Turovsky
de9bfba3a8 allow auto connect on mcraft for last integrations 2025-07-18 08:02:13 +03:00
Vitaly Turovsky
45408476a5 fix(appStorage): Fix that settings were not possible to save on vercel domains, use robust self-checking mechanism to ensure user data never lost when cookies storage enabled! 2025-07-18 07:53:47 +03:00
Vitaly Turovsky
c360115f60 fix: fix rare ios safari bug where hotbar would not be visible due to unclear fixed&bottom:0 css using 2025-07-18 07:46:25 +03:00
Max Lee
a8635e9e2f
fix: Effects and Game Indicators overlay toggles didn't work (#397) 2025-07-18 05:55:29 +03:00
Vitaly
5bd33a546a
More build configs & optimise reconnect and immediate game enter (#398)
feat(custom-builds): Add a way to bundle only specific minecraft version data, this does not affect assets though
env:
MIN_MC_VERSION
MAX_MC_VERSION
new SKIP_MC_DATA_RECIPES - if recipes are not used in game
fix: refactor QS params handling to ensure panorama & main menu never loaded when immedieate game enter action is expected (eg ?autoConnect=1)
2025-07-18 04:39:05 +03:00
Vitaly Turovsky
e9c7840dae feat(mobile): fix annoying issues with box and foods usage on screen hold 2025-07-16 16:18:15 +03:00
Vitaly
52c0c75ccf docs: update readme 2025-07-16 12:09:34 +03:00
Vitaly Turovsky
b2f2d85e4f feat(setting): add a way to specify default perspective view 2025-07-14 00:18:42 +03:00
Vitaly Turovsky
7a83a2a657 fix(important): fix all known issues wiht panorama crashing whole game in single file build (minecraft.html) 2025-07-14 00:13:51 +03:00
Vitaly Turovsky
64da602294 add creative server 2025-07-12 05:38:24 +03:00
Max Lee
a09cd7d3ed
fix: Nametag & sign text fixes (#391) 2025-07-11 17:56:37 +03:00
Max Lee
39aca1735e
fix: custom item model data on 1.21.4+ (#392) 2025-07-11 17:13:24 +03:00
Vitaly Turovsky
e9320c68d2 fix metrics port 2025-07-09 17:13:36 +03:00
Vitaly Turovsky
95cc0e6c74 reduce ram usage by 15% 2025-07-09 16:33:50 +03:00
Vitaly
826b24d9e2
Metrics server (#390) 2025-07-09 16:10:50 +03:00
Vitaly Turovsky
16609aa010 fix falsey settings apply 2025-07-08 16:35:05 +03:00
Vitaly Turovsky
09b0e2e493 add a way to disable some parts of bars ui via config and select them via devtool elem select 2025-07-08 15:42:06 +03:00
Vitaly Turovsky
c844b99cf2 fix(regression): fix chat completions were not visible on pc 2025-07-08 15:15:54 +03:00
Vitaly Turovsky
089f2224e2 fix(mobile): drop stack on hotbar hold
feat(config): add powerful way to disable some actions in the client entirely (eg opening inventory or dropping items)
2025-07-08 15:12:23 +03:00
Vitaly Turovsky
2f93c08b1e fix lint 2025-07-04 18:05:26 +03:00
Vitaly Turovsky
fa56d479b1 feat: add report bug button 2025-07-04 18:04:24 +03:00
Max Lee
f489c5f477
fix: skin from textures property would not show (#385) 2025-07-04 17:33:36 +03:00
Vitaly Turovsky
45bc76d825 hotfix(chat): fix all annoying issues on mobile. fix chat was not visible on mobile at all! 2025-07-04 17:29:55 +03:00
Vitaly Turovsky
01567ea589 add "mcraft.fun/debug-inputs.html" 2025-07-04 01:44:26 +03:00
Vitaly Turovsky
e8b0a34c0b fix: fix chat visual text alignment issues on chat opening 2025-07-03 19:52:49 +03:00
Vitaly Turovsky
5cfd301d10 fix: fix controls debug was not visible 2025-07-03 19:35:19 +03:00
Vitaly Turovsky
cdd23bc6a6 fix(critical): fix support for all versions above 1.20.2 in mineflayer dependency 2025-07-03 19:29:27 +03:00
Max Lee
4277c3a262
feat: Support nameTagVisibility option of teams (#373) 2025-07-03 18:15:24 +03:00
Vitaly
9f3d3f93fb
docs: update README to clarify BrowserStack testing (#384) 2025-07-03 18:01:38 +03:00
Vitaly Turovsky
7162d2f549 fix music crash 2025-07-02 19:22:47 +03:00
Vitaly Turovsky
fcf987efe4 always display reconnect button on mcraft.fun 2025-07-02 19:21:18 +03:00
Vitaly Turovsky
d112b01177 fix gameLoaded 2025-07-02 18:55:33 +03:00
Vitaly Turovsky
043e28ed97 add more visible integration trigger 2025-07-02 18:16:44 +03:00
Vitaly Turovsky
08fbc67c31 add update git deps script, fix inventory crashes 2025-07-02 17:22:44 +03:00
Vitaly Turovsky
3bf34a8781 add browserstack partner 2025-07-02 16:55:32 +03:00
Vitaly Turovsky
3cc862b05d up mineflayer 2025-07-02 16:53:30 +03:00
Vitaly Turovsky
c913d63c46 fix(regression): fix ios 16 world rendering support! 2025-07-02 16:25:36 +03:00
Vitaly Turovsky
3320f65b9c now app crash should be fixed 2025-07-02 06:24:02 +03:00
Vitaly Turovsky
ed7c33ff9f up mouse 2025-07-02 06:22:17 +03:00
Vitaly Turovsky
9086435aee rm building storybook 2025-07-02 06:21:15 +03:00
Vitaly Turovsky
8a50412395 fix app crash 2025-07-02 06:19:31 +03:00
Vitaly Turovsky
71257bdf13 add fuchsmc.net server partner back! 2025-07-02 06:16:06 +03:00
Vitaly Turovsky
7aea07f83a up patch again 2025-07-02 06:13:40 +03:00
Vitaly Turovsky
3bcf0f533a up protocol patch 2025-07-02 06:06:21 +03:00
Max Lee
b1298cbe1f
feat: Config option to proxy skin textures (#382) 2025-07-02 05:50:15 +03:00
Vitaly Turovsky
661892af7c up mineflayer 2025-07-02 05:47:49 +03:00
Vitaly Turovsky
c55827db96 up mc-data 2025-07-02 05:46:53 +03:00
Vitaly Turovsky
a2711dbe6c up mc-assets 2025-07-02 05:45:18 +03:00
Vitaly Turovsky
f79e54f11d limit columns in player tab 2025-07-02 05:45:14 +03:00
Vitaly Turovsky
6f5239e1d8 fix: fix inventory crash on picking item with gui-generated texture, fix shulker box 2025-07-01 02:29:37 +03:00
Vitaly Turovsky
13e145cc3a docs: add one liner script! 2025-07-01 00:05:43 +03:00
Vitaly Turovsky
d4ff7de64e stop building storybook... 2025-06-30 19:53:09 +03:00
Vitaly Turovsky
1310109c01 fix: username was not saved after properly after resolving the storage conflict 2025-06-30 19:48:43 +03:00
Vitaly Turovsky
dc2c5a2d88 fix: make it run on ios 15! 2025-06-30 19:11:59 +03:00
Vitaly
31b91e5a33
add logging to sw unregistration on error 2025-06-30 02:06:45 +03:00
Vitaly
f2a11d0a73
Steingify if needed 2025-06-29 20:44:58 +03:00
Vitaly Turovsky
6eae7136ec fix storage conflict modal 2025-06-29 15:34:25 +03:00
Vitaly Turovsky
34eecc166f feat: rework singleplayer generators types. now any generator can be used internally. add a few 2025-06-29 02:38:19 +03:00
Vitaly Turovsky
fec887c28d deeply stringify gui items to avoid futher modifications 2025-06-29 00:56:37 +03:00
Vitaly Turovsky
369166e094 fix tsc, up readme 2025-06-28 00:45:54 +03:00
Vitaly Turovsky
e161426caf always dipslay close buttons from settings 2025-06-27 22:11:49 +03:00
Vitaly Turovsky
0e4435ef91 feat: add support for /ping command, fix chat fading! 2025-06-27 22:06:10 +03:00
Vitaly Turovsky
3336680a0e fix z index of modal 2025-06-27 18:08:33 +03:00
Vitaly Turovsky
83d783226f fix migration marking 2025-06-27 18:08:03 +03:00
Vitaly Turovsky
af5a0b2835 fix: fix camera desync updates in 3rd view and starfield 2025-06-27 16:50:44 +03:00
Vitaly Turovsky
eedd9f1b8f feat: Now settings and servers list synced via top-domain cookies! Eg different subdomains like s.mcraft.fun and mcraft.fun will now share the same settings! Can be disabled.
feat: Now its possible to import data!
2025-06-27 16:28:15 +03:00
Vitaly Turovsky
0b1bc76327 fix ws 2025-06-26 06:22:38 +03:00
Vitaly Turovsky
b839bb8b9b rm readme patching 2025-06-26 06:01:13 +03:00
Vitaly Turovsky
3a7f267b5b dont ignore patch failures 2025-06-26 04:34:08 +03:00
Vitaly Turovsky
2055579b72 feat: finally add block RESCALE support! Cobwebs and ascending rails are now rendered correctly 2025-06-25 15:22:07 +03:00
Vitaly Turovsky
1148378ce6 fix vr again 2025-06-25 15:11:18 +03:00
Vitaly
383e6c4d80 fix lint 2025-06-24 10:03:23 +00:00
Vitaly Turovsky
e9e144621f improve auth features in edge cases 2025-06-24 02:13:06 +03:00
Vitaly
332bd4e0f3
Display auth button 2025-06-22 15:18:51 +03:00
Vitaly Turovsky
32b19ab7af fix: fix elytra skin 2025-06-22 01:20:34 +03:00
Vitaly Turovsky
5221104980 feat: F5: 3rd person view camera! 2025-06-22 01:14:15 +03:00
Vitaly Turovsky
7c8ccba2c1 add testIosCrash for debugging these scenarios 2025-06-21 03:34:04 +03:00
Vitaly Turovsky
fdeb78d96b add /ping and /connect GET endpoints for server info/ping data 2025-06-20 13:56:25 +03:00
Max Lee
5269ad21b5
feat: Add spectator mode entity spectating (#369) 2025-06-18 17:07:00 +03:00
Vitaly Turovsky
f126f56844 fix: fix visual gaps between blocks of water! 2025-06-18 16:57:03 +03:00
Vitaly Turovsky
1b20845ed5 fix initial component mount sometimes displays not found modal
the reason why it happens is known
2025-06-18 08:37:22 +03:00
Vitaly
f3ff4bef03
big renderer codebase cleanup: clean player state (#371) 2025-06-18 08:19:04 +03:00
Vitaly Turovsky
679c3775f7 feat: convert formatted text color to display p3 display so its more vibrant on macbooks with xdr display and other p3 monitors 2025-06-15 16:52:24 +03:00
Vitaly Turovsky
8c71f70db2 fix: fix shifting didn't work on some servers after check 2025-06-13 15:12:58 +03:00
Vitaly Turovsky
9f3079b5f5 feat: rework effects display with new UI! fix a few related bugs 2025-06-13 14:14:08 +03:00
Vitaly Turovsky
794cafb1f6 feat: add useful entities debug entry to F3 2025-06-13 13:22:15 +03:00
Vitaly Turovsky
a3dcfed4d0 feat: add time and battery status that is displayed in fullscreen by default 2025-06-13 13:11:06 +03:00
Vitaly Turovsky
b69813435c up test 2025-06-13 08:06:09 +03:00
Vitaly Turovsky
1e513f87dd feat: add End portal & gateway rendering 2025-06-13 05:23:58 +03:00
Vitaly Turovsky
243db1dc45 use range generation instead 2025-06-13 04:56:40 +03:00
Vitaly Turovsky
ac7d28760f feat: Implement always up-to-date recommended servers display! Fix other annoying issues in servers list 2025-06-13 04:51:48 +03:00
Vitaly Turovsky
cfce898918 make random username configurable 2025-06-11 03:19:14 +03:00
Vitaly
14effc7400
Should fix lint 2025-06-10 07:26:50 +03:00
Vitaly Turovsky
a562316cba rm dead servers 2025-06-10 06:33:18 +03:00
Vitaly Turovsky
6a583d2a36 feat: Custom chat ping functionality!!! To ping on any server type @ and on other web client such ping will be highlighted. Can be disabled.
todo: enable a way to display ping-only messages
todo: reply (public/private) command
todo: fix copmletion offset
2025-06-10 06:33:01 +03:00
Vitaly Turovsky
5575933559 change title for mcraft.fun version 2025-06-10 05:26:32 +03:00
Vitaly Turovsky
e982bf1493 fix(important): make chat word breaking match Minecraftt behavior 2025-06-10 05:24:28 +03:00
Vitaly Turovsky
1c93fd7f60 fix wording 2025-06-10 05:22:52 +03:00
Vitaly
a2e9404a70
feat: Simple but effective renderer perf debug features (#347) 2025-06-05 20:22:58 +03:00
Vitaly Turovsky
38a1d83cf2 fix(regression): B on gamepad was opening pause menu instead of start (default actions map conflict). Now start if clicked again closes all modals 2025-06-05 04:07:22 +03:00
Vitaly Turovsky
314ddf7215 mobile: add back select item button, drop stack on hold action (add command!) 2025-06-05 03:37:17 +03:00
Vitaly Turovsky
829e588ac1 fix F3 handling when other keys are pressed (restore), adjust mic button 2025-06-05 03:32:09 +03:00
Max Lee
8b2276a7ae
Add server-side logging and timeout option (#366) 2025-06-04 17:07:00 +03:00
Maksim Grigorev
7635375471
Resolve issue with non-functional F3 key (#365) 2025-06-03 13:14:34 +03:00
Maksim Grigorev
087e167826
feat: configurable mobile top buttons! (#361) 2025-05-30 16:43:06 +03:00
Vitaly Turovsky
c500d08ed7 hotfix: restore hand 2025-05-27 11:30:21 +03:00
Vitaly Turovsky
50907138f7 fix edge case infinite loop in mesher 2025-05-26 01:09:43 +03:00
Vitaly Turovsky
99d05fc94b improve stability of minimap
(though full refactor is still needed)
2025-05-25 16:16:14 +03:00
Vitaly Turovsky
0c68e63ba6 fix: restore VR support. Fix rotation / position camera bugs 2025-05-25 12:55:27 +03:00
Vitaly Turovsky
04a85e9bd1 minimap: don't do more 20 updates per seconds 2025-05-24 18:26:47 +03:00
Vitaly
3cd778538c
feat: Sync armor rotation for players (#363) 2025-05-22 14:50:58 +03:00
Vitaly Turovsky
9726257577 fix: do not display capture lock message when possilbe (avoid flickering - do strategy switch)
feat: make tab (players list) keybindign configurable and add a way to assign to a gamepad button
2025-05-22 14:46:44 +03:00
Max Lee
5a663aac2f
fix: item textures in inventory break after loading resourcepack (#362) 2025-05-21 18:53:20 +03:00
Vitaly
7cea1b8755
fix tsc 2025-05-21 18:45:54 +03:00
Vitaly Turovsky
5ea2ab9c1a fix: update tab header/footer in real time, use player display name in tab 2025-05-21 13:55:52 +03:00
Vitaly Turovsky
b36d08528f fix: fix hanging forever server ping and weboscket connections 2025-05-21 06:36:39 +03:00
Vitaly Turovsky
a4e70768dd fix test 2025-05-21 05:53:59 +03:00
Vitaly Turovsky
ecb53fab88 up tracker 2025-05-21 05:46:36 +03:00
Vitaly Turovsky
b2ef71fc19 up mc data, physics, autojump 2025-05-21 05:37:33 +03:00
Vitaly
f4196d6aba
feat(config): Add support for remote dynamic splash text (#358) 2025-05-20 19:50:26 +03:00
Vitaly
4f78534ca4
Update config.json 2025-05-20 19:49:36 +03:00
Maksim Grigorev
7799ccc370
Update src/react/MainMenu.tsx
Co-authored-by: Vitaly <vital2580@icloud.com>
2025-05-20 19:08:34 +03:00
Maxim Grigorev
5efe3508df fix: optimized splash text loading 2025-05-20 22:03:31 +07:00
Vitaly
4d70128ac6
Update config.json 2025-05-20 13:53:26 +03:00
Maxim Grigorev
b4df2e1837 feat: improved splash text loading for better UI 2025-05-20 16:56:38 +07:00
Vitaly Turovsky
83366ec5fa improve Click to capture mouse by not displaying it in less cases 2025-05-20 02:02:43 +03:00
Maxim Grigorev
b2a1bd10e4 fix: improoved the code 2025-05-19 19:26:12 +07:00
Maksim Grigorev
90c283c5ee
Update src/utils/splashText.ts
Co-authored-by: Vitaly <vital2580@icloud.com>
2025-05-19 15:17:36 +03:00
Vitaly Turovsky
67dbd56f14 dont display new hint when gamepad is used 2025-05-19 03:07:01 +03:00
Vitaly Turovsky
517f5d3501 feat(ui): display persistent capture mouse indicator instead of notification 2025-05-19 03:02:23 +03:00
Vitaly Turovsky
7f6fc00f02 fix: fix lever interaction shape 2025-05-19 02:21:23 +03:00
Vitaly Turovsky
f5835f54fa fix: was not possible to open blast furnace GUI 2025-05-19 02:05:14 +03:00
Vitaly Turovsky
970ed614ae fix crash on error message 2025-05-19 02:04:53 +03:00
Vitaly Turovsky
080d75f939 less annoying false block swap animations 2025-05-19 02:01:11 +03:00
Vitaly Turovsky
67d365b9c3 fix: fix crafting on 1.16.5 and below 2025-05-19 01:57:48 +03:00
Vitaly Turovsky
e2a0df748e rorce button height 2025-05-19 00:32:44 +03:00
Vitaly Turovsky
2dc811b2a1 adjust settings visually 2025-05-19 00:23:20 +03:00
Vitaly Turovsky
a5d16a75ef fix(regression): hotbar on mobile was broken 2025-05-19 00:16:18 +03:00
gguio
785ab490f2
fix: restore minimal and full map (#348)
Co-authored-by: gguio <nikvish150@gmail.com>
2025-05-18 09:58:52 +03:00
Vitaly Turovsky
a9b94ec897 fix: fix sneaking didnt work with water 2025-05-17 06:58:33 +03:00
Vitaly
42f973e057
Update config.json 2025-05-17 05:33:31 +03:00
Maksim Grigorev
ff29fc1fc5
feat: Credits modal (#354) 2025-05-16 19:50:38 +03:00
Maxim Grigorev
051cc5b35c fix: optimized splashText working process 2025-05-16 22:47:59 +07:00
Maxim Grigorev
f921275c87 feat: improoved code safety 2025-05-16 20:44:32 +07:00
Maxim Grigorev
2b0f178fe0 feat: improoved the code 2025-05-16 20:35:04 +07:00
Maxim Grigorev
6302a3815f feat: Add remote splash text loading via URLs 2025-05-16 20:19:23 +07:00
M G
0a61bbba75
Fixed button shown even when cursor is not over the entity (e.g., a video) (#356)
fix: button shown even when cursor is not over the entity (e.g., a video)
2025-05-14 18:15:21 +03:00
Vitaly Turovsky
7ed3413b28 fix save is undefined in rare cases 2025-05-14 03:12:55 +03:00
Vitaly Turovsky
75adc29bf0 up mc assets 2025-05-14 03:07:02 +03:00
Vitaly Turovsky
489c16793b fix original name logging on error 2025-05-14 01:24:53 +03:00
Vitaly Turovsky
48cdd9484f annoying contextmenu on windows on right click sometimes was appearing 2025-05-12 18:56:20 +03:00
M G
e2400ee667
Entity Interaction Button works now (#352) 2025-05-12 18:15:45 +03:00
M G
a58ff0776e
server name field "flex-start" now (#351) 2025-05-12 18:09:10 +03:00
Vitaly Turovsky
674b6ab00d feat: add chat scroll debug and use alternative ways of forcing scroll in DOM! 2025-05-08 21:23:04 +03:00
Vitaly Turovsky
25f2fdef4e fix: dont display hand & cursor block outline in spectator. 2025-05-08 19:56:13 +03:00
Vitaly Turovsky
f76c7fb782 fix: water now supports lighting when its enabled 2025-05-08 19:51:42 +03:00
Vitaly Turovsky
aa817139b7 fix: fix auto jump on mobile! 2025-05-08 04:11:17 +03:00
Vitaly Turovsky
58799d973c fix transfer on 1.20 and above (should have added patch a long time ago) 2025-05-08 00:07:33 +03:00
M G
b3392bea6b
feat: Entity interaction button for mobile (#346) 2025-05-07 16:31:36 +03:00
Vitaly
bf3381c803
Do not automatically enable renderer debug in dev 2025-05-07 15:03:48 +03:00
M G
bb9bb48efd
feat: Combine IP and port input fields (#345) 2025-05-06 16:39:42 +03:00
Vitaly Turovsky
28022a2054 feat: rework server data saving and auto login by using more aggressive strategy 2025-05-06 16:36:31 +03:00
Vitaly
1845530e22
Make link clickable in release body 2025-05-04 14:21:05 +03:00
Vitaly Turovsky
b01cfe475d change color and fix rsbuild hoisting var bug 2025-05-04 11:35:43 +03:00
Vitaly Turovsky
22483d7a76 regression: some UIs like settings became not scrollable 2025-05-04 11:24:15 +03:00
Vitaly Turovsky
e250061757 smooth appear panorama on load textures 2025-05-03 23:11:15 +03:00
Vitaly Turovsky
1fd9a29192 up mouse & chunk 2025-05-03 23:01:47 +03:00
Vitaly Turovsky
29c6a3d739 up browserify and mineflayer to fix a few bugs 2025-05-03 12:50:55 +03:00
Vitaly Turovsky
cd7c053a3c fix(critical-regression): player animation was glitching just a lot 2025-05-03 12:50:55 +03:00
Vitaly Turovsky
5bfb9bebd7 f3 hint 2025-05-03 12:50:55 +03:00
M G
951790dad6
feat: Debug Response Time Indicator (#342) 2025-05-03 11:00:31 +03:00
M G
c5f72f2fb3
fix: corrected scroll of main component at client mods; feat: improoved ux (#341) 2025-05-02 19:10:43 +03:00
Vitaly Turovsky
f12de4ea23 fix releasing alias 2025-05-01 15:23:58 +03:00
Vitaly Turovsky
813c952420 fix: fix lava rendering 2025-05-01 12:53:44 +03:00
Vitaly Turovsky
ec142c0ce4 fix heads & server lighting 2025-04-30 18:33:30 +03:00
Vitaly Turovsky
378b668d46 fix: sometimes walking animation was stuck 2025-04-30 18:29:39 +03:00
M G
0d9cb0625e
chore: migrated from react-transition-group to framer-motion (#339)
Co-authored-by: nikandrovaelena93@gmail.com <ashcat2507@gmail.com>
2025-04-30 14:26:59 +03:00
Vitaly Turovsky
221f99ffdf fix: fix players list disappear on dimension switch 2025-04-30 12:17:47 +03:00
Vitaly Turovsky
4f1cb85301 fix: fix p2p where peerjs server works 2025-04-30 08:11:38 +03:00
Vitaly Turovsky
5bf66b8e50 ci: add fixed short link for released version eg v90.mcraft.fun 2025-04-29 04:22:59 +03:00
Vitaly Turovsky
5caca68e8e disable pr update desc 2025-04-28 07:09:54 +03:00
Vitaly Turovsky
95163fb288 update page description and tech 2025-04-27 11:19:29 +03:00
Vitaly Turovsky
0f2e4f1329 add allEntitiesDebug 2025-04-26 10:50:29 +03:00
Vitaly Turovsky
aa0024faa2 experimental mods actions 2025-04-26 10:39:12 +03:00
Vitaly Turovsky
1599917134 feat: falling block & summoned tnt entities rendering support 2025-04-26 10:38:59 +03:00
Vitaly Turovsky
e706f7d086 fix ocelot rendering 2025-04-26 10:38:32 +03:00
Vitaly Turovsky
e20fb8be53 add ?debugEntities for list of supported entities 2025-04-26 10:38:19 +03:00
Vitaly Turovsky
cd2ff62d6d fix: remove skeleton helper which was causing them flying for ALL bedrock entities
fixes #270
2025-04-26 09:51:43 +03:00
Vitaly Turovsky
fa36ed2678 fix tsc 2025-04-26 06:19:05 +03:00
Vitaly Turovsky
305f4d8a31 fix surrogate pair check in js 2025-04-26 05:51:00 +03:00
Vitaly Turovsky
86ef4f268e fix sound id mapping for versions before 1.19.3 2025-04-26 05:32:17 +03:00
Vitaly Turovsky
0c7900a655 fix skins loading on real prod 2025-04-26 05:19:06 +03:00
Vitaly Turovsky
8c37db4051 fix(critical-regression): chunks never loaded when dimension was changed but position chunk was the same (eg spawn 0,0) 2025-04-26 05:14:33 +03:00
Vitaly Turovsky
4ded3b5d2b feat: implement safe features for chat: limit length, avoid sending formatting symbol to avoid kicks (with very nice ux!) 2025-04-26 05:01:20 +03:00
Vitaly Turovsky
db1b72a582 fix: fix bow usage visually 2025-04-25 09:27:41 +03:00
Vitaly Turovsky
89fc31a2c2 feat: holding item/block display for players! 2025-04-25 09:05:49 +03:00
Vitaly Turovsky
01b6d87331 feat: player crouching and better hit animation 2025-04-25 08:29:18 +03:00
Vitaly Turovsky
a654396238 try to not to use unreliable bot.player 2025-04-25 07:40:31 +03:00
Vitaly Turovsky
948a52a2a5 feat: restore skins display with new API (thanks to Nodecraft!). Unfortunately capes are not supported from API anymore. Restore skin display from server properties. 2025-04-25 07:26:23 +03:00
Vitaly Turovsky
d0ac00843d up packets list 2025-04-25 04:45:59 +03:00
Vitaly Turovsky
4ca9a801a8 fix sound.js caching, increase priority of scripts (make difference only in very rare cases) 2025-04-25 04:04:59 +03:00
Vitaly Turovsky
510d163067 allow client to be loaded from other domains 2025-04-24 23:19:12 +03:00
Vitaly Turovsky
97533cfddb cache sounds, report downloading assets 2025-04-24 21:37:49 +03:00
Vitaly Turovsky
b30e7fc152 dont display no sound id mapping in default sp 2025-04-24 05:36:35 +03:00
Vitaly Turovsky
d7fdf18416 feat: jokes over. use a reliable source (thanks to ViaVersion!) for sound id mappings to avoid screamers on mobs in latest versions 2025-04-24 03:40:40 +03:00
Vitaly
28faa9417a
feat: Client side js mods. Modding! (#255) 2025-04-23 09:17:33 +03:00
Vitaly Turovsky
109daa2783 fix imports 2025-04-23 05:59:49 +03:00
Vitaly Turovsky
14ad1c5934 sync fork: add a bunch of not needed side core features like translation
AND fix critical performance regression (& ram)
2025-04-23 05:55:59 +03:00
Vitaly Turovsky
585b19d8dc up mcraft-fun-mineflayer support 2025-04-22 19:19:59 +03:00
Vitaly Turovsky
71f63a3be0 sync fork: add loading timer for debug, better connecting messages, better recording panel 2025-04-22 19:01:04 +03:00
Vitaly Turovsky
2b881ea5ba fix: disable physics for viewer 2025-04-21 16:06:01 +03:00
Vitaly Turovsky
a0bfa275af lets be safer and use 32array 2025-04-19 00:43:31 +03:00
Vitaly Turovsky
193c748feb fix: add chunks a little faster on low tier devices: use indicies 2025-04-19 00:43:20 +03:00
Vitaly Turovsky
c3112794c0 try to fix scroll chat bug 2025-04-19 00:07:41 +03:00
Vitaly Turovsky
dbfd2b23f6 up physics 2025-04-18 23:43:26 +03:00
Vitaly Turovsky
9646fbbc0f fix(regression): hotbar switch on mobile was broken 2025-04-18 23:42:51 +03:00
Vitaly Turovsky
a7c35df959 fix: fix movement sound 2025-04-18 17:46:10 +03:00
Vitaly Turovsky
529b465d32 sync for changes between forks
fix: sometimes autlogin save prompt is not displayed
fix: add connect server only ui
add some other components for future usage
fix: make some fields custommization in main menu from config.json
fix: adjust logic on player disconnect in some rare cases
2025-04-17 20:51:06 +03:00
Vitaly Turovsky
1582e16d3b fix critical regression that led to not loading chunks twice that was a valid behavior before 2025-04-17 19:58:30 +03:00
Vitaly Turovsky
e8b1f190a7 add more debug into to f3+h 2025-04-17 19:58:03 +03:00
Vitaly Turovsky
73ccb48d02 feat: Add F3+H chunks debug screen! not really useful for now since chunks not visible bug was not fixed yet 2025-04-16 18:24:19 +03:00
Vitaly Turovsky
143d4a3bb3 fix: fix double chests. fix inventory crashing when it doesnt know the texture to render 2025-04-16 14:34:51 +03:00
Vitaly Turovsky
f5ed17d2fb fix(critical-regression): FIX broken inventory! There was a huge regression with a month-old inventory update which was breaking it in some ways 2025-04-16 03:23:41 +03:00
Vitaly Turovsky
6a8de1fdfb fix: sometimes auto login suggestion was not visible due to overlapping notification issue 2025-04-16 03:22:25 +03:00
Vitaly Turovsky
a541e82e04 fix: add freezeSettings param 2025-04-15 02:29:09 +03:00
Vitaly Turovsky
c5e8fcb90c feat: add controls debug interface! Debug happening actions in your game & keyboard buttons!!!! 2025-04-14 17:21:22 +03:00
Vitaly Turovsky
7a53d4de63 fix(feedback-controls): prevent default action for side mouse buttons to avoid page leave annoying modal on accidental clicks 2025-04-14 17:06:13 +03:00
Vitaly Turovsky
83502eba60 fix(important): Formatted text display: fix reading extra when its text which might happen in kick messages or server info 2025-04-12 03:10:29 +03:00
Vitaly Turovsky
70557a6282 fix dockerfile 2025-04-11 21:44:40 +03:00
Vitaly Turovsky
7e5a12934c fix: fix looking camera desync in local saves 2025-04-11 21:44:08 +03:00
Vitaly Turovsky
4b85b16b73 feat(experimental-part1): rework chunk loading strategy by forcing spiral order loading into renderer processor and ignoring server order 2025-04-11 21:42:55 +03:00
Vitaly Turovsky
27df313f26 microoptimisation on big number of chunks load 2025-04-11 18:08:21 +03:00
Vitaly Turovsky
77449c5c12 forgot to fix what codereabbit told to fix 2025-04-10 19:33:39 +03:00
Vitaly Turovsky
deb8ec6c0f build sharp to avoid crashes 2025-04-10 19:25:43 +03:00
Vitaly Turovsky
024da5bf6d fix: now media dont receive global lighting by default 2025-04-10 18:54:02 +03:00
Vitaly
c755f085d9
fix: update rsbuild and pnpm to latest version to resolve long-standi… (#328) 2025-04-10 18:53:33 +03:00
Vitaly Turovsky
3c6ee2dbb3 up lockfile 2025-04-10 03:15:01 +03:00
Vitaly Turovsky
bf790861d9 fix: finally fix plants rendering in underwater and near water! 2025-04-10 01:20:29 +03:00
Vitaly
a977d09031
Up physics to fix jump 2025-04-09 16:53:42 +03:00
Vitaly
1fbbf36859
feat: NEW PHYSICS ENGINE - GRIM PASSES >1.19.4 (#327) 2025-04-09 05:51:34 +03:00
Vitaly Turovsky
31d5089e9c should fix rare case when some tiles are not rendered 2025-04-09 05:50:39 +03:00
Vitaly Turovsky
7824cf64a2 fix lint 2025-04-09 05:46:40 +03:00
Vitaly Turovsky
f4bd38fa5c use more reliable bot entity instead of player.entity 2025-04-09 05:30:51 +03:00
Vitaly Turovsky
5adbce39e0 pick changes from webgpu:
fix cursor lines
fix background color change
2025-04-09 05:07:41 +03:00
Vitaly Turovsky
0b72ea61c7 fix: add time to debug overlay 2025-04-09 04:34:58 +03:00
Vitaly Turovsky
33a6f4d088 fix: a little better perf when media is not in use 2025-04-08 17:23:32 +03:00
Vitaly Turovsky
758405da03 [skip ci] add doc 2025-04-07 19:16:16 +03:00
Vitaly Turovsky
4fc8011413 feat(customChannelsPreview): add sections moving animations 2025-04-07 19:00:21 +03:00
Vitaly Turovsky
d347957f64 Update pnpm install command in benchmark workflow to use --no-frozen-lockfile 2025-04-07 17:07:32 +03:00
Vitaly Turovsky
0a85de180e should fix modal not found error on server connect UI 2025-04-07 17:00:39 +03:00
Vitaly Turovsky
cc264e895f Move Cypress from optionalDependencies to dependencies in benchmark workflow 2025-04-07 17:00:16 +03:00
Vitaly Turovsky
4dce591f8b fix if for benchmark 2025-04-07 16:45:48 +03:00
Vitaly Turovsky
b35b88236d use whitelist to active touch cancel hack to avoid issues with side integrations 2025-04-07 16:20:06 +03:00
Vitaly Turovsky
f79472a1da add client info, fix small width data display 2025-04-07 16:09:01 +03:00
Vitaly Turovsky
d1a646ed54 never wait for load waitForChunksRender 2025-04-07 14:21:25 +03:00
Vitaly Turovsky
914dcb6110 add more fixtures support 2025-04-07 14:16:44 +03:00
Vitaly Turovsky
23bab8dbd5 basically fix all the debug panes, record that info 2025-04-07 13:55:18 +03:00
Vitaly Turovsky
881d105c57 always render debug 2025-04-07 13:42:13 +03:00
Vitaly Turovsky
3109be2d8a universal load backend + fallback 2025-04-07 12:49:33 +03:00
Vitaly Turovsky
568ea3d18b adjust default render distance to match testing on ci 2025-04-07 12:49:24 +03:00
Vitaly Turovsky
27e51b65df refactor: rename benchmarkName to fixture and update related logic in benchmark files 2025-04-07 02:26:11 +03:00
Vitaly
0aa4d11bdd
feat: Performance benchmark!! (#153) 2025-04-07 02:21:37 +03:00
Vitaly Turovsky
ce5ef7c7cb remove not neede functionality 2025-04-06 11:09:17 +03:00
Vitaly Turovsky
9b71ae1a24 feat: rework mobile button control sizes & joystick. Make size of every button and joystick configurable via configurator UI from settings! 2025-04-06 00:23:17 +03:00
Vitaly
908fa64f2f
pick most of changes from webgpu for better stability (#322) 2025-04-06 00:22:27 +03:00
Vitaly Turovsky
c025a1c75a fix a lot annoying sentry errors 2025-04-05 18:55:59 +03:00
Vitaly Turovsky
04c37c1eef fix: fix durability is nan 2025-04-05 18:37:21 +03:00
Vitaly Turovsky
dbfadde044 fix(renderer): rendering of ALL blocks of north side was not correct. Fix texture flipping 2025-04-05 11:15:39 +03:00
Vitaly Turovsky
d78a8b1220 add mesher logging functionality for advanced debugging on other client end 2025-03-31 22:55:17 +03:00
Vitaly Turovsky
70fbe1b0e2 dont clip edition 2025-03-31 22:06:30 +03:00
Vitaly Turovsky
394a12b147 fix(important): fix physics crash in powder snow and pink petals 2025-03-31 14:08:56 +03:00
Vitaly Turovsky
f895304380 add debug method and fix f3 custom block name display 2025-03-31 13:32:40 +03:00
Vitaly Turovsky
4f45cd072a fix(perf): dont load gui textures on panorama start in singlefile build
fix: update textures in inventory & hotbar after textures load, including jei
fix: one row of jei was out of the screen
2025-03-31 13:16:57 +03:00
Vitaly Turovsky
5af290ac4e also remove media on world remove, and stop 2025-03-31 11:49:05 +03:00
Vitaly Turovsky
c5c9fd9bcd fix: fix name display on new versions in edge cases 2025-03-31 11:43:21 +03:00
Vitaly Turovsky
1b9b6c954c fix: remove media and custom blocks on world switch, minor fixes 2025-03-31 11:43:07 +03:00
Vitaly Turovsky
3c2ed440b6 up proxy software to avoid new crashes 2025-03-31 08:22:29 +03:00
Vitaly Turovsky
9c6bc49921 fix msg for proxy 2025-03-30 18:01:21 +03:00
Vitaly Turovsky
1a87951bc8 up proxy 2025-03-30 16:09:50 +03:00
Vitaly Turovsky
73e65c6656 try different intersect raycast media approach 2025-03-30 16:09:45 +03:00
Vitaly Turovsky
c324ce29ab fix: always limit error texture dimension to avoid crashes on high width/height 2025-03-30 14:03:50 +03:00
Vitaly Turovsky
b666f6e3c3 ci: disable functionality that should work but doesnt work because github doesnt make any sense 2025-03-29 11:27:10 +03:00
Vitaly Turovsky
115022a21b ci: use github_token var 2025-03-29 11:23:48 +03:00
Vitaly Turovsky
18ee1dc532 ci: fix syntax 2025-03-29 11:15:54 +03:00
Vitaly Turovsky
291ead079a use token: workaround 2025-03-29 11:13:33 +03:00
Vitaly Turovsky
983b8a184b refactor: remove bundle-stats workflow and integrate its functionality directly into CI 2025-03-29 11:01:35 +03:00
Vitaly Turovsky
66fa59a87a ci: add size tracking for minecraft.html and adjust build steps 2025-03-29 10:53:50 +03:00
Vitaly
47864f0023
ci: report size change + always check single file build (#320)
* init

* should be final

* mbmb

* Update .github/workflows/bundle-stats.yml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-03-29 10:41:25 +03:00
Vitaly Turovsky
6f15fcc726 fix: display proxy disconnect message to the client when proxy server is being shutdown 2025-03-29 09:57:57 +03:00
Vitaly Turovsky
af9da93978 enabling parsing kick messages 2025-03-29 09:19:54 +03:00
Vitaly Turovsky
08bb0b6777 fix: fix almost all known inventory update bugs 2025-03-29 09:17:48 +03:00
Vitaly Turovsky
187e9fa6b4 important: fix visual update issues in inventory after server packet 2025-03-29 09:16:36 +03:00
Vitaly Turovsky
4fd290c636 fix(regression): fix rendering almost all items in the game, like shield, banners, beds, filled maps, ... 2025-03-29 06:44:48 +03:00
Vitaly Turovsky
e2f28e4975 fix: fix hotbar & inv texture updates on resources change
fix: delay autologin /login sending
2025-03-29 05:34:32 +03:00
Vitaly Turovsky
c3b4eb953f feat: add much better video support 2025-03-29 04:48:39 +03:00
Vitaly Turovsky
850ae6c2da fix: put just connected server to start of list 2025-03-29 02:01:05 +03:00
Vitaly Turovsky
66b9f58c6f fix error not displayed when crash during world display happens, dont crash world display on entity error 2025-03-29 01:53:54 +03:00
Vitaly Turovsky
b58950bec2 update list of servers 2025-03-28 12:46:05 +03:00
Max Lee
47be0ac865
feat: Item custom model data support (#318)
* feat: Item custom model data support

* rename prop, jsdoc for clarity

* explicit resource manager because it can be run in different threads, up mc-assets

* fix tsc

---------

Co-authored-by: Vitaly Turovsky <vital2580@icloud.com>
2025-03-26 08:22:38 +03:00
Vitaly Turovsky
cd9b796f16 feat: now save actually changed options instead of all options in new localstorage key changedSettings for clarity 2025-03-26 07:12:53 +03:00
Vitaly Turovsky
b32bab8211 fix inscreen button 2025-03-26 06:55:09 +03:00
Vitaly Turovsky
52755fc18f feat(renderer): add toggle for batch chunks display in options GUI 2025-03-26 06:47:21 +03:00
Vitaly Turovsky
f8800d5a31 ci: add step to update deployAlwaysUpdate packages in preview workflow 2025-03-26 06:46:51 +03:00
Vitaly Turovsky
797459b0fc fix: pass custom brand for ws:// servers 2025-03-26 05:08:10 +03:00
Vitaly Turovsky
f8ef748e58 fix: disable chunk batch display by default because of incorrect order and its too slow 2025-03-26 05:01:17 +03:00
Vitaly Turovsky
f161fd31d4 feat(renderer): dont display underground sections. Display chunks only when they are fully loaded 2025-03-26 04:40:11 +03:00
Vitaly Turovsky
3690cb22aa fix classic panorama regression 2025-03-26 04:39:33 +03:00
Vitaly Turovsky
33debc1475 feat(experimental): make loading chunks as smooth as possible by delaying work to get more rendered fps at that time. It helps to eliminate massive fps drops on big chunks of data, however might introduce new possible race conditions.
Also disabled full maps completely because it was hard to optimize atm.
Now chunks load ~30-50% slower but smoother. Note that using waitChunksToLoad settings remains unchanged - time is the same
2025-03-26 03:48:22 +03:00
Vitaly Turovsky
46787309e2 fix: fix camera shake effect! rewrite impl. Fix offhand holding block now can become empty 2025-03-25 22:58:24 +03:00
Vitaly Turovsky
1015556834 use graphics backend id 2025-03-25 21:33:12 +03:00
Vitaly Turovsky
db1c8a1e1a fix: allow custom media to be transparent 2025-03-25 21:25:14 +03:00
Vitaly Turovsky
761c92e27c fix: always set sign text even in rich formatted mode so you dont lose your text if nbt edit command doesnt work (which is the case for the latest versions) 2025-03-25 21:09:48 +03:00
Vitaly Turovsky
118377cbc3 ci: fix relative import path 2025-03-25 04:15:05 +03:00
Vitaly Turovsky
8786448d07 fix tsc 2025-03-25 04:07:34 +03:00
Vitaly Turovsky
2d288153e3 move three.js related files to its own renderer dir 2025-03-25 04:01:43 +03:00
Vitaly Turovsky
a53a6e5f03 fix chests regression 2025-03-25 03:55:06 +03:00
Vitaly Turovsky
237aeec6ac feat: add preventBackgroundTimeoutKick which is disabled by default but can be enabled from advanced settings. Allows to avoid kicking you out of the server when tab is not focused for long time by playing 1hz sound at very low volume to keep tab active 2025-03-25 03:46:08 +03:00
Vitaly
0f3145bb8e
feat: app <-> renderer REWORK. Add new layers for stability (#315) 2025-03-25 02:08:32 +03:00
Vitaly Turovsky
df10bc6f1b feat: add client tps info to f3 2025-03-25 00:50:28 +03:00
Vitaly Turovsky
89a8584060 fix test 2025-03-25 00:48:15 +03:00
Vitaly Turovsky
b6842508ae fix(blockPlacing): fix packets order on latest version. Fix placing end crystals. Fix using hoe and axe. Fix offhand placing. Validate sending placing packets so its 90% accurate
fixes #316
2025-03-24 23:58:16 +03:00
Vitaly
563f5fa007
feat: Add videos/images from source with protocol extension (#301) 2025-03-24 20:17:54 +03:00
Vitaly Turovsky
4a4823fd6a highlight main actions 2025-03-21 04:18:24 +03:00
Vitaly Turovsky
f87e7850ec fix: fix false hurt_animation packet handlings 2025-03-20 22:32:37 +03:00
Vitaly Turovsky
e1758a84d0 fix bossbar crash when server tries to upate/remove non existent one 2025-03-20 05:14:23 +03:00
Vitaly Turovsky
fdd770eeb9 fix: per component error boundary was not working properly crashing whole HUD gui when only component was going out of order 2025-03-20 05:07:55 +03:00
Vitaly Turovsky
abe75c7b8d 10x inventory performance improvements in freq updated inventories eg roulettes 2025-03-20 04:53:44 +03:00
Vitaly Turovsky
6b1a82a6b3 fix possible crash on non existing server data update 2025-03-20 04:37:42 +03:00
Vitaly Turovsky
b0eb73cd76 proper functionality for packets recording at any time: only via console for now 2025-03-20 04:33:26 +03:00
Vitaly Turovsky
8714fd484b fix: fix resource pack = never 2025-03-20 04:23:12 +03:00
Vitaly Turovsky
ba0287f278 fix: preserve list of initial servers when adding server, add always to top
feat: focus server ip input on adding or editing
2025-03-20 04:19:11 +03:00
Vitaly Turovsky
2277020de7 fix swing animations and improve replay server functions 2025-03-18 21:46:44 +03:00
Vitaly Turovsky
c1012a77d0 should also export servers list when requested, fix versions slider 2025-03-17 20:14:39 +03:00
Vitaly Turovsky
1b96577402 update group name 2025-03-17 20:09:57 +03:00
Vitaly Turovsky
5bb09a88bc fix: dont confuse with incorrect version display, allow to use config values as params in real time 2025-03-17 20:08:32 +03:00
Vitaly
36bf18b02f
feat: refactor all app storage managment (#310) 2025-03-17 16:05:04 +03:00
Vitaly Turovsky
da35cfb8a2 up mouse 2025-03-15 16:46:51 +03:00
Vitaly Turovsky
3e056946ec add world download button 2025-03-15 02:20:47 +03:00
Vitaly Turovsky
72028d925d feat: revamp right click experience by reworking block placing prediction and extending activatble items list 2025-03-14 23:25:13 +03:00
Vitaly
897c991a0e
fix: respect main menu links display from config (#308) 2025-03-14 19:53:59 +03:00
Vitaly Turovsky
baa6158872 fix: support custom names search & display in jei 2025-03-14 19:32:02 +03:00
Vitaly Turovsky
a67b9d7aa2 active back all vanilla mechanics like hotbar wheel when replay window is minimized 2025-03-14 19:11:14 +03:00
Vitaly Turovsky
518d6ad866 fix always display reconnect and better last packets display (time) 2025-03-14 02:13:04 +03:00
Vitaly Turovsky
09cd2c3f64 fix(guiRenderer): dont break textures with custom namespaces rendering 2025-03-14 01:50:54 +03:00
Vitaly Turovsky
a8564232f7 fix: make chat arrows work on ios
fix: disable annoying in many cases auto correct on ios (more annoying than useful especially in commands)
fix: make stats dont overlap with chat
fix: fix edgecases when focusing on chat was not possible on mobile
2025-03-14 01:36:14 +03:00
Vitaly Turovsky
91dc4d1007 fix build info alert 2025-03-13 23:27:44 +03:00
Vitaly Turovsky
d921977caf on item giving preserve all metadata & nbt 2025-03-13 23:21:50 +03:00
Vitaly Turovsky
1267dcceae fix: reconnect button sometimes was not displayed 2025-03-13 23:11:12 +03:00
Vitaly Turovsky
c947b285ea fix: fix action bar text was not visible on ios (when landscape) 2025-03-13 23:06:41 +03:00
Vitaly Turovsky
09e61c9aa0 fix: cancel block placements in adventure and when interaction is expected eg crafting table! 2025-03-13 21:57:50 +03:00
Vitaly Turovsky
e1831eea38 fix placing panorama after login (again) 2025-03-13 20:59:07 +03:00
Vitaly Turovsky
214828df0c add option to enable/disable jei even depending on gamemode 2025-03-13 20:46:48 +03:00
Vitaly Turovsky
ad13ab83f2 load panorama earlier 2025-03-12 19:17:02 +03:00
Vitaly Turovsky
fbb3d08bfa use 1.19.4 as version by default because of known issues of configuration stage (like auth doesnt work with some bungee setups, incorrect re-login handling) 2025-03-12 15:59:26 +03:00
Vitaly Turovsky
78313ee225 fix more annoying crash edge cases 2025-03-12 01:55:18 +03:00
Vitaly Turovsky
10ed5b6dfb update to wss 2025-03-12 00:17:24 +03:00
Vitaly Turovsky
87e5ae253d simplify panorama, sfp fixes! 2025-03-12 00:14:16 +03:00
Vitaly
b8b1320258
feat: single file build! (#181) 2025-03-11 23:58:52 +03:00
Vitaly Turovsky
f9c042b00f add groups into UI for future work 2025-03-11 19:55:03 +03:00
Vitaly Turovsky
a6018c6891 remove not working google drive button to avoid confusion 2025-03-11 19:42:13 +03:00
Vitaly Turovsky
ec953fd5d1 feat: never force resort items in the list and allow to resort manually with shift+up/down 2025-03-11 19:37:34 +03:00
Vitaly Turovsky
6263c9ae66 also rename in manifest 2025-03-11 19:22:50 +03:00
Vitaly Turovsky
d5cc6d325e just casually doing a major client rename 2025-03-11 19:21:41 +03:00
Vitaly Turovsky
734d195be0 fix errors spamming from players list 2025-03-11 02:11:36 +03:00
Vitaly Turovsky
14d3bba5f5 rm serverslist storybook 2025-03-11 02:04:22 +03:00
Vitaly Turovsky
e60d10e121 fix panorama seams! 2025-03-11 01:40:56 +03:00
Vitaly Turovsky
8232737a75 fix panorama files order 2025-03-11 01:34:37 +03:00
Vitaly Turovsky
1c2e249031 fix panorama direction 2025-03-10 22:48:51 +03:00
Max Lee
a7e6f9772c
feat: display items in hand of entities (#293) 2025-03-07 15:18:54 +03:00
Vitaly Turovsky
28da4e60f0 hotfix for ui crash on disconnect 2025-03-06 20:12:05 +03:00
Vitaly Turovsky
4cde65e635 hotfix: fix old maps broken regression after gui renderer introduction, fix no connection indicator 2025-03-06 19:35:37 +03:00
Vitaly Turovsky
d35bf41e8c fix: fix displaying of unsigned messages (still need to simplify weird mineflayer parsing) 2025-03-05 23:12:19 +03:00
Vitaly Turovsky
e7b012c08d feat: Display players list on long chat button hold 2025-03-05 22:58:11 +03:00
Vitaly Turovsky
a27fa4cd1d feat: Add interaction hint for touch-based entity targeting 2025-03-05 22:49:36 +03:00
Vitaly Turovsky
c6b8efe4e8 hotfix: should fix edge case when canvas was out of viewport bounds on ios 2025-03-05 22:22:35 +03:00
Vitaly Turovsky
a846eb4500 hotfix: fix world interaction crashes 2025-03-05 20:45:34 +03:00
Vitaly Turovsky
6fb18d4438 fixes & workarounds rendering items in inventory (some were broken since last commit) 2025-03-05 15:26:59 +03:00
Vitaly Turovsky
b9df1bcf9e fix enabling lighting falsey when load for chunks is enabled 2025-03-05 15:12:05 +03:00
Vitaly
0db49e7879
feat: Full support for rendering blocks in inventory GUI powered by deeplsate (#292) 2025-03-05 15:11:42 +03:00
Vitaly Turovsky
998f0f0a85 fix: fix sentry #6092213276 DataCloneError: Cannot decode detached ArrayBuffer 2025-03-05 13:07:21 +03:00
Vitaly Turovsky
465ce35e83 feat: display motd/players info for ws servers (still no icon sadly)
add new server
2025-03-05 13:02:55 +03:00
Vitaly
1c700aac1e
feat(config-json): Only either bundle or load from remote (#291) 2025-03-04 19:00:20 +03:00
Vitaly Turovsky
4b54be637d ci: adjust esbuild build arg syntax for prod 2025-03-03 18:48:27 +03:00
Vitaly Turovsky
1d4dc0ddaa fix define in arg build 2025-03-03 18:45:25 +03:00
Vitaly Turovsky
874cafc75e add self host zip publishing with release 2025-03-03 18:42:08 +03:00
Vitaly Turovsky
2a8f514095 add build zip workflow 2025-03-03 18:24:06 +03:00
Vitaly Turovsky
2619e5da89 fix: was not possible to click notification, make error routing more strict & obvious 2025-03-03 17:42:01 +03:00
Vitaly Turovsky
b0da1e41d6 fix: fix crashes on packets logging recording
fix: make replay panel minmizable
2025-03-03 15:31:25 +03:00
Vitaly Turovsky
10f17063c0 fix: fix whole pipeline of rendering custom items from rp: add them to atlas and update texture propertly. align behavior blocks vs items and gui vs hand/floor 2025-03-03 14:19:38 +03:00
Vitaly
ceb4cb0b66
feat: Refactor mouse controls, fixing all false entity/item interaction issues (#286) 2025-02-27 15:26:38 +03:00
Vitaly Turovsky
fa9c0813c3 fix: seagrass and kelp are always waterlogged 2025-02-27 05:17:18 +03:00
Vitaly Turovsky
dec93c2b64 fix react warning 2025-02-27 04:48:16 +03:00
Vitaly Turovsky
d348a44bb8 add a way to disable recording button on pause menu, refactor 2025-02-27 04:48:03 +03:00
Vitaly Turovsky
dffadbb06c wip jei channel 2025-02-26 23:33:13 +03:00
Vitaly Turovsky
2414111b9c feat: add packets recording control to pause menu, display packets view after recording was started for in real time server packets debug, fix auto captured packets display 2025-02-26 23:29:18 +03:00
Vitaly Turovsky
edad57a225 feat: allow to load client without free space on device (or no write permissions) 2025-02-26 22:56:02 +03:00
Max Lee
52ae41a78d
Add better chat link prompt screen (#290) 2025-02-26 22:33:50 +03:00
Max Lee
8ff05924dd
feat: add config option for pause screen links (#288) 2025-02-26 22:31:22 +03:00
Vitaly Turovsky
322e2f9b44 fix sounds 2025-02-26 22:18:51 +03:00
Vitaly Turovsky
89fd5dde71 add external folder for forks code (ext functionality) 2025-02-26 04:36:15 +03:00
Vitaly Turovsky
deedcda467 correctly merge local config when building 2025-02-26 03:54:25 +03:00
Vitaly Turovsky
ecf55727bc stop publishing UI to npm since no one uses it 2025-02-26 03:52:32 +03:00
Vitaly Turovsky
59cb442225 fix: display notification on user resourecepack enable 2025-02-26 03:33:49 +03:00
Vitaly Turovsky
e8d980b790 add brand new progress reporter 2025-02-26 03:31:02 +03:00
Vitaly Turovsky
acd8144d76 feat: initial config.json is now bundled on build step, which is required for defaultSettings
feat: allow to specify default and locked settings in config.json
feat: allow to specify default app params in config.json
feat: rework how loading progress is reported in app on connect
feat: add setting to wait for chunks to load before starting rendering (which is highly recommended to enable), however not enabled by default because functionality is top priority of the client out of the box, not pleasent ux, so pls enable yourself
2025-02-26 03:29:10 +03:00
Vitaly Turovsky
b7560f716a fix custom block models regression 2025-02-25 23:11:02 +03:00
Vitaly Turovsky
2833b33b4e fix: display err when sound mappings not found 2025-02-25 00:29:34 +03:00
Vitaly Turovsky
077dc9df26 fix: fix hand performance because of unnecessary texture rewrites 2025-02-25 00:02:35 +03:00
Vitaly Turovsky
0b2d676d93 fix: fix a lof of bugs in base transition classes and fix bug with possibly wrong item display in hand 2025-02-25 00:01:56 +03:00
Vitaly Turovsky
6d29413a5d add transparent block model override 2025-02-24 21:53:43 +03:00
Vitaly Turovsky
2f200a876a fix: fix hardcoded sounds played when resource is requested 2025-02-24 21:22:06 +03:00
Vitaly Turovsky
cde239211c fix: change chat completions filtering to be not aggressive 2025-02-24 21:21:48 +03:00
Vitaly Turovsky
2f29a9a5cb fix stupid errors 2025-02-24 07:44:24 +03:00
Vitaly Turovsky
2e3363dce8 a lot of replay code cleanup 2025-02-24 01:18:48 +03:00
Vitaly Turovsky
2cb8bea374 fix types 2025-02-23 21:55:07 +03:00
Vitaly Turovsky
bdea1fc50c a huge progress on packet replay component, fix bugs add switches 2025-02-23 21:52:12 +03:00
Vitaly Turovsky
9613a0e644 fix: fix bossbar flickering 2025-02-23 20:06:47 +03:00
Vitaly Turovsky
75e44407ed fix autocomplete in replay 2025-02-23 20:06:25 +03:00
Vitaly Turovsky
0505b64539 fix: fix shift desync issue, dedupe 2025-02-23 19:08:35 +03:00
Vitaly Turovsky
334e8a502d fix a few bugs in packet, replayer
feat: add a way to ALWAYS inspect 30 packets on disconnect
fix: fix a few server packets
2025-02-23 04:45:33 +03:00
Vitaly
1387cb036b
feat: Replay packets server functionality! (#287)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-02-23 03:48:15 +03:00
Vitaly Turovsky
81a692272c fix: fix sound id mapping for some versions like 1.16.5 2025-02-22 20:57:40 +03:00
Vitaly Turovsky
78d923d817 a way to have packets logger always enabled 2025-02-21 21:33:21 +03:00
Vitaly Turovsky
f0d5ad616d fix: fix visual issue when loading screen disappears
fix: display loading pregress of server resourecepacks
2025-02-21 20:38:24 +03:00
Vitaly Turovsky
ba6a618443 feat: add support for custom sounds and sounds.json 2025-02-21 20:15:49 +03:00
Vitaly Turovsky
a268c69879 feat: rework atlas packing so now almost any resourepack with different tile sizes can be used 2025-02-21 20:15:38 +03:00
Vitaly
2f81bafd75
fix: improve rendering of armor to closer match game and prevent z-fighting (#285) 2025-02-21 17:31:07 +03:00
Phoenix616
7110b8c66d
fix: improve rendering of armor to closer match game and prevent z-fighting 2025-02-20 14:53:42 +01:00
Vitaly Turovsky
8e4987e685 fix: add resourcepack texture processing error catching 2025-02-20 04:02:15 +03:00
Vitaly Turovsky
2ea74b22fd fix: literally fix the notification component 2025-02-20 04:01:48 +03:00
Vitaly Turovsky
730cf656de feat: add detailed block assets parsing (blockstates + models) in F3 on right side (includes parsing errors! super useful for resourcepacks) 2025-02-20 03:41:44 +03:00
Vitaly Turovsky
fda38bbf59 fix custom channel crash 2025-02-20 02:30:33 +03:00
Max Lee
f1c945d22a
fix: baby mobs are not rendered smaller (#283) 2025-02-19 17:00:43 +03:00
Vitaly Turovsky
3b9503982c fix: fix custom item display on 1.21.4, fix text component potential crash 2025-02-19 04:03:32 +03:00
Max Lee
795f241cbd
fix: floor and ceiling button hitboxes (#282)
Co-authored-by: Vitaly <vital2580@icloud.com>
2025-02-19 03:53:36 +03:00
Vitaly Turovsky
5fd3584823 move reconnect button below disconnect 2025-02-19 03:44:50 +03:00
Vitaly Turovsky
11bfcb8f1a fix wasnt possible to click suggestions 2025-02-19 03:40:02 +03:00
Vitaly Turovsky
bfd88ce544 add stale ping status display 2025-02-19 03:33:39 +03:00
Vitaly Turovsky
0bb6301056 feat: now always focus chat when letter or ctrl+v is pressed (like in discord)
feat: hide completions when input is not focused
2025-02-19 03:26:14 +03:00
Vitaly Turovsky
b7e6793c07 do not display invisible ears on steve (default skin) 2025-02-19 03:13:28 +03:00
Vitaly Turovsky
a0a01d9a3f disable possibly invalid entity checks 2025-02-19 03:01:52 +03:00
Vitaly Turovsky
f8c44ae4f0 add mirror server for play.mcraft.fun 2025-02-19 02:57:21 +03:00
Vitaly Turovsky
44d630b1b3 fix entities apply skin crash
fix websocket ping display for ws
2025-02-18 21:25:18 +03:00
Vitaly Turovsky
0cd11ebe29 regression hand disappeared 2025-02-18 20:58:19 +03:00
Vitaly Turovsky
7196623853 minor visual updates to status component 2025-02-18 20:37:15 +03:00
Vitaly Turovsky
612b35426e feat: add ping display view in pause menu! 2025-02-18 20:36:59 +03:00
Vitaly Turovsky
191043380e fix textures loading regression, fix entity tex resizing 2025-02-18 20:10:33 +03:00
Max Lee
d83fbb0393
fix: maps on invisible Item Frames would have gap (#281) 2025-02-18 17:45:47 +03:00
Vitaly Turovsky
9a8451fff7 fix: fix all known issues with chat auto scroll! 2025-02-18 04:28:48 +03:00
Vitaly Turovsky
5e0ece8288 fix arrow file casing 2025-02-18 04:01:17 +03:00
Vitaly Turovsky
c1bf8bf1d7 add arrow obj 2025-02-18 03:53:13 +03:00
Vitaly Turovsky
4223800fd8 fix: improve rendering of some entites like arrow and sheep 2025-02-18 03:47:24 +03:00
Vitaly Turovsky
4a1138f21c fix: fix memory leak with textures when a lot of entities are loaded at the same time (eg big servers like hypixel), make entities def loader faster 2025-02-18 02:06:00 +03:00
Vitaly
5f6ce69b51
feat: custom block models via custom channel (#277) 2025-02-17 19:40:17 +03:00
Max Lee
a20dca18f8
fix: autoConnect parameter wouldn't do anything (#276) 2025-02-17 16:42:31 +03:00
Max Lee
874a3b3ab0
feat: option to disable ears on all skins (#275) 2025-02-17 16:41:52 +03:00
Vitaly Turovsky
847fed5142 fix: handle unsuccessful block breaking packet (eg spawn protection) 2025-02-15 21:28:37 +03:00
Vitaly
65af9a73c2
feat: rework hand! enable by default, fix bow anim (#261)
* refactor swing animation to controller

* idle animator!!!!

* implelment state switch transition

* a huge fix for UI server edit!

* adjust ui scaling so main menu elements clip less

* view bobbing, new config name, ws:

* EXTREMELY important fixes to entities rendering

* a lot of fixes, add dns resolve fallback

* improve f3 E, fix modal not found edge case

* set correctly target for old browsers, should fix ios 14 crash

* unecessary big refactor, to fix ts err

* fix isWysiwyg check

* fix entities rendering count
2025-02-15 05:14:36 +03:00
Vitaly Turovsky
0f29053ca6 fix(important): save username instantly so it doesn't reset when you reload the page if you didn't change it 2025-02-14 14:31:02 +03:00
Vitaly
df4dd69c80 feat: display reconnect button in pause menu when connection seems to be lost 2025-02-13 23:37:34 +03:00
Vitaly Turovsky
2f70575534 finishing support for mcraft viewer plugin:( 2025-02-13 21:31:55 +03:00
gguio
25c07b14a9
feat: some minimap fixes (#249) 2025-02-13 05:07:19 +03:00
Max Lee
cab9eed5e7
fix: resource pack armor textures and leather armor color bleed (#274) 2025-02-12 21:26:23 +03:00
Vitaly
e2ee1ff133
Fix setting of lastError to avoid false spam into console 2025-02-12 16:06:43 +03:00
Vitaly Turovsky
193616b147 fix npm lib building 2025-02-11 17:19:42 +03:00
Vitaly Turovsky
90e002a3a1 fix lint 2025-02-11 17:12:39 +03:00
Vitaly Turovsky
8595d545a5 Restore support for old browsers! Restore and revisit browserslist 2025-02-11 17:11:41 +03:00
Vitaly Turovsky
cf8d8e51fc fix quickconnect port ignored regression 2025-02-11 16:54:58 +03:00
Vitaly Turovsky
946fc26d86 correctly open release link, show build info 2025-02-11 16:26:17 +03:00
Vitaly Turovsky
d05898ab1c display stack on app crash, more advanced logic on app refresh 2025-02-11 15:51:56 +03:00
Vitaly Turovsky
85fbe0cec0 seems all versions auth is supported, remove restriction 2025-02-11 01:08:41 +03:00
Vitaly Turovsky
0dd7b4d802 plugin: register custom channel later 2025-02-11 00:40:52 +03:00
Vitaly Turovsky
f03046573d hotfix: workaround hotbar crash on 1.21.1 2025-02-10 22:47:43 +03:00
Vitaly Turovsky
208f5b7e0d fix chat placeholder 2025-02-10 21:04:40 +03:00
Vitaly Turovsky
d27f2b4a61 fix(big-perf): do not render entities in not loaded chunks that are far away 2025-02-10 21:04:33 +03:00
Vitaly Turovsky
e9e8641f10 should fix build error, fix viewer memory leak 2025-02-10 20:04:04 +03:00
Vitaly Turovsky
cea4d7f277 feat: implement full support for new mineflayer plugin! add UI customization, motion effect, JS repl console and more! 2025-02-10 19:54:38 +03:00
Vitaly Turovsky
dcbeed42ad fix regression https parsing, improve/fix http, ws parsing in all places of app. finally we parsing a single string in a decent way 2025-02-09 23:36:30 +03:00
Vitaly Turovsky
82693ac80c feat: add fov animation that should have added from first release 2025-02-09 22:12:50 +03:00
Vitaly Turovsky
7ab03e3245 ci: fix crash no deploy 2025-02-09 22:12:50 +03:00
Vitaly Turovsky
f560e952d3 add debugToggle 2025-02-09 19:09:13 +03:00
Vitaly Turovsky
c063ff7244 regression fixes
- Modify proxy connection logic to check for server configuration
- Enhance resource pack handling to deny pack if not accepted
- Improve server address parsing by stripping http/https protocols
2025-02-09 13:58:43 +03:00
Vitaly
f96673bc17
rename prismarine-viewer dir to renderer to avoid confusion (#269) 2025-02-08 14:17:27 +03:00
Vitaly
b196ea5955 should fix storybook build 2025-02-08 12:15:14 +03:00
Vitaly
43580511e2 refactor entitymesh to ts 2025-02-08 12:05:15 +03:00
Vitaly
17dc564ef1 fix playground crash, fix entities crash 2025-02-08 11:54:41 +03:00
Vitaly
c0dc516a2d improve button hover ui on mobile 2025-02-08 11:45:08 +03:00
Vitaly
a0bf1e6a71 fix: fix critical bug with joystick when opening other ui
feat: add history tracking of ever connected servers

- Implement server connection history tracking in localStorage
- Update server connection UI with autocomplete and history suggestions
- Refactor connection logic to use new server address parsing
- Improve touch joystick handling with more robust state management
2025-02-08 11:43:08 +03:00
Max Lee
d295100f34
fix: skins of NPCs would not get applied properly (#265)
feat: heads render texture from properties
2025-02-08 10:33:44 +03:00
Vitaly Turovsky
d7374a5206 fix tsc 2025-02-07 14:23:47 +03:00
Vitaly Turovsky
2993bd2e45 fix bugs with blocks models atlas usage 2025-02-07 14:16:01 +03:00
Vitaly Turovsky
c5a895a18e feat: revamp dropped items & holding blocks/items display! now using flexible item definition resolution 2025-02-07 13:54:38 +03:00
Vitaly
9be5950760
feat: Connections issues icon & reconnect button (#268) 2025-02-07 13:01:22 +03:00
Vitaly
c81da88eb7 up guide!!! add what many didnt know 2025-02-07 10:00:30 +00:00
Vitaly Turovsky
3e00dcb3e9 fix: one of the biggest ios fixes yet: reset viewport state (scale) on unexpected vieport changes and fix possible touch camera movement misaldment 2025-02-07 06:58:33 +03:00
Vitaly Turovsky
1b7ccbd77f up workflows 2025-02-06 08:47:12 +03:00
Vitaly Turovsky
3bfaa2980c up mc-assets to resolve issues with some items custom texture rendering 2025-02-06 08:45:05 +03:00
Vitaly Turovsky
09f91d1491 try to use pnpm action setup and recommend usign corepack 2025-02-06 08:40:24 +03:00
Vitaly Turovsky
42275df14d feedback: remove setting default keybindings to arrows to avoid confusion on accidental arrow keys pressing which might happen in super comfy gamer setups like boxes falling on right part of keyboard which is never used 2025-02-05 14:51:44 +03:00
Vitaly Turovsky
e54c262972 fix dropped blocks rendering 2025-02-05 14:38:18 +03:00
Vitaly Turovsky
20830bc257 fix(resourcepack): fix custom items textures
feat: implement support for custom models tags - new minecraft feature for resourcepack for items
feat: dropped blocks rendered as blocks
feat: add support for new
2025-02-05 14:05:23 +03:00
Vitaly Turovsky
6bf4a4f2a3 hotfix: allow complex messages to be displayed in chat 2025-02-05 07:21:42 +03:00
Vitaly Turovsky
97e1395464 up tech 2025-02-04 16:46:23 +03:00
Vitaly Turovsky
2ccc462679 fix eslint 2025-02-04 08:42:31 +03:00
Max Lee
e2a093baf1
feat: enable bossbar by default & support for custom title texts (#259) 2025-02-04 08:11:11 +03:00
Vitaly Turovsky
50baabad90 dont ignore version override! 2025-02-04 07:04:34 +03:00
Vitaly Turovsky
0d778dad80 fix auto wss 2025-02-04 05:22:22 +03:00
Vitaly Turovsky
d4345b19f1 linting 2025-02-04 05:15:41 +03:00
Vitaly Turovsky
b5a16d520a feat: Add WebSocket server support for direct connections! (no proxy url is used then) 2025-02-04 04:56:37 +03:00
Vitaly Turovsky
32acb55526 better loading msg 2025-02-03 11:04:35 +03:00
Vitaly Turovsky
41489130d6 feat: wait for resource pack install before displaying world to avoid chunks reloading 2025-02-03 11:02:12 +03:00
Vitaly
72058f14f2
feat: reimplement auto version - fix bugs, +config, make faster!! (#262) 2025-02-03 10:19:59 +03:00
Vitaly Turovsky
4a77ba15b6 disable dedupe check until usability and performanse problemas are fixed in pnpm 2025-02-03 05:16:46 +03:00
Vitaly Turovsky
84d38ba21c feat: now keyboard arrows control camera rotation 2025-02-03 04:10:45 +03:00
Max Lee
d4e93d4d39
fix: kick screen would allow going back with lockConnect parameter (#253)
refactor qs getters in codebase
Co-authored-by: Vitaly <vital2580@icloud.com>
2025-02-01 06:24:04 +03:00
Max Lee
6ad1a4d63b
feat: item & maps in frame rendering (#243)
Co-authored-by: Vitaly Turovsky <vital2580@icloud.com>
2025-02-01 05:46:05 +03:00
Vitaly Turovsky
b35685fc13 fix lint 2025-02-01 05:03:13 +03:00
Vitaly Turovsky
5bc55c1bdf adjust config merging 2025-02-01 04:30:20 +03:00
Vitaly Turovsky
4cc6767c78 docker: fix volume path to use with public 2025-02-01 04:23:32 +03:00
Vitaly Turovsky
297d94d419 fix sounds enabling in dockerfile 2025-02-01 03:20:13 +03:00
Vitaly
379484327e
refactor: Rework mobile controls! (#260)
Separate movement and interaction touch controls
Add new options for touch control types (modern/classic)
Refactor touch interaction handling and event management
Create a new GameInteractionOverlay component for touch interactions for less bugs in future
Update touch controls UI and positioning logic

Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com>
2025-02-01 02:51:56 +03:00
Vitaly Turovsky
b5a8bf16ff fix critical inventory regression 2025-02-01 02:47:08 +03:00
Vitaly Turovsky
978bd16785 ci: rm --offline flag 2025-02-01 02:34:49 +03:00
Vitaly Turovsky
51d8975f5e chore: Update net-browserify dependency to GitHub source 2025-02-01 02:27:43 +03:00
Vitaly Turovsky
28552bd1de feat: Add option to disable service worker during build 2025-02-01 02:23:47 +03:00
Vitaly Turovsky
ad2ba0e24f feat(server): Add server connection validation and username validation on connect server UI with QS 2025-02-01 02:23:24 +03:00
Vitaly Turovsky
b7da6e201f up browserify 2025-02-01 01:52:04 +03:00
Vitaly Turovsky
81aaaab76d fix(proxy): Improve proxy URL handling for non-standard ports 2025-02-01 00:58:46 +03:00
Vitaly Turovsky
8766eaae21 fix signal crash regression 2025-02-01 00:42:49 +03:00
Vitaly Turovsky
92ce4dd0d8 chore(ci): Update GitHub Actions workflow dependencies
- Upgrade actions/upload-artifact to v4
- Minor workflow configuration refinements
2025-02-01 00:35:06 +03:00
Vitaly Turovsky
f755385981 refactor: rm loadedGameState, add a way to copy server resource pack to local 2025-01-31 05:52:55 +03:00
Vitaly Turovsky
14b7cb039a feat(ui): Improve server list UI
- Implement concurrent server status fetching with rate limiting
- Show offline status for servers that cannot be reached
2025-01-31 04:40:15 +03:00
Vitaly Turovsky
28f0546f3b feat(ui): add back, close buttons in settings for mobile 2025-01-31 03:52:07 +03:00
Vitaly Turovsky
317b84943a feat: Add block highlight color option to allow always it to be blue (useful!) 2025-01-29 19:33:55 +03:00
Vitaly Turovsky
2490fbe211 fix(regression): minecity and some other maps broke 2025-01-29 05:26:46 +03:00
Vitaly
df442338f8
feat(sounds): Add sound variants and resource pack support! (#258)
feat: add in-game music support! Enable it with `options.enableMusic = true`
2025-01-29 04:54:51 +03:00
Vitaly Turovsky
a628f64d37 fix: deactivate mouse buttons when window loses focus (edge case) 2025-01-29 04:03:04 +03:00
Vitaly Turovsky
1f5404be9d DOWNLOAD_SOUNDS flag 2025-01-29 04:02:21 +03:00
Vitaly
3de1089a1c
fix: disable block breaking in adventure (#252) 2025-01-29 03:01:08 +03:00
Vitaly
062115c42b
feat: try to bring tab attention or focus it when you are finally joined the server 2025-01-28 21:29:53 +03:00
Max Lee
41684bc028
fix: title would not get rendered and rendering times be wrong (#254) 2025-01-28 19:42:06 +03:00
Vitaly Turovsky
a442acf49a fix build 2025-01-28 19:25:22 +03:00
Vitaly
ff36f75e44
fix regression to quick elem re-focus 2025-01-27 23:09:32 +03:00
Vitaly Turovsky
91ef3ccd3c wip colored button options 2025-01-27 19:49:27 +03:00
Vitaly Turovsky
e6ce6dc268 feat: enhance OptionButton navigation with shift key support for reverse cycling 2025-01-27 19:47:20 +03:00
Vitaly
5a948828b7
Should fix test 2025-01-27 14:35:33 +03:00
Vitaly Turovsky
6d91ad3d41 add lint-fix script 2025-01-27 06:21:42 +03:00
Vitaly Turovsky
ccc1d760e0 run dedupe 2025-01-27 06:19:06 +03:00
Vitaly Turovsky
463b9ef80a feat: enable global arrows navigation in server/saves list, always focus on first server 2025-01-27 06:19:02 +03:00
Vitaly Turovsky
382a685b65 fix: fix light and bubble_column blocks rendering, fix waterlogging for ocean vegetation (plants) 2025-01-27 06:10:31 +03:00
Vitaly Turovsky
5694d14d58 feat: add camera shake effect when player takes damage 2025-01-27 05:57:58 +03:00
Vitaly Turovsky
491b5d66c5 feat: implement player skin display based on texture properties from server packets eg your custom plugin skins! 2025-01-27 03:48:42 +03:00
Vitaly Turovsky
3fc644082a update button disabled state to depend on recorded packets 2025-01-27 03:40:24 +03:00
Vitaly Turovsky
7186b183f1 feat: add packets logger preset option and process packet data based on preset 2025-01-27 02:22:31 +03:00
Vitaly Turovsky
84bc3d5911 add recommended but optional mount volume to dockerfile config 2025-01-26 15:25:50 +03:00
Vitaly Turovsky
540f90fd0c ci: fix build 2025-01-25 22:37:24 +03:00
Vitaly Turovsky
1a45381b3d fix: fix maps display for 1.21+ 2025-01-25 20:25:52 +03:00
Vitaly Turovsky
e0be30b86f fix: adjust FOV calculations for sprinting and flying to increase field of view instead of decreasing it 2025-01-25 20:02:15 +03:00
Vitaly Turovsky
7d6986ccb4 update all deps 2025-01-25 19:38:50 +03:00
Vitaly Turovsky
798eb34466 fix: fix Y keybinding: fix URL on server, add proxy and username parameters 2025-01-25 18:39:26 +03:00
Vitaly Turovsky
77e529b6f4 feat: ctrl or cmd + Escape now closes all modals! 2025-01-25 18:34:18 +03:00
Vitaly Turovsky
f2fde37e6a ci: update patch, should fix dedupe 2025-01-22 21:04:14 +03:00
Vitaly Turovsky
61659d82b4 feat: experimental namespaces support in resource packs 2025-01-22 20:53:30 +03:00
Vitaly Turovsky
4ce95de03f fix: fov changing based on player state was changed incorrectly 2025-01-22 20:03:54 +03:00
Vitaly Turovsky
d0f7f57400 disable parsing of disconnect kick messages that works in the wrong way 2025-01-22 20:02:07 +03:00
Vitaly Turovsky
b052422c99 ci: fix update CI workflow condition for dedupe-check to use pull request head ref 2025-01-16 18:09:49 +07:00
Vitaly Turovsky
ce8e414528 fix: sometimes kick messages where not formatted 2025-01-16 18:04:33 +07:00
Vitaly Turovsky
8f91d282d2 ci: add deduped packages check 2025-01-16 18:01:54 +07:00
Vitaly Turovsky
b946271d87 pnpm dedupe: remove a lot of duplicated packages 2025-01-16 17:35:27 +07:00
Vitaly Turovsky
102a3e499b fix mineflayer, prismarine package versions 2025-01-16 17:27:34 +07:00
Vitaly Turovsky
2edf425ecf fix: fix 1.20.3 and 1.20.4 was not playable, fix issues on these versions 2025-01-16 14:37:05 +07:00
Vitaly Turovsky
5d480d4265 fix: full support for minecity, no more map loading crashes 2025-01-14 17:31:01 +07:00
Vitaly Turovsky
a5bd38619c fix test 2025-01-13 10:39:12 +07:00
Vitaly Turovsky
cbd90aeb53 add release pr auto opening 2025-01-13 10:38:29 +07:00
Vitaly Turovsky
c070fe836f fix type regression 2025-01-12 12:58:36 +07:00
Vitaly Turovsky
30dd64a3c6 feat: add 1.20.4 data
fix: data for some versions were missing like 1.16.4 or 1.16.5
2025-01-10 15:58:13 +07:00
Vitaly Turovsky
87b795bdc4 fix(regression): ip qs param was ignored
fix: add local (gitignored) config that is merged into single config after the build
2025-01-09 21:44:59 +07:00
Vitaly Turovsky
36747e6bef add screen edges debug component (#edges in url)
fix: provide possible fix for rare issue when mobile control elements were going outside of the screen on ios
2024-12-21 00:00:27 +03:00
Vitaly Turovsky
9dad509bc2 feat: improve main menu and sign in UIs. Add QR code so you can login to MS account on mobile 2024-12-20 23:18:16 +03:00
Vitaly Turovsky
d754593849 add christmas textures, update docs, fix singleplayer initial gamemode 2024-12-20 22:37:16 +03:00
Vitaly Turovsky
a064892a9b fix rare crash on auth error (cancel server connect later) 2024-12-20 17:54:38 +03:00
Vitaly Turovsky
dd7c9c172e feat: add modal query parameter for page load
fix: improve servers list UI for small width screens
2024-12-19 20:44:27 +03:00
Max Lee
cb82963b86
feat: Armor Stand and armor model support (#242)
Co-authored-by: Vitaly <vital2580@icloud.com>
2024-12-19 15:24:39 +03:00
Vitaly Turovsky
16fe17edf5 fix lint 2024-12-18 22:49:17 +03:00
Vitaly Turovsky
db7a9b9dd2 feat: add zoom keybinding (Key - C) 2024-12-18 22:22:11 +03:00
Vitaly Turovsky
bf676cdf52 up mc-assets 2024-12-18 14:12:09 +03:00
Vitaly Turovsky
b13c8df469 implement auto version configuration for viewer 2024-12-18 13:59:55 +03:00
Vitaly Turovsky
c289283e7f meaningful errors 2024-12-18 12:57:43 +03:00
Vitaly Turovsky
10e14bd675 add viewer connector impl 2024-12-18 12:53:54 +03:00
Vitaly Turovsky
10ee4c00ae feat: initial support for websocket (direct connection) servers. mcraft-fun-mineflayer plugin 2024-12-18 12:53:27 +03:00
Vitaly Turovsky
961cf01a0e allow inspectFn to be function eg for debugger statement 2024-12-18 11:01:50 +03:00
Vitaly Turovsky
2c0b99ffdb fix wrong cache hit on local dev 2024-12-18 10:23:58 +03:00
Vitaly Turovsky
51c1346456 print debug duration 2024-12-18 09:20:59 +03:00
Vitaly Turovsky
064d70480d fix: a lot of edge case world block updates fixes & improvements. Fix all known visual incosistencies after chunk edge block updates, make them faster 2024-12-18 09:06:31 +03:00
Vitaly Turovsky
7b0ead5595 fix renderer string 2024-12-17 11:00:40 +03:00
Vitaly
ee257d7916
Docs eaglercraft change (#239)
A lot misleading information removed
2024-12-16 19:16:58 +03:00
Vitaly Turovsky
45b8ae6f7c docs: more comparison eagler 2024-12-15 17:54:22 +03:00
Vitaly Turovsky
652120c71b docs: more eaglercraft comparison 2024-12-15 17:48:05 +03:00
Vitaly Turovsky
963a769db3 docs: change description to avoid confustion with eaglercraft project 2024-12-15 17:00:59 +03:00
Vitaly Turovsky
5f87385486 make title selectable 2024-12-14 10:34:28 +03:00
Vitaly Turovsky
372583be7d fix ci build type checks 2024-12-14 10:31:12 +03:00
Vitaly Turovsky
725f6ec364 restore hand display setting 2024-12-14 08:17:01 +03:00
Vitaly Turovsky
ad0502dcb9 recenter edition in ui 2024-12-14 08:09:09 +03:00
Vitaly Turovsky
9b5155d3fe up singleplayer version 2024-12-14 08:08:58 +03:00
Vitaly Turovsky
b10c6809ff fix: fix signs could not be rendered properly because font was not in the build 2024-12-14 08:08:30 +03:00
Vitaly
4d411fe561
feat: true hand display behind the setting (#217) 2024-12-13 21:04:45 +03:00
Vitaly Turovsky
c783094068 suppress test error 2024-12-13 20:56:20 +03:00
Vitaly
13a55b4414 update all latest models! fix test 2024-12-12 14:46:45 +03:00
Vitaly
0624e018dd fix critical but rare bug in new change worker implementation 2024-12-12 14:17:25 +03:00
Vitaly
a4c86d707b fix: update changed blocks quicker in the world by using a dedicated mesher thread reserved for blocks updates only 2024-12-12 09:18:54 +03:00
Vitaly
689bebde3d cleanup eyeHeight code 2024-12-12 07:45:20 +03:00
Vitaly Turovsky
376c358d43 feat: add support for 1.21.3 2024-12-12 07:43:46 +03:00
Vitaly Turovsky
5902918729 fix: finally fix issues with ray casting on small distances. which was easy to notice on blocks like buttons when very close 2024-12-12 07:31:00 +03:00
Vitaly Turovsky
c5f6c087ac ci: restore linkin preview domains 2024-12-12 02:07:36 +03:00
Vitaly Turovsky
75965203fc fix: replace Available Offline text with Downloaded to avoid confustion when the app is Offline 2024-12-12 02:05:53 +03:00
Vitaly Turovsky
2d77bdb9b2 ci: allow setting multiple aliases 2024-12-12 01:30:51 +03:00
Vitaly Turovsky
50d1d37ff3 dev: lock url so app restores the same game state after reloads 2024-12-11 08:38:46 +03:00
Vitaly Turovsky
37b84ae003 ci: fix use pr commit SHA, not base branch 2024-12-10 07:17:11 +03:00
Vitaly Turovsky
74cb815940 feat: support for multiple sources for mapDir url in case if first source can't be fetched 2024-12-10 07:03:55 +03:00
Vitaly Turovsky
b02b250ee8 ci(preview): correctly set pr number in url 2024-12-09 09:48:16 +03:00
Vitaly Turovsky
68dba89bf5 ci: update paths for PR and commit redirect index.html in preview workflow 2024-12-09 09:32:37 +03:00
Vitaly Turovsky
2f21e2b453 write pr & commit redirect 2024-12-09 09:28:14 +03:00
Vitaly
3c5c8b78e3 implement changeBackgroundColor method 2024-12-07 03:27:48 +03:00
Vitaly
18a191a03c feat: add ping display on mobile & via f3, playground ui improvements 2024-12-07 01:57:54 +03:00
Vitaly
3bacef1251 fix: fix race condition when state id change was received from server faster than chunk was processed by mesher: now the change will await for the chunk load first instead of ignoring it. sync common code from webgpu 2024-12-07 01:54:00 +03:00
Vitaly
5783b98e74 fix: spectator: display cursor block in f3, hide hotbar and hunger 2024-12-06 09:53:13 +03:00
Vitaly
84dce0941c write build info to all deploys!
(cherry picked from commit 4afccbefbdf96beb62fbc2ea0d442d9f7cd1c370)
2024-12-04 23:19:37 +03:00
Vitaly Turovsky
cf7c4664f2 copy .map file for mesher in dev for better dx 2024-12-03 10:06:18 +03:00
Vitaly Turovsky
0b8eaa4ad2 fix: fix renderer bug cactus was exposing the world 2024-12-03 03:54:43 +03:00
Max Lee
dd20994f78
feat: Text display orientation and rendering fixes (#235) 2024-11-27 23:12:43 +03:00
gguio
af088d92e1
feat: minimap and full screen map (disabled by default) (#147) 2024-11-27 23:07:28 +03:00
Vitaly Turovsky
b89cf522a0 docs: move general params to top 2024-11-25 16:26:30 +03:00
Max Lee
50f35cf176
Fix rendering of custom names and more text display options (#233)
Fix rendering of custom names of normal entities and add more text display options. Newly supported options for text displays:
- background color
- text opacity

Also exclude `mcDataTypes.ts` from linting and fix a possible exception due to no `entityData` being defined
2024-11-22 21:24:45 +01:00
Vitaly Turovsky
c97dbbde9a feat: add query parameter to specify your custom server lists to display in UI: ?serversList
feat: custom proxy is applied in servers list ui when ?proxy is specified
feat: add a way to quickly copy server list with ctrl+c in servers list ui
2024-11-22 15:44:08 +03:00
Vitaly Turovsky
b881a61610 fix: improve output commands formatting, especially errored ones
feat: add gamerules support: basic data parsing, now possible to control/change them via /gamerule only these gamerules effect implemented: doDaylightCycle
fix: better messaging from output commands (now green)
2024-11-22 15:16:20 +03:00
Vitaly Turovsky
c44ad90acf add finally a way to get entities metadata by key 2024-11-21 14:56:39 +03:00
Max Lee
4dac577dfc
feat: basic support for text_display entity (#230) 2024-11-20 09:04:59 +01:00
Vitaly
f2552e70e1
fix ci (#231) 2024-11-20 09:03:10 +01:00
Vitaly Turovsky
c441792d3f fix failing test ci 2024-11-19 23:00:32 +03:00
Vitaly Turovsky
85ece5b4eb fix: some keybinding names were incorrectly parsed
feat: make fullscreen button (F11) configurable in the keybinding panel
2024-11-18 18:53:31 +03:00
Vitaly Turovsky
0506d9de47 fix(ui): better support formatted minecraft messages (handle packets in general way) 2024-11-14 15:40:33 +03:00
Vitaly Turovsky
a0a2c628b4 fix: preserve server selection after going back to the servers list (eg from edit modal)
fix: was not possible to remove saved MS account by clicking on the profile picture
fix: highlight active zone for interaction to avoid UI confusion
fix: save the proxy to memory immedieately after editing it in the servers list screen
2024-11-14 13:41:28 +03:00
Vitaly Turovsky
e28608f86e also cache manifest.json into offline 2024-11-11 18:04:54 +03:00
Vitaly Turovsky
c8c4e3267d feat: auto save worlds in singleplayer every 2 seconds since crashes might happen. still possible to cancel this behavior via new setting or ?noSave=true
feat: add setting for debugging to disable signs text rendering
2024-11-11 17:49:33 +03:00
Vitaly Turovsky
d32f510744 fix: Error messages were not displayed after unsuccessful Microsoft auth. Fixed all error messages. 2024-11-10 11:44:29 +03:00
Vitaly Turovsky
32931efef0 feat: implement experimental clipWorldBelowY setting for testing 2024-11-09 13:01:37 +03:00
Vitaly Turovsky
574cdfc531 docs: add deploy to Koyeb button 2024-11-09 11:19:40 +03:00
Vitaly Turovsky
c303a0e0d5 feat: allow singleplayer mode to be triggered by 'sp=1' query parameter along with singleplayer=1 2024-11-07 19:09:17 +03:00
Vitaly Turovsky
7284d88ae9 update npm banner to a working one 2024-11-05 13:37:30 +03:00
Vitaly Turovsky
270da682da ci: fix release info writing 2024-11-05 13:12:15 +03:00
Vitaly Turovsky
ab3174a45f hotfix: disable fly after going out of spectator mode 2024-11-05 07:10:57 +03:00
Vitaly Turovsky
0f2bc5c1d4 fox issues with chat on 1.8 2024-11-05 07:07:04 +03:00
Vitaly Turovsky
dcc0960d7f fix the auth check 2024-11-04 02:51:37 +03:00
Vitaly Turovsky
c279b4bbe6 do a restoredData validation 2024-11-04 02:49:23 +03:00
Vitaly Turovsky
b267cb77be send connecting version to auth endpoint 2024-11-04 02:32:26 +03:00
Vitaly Turovsky
b3a089323f fix auth err message 2024-11-04 02:23:15 +03:00
Vitaly Turovsky
2c441c3434 fix: fix display names in inventories on 1.8 2024-11-02 12:45:47 +03:00
Vitaly Turovsky
ad9f5be486 spectator gamemode fixes 2024-11-02 12:45:27 +03:00
Vitaly Turovsky
5405987de3 fix: fix entities display on old versions like 1.8 2024-11-02 12:45:11 +03:00
Vitaly Turovsky
667eff49af feat: write published version name on prod website! 2024-11-01 06:40:37 +03:00
Vitaly Turovsky
26d25b77f5 feat: format disconnect messages from minecraft servers (display correctly)
fixes #26
2024-11-01 02:55:19 +03:00
Vitaly Turovsky
7d0c3643d3 fix: don't reset the world in mesher after resourece pack textures update 2024-10-31 01:34:21 +03:00
Vitaly Turovsky
5791626cc5 fix: (latest) or (offline) status text was not displayed sometimes after the page load 2024-10-31 00:28:54 +03:00
Vitaly Turovsky
84b3f8913d fix: don't make text move in loading screens 2024-10-30 23:53:45 +03:00
Vitaly
03c6a3f724
feat: server resource packs (#215)
Co-authored-by: Mqx <62719703+Mqxx@users.noreply.github.com>
2024-10-30 14:40:18 +03:00
Vitaly Turovsky
f13c4e4581 feat: turn back smooth lighting on old maps 2024-10-30 14:23:45 +03:00
Vitaly Turovsky
2fbfc18d2e feat: optimize slabs render performance by rendering less not visible tiles
Improve performance in Greenfield by 6%
2024-10-30 11:33:26 +03:00
Vitaly Turovsky
7d699f24bb fix: some areas in old world were competely white 2024-10-30 08:42:27 +03:00
Vitaly Turovsky
547525d615 fix: fix some blocks were rendered as transparent in old versions. Speed up Greenfield renderer by 18% in tunnels. 2024-10-30 08:40:44 +03:00
Vitaly Turovsky
900bcb0b56 fix: don't crash and conflict with g663 spyware installed 2024-10-30 07:02:33 +03:00
Vitaly Turovsky
dbd4058912 should fix publishing to npm 2024-10-28 17:57:40 +03:00
Vitaly Turovsky
153101fa6f ci: fix lint 2024-10-28 05:49:27 +03:00
Vitaly Turovsky
d497299235 feat: 1.21.1 support 2024-10-28 05:48:51 +03:00
Vitaly Turovsky
6b23eb6bad fix: never get stuck in unloaded chunks! @sa2urami
feat: fully supported spectator mode & basic creative fly fixes
2024-10-28 05:07:52 +03:00
Vitaly
5fa019e7b3
fix: don’t display advanced stats on prod deploy 2024-10-24 14:36:44 +03:00
Vitaly Turovsky
ebb5056540 ci: refactor deployment workflow in preview.yml by removing unused checks and adding alias retrieval step 2024-10-23 02:31:04 +03:00
Vitaly Turovsky
ece59e1744 ci: update event trigger from pull_request to pull_request_target in preview.yml (fix) 2024-10-23 02:20:44 +03:00
Vitaly
8955075d75
ci: trying to fix auto preview workflow! (#221) 2024-10-23 02:11:11 +03:00
Vitaly Turovsky
427ec21213 check that! 2024-10-23 02:03:05 +03:00
Vitaly Turovsky
42cc0bd818 ci: add push event trigger and refine deployment conditions in preview.yml for improved deployment handling 2024-10-23 01:38:55 +03:00
Vitaly Turovsky
9a7a13c2dd ci: enhance trigger conditions for deployment in preview.yml using variables for issue comments and pull requests 2024-10-23 01:00:34 +03:00
Vitaly Turovsky
e35873e106 ci: refactor variable usage from env to vars for PR check in preview.yml 2024-10-23 00:43:43 +03:00
Vitaly Turovsky
f900d6933c ci: simplify PR number check logic using fromJSON in AUTO_DEPLOY_PRS in preview.yml 2024-10-23 00:42:04 +03:00
Vitaly Turovsky
6354ba6bb8 ci: update variable name from env to vars in AUTO_DEPLOY_PRS check in preview.yml 2024-10-23 00:36:58 +03:00
Vitaly Turovsky
95c185fc0b fix: correct syntax for checking PR numbers in AUTO_DEPLOY_PRS in preview.yml 2024-10-23 00:35:33 +03:00
Vitaly
6c994a54f0
autodeploy PRs (#218) 2024-10-22 23:41:19 +03:00
Vitaly Turovsky
de6e82d94f quickly copy your positions with /pos in singleplayer 2024-10-22 23:11:42 +03:00
Vitaly Turovsky
347d155884 fix: improve playground by allowing sync world for fast iterating of advanced use cases 2024-10-22 19:28:41 +03:00
Vitaly Turovsky
70867564ed restore a way t ocreate worlds by enter 2024-10-21 00:41:35 +03:00
Vitaly Turovsky
a4180500d1 update types after minecraft-data types update 2024-10-21 00:05:33 +03:00
Vitaly Turovsky
b21146b92a fix: fix crafting in singleplayer
fix(generator): leaves were filled with water in new versions
fix(generator): set plains biome for light grass color
2024-10-20 23:54:25 +03:00
Vitaly Turovsky
c53ba87309 feat: add a setting to either completely hide perf stats or enable more advanced display
can be used for demos like: `?setting=renderDebug:"none"`
2024-10-20 20:27:03 +03:00
Vitaly Turovsky
a1c41e8767 fix: up deps to support 1.20.5 and 1.20.6
fix: fix raycast. Wasn't possible to active the block when inside of it
2024-10-20 18:26:05 +03:00
Vitaly
e19980800b
feat: rewrite playground from scratch + extras (#202) 2024-10-18 02:27:45 +03:00
Vitaly Turovsky
0368e12635 feat: add crafting and fall damage in survival 2024-10-17 15:58:45 +03:00
Valery Koultchitzky
bdcde9a4bb
fix: add a way to disable VR button (important for android users) (#209) 2024-10-17 14:31:01 +03:00
Vitaly Turovsky
af0d7d14ec fix: stop requesting server info on connect 2024-10-17 14:09:22 +03:00
Vitaly
bd180ef652 fix: correctly lock URL when connected to a server (Y) 2024-10-01 10:18:52 +03:00
Vitaly
0a0b87bee6
fallback p2p discovery server (#211) 2024-10-01 01:39:15 +03:00
Vitaly Turovsky
5b56518122 should fix build 2024-10-01 01:35:01 +03:00
Vitaly Turovsky
ab5f6ab448 fix: add fallback peerjs discovery server to bypass geo restrictions and because sometimes official server is down
feat: allow to use custom peerjs server via config
2024-10-01 01:34:42 +03:00
Vitaly Turovsky
2953554c53 fix(regression): player walking animation was broken 2024-09-28 03:28:27 +03:00
Vitaly Turovsky
00150dda1d fix: inventory UI crash in some cases with some specific window titles
fix: client messages were not displayed on the latest version
2024-09-28 02:57:18 +03:00
Valery-a
40f81d84cd
server change (#207) 2024-09-28 01:43:47 +03:00
Vitaly Turovsky
3ea95d509a fix: fix github pages main deploy 2024-09-19 02:34:45 +03:00
Vitaly Turovsky
9bac681c29 use correct zombie model 2024-09-12 23:38:45 +03:00
Valery-a
7da41b02c9
fix: fixed zombies and husks not having texture (#203) 2024-09-12 23:00:58 +03:00
Vitaly Turovsky
18a6f2c1f5 fix: rare case where digging animation was not cancelled after actual dig cancel after respawn 2024-09-12 04:32:37 +03:00
Vitaly Turovsky
33437823f3 disable outdated packages check for now 2024-09-11 22:40:41 +03:00
Vitaly Turovsky
d0b921a48e revert update current ref 2024-09-11 22:39:51 +03:00
Vitaly Turovsky
16bb43c7d9 update current ref 2024-09-11 22:16:26 +03:00
Vitaly Turovsky
5a3fb6f225 disable Java integration test because of issues with downloading server 2024-09-11 22:06:14 +03:00
Vitaly Turovsky
755eead976 correctly capture screenshots of cypress 2024-09-11 21:51:12 +03:00
Vitaly Turovsky
25db002bc3 redirect to correct playground url 2024-09-11 21:39:15 +03:00
Vitaly
74fe84e10d fix playground on windows: rsbuild does not implement --root option for win 2024-09-11 19:27:17 +03:00
Vitaly Turovsky
76bed4d496 eslint: ignore dist linting! 2024-09-11 03:34:55 +03:00
Vitaly Turovsky
4d3c92f611 some playground fixes 2024-09-11 03:19:24 +03:00
Vitaly Turovsky
1446ccc0a7 should fix playground build 2024-09-11 02:35:29 +03:00
Vitaly Turovsky
f9b87d5087 move playground to rsbuild! now fast reloads!
reload on workers change

fix cd
2024-09-11 02:28:56 +03:00
Vitaly Turovsky
18bf1aa80a feat: The commit also adds a new keybind action for the 'F4' key, allowing the user to cycle through different game modes. Depending on the current game mode, the bot's chat command is updated accordingly. 2024-09-10 20:00:09 +03:00
Vitaly Turovsky
2c971f331e fix: update entities tracker which should fix playing walking animations when players are standing still 2024-09-10 01:34:26 +03:00
Vitaly Turovsky
a5dddfaad5 up mineflayer-auto-jump 2024-09-10 01:20:39 +03:00
Vitaly Turovsky
c6c25a7bb9 up again 2024-09-10 00:52:39 +03:00
Vitaly Turovsky
3fb872129e fix: update autojump module 2024-09-10 00:29:30 +03:00
Vitaly Turovsky
ad8dc1a21a fix: fix compatibility with some versions of new region format files 2024-09-08 21:03:11 +03:00
Vitaly Turovsky
a3ef16a81a ci: checkout pr before merge 2024-09-08 18:40:37 +03:00
Vitaly Turovsky
f518dce04d ci: checkout pr before merge 2024-09-08 18:39:13 +03:00
Vitaly Turovsky
e89196041e ci: update commands 2024-09-08 18:37:06 +03:00
Vitaly Turovsky
f9a4960c31 ci: try to fix the commands 2024-09-08 18:28:55 +03:00
Vitaly Turovsky
d743981fc2 up workflow files: new commands 2024-09-08 18:20:34 +03:00
Vitaly Turovsky
fad9fd6e3a fix: provide a hack to just render blocks all the blocks even with unknown states for preflat versions 2024-09-07 19:48:08 +03:00
Vitaly Turovsky
a063a0d75b fix: fix cobblestone_wall and player head (skull) rendering in preflat versions 2024-09-07 19:42:50 +03:00
Vitaly Turovsky
1c7fdc21a6 add a way to to disable neighbor chunk updates and all the UI (needed for perf testing) 2024-09-07 19:33:16 +03:00
Vitaly Turovsky
a30106342e feat: add a way to disable some of the UI parts in settings (for testing and other use cases)
feat: re-add bossbars, but it's still disabled by default
2024-09-07 18:37:57 +03:00
Vitaly Turovsky
9160ff33c2 fix: fix joining to some popular servers (since dns was resovle was incorrectly used)
fix: fix some auth issues when starting the app locally
2024-09-07 17:46:34 +03:00
Vitaly Turovsky
cd9ead74d2 fix(regression,critical): chunks were stopped loading after moving to another chunk
fix: fix command blocks parsing for most versions
fix: chat was not working for 1.19+
fix: better range adjustment for plate command block activation
fix: was not possible to change the warp
2024-09-06 10:23:46 +03:00
Vitaly Turovsky
3f761430d7 change name of builtin server 2024-09-06 03:59:07 +03:00
Vitaly Turovsky
8e330c0253 feat(devtools): downloadFile global function
fix: local server wasnt saving the time of the world
feat(advanced): add a way to specify local server options
2024-09-06 03:48:54 +03:00
Vitaly Turovsky
d6964b89eb fix typings 2024-09-05 18:23:05 +03:00
Vitaly Turovsky
ea5a48967b fix: improve signs viewer by allow to copy position and select signs 2024-09-05 16:48:04 +03:00
Vitaly Turovsky
9dfff40afd fix(regression): fix dropped items display (for preflat versions it is still broken like many other entities display) 2024-09-05 15:13:26 +03:00
Vitaly Turovsky
65ba687e08 force make crosshair transparent to avoid issues with old darkreader safari version 2024-09-04 19:17:02 +03:00
Vitaly Turovsky
a9d2104dbf fix: Force disable dark reader as it was making the crosshair (reticle) black 2024-09-04 19:14:09 +03:00
Vitaly Turovsky
c6ea9f79dc fix(regression): instruct the browser that the web app uses dark mode (which fixes some edge-case issues) 2024-09-04 19:02:02 +03:00
Vitaly Turovsky
266d34c1cf fixup for player pos in chunk loader 2024-09-04 05:48:00 +03:00
Vitaly
5aaa687392
feat: display progress of downloading chunks visually (#195) 2024-09-04 05:03:17 +03:00
Vitaly Turovsky
306f894d8c fix(important,singleplayer): stop loading chunks in previous position when teleported to a new pos and always start loading chunks in new pos 2024-09-04 03:18:56 +03:00
Vitaly Turovsky
9e7711e386 add eaglercraft as alternative, fix types again 2024-09-03 03:15:17 +03:00
Vitaly Turovsky
684261e515 fix building, update test types 2024-09-03 03:08:19 +03:00
Vitaly Turovsky
c2a34ea9f1 fix(preflat-worlds): improve mesher performance by 2x by syncing the code from webgpu branch
fixes #191
2024-09-03 02:48:16 +03:00
Vitaly Turovsky
698fb1d388 fix tsc 2024-09-03 01:13:12 +03:00
Vitaly Turovsky
559f535207 don't lie of resoure pack support 2024-09-03 01:11:49 +03:00
Vitaly Turovsky
b2ac80602c feat(important): redirect to origin website from maps.mcraft.fun which makes testing maps so much easier on preview deploys and locally 2024-09-03 01:10:11 +03:00
Vitaly Turovsky
0d3a3affd7 fix recently introduced bug with crafting in singleplayer 2024-09-03 01:00:54 +03:00
Vitaly Turovsky
00dd606091 [skip ci] cleanup starfield code 2024-09-02 23:50:46 +03:00
Vitaly Turovsky
574dbafc28 fix(renderer,important): fix all known rendering issues with starfield by @sa2urami 2024-09-02 23:46:22 +03:00
Valery-a
66d26ad2e6
feat: add visuals for entities damaging (#186) 2024-09-01 17:53:48 +03:00
Vitaly
ee966395c6
feat: Display holding block (experimental setting) (#190) 2024-09-01 03:32:53 +03:00
Vitaly Turovsky
bbd01d9682 fix: when left click was pressed down the swing arm packet sending was not limited 2024-09-01 02:38:07 +03:00
Vitaly
eb0bc02647
feat: All versions now are available offline! (#174) 2024-08-31 19:50:33 +03:00
Vitaly Turovsky
0dc261258a fix: fix bug ?singleplayer=1&version=1.20.4 didn't work in safari because of different setTimeout timing 2024-08-31 19:05:20 +03:00
Vitaly Turovsky
447f5eabc8 skip building worker in dev by default because of new scripts change 2024-08-31 18:51:40 +03:00
Vitaly
17a3166f7d fix lockfile 2024-08-31 17:56:35 +03:00
Vitaly
7748e8c384 fix: cleanup entities in all cases on world switch 2024-08-31 17:48:13 +03:00
Vitaly
72a54989ad fix(important): chunk unload was never implemented 2024-08-31 17:47:46 +03:00
Vitaly
b472849c47 fix: packets replay crash on message send on latest versions 2024-08-31 17:47:17 +03:00
Vitaly Turovsky
f32f30ca5a also watch mesher when running the main start script 2024-08-29 16:28:45 +03:00
Vitaly Turovsky
aa9400e885 always enable cors so able to connect from prod domains 2024-08-29 16:28:26 +03:00
Vitaly Turovsky
00e7d4a065 fix(regression): most of the items were not renderer in old versions (before 1.13) 2024-08-27 01:12:10 +03:00
Vitaly Turovsky
83ea44b099 feat: sort worlds in singleplayer menu by last saved world (last level.dat edited) 2024-08-27 01:07:04 +03:00
Vitaly Turovsky
69cfb89a8d display notification on save 2024-08-26 03:15:02 +03:00
Vitaly Turovsky
ffc9a0c458 fix: update "save remote world to your device" function to support new HTTP backend 2024-08-26 03:11:23 +03:00
Vitaly Turovsky
34a6f1d9c3 fix: fix critical bug which was resulting in incorrect modals (and whole app) state in some extremely rare cases 2024-08-26 00:41:23 +03:00
Vitaly Turovsky
de3907aefe add more context to item render error 2024-08-25 23:53:07 +03:00
Vitaly Turovsky
99c8d4091f fix: make entities movements smoother 2024-08-25 23:43:10 +03:00
Vitaly Turovsky
d8f80e1980 cleanup: remove legacy options 2024-08-24 05:19:38 +03:00
Vitaly Turovsky
3a646121ed fix(regression): MS auth was completely broken 2024-08-24 05:13:55 +03:00
Vitaly Turovsky
69e0616240 fix(regression): select version was not visible in server options 2024-08-24 04:54:20 +03:00
Vitaly Turovsky
42e53c8efc fix: correctly display versions list in Select component
fix: was unable to create new worlds by pressing Enter
2024-08-24 04:34:35 +03:00
Vitaly Turovsky
372059f0be add highest block implementation for a few other PRs, up server 2024-08-22 15:47:29 +03:00
Vitaly
04d79c16be
feat: minimize lags when moving between chunks (lazily unload chunks (#179)
+ a setting to control that
2024-08-21 00:25:35 +03:00
Vitaly Turovsky
01c82e3c74 minor infra changes for app links 2024-08-19 14:35:51 +03:00
Vitaly
24fd4d4fc0
feat: implement fast world loading with file descriptor & http backend! (#182) 2024-08-19 14:01:13 +03:00
Vitaly
9e055ebb09
optimize build and world chunk processing (#180) 2024-08-18 15:21:07 +03:00
Vitaly Turovsky
adca2bc494 fix lint 2024-08-18 15:16:19 +03:00
Vitaly Turovsky
0c99f4d5e0 feat: optimize chunks loading: do less duplicated work when chunks are received quickly 2024-08-18 14:39:14 +03:00
Vitaly Turovsky
89f7cfa644 feat: optimize build: load faster by 15% and do not duplicate three js import (tree-shake instead) 2024-08-18 14:39:02 +03:00
Vitaly Turovsky
8374eb6ed6 fix link format 2024-08-16 19:54:23 +03:00
Vitaly Turovsky
044ada20d8 fix build on ci 2024-08-16 19:46:10 +03:00
Vitaly Turovsky
39da2719a8 fix local start crash 2024-08-16 19:31:25 +03:00
Vitaly Turovsky
bf44b07d00 make links in the app more flexible 2024-08-16 19:21:58 +03:00
gguio
f5da7f2261
feat: New select component (#169) 2024-08-16 12:24:13 +03:00
Valery-a
d903c47d3f
fix: do not interact with blocks behind entities (#177) 2024-08-16 02:55:10 +03:00
Vitaly
71289e3ef4
Lint JSX (#175) 2024-08-15 03:12:32 +03:00
Vitaly Turovsky
be78985edf hotfix: better render items for legacy versions (still some blocks are not rendered) 2024-08-14 14:46:40 +03:00
Vitaly Turovsky
39ccf846c2 fix more grass block item render and remove misleading sounds warning 2024-08-13 02:06:04 +03:00
Vitaly Turovsky
dd285544c3 enable mouse raw input and chat select by default for new users! 2024-08-13 02:03:16 +03:00
Vitaly Turovsky
f7615ae581 experimental: faster server start & build 2024-08-13 01:18:23 +03:00
Vitaly Turovsky
fc7df81b0f up mc-assets to fix chest 2024-08-13 01:07:21 +03:00
Vitaly Turovsky
25f04f4959 fix: fix rendering of all remaining blocks except chest, fix rendering of pistons by rotating down texture by another 180deg of any block 2024-08-11 23:28:16 +03:00
Vitaly
6bac74b6c1
cleanup, better resource pack support (#173) 2024-08-06 20:42:33 +03:00
Vitaly Turovsky
067d4b527f correct linting 2024-08-06 19:30:16 +03:00
Vitaly Turovsky
b62d29505f feat: display UI for connecting with bypass VPN detection proxy 2024-08-06 04:12:36 +03:00
Vitaly
d4f06aa1a4
lint prismarine-viewer & better ci checks (#171) 2024-08-06 03:20:38 +03:00
Vitaly Turovsky
c680c6a7d1 fix Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. 2024-08-05 19:05:24 +03:00
Vitaly Turovsky
34e02cfc1b up mc-assets to fix a few critical bugs 2024-08-05 18:51:40 +03:00
Vitaly Turovsky
9f90d6a187 revert setting save loaded early 2024-08-05 05:21:31 +03:00
Vitaly Turovsky
be5c1bcc81 fix item tooltips on mobile 2024-08-05 03:18:49 +03:00
Vitaly Turovsky
647d33bba4 fix build: create generated dir 2024-08-05 03:09:27 +03:00
Vitaly Turovsky
df87216b18 fix: always disable all default touch actions in mobile browser to fix certain critical touch issues 2024-08-05 02:58:39 +03:00
Vitaly Turovsky
43d4555aa8 feat: support displaying custom window titles in inventories GUI 2024-08-05 01:22:19 +03:00
Vitaly Turovsky
c763bb71f6 fix some items display in inventory 2024-08-05 00:37:11 +03:00
Vitaly Turovsky
1e8dcdb170 feat(mobile): add helpers like shift & only hover modes to inventory
fix: now inventory correctly displays tooltip for JEI area
chore: fix a few other minor bugs
2024-08-05 00:30:33 +03:00
Vitaly Turovsky
573e025de2 rework contributing to outline all scripts, remove postinstall 2024-08-04 23:42:51 +03:00
Vitaly Turovsky
95469521fb fix: fix water rendering when plant was neihbor 2024-08-02 01:53:51 +03:00
Vitaly Turovsky
cd39c3d873 docs: complete servers docs 2024-07-31 03:06:12 +03:00
Vitaly
9b72cdb8f0
feat: migrate to mc-assets & Rsbuild better resource pack support (#164)
The complete migration from `minecraft-assets` to [`mc-assets`](https://npmjs.com/mc-assets).

Now all block states & block models are processed dynamically! So it is now easily possible to implement custom models

- no post-install work anymore: the building is now 3x faster and 4x faster in docker
- drop 10x total deploy size
- display world ~1.5x faster
- fix snow & repeater state parser (they didn't render correctly)

rsbuild pipeline!

- the initial app load is faster ~1.2
- much fewer requests are made & cached
- dev reloads are fast now

Resource pack changes:

- now textures are reloaded much more quickly on the fly
- add hotkey to quickly reload textures (for debugging) assigned to F3+T (open dev widget is now assigned to F3+Y)
- add a way to disable resource pack instead of uninstalling it
- items render from resource pack are now support
- resource pack widgets & icons are now supported
2024-07-26 13:12:28 +03:00
Vitaly
a76c98da71 fix typo in readme 2024-07-25 13:44:17 +03:00
Vitaly
0e991c7338
feedback: update discord links (provide alt) (#167) 2024-07-23 20:36:54 +03:00
gguio
bf27f800a1
fix(keybindings): fix key display on other layouts like QWERTZ (#165)
Co-authored-by: gguio <nikvish150@gmail.com>
Co-authored-by: Vitaly Turovsky <vital2580@icloud.com>
2024-07-23 20:33:21 +03:00
Vitaly
04e1cd5707
update typescript to latest (#166) 2024-07-23 20:31:20 +03:00
Vitaly Turovsky
2c3a38a678 add gzip size logging which is actually more important than regular size for prod build 2024-07-19 06:08:37 +03:00
qqq
b7356efbce
feat: Add a way to view / sign books (#158)
---------

Co-authored-by: soulless-ai <mr.chupak@gmail.com>
Co-authored-by: Vitaly Turovsky <vital2580@icloud.com>
2024-07-17 02:53:26 +03:00
Wolf2323
cda1d59d3b
improved query parameters handling (#162)
Co-authored-by: Vitaly <vital2580@icloud.com>
2024-07-16 12:05:57 +03:00
Wolf2323
9ee67cc827
fix: ignoring exception ResizeObserver loop exceptions (#161) 2024-07-16 11:37:01 +03:00
Vitaly Turovsky
b49e988090 fix: inventory didn't update after dropping an item 2024-07-16 10:35:01 +03:00
Vitaly Turovsky
512bc09477 make main docker build 5x smaller, add only proxy dockerfile 2024-07-14 01:55:41 +03:00
Vitaly Turovsky
698779f6b5 fix: toggle entity visiiblity even after it's creation 2024-07-13 21:31:29 +03:00
Vitaly Turovsky
b13ef443bb feat: rework sound system. Now sounds are 100% implemented for all current & future supported versions 2024-07-13 21:30:44 +03:00
Vitaly Turovsky
6c5f72e1f2 feat: implement pitch which was required for note blocks to work properly 2024-07-13 20:31:27 +03:00
Vitaly Turovsky
04b5d1ac3f add handled packets stats
TODO make auto-updated
2024-07-13 06:17:47 +03:00
Vitaly Turovsky
07002a7437 fix: remove entities from the scene on login packet (usually on the world switch) 2024-07-13 05:49:38 +03:00
Vitaly Turovsky
d0205a970b up mineflayer for better vehicle move 2024-07-11 20:08:10 +03:00
Vitaly Turovsky
d02008c2ef fix(regression): signs lighting was compltely broken 2024-07-11 18:33:16 +03:00
Wolf2323
8c4c58b09c
fix: Hide disconnect button, when lockConnect QS is set (#160) 2024-07-11 18:31:50 +03:00
Vitaly Turovsky
0b4959b144 fix: align entity activation with vanilla client so it's harder to detect and now work in most cases 2024-07-11 17:38:17 +03:00
Vitaly Turovsky
ac04d45fe1 fix issue with disconnecting after 10s 2024-07-10 21:10:15 +03:00
Vitaly Turovsky
30905754f9 add client-side timeout when connecting to proxy 2024-07-10 20:01:19 +03:00
Vitaly Turovsky
0a563488c2 fix(regression): chat on mobile was not fully visible in some cases 2024-07-09 02:41:41 +03:00
Vitaly Turovsky
0eafc5e3df fix: don't clip text in error screen 2024-07-08 16:56:32 +03:00
Vitaly
0a6fa7d7b0
feat: improve scaling in portrait mobile. Add close button for mobile chat (#157) 2024-07-08 03:49:10 +03:00
Vitaly Turovsky
1a2fd9c271 fix lint 2024-07-08 03:24:54 +03:00
Vitaly
8674a1d376
Update notes in CONTRIBUTING.md 2024-07-07 20:28:19 +03:00
Vitaly Turovsky
d8261c2659 fix: bundle pixealrticons with the app instead of fetching them from remote location in runtime 2024-07-07 19:18:48 +03:00
Vitaly Turovsky
a6fc5de1f9 ci: add missing build artifacts 2024-07-07 01:31:33 +03:00
Vitaly Turovsky
d1d0959cb4 feat: add Sonar bypass making possible to join to projects like ruhypixel.net
wip: packets replay feature!
2024-07-07 01:12:25 +03:00
Vitaly Turovsky
d80b71ede7 add demo to npm readme file 2024-07-07 00:52:22 +03:00
Vitaly Turovsky
324c08ef43 finally update dockerfile to a working one! uses 6gb of ram to build btw. image size: 2.4gb, ~200mb when running 2024-07-07 00:32:04 +03:00
Vitaly
860d2ad2f6
feat: Microsoft authentication! (#150)
fixes #36
2024-07-06 00:54:03 +03:00
Vitaly
d81aace2d6
feat: new clean favicon (#155)
by hrgembakh
2024-07-06 00:28:59 +03:00
Vitaly Turovsky
c5d3ac1678 fix: inventory in singleplayer didn't update on gui interactions 2024-07-03 13:15:55 +03:00
Vitaly Turovsky
e7e10f6fbd fix: don't crash mesher on unknown blocks in preflat 2024-07-03 13:13:38 +03:00
Vitaly
74c551839d hotfix: hotbar display was broken 2024-06-30 16:14:05 +05:00
731 changed files with 65400 additions and 50191 deletions

View file

@ -0,0 +1,18 @@
---
description: Restricts usage of the global Mineflayer `bot` variable to only src/ files; prohibits usage in renderer/. Specifies correct usage of player state and appViewer globals.
globs: src/**/*.ts,renderer/**/*.ts
alwaysApply: false
---
Ask AI
- The global variable `bot` refers to the Mineflayer bot instance.
- You may use `bot` directly in any file under the `src/` directory (e.g., `src/mineflayer/playerState.ts`).
- Do **not** use `bot` directly in any file under the `renderer/` directory or its subfolders (e.g., `renderer/viewer/three/worldrendererThree.ts`).
- In renderer code, all bot/player state and events must be accessed via explicit interfaces, state managers, or passed-in objects, never by referencing `bot` directly.
- In renderer code (such as in `WorldRendererThree`), use the `playerState` property (e.g., `worldRenderer.playerState.gameMode`) to access player state. The implementation for `playerState` lives in `src/mineflayer/playerState.ts`.
- In `src/` code, you may use the global variable `appViewer` from `src/appViewer.ts` directly. Do **not** import `appViewer` or use `window.appViewer`; use the global `appViewer` variable as-is.
- Some other global variables that can be used without window prefixes are listed in src/globals.d.ts
Rationale: This ensures a clean separation between the Mineflayer logic (server-side/game logic) and the renderer (client-side/view logic), making the renderer portable and testable, and maintains proper usage of global state.
For more general project contributing guides see CONTRIBUTING.md on like how to setup the project. Use pnpm tsc if needed to validate result with typechecking the whole project.

View file

@ -1,5 +1,3 @@
# we dont want default config to be loaded in the dockerfile, but rather using a volume
config.json
# build stuff
node_modules
public

View file

@ -1 +1,9 @@
node_modules
rsbuild.config.ts
*.module.css.d.ts
*.generated.ts
generated
dist
public
**/*/rsbuildSharedConfig.ts
src/mcDataTypes.ts

View file

@ -1,32 +1,76 @@
{
"extends": "zardoy",
"extends": [
"zardoy",
"plugin:@stylistic/disable-legacy"
],
"ignorePatterns": [
"!*.js",
"prismarine-viewer/"
"!*.js"
],
"plugins": [
"@stylistic"
],
"rules": {
"space-infix-ops": "error",
"no-multi-spaces": "error",
"space-before-function-paren": "error",
"space-in-parens": [
// style
"@stylistic/space-infix-ops": "error",
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/space-before-function-paren": "error",
"@stylistic/array-bracket-spacing": "error",
// would be great to have but breaks TS code like (url?) => ...
// "@stylistic/arrow-parens": [
// "error",
// "as-needed"
// ],
"@stylistic/arrow-spacing": "error",
"@stylistic/block-spacing": "error",
"@typescript-eslint/no-this-alias": "off",
"@stylistic/brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
// too annoying to be forced to multi-line, probably should be enforced to never
// "@stylistic/comma-dangle": [
// "error",
// "always-multiline"
// ],
"@stylistic/computed-property-spacing": "error",
"@stylistic/dot-location": [
"error",
"property"
],
"@stylistic/eol-last": "error",
"@stylistic/function-call-spacing": "error",
"@stylistic/function-paren-newline": [
"error",
"consistent"
],
"@stylistic/generator-star-spacing": "error",
"@stylistic/implicit-arrow-linebreak": "error",
"@stylistic/indent-binary-ops": [
"error",
2
],
"@stylistic/function-call-argument-newline": [
"error",
"consistent"
],
"@stylistic/space-in-parens": [
"error",
"never"
],
"object-curly-spacing": [
"@stylistic/object-curly-spacing": [
"error",
"always"
],
"comma-spacing": "error",
"semi": [
"@stylistic/comma-spacing": "error",
"@stylistic/semi": [
"error",
"never"
],
"comma-dangle": [
"error",
// todo maybe "always-multiline"?
"only-multiline"
],
"indent": [
"@stylistic/indent": [
"error",
2,
{
@ -36,13 +80,72 @@
]
}
],
"quotes": [
"@stylistic/quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"@stylistic/key-spacing": "error",
"@stylistic/keyword-spacing": "error",
// "@stylistic/line-comment-position": "error", // not needed
// "@stylistic/lines-around-comment": "error", // also not sure if needed
// "@stylistic/max-len": "error", // also not sure if needed
// "@stylistic/linebreak-style": "error", // let git decide
"@stylistic/max-statements-per-line": [
"error",
{
"max": 5
}
],
// "@stylistic/member-delimiter-style": "error",
// "@stylistic/multiline-ternary": "error", // not needed
// "@stylistic/newline-per-chained-call": "error", // not sure if needed
"@stylistic/new-parens": "error",
"@typescript-eslint/class-literal-property-style": "off",
"@stylistic/no-confusing-arrow": "error",
"@stylistic/wrap-iife": "error",
"@stylistic/space-before-blocks": "error",
"@stylistic/type-generic-spacing": "error",
"@stylistic/template-tag-spacing": "error",
"@stylistic/template-curly-spacing": "error",
"@stylistic/type-annotation-spacing": "error",
"@stylistic/jsx-child-element-spacing": "error",
// buggy
// "@stylistic/jsx-closing-bracket-location": "error",
// "@stylistic/jsx-closing-tag-location": "error",
"@stylistic/jsx-curly-brace-presence": "error",
"@stylistic/jsx-curly-newline": "error",
"@stylistic/jsx-curly-spacing": "error",
"@stylistic/jsx-equals-spacing": "error",
"@stylistic/jsx-first-prop-new-line": "error",
"@stylistic/jsx-function-call-newline": "error",
"@stylistic/jsx-max-props-per-line": [
"error",
{
"maximum": 7
}
],
"@stylistic/jsx-pascal-case": "error",
"@stylistic/jsx-props-no-multi-spaces": "error",
"@stylistic/jsx-self-closing-comp": "error",
// "@stylistic/jsx-sort-props": [
// "error",
// {
// "callbacksLast": false,
// "shorthandFirst": true,
// "shorthandLast": false,
// "multiline": "ignore",
// "ignoreCase": true,
// "noSortAlphabetically": true,
// "reservedFirst": [
// "key",
// "className"
// ],
// "locale": "auto"
// }
// ],
// perf
"import/no-deprecated": "off",
// ---
@ -52,6 +155,7 @@
// intentional: improve readability in some cases
"no-else-return": "off",
"@typescript-eslint/padding-line-between-statements": "off",
"@typescript-eslint/no-dynamic-delete": "off",
"arrow-body-style": "off",
"unicorn/prefer-ternary": "off",
"unicorn/switch-case-braces": "off",
@ -88,13 +192,15 @@
"@typescript-eslint/no-confusing-void-expression": "off",
"unicorn/no-empty-file": "off",
"unicorn/prefer-event-target": "off",
"@typescript-eslint/member-ordering": "off",
// needs to be fixed actually
"complexity": "off",
"@typescript-eslint/no-floating-promises": "warn",
"no-async-promise-executor": "off",
"no-bitwise": "off",
"unicorn/filename-case": "off",
"max-depth": "off"
"max-depth": "off",
"unicorn/no-typeof-undefined": "off"
},
"overrides": [
{
@ -102,7 +208,7 @@
"*.js"
],
"rules": {
"space-before-function-paren": [
"@stylistic/space-before-function-paren": [
"error",
{
"anonymous": "always",

59
.github/workflows/benchmark.yml vendored Normal file
View file

@ -0,0 +1,59 @@
name: Benchmark
on:
issue_comment:
types: [created]
push:
branches:
- perf-test
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
(github.event_name == 'push' && github.ref == 'refs/heads/perf-test') ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request != '' &&
(startsWith(github.event.comment.body, '/benchmark'))
)
permissions:
pull-requests: write
steps:
- run: lscpu
- name: Checkout
uses: actions/checkout@v2
- name: Setup pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Move Cypress to dependencies
run: |
jq '.dependencies.cypress = .optionalDependencies.cypress | del(.optionalDependencies.cypress)' package.json > package.json.tmp
mv package.json.tmp package.json
- run: pnpm install --no-frozen-lockfile
- run: pnpm build
- run: nohup pnpm prod-start &
- run: pnpm test:benchmark
id: benchmark
continue-on-error: true
# read benchmark results from stdout
- run: |
if [ -f benchmark.txt ]; then
# Format the benchmark results for GitHub comment
BENCHMARK_RESULT=$(cat benchmark.txt | sed 's/^/- /')
echo "BENCHMARK_RESULT<<EOF" >> $GITHUB_ENV
echo "$BENCHMARK_RESULT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "BENCHMARK_RESULT=Benchmark failed to run or produce results" >> $GITHUB_ENV
fi
- uses: mshick/add-pr-comment@v2
with:
allow-repeats: true
message: |
Benchmark result: ${{ env.BENCHMARK_RESULT }}

33
.github/workflows/build-single-file.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: build-single-file
on:
workflow_dispatch:
jobs:
build-and-bundle:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@master
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install dependencies
run: pnpm install
- name: Build single-file version - minecraft.html
run: pnpm build-single-file && mv dist/single/index.html minecraft.html
env:
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: minecraft.html
path: minecraft.html

45
.github/workflows/build-zip.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Make Self Host Zip
on:
workflow_dispatch:
jobs:
build-and-bundle:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@master
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install dependencies
run: pnpm install
- name: Build project
run: pnpm build
env:
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Bundle server.js
run: |
pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="'production'"
- name: Create distribution package
run: |
mkdir -p package
cp -r dist package/
cp bundled-server.js package/server.js
cd package
zip -r ../self-host.zip .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: self-host
path: self-host.zip

View file

@ -13,28 +13,165 @@ jobs:
with:
java-version: 17
java-package: jre
- name: Install pnpm
run: npm i -g pnpm@9.0.4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
# cache: "pnpm"
- name: Install pnpm
uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm build-single-file
- name: Store minecraft.html size
run: |
SIZE_BYTES=$(du -s dist/single/minecraft.html 2>/dev/null | cut -f1)
echo "SIZE_BYTES=$SIZE_BYTES" >> $GITHUB_ENV
- run: pnpm check-build
- name: Create zip package for size comparison
run: |
mkdir -p package
cp -r dist package/
cd package
zip -r ../self-host.zip .
- run: pnpm build-playground
# - run: pnpm build-storybook
- run: pnpm test-unit
- run: pnpm lint
- run: pnpm tsx scripts/buildNpmReact.ts
- name: Parse Bundle Stats
run: |
GZIP_BYTES=$(du -s self-host.zip 2>/dev/null | cut -f1)
SIZE=$(echo "scale=2; $SIZE_BYTES/1024/1024" | bc)
GZIP_SIZE=$(echo "scale=2; $GZIP_BYTES/1024/1024" | bc)
echo "{\"total\": ${SIZE}, \"gzipped\": ${GZIP_SIZE}}" > /tmp/bundle-stats.json
# - name: Compare Bundle Stats
# id: compare
# uses: actions/github-script@v6
# env:
# GITHUB_TOKEN: ${{ secrets.GIST_TOKEN }}
# with:
# script: |
# const gistId = '${{ secrets.BUNDLE_STATS_GIST_ID }}';
# async function getGistContent() {
# const { data } = await github.rest.gists.get({
# gist_id: gistId,
# headers: {
# authorization: `token ${process.env.GITHUB_TOKEN}`
# }
# });
# return JSON.parse(data.files['bundle-stats.json'].content || '{}');
# }
# const content = await getGistContent();
# const baseStats = content['${{ github.event.pull_request.base.ref }}'];
# const newStats = require('/tmp/bundle-stats.json');
# const comparison = `minecraft.html (normal build gzip)\n${baseStats.total}MB (${baseStats.gzipped}MB compressed) -> ${newStats.total}MB (${newStats.gzipped}MB compressed)`;
# core.setOutput('stats', comparison);
# - run: pnpm tsx scripts/buildNpmReact.ts
- run: nohup pnpm prod-start &
- run: nohup pnpm test-mc-server &
- uses: cypress-io/github-action@v5
with:
install: false
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-images
path: cypress/integration/__image_snapshots__/
- run: node scripts/outdatedGitPackages.mjs
if: github.ref == 'refs/heads/next'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
path: cypress/screenshots/
# - run: node scripts/outdatedGitPackages.mjs
# if: ${{ github.event.pull_request.base.ref == 'release' }}
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: Store Bundle Stats
# if: github.event.pull_request.base.ref == 'next'
# uses: actions/github-script@v6
# env:
# GITHUB_TOKEN: ${{ secrets.GIST_TOKEN }}
# with:
# script: |
# const gistId = '${{ secrets.BUNDLE_STATS_GIST_ID }}';
# async function getGistContent() {
# const { data } = await github.rest.gists.get({
# gist_id: gistId,
# headers: {
# authorization: `token ${process.env.GITHUB_TOKEN}`
# }
# });
# return JSON.parse(data.files['bundle-stats.json'].content || '{}');
# }
# async function updateGistContent(content) {
# await github.rest.gists.update({
# gist_id: gistId,
# headers: {
# authorization: `token ${process.env.GITHUB_TOKEN}`
# },
# files: {
# 'bundle-stats.json': {
# content: JSON.stringify(content, null, 2)
# }
# }
# });
# }
# const stats = require('/tmp/bundle-stats.json');
# const content = await getGistContent();
# content['${{ github.event.pull_request.base.ref }}'] = stats;
# await updateGistContent(content);
# - name: Update PR Description
# uses: actions/github-script@v6
# with:
# script: |
# const { data: pr } = await github.rest.pulls.get({
# owner: context.repo.owner,
# repo: context.repo.repo,
# pull_number: context.issue.number
# });
# let body = pr.body || '';
# const statsMarker = '### Bundle Size';
# const comparison = '${{ steps.compare.outputs.stats }}';
# if (body.includes(statsMarker)) {
# body = body.replace(
# new RegExp(`${statsMarker}[^\n]*\n[^\n]*`),
# `${statsMarker}\n${comparison}`
# );
# } else {
# body += `\n\n${statsMarker}\n${comparison}`;
# }
# await github.rest.pulls.update({
# owner: context.repo.owner,
# repo: context.repo.repo,
# pull_number: context.issue.number,
# body
# });
# dedupe-check:
# runs-on: ubuntu-latest
# if: github.event.pull_request.head.ref == 'next'
# steps:
# - name: Checkout repository
# uses: actions/checkout@v2
# - name: Install pnpm
# run: npm install -g pnpm@9.0.4
# - name: Run pnpm dedupe
# run: pnpm dedupe
# - name: Check for changes
# run: |
# if ! git diff --exit-code --quiet pnpm-lock.yaml; then
# echo "pnpm dedupe introduced changes:"
# git diff --color=always pnpm-lock.yaml
# exit 1
# else
# echo "No changes detected after pnpm dedupe in pnpm-lock.yaml"
# fi

29
.github/workflows/fix-lint.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Fix Lint Command
on:
issue_comment:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
github.event.issue.pull_request != '' &&
(
contains(github.event.comment.body, '/fix')
)
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v2
with:
ref: refs/pull/${{ github.event.issue.number }}/head
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm lint --fix
- name: Push Changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

28
.github/workflows/merge-next.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: Update Base Branch Command
on:
issue_comment:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
github.event.issue.pull_request != '' &&
(
contains(github.event.comment.body, '/update')
)
permissions:
pull-requests: write
contents: write
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Fetch all history so we can merge branches
ref: refs/pull/${{ github.event.issue.number }}/head
- name: Fetch All Branches
run: git fetch --all
# - name: Checkout PR
# run: git checkout ${{ github.event.issue.pull_request.head.ref }}
- name: Merge From Next
run: git merge origin/next --strategy-option=theirs
- name: Push Changes
run: git push

View file

@ -3,6 +3,7 @@ env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
ALIASES: ${{ vars.ALIASES }}
MAIN_MENU_LINKS: ${{ vars.MAIN_MENU_LINKS }}
on:
push:
branches:
@ -15,25 +16,76 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Global Dependencies
run: npm install --global vercel pnpm@9.0.4
run: pnpm add -g vercel
- name: Install Dependencies
run: pnpm install
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Write Release Info
run: |
echo "{\"latestTag\": \"$(git rev-parse --short $GITHUB_SHA)\", \"isCommit\": true}" > assets/release.json
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
CONFIG_JSON_SOURCE: BUNDLED
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
- name: Deploy Project Artifacts to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
id: deploy
- name: Set deployment alias
run: vercel alias set ${{ steps.deploy.outputs.stdout }} ${{ secrets.TEST_PREVIEW_DOMAIN }} --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
# - uses: mshick/add-pr-comment@v2
# with:
# message: |
# Deployed to Vercel Preview: ${{ steps.deploy.outputs.stdout }}
- name: Start servers for testing
run: |
nohup pnpm prod-start &
nohup pnpm test-mc-server &
- name: Run Cypress smoke tests
uses: cypress-io/github-action@v5
with:
install: false
spec: cypress/e2e/smoke.spec.ts
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-smoke-test-screenshots
path: cypress/screenshots/
- name: Set deployment aliases
run: |
for alias in $(echo ${{ secrets.TEST_PREVIEW_DOMAIN }} | tr "," "\n"); do
vercel alias set ${{ steps.deploy.outputs.stdout }} $alias --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
done
- name: Create Release Pull Request
uses: actions/github-script@v6
with:
script: |
const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:next`,
base: 'release',
state: 'open'
});
if (pulls.length === 0) {
await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Release',
head: 'next',
base: 'release',
body: 'PR was created automatically by the release workflow, hope you release it as soon as possible!',
});
}

View file

@ -1,4 +1,4 @@
name: Vercel Deploy Preview
name: Vercel PR Deploy (Preview)
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
@ -6,57 +6,109 @@ env:
on:
issue_comment:
types: [created]
pull_request_target:
jobs:
deploy:
runs-on: ubuntu-latest
# todo skip already created deploys on that commit
if: >-
github.event.issue.pull_request != '' &&
(
contains(github.event.comment.body, '/deploy')
(
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '/deploy') &&
github.event.issue.pull_request != null
) ||
(
github.event_name == 'pull_request_target' &&
contains(fromJson(vars.AUTO_DEPLOY_PRS), github.event.pull_request.number)
)
)
permissions:
pull-requests: write
steps:
- name: Checkout
- name: Checkout Base To Temp
uses: actions/checkout@v2
with:
path: temp-base-repo
- name: Get deployment alias
run: node temp-base-repo/scripts/githubActions.mjs getAlias
id: alias
env:
ALIASES: ${{ env.ALIASES }}
PULL_URL: ${{ github.event.issue.pull_request.url || github.event.pull_request.url }}
- name: Checkout PR (comment)
uses: actions/checkout@v2
if: github.event_name == 'issue_comment'
with:
ref: refs/pull/${{ github.event.issue.number }}/head
- run: npm i -g pnpm@9.0.4
- name: Checkout PR (pull_request)
uses: actions/checkout@v2
if: github.event_name == 'pull_request_target'
with:
ref: refs/pull/${{ github.event.pull_request.number }}/head
- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
cache: "pnpm"
- name: Update deployAlwaysUpdate packages
run: |
if [ -f package.json ]; then
PACKAGES=$(node -e "const pkg = require('./package.json'); if (pkg.deployAlwaysUpdate) console.log(pkg.deployAlwaysUpdate.join(' '))")
if [ ! -z "$PACKAGES" ]; then
echo "Updating packages: $PACKAGES"
pnpm up -L $PACKAGES
else
echo "No deployAlwaysUpdate packages found in package.json"
fi
else
echo "package.json not found"
fi
- name: Install Global Dependencies
run: npm install --global vercel
run: pnpm add -g vercel
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Write Release Info
run: |
echo "{\"latestTag\": \"$(git rev-parse --short ${{ github.event.pull_request.head.sha }})\", \"isCommit\": true}" > assets/release.json
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
CONFIG_JSON_SOURCE: BUNDLED
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
- name: Write pr redirect index.html
run: |
mkdir -p .vercel/output/static/pr
echo "<meta http-equiv='refresh' content='0;url=https://github.com/${{ github.repository }}/pull/${{ github.event.issue.number || github.event.pull_request.number }}'>" > .vercel/output/static/pr/index.html
- name: Write commit redirect index.html
run: |
mkdir -p .vercel/output/static/commit
echo "<meta http-equiv='refresh' content='0;url=https://github.com/${{ github.repository }}/pull/${{ github.event.issue.number || github.event.pull_request.number }}/commits/${{ github.event.pull_request.head.sha }}'>" > .vercel/output/static/commit/index.html
- name: Deploy Project Artifacts to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
id: deploy
- uses: mshick/add-pr-comment@v2
# if: github.event_name == 'issue_comment'
with:
allow-repeats: true
message: |
Deployed to Vercel Preview: ${{ steps.deploy.outputs.stdout }}
[Playground](${{ steps.deploy.outputs.stdout }}/playground.html)
[Playground](${{ steps.deploy.outputs.stdout }}/playground/)
[Storybook](${{ steps.deploy.outputs.stdout }}/storybook/)
# - run: git checkout next scripts/githubActions.mjs
- name: Get deployment alias
run: node scripts/githubActions.mjs getAlias
id: alias
env:
ALIASES: ${{ env.ALIASES }}
PULL_URL: ${{ github.event.issue.pull_request.url }}
- name: Set deployment alias
if: ${{ steps.alias.outputs.alias != '' && steps.alias.outputs.alias != 'mcraft.fun' && steps.alias.outputs.alias != 's.mcraft.fun' }}
run: vercel alias set ${{ steps.deploy.outputs.stdout }} ${{ steps.alias.outputs.alias }} --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
run: |
for alias in $(echo ${{ steps.alias.outputs.alias }} | tr "," "\n"); do
vercel alias set ${{ steps.deploy.outputs.stdout }} $alias --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
done

View file

@ -1,48 +0,0 @@
name: Deploy to GitHub pages
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
push:
branches: [release]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@master
- name: Install pnpm
run: npm i -g vercel pnpm@9.0.4
# - run: pnpm install
# - run: pnpm build
- run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
# will install + build to .vercel/output/static
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} --prod
id: deploy
- run: |
pnpx zardoy-release node --footer "This release URL: ${{ steps.deploy.outputs.stdout }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# has possible output: tag
id: release
# has output
- run: cp vercel.json .vercel/output/static/vercel.json
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .vercel/output/static
force_orphan: true
- run: pnpm tsx scripts/buildNpmReact.ts ${{ steps.release.outputs.tag }}
if: steps.release.outputs.tag
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

116
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,116 @@
name: Release
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
MAIN_MENU_LINKS: ${{ vars.MAIN_MENU_LINKS }}
on:
push:
branches: [release]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@master
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Global Dependencies
run: pnpm add -g vercel
# - run: pnpm install
# - run: pnpm build
- run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- run: node scripts/replaceFavicon.mjs ${{ secrets.FAVICON_MAIN }}
# will install + build to .vercel/output/static
- name: Get Release Info
run: pnpx zardoy-release empty --skip-github --output-file assets/release.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
env:
CONFIG_JSON_SOURCE: BUNDLED
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
# publish to github
- run: cp vercel.json .vercel/output/static/vercel.json
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .vercel/output/static
force_orphan: true
# Create CNAME file for custom domain
- name: Create CNAME file
run: echo "github.mcraft.fun" > .vercel/output/static/CNAME
- name: Deploy to mwc-mcraft-pages repository
uses: peaceiris/actions-gh-pages@v3
with:
personal_token: ${{ secrets.MCW_MCRAFT_PAGE_DEPLOY_TOKEN }}
external_repository: ${{ github.repository_owner }}/mwc-mcraft-pages
publish_dir: .vercel/output/static
publish_branch: main
destination_dir: docs
force_orphan: true
- name: Change index.html title
run: |
# change <title>Minecraft Web Client</title> to <title>Minecraft Web Client — Free Online Browser Version</title>
sed -i 's/<title>Minecraft Web Client<\/title>/<title>Minecraft Web Client — Free Online Browser Version<\/title>/' .vercel/output/static/index.html
- name: Deploy Project to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} --prod
id: deploy
- name: Get releasing alias
run: node scripts/githubActions.mjs getReleasingAlias
id: alias
- name: Set deployment alias
run: |
for alias in $(echo ${{ steps.alias.outputs.alias }} | tr "," "\n"); do
vercel alias set ${{ steps.deploy.outputs.stdout }} $alias --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
done
- name: Build single-file version - minecraft.html
run: pnpm build-single-file && mv dist/single/index.html minecraft.html
- name: Build self-host version
run: pnpm build
- name: Bundle server.js
run: |
pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="'production'"
- name: Create zip package
run: |
mkdir -p package
cp -r dist package/
cp bundled-server.js package/server.js
cd package
zip -r ../self-host.zip .
- run: |
pnpx zardoy-release node --footer "This release URL: https://$(echo ${{ steps.alias.outputs.alias }} | cut -d',' -f1) (Vercel URL: ${{ steps.deploy.outputs.stdout }})"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# has possible output: tag
id: release
# has output
- name: Set publishing config
run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# - run: pnpm tsx scripts/buildNpmReact.ts ${{ steps.release.outputs.tag }}
# if: steps.release.outputs.tag
# env:
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

4
.gitignore vendored
View file

@ -10,7 +10,7 @@ localSettings.mjs
dist*
.DS_Store
.idea/
world
/world
data*.json
out
*.iml
@ -18,5 +18,7 @@ out
generated
storybook-static
server-jar
config.local.json
logs/
src/react/npmReactComponents.ts

View file

@ -1,9 +1,10 @@
import React from 'react'
import type { Preview } from "@storybook/react";
import type { Preview } from "@storybook/react"
import '../src/styles.css'
import './storybook.css'
import '../src/styles.css'
import '../src/scaleInterface'
const preview: Preview = {
decorators: [
@ -11,7 +12,7 @@ const preview: Preview = {
const noScaling = c.parameters.noScaling
return <div id={noScaling ? '' : 'ui-root'}>
<Story />
</div>;
</div>
},
],
parameters: {
@ -23,6 +24,6 @@ const preview: Preview = {
},
},
},
};
}
export default preview;
export default preview

5
.vscode/launch.json vendored
View file

@ -1,6 +1,5 @@
{
"configurations": [
// UPDATED: all configs below are misconfigured and will crash vscode, open dist/index.html and use live preview debug instead
// recommended as much faster
{
// to launch "C:\Program Files\Google\Chrome Beta\Application\chrome.exe" --remote-debugging-port=9222
@ -29,7 +28,7 @@
"type": "chrome",
"name": "Launch Chrome",
"request": "launch",
"url": "http://localhost:8080/",
"url": "http://localhost:3000/",
"pathMapping": {
"/": "${workspaceFolder}/dist"
},
@ -50,7 +49,7 @@
"name": "Attach Firefox",
"request": "attach",
// comment if using webpack
"url": "http://localhost:8080/",
"url": "http://localhost:3000/",
"webRoot": "${workspaceFolder}/",
"skipFiles": [
// "<node_internals>/**/*vendors*"

View file

@ -2,26 +2,84 @@
After forking the repository, run the following commands to get started:
0. Ensure you have [Node.js](https://nodejs.org) and `pnpm` installed. To install pnpm run `npm i -g pnpm@9.0.4`.
0. Ensure you have [Node.js](https://nodejs.org) installed. Enable corepack with `corepack enable` *(1).
1. Install dependencies: `pnpm i`
2. Start the project in development mode: `pnpm start`
2. Start the project in development mode: `pnpm start` or build the project for production: `pnpm build`
3. Read the [Tasks Categories](#tasks-categories) and [Workflow](#workflow) sections below
4. Let us know if you are working on something and be sure to open a PR if you got any changes. Happy coding!
*(1): If you are getting `Cannot find matching keyid` update corepack to the latest version with `npm i -g corepack`.
*(2): If still something doesn't work ensure you have the right nodejs version with `node -v` (tested on 22.x)
<!-- *(3): For GitHub codespaces (cloud ide): Run `pnpm i @rsbuild/core@1.2.4 @rsbuild/plugin-node-polyfill@1.3.0 @rsbuild/plugin-react@1.1.0 @rsbuild/plugin-typed-css-modules@1.0.2` command to avoid crashes because of limited ram -->
## Project Structure
There are 3 main parts of the project:
### Core (`src`)
This is the main app source code which reuses all the other parts of the project.
> The first version used Webpack, then was migrated to Esbuild and now is using Rsbuild!
- Scripts:
- Start: `pnpm start`, `pnpm dev-rsbuild` (if you don't need proxy server also running)
- Build: `pnpm build` (note that `build` script builds only the core app, not the whole project!)
Paths:
- `src` - main app source code
- `src/react` - React components - almost all UI is in this folder. Almost every component has its base (reused in app and storybook) and `Provider` - which is a component that provides context to its children. Consider looking at DeathScreen component to see how it's used.
- `src/menus` - Old Lit Element GUI. In the process of migration to React.
- `prismarine-viewer` - Improved version of <https://github.com/prismarineJS/prismarine-viewer>. Here is everything related to rendering the game world itself (no ui at all). Two most important parts here are:
- `prismarine-viewer/viewer/lib/worldrenderer.ts` - adding new objects to three.js happens here (sections)
- `prismarine-viewer/viewer/lib/models.ts` - preparing data for rendering (blocks) - happens in worker: out file - `worker.js`, building - `prismarine-viewer/buildWorker.mjs`
- `prismarine-viewer/examples/playground.ts` - Playground (source of <mcraft.fun/playground.html>) Use this for testing render changes. You can also modify playground code.
### Renderer: Playground & Mesher (`renderer`)
- Playground Scripts:
- Start: `pnpm run-playground` (playground, mesher + server) or `pnpm watch-playground`
- Build: `pnpm build-playground` or `node renderer/esbuild.mjs`
- Mesher Scripts:
- Start: `pnpm watch-mesher`
- Build: `pnpm build-mesher`
Paths:
- `renderer` - Improved and refactored version of <https://github.com/PrismarineJS/prismarine-viewer>. Here is everything related to rendering the game world itself (no ui at all). Two most important parts here are:
- `renderer/viewer/lib/worldrenderer.ts` - adding new objects to three.js happens here (sections)
- `renderer/viewer/lib/models.ts` - preparing data for rendering (blocks) - happens in worker: out file - `worker.js`, building - `renderer/buildWorker.mjs`
- `renderer/playground/playground.ts` - Playground (source of <mcraft.fun/playground.html>) Use this for testing any rendering changes. You can also modify the playground code.
### Storybook (`.storybook`)
Storybook is a tool for easier developing and testing React components.
Path of all Storybook stories is `src/react/**/*.stories.tsx`.
- Scripts:
- Start: `pnpm storybook`
- Build: `pnpm build-storybook`
## Core-related
How different modules are used:
- `mineflayer` - provider `bot` variable and as mineflayer states it is a wrapper for the `node-minecraft-protocol` module and is used to connect and interact with real Java Minecraft servers. However not all events & properties are exposed and sometimes you have to use `bot._client.on('packet_name', data => ...)` to handle packets that are not handled via mineflayer API. Also you can use almost any mineflayer plugin.
## Making protocol changes
## Running Main App + Playground
To start the main web app and playground, run `pnpm run-all`. Note is doesn't start storybook and tests.
## Cypress Tests (E2E)
Cypress tests are located in `cypress` folder. To run them, run `pnpm test-mc-server` and then `pnpm test:cypress` when the `pnpm prod-start` is running (or change the port to 3000 to test with the dev server). Usually you don't need to run these until you get issues on the CI.
## Unit Tests
There are not many unit tests for now (which we are trying to improve).
Location of unit tests: `**/*.test.ts` files in `src` folder and `renderer` folder.
Start them with `pnpm test-unit`.
## Making protocol-related changes
You can get a description of packets for the latest protocol version from <https://wiki.vg/Protocol> and for previous protocol versions from <https://wiki.vg/Protocol_version_numbers> (look for *Page* links that have *Protocol* in URL).
@ -37,6 +95,100 @@ Also there are [src/generatedClientPackets.ts](src/generatedClientPackets.ts) an
- Some data are cached between restarts. If you see something doesn't work after upgrading dependencies, try to clear the by simply removing the `dist` folder.
- The same folder `dist` is used for both development and production builds, so be careful when deploying the project.
- Use `start-prod` script to start the project in production mode after running the `build` script to build the project.
- If CI is failing on the next branch for some reason, feel free to use the latest commit for release branch. We will update the base branch asap. Please, always make sure to allow maintainers do changes when opening PRs.
## Tasks Categories
(most important for now are on top).
## 1. Client-side Logic (most important right now)
Everything related to the client side packets. Investigate issues when something goes wrong with some server. It's much easier to work on these types of tasks when you have experience in Java with Minecraft, a deep understanding of the original client, and know how to debug it (which is not hard actually). Right now the client is easily detectable by anti-cheat plugins, and the main goal is to fix it (mostly because of wrong physics implementation).
Priority tasks:
- Rewrite or fix the physics logic (Botcraft or Grim can be used as a reference as well)
- Implement basic minecart / boat / horse riding
- Fix auto jump module (false triggers, performance issues)
- Investigate connection issues to some servers
- Setup a platform for automatic cron testing against the latest version of the anti-cheat plugins
- ...
Goals:
- Make more servers playable. Right now on hypixel-like servers (servers with minigames), only tnt run (and probably ) is fully playable.
Notes:
- You can see the incoming/outgoing packets in the console (F12 in Chrome) by enabling `options.debugLogNotFrequentPackets = true`. However, if you need a FULL log of all packets, you can start recording the packets by going into `Settings` > `Advanced` > `Enable Packets Replay` and then you can download the file and use it to replay the packets.
- You can use mcraft-e2e studio to send the same packets over and over again (which is useful for testing) or use the packets replayer (which is useful for debugging).
## 2. Three.js Renderer
Example tasks:
- Improve / fix entity rendering
- Better update entities on specific packets
- Investigate performance issues under different conditions (instructions provided)
- Work on the playground code
Goals:
- Fix a lot of entity rendering issues (including position updates)
- Implement switching camera mode (first person, third person, etc)
- Animated blocks
- Armor rendering
- ...
Note:
- It's useful to know how to use helpers & additional cameras (e.g. setScissor)
## 3. Server-side Logic
Flying squid fork (space-squid).
Example tasks:
- Add missing commands (e.g. /scoreboard)
- Basic physics (player fall damage, falling blocks & entities)
- Basic entities AI (spawning, attacking)
- Pvp
- Emit more packets on some specific events (e.g. when a player uses an item)
- Make more maps playable (e.g. fix when something is not implemented in both server and client and blocking map interaction)
- ...
Long Term Goals:
- Make most adventure maps playable
- Make a way to complete the game from the scratch (crafting, different dimensions, terrain generation, etc)
- Make bedwars playable!
Most of the tasks are straightforward to implement, just be sure to use a debugger ;). If you feel you are stuck, ask for help on Discord. Absolutely any tests / refactor suggestions are welcome!
## 4. Frontend
New React components, improve UI (including mobile support).
## Workflow
1. Locate the problem on the public test server & make an easily reproducible environment (you can also use local packets replay server or your custom server setup). Dm me for details on public test server / replay server
2. Debug the code, find an issue in the code, isolate the problem
3. Develop, try to fix and test. Finally we should find a way to fix it. It's ideal to have an automatic test but it's not necessary for now
3. Repeat step 1 to make sure the task is done and the problem is fixed (or the feature is implemented)
## Updating Dependencies
1. Use `pnpm update-git-deps` to check and update git dependencies (like mineflayer fork, prismarine packages etc). The script will:
- Show which git dependencies have updates available
- Ask if you want to update them
- Skip dependencies listed in `pnpm.updateConfig.ignoreDependencies`
2. Update PrismarineJS dependencies to the latest version: `minecraft-data` (be sure to replace the version twice in the package.json), `mineflayer`, `minecraft-protocol`, `prismarine-block`, `prismarine-chunk`, `prismarine-item`, ...
3. If `minecraft-protocol` patch fails, do this:
1. Remove the patch from `patchedDependencies` in `package.json`
2. Run `pnpm patch minecraft-protocol`, open patch directory
3. Apply the patch manually in this directory: `patch -p1 < minecraft-protocol@<version>.patch`
4. Run the suggested command from `pnpm patch ...` (previous step) to update the patch
### Would be useful to have

View file

@ -1,9 +1,43 @@
FROM node:14-alpine
# ---- Build Stage ----
FROM node:18-alpine AS build
# Without git installing the npm packages fails
RUN apk add git
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN npm install
RUN npm run build
ENTRYPOINT ["npm", "run", "prod-start"]
# install pnpm with corepack
RUN corepack enable
# Build arguments
ARG DOWNLOAD_SOUNDS=false
ARG DISABLE_SERVICE_WORKER=false
ARG CONFIG_JSON_SOURCE=REMOTE
# TODO need flat --no-root-optional
RUN node ./scripts/dockerPrepare.mjs
RUN pnpm i
# Download sounds if flag is enabled
RUN if [ "$DOWNLOAD_SOUNDS" = "true" ] ; then node scripts/downloadSoundsMap.mjs ; fi
# TODO for development
# EXPOSE 9090
# VOLUME /app/src
# VOLUME /app/renderer
# ENTRYPOINT ["pnpm", "run", "run-all"]
# only for prod
RUN DISABLE_SERVICE_WORKER=$DISABLE_SERVICE_WORKER \
CONFIG_JSON_SOURCE=$CONFIG_JSON_SOURCE \
pnpm run build
# ---- Run Stage ----
FROM node:18-alpine
RUN apk add git
WORKDIR /app
# Copy build artifacts from the build stage
COPY --from=build /app/dist /app/dist
COPY server.js /app/server.js
# Install express
RUN npm i -g pnpm@10.8.0
RUN npm init -yp
RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors
EXPOSE 8080
VOLUME /app/public
ENTRYPOINT ["node", "server.js", "--prod"]

11
Dockerfile.proxy Normal file
View file

@ -0,0 +1,11 @@
# ---- Run Stage ----
FROM node:18-alpine
RUN apk add git
WORKDIR /app
COPY server.js /app/server.js
# Install server dependencies
RUN npm i -g pnpm@9.0.4
RUN npm init -yp
RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors
EXPOSE 8080
ENTRYPOINT ["node", "server.js"]

184
README.MD
View file

@ -2,32 +2,65 @@
![banner](./docs-assets/banner.jpg)
A true Minecraft client running in your browser! A port of the original game to the web, written in JavaScript using modern web technologies.
Minecraft **clone** rewritten in TypeScript using the best modern web technologies. Minecraft vanilla-compatible client and integrated server packaged into a single web app.
If you encounter any bugs or usability issues, please report them!
You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm.gg) (short link), [mcon.vercel.app](https://mcon.vercel.app/) or the GitHub pages deploy. Every commit from the default (`develop`) branch is deployed to [s.mcraft.fun](https://s.mcraft.fun/) and [s.pcm.gg](https://s.pcm.gg/) - so it's usually newer, but might be less stable.
You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm.gg) (short link) [mcon.vercel.app](https://mcon.vercel.app/) or the GitHub pages deploy. Every commit from the `develop` (default) branch is deployed to [s.mcraft.fun](https://s.mcraft.fun/) - so it's usually newer, but might be less stable.
> For Turkey/Russia use [ru.mcraft.fun](https://ru.mcraft.fun/) (since Cloudflare is blocked)
Don't confuse with [Eaglercraft](https://git.eaglercraft.rip/eaglercraft/eaglercraft-1.8) which is a REAL vanilla Minecraft Java edition port to the web (but with its own limitations). Eaglercraft is a fully playable solution, meanwhile this project is aimed for *device-compatiiblity* and better performance so it feels portable, flexible and lightweight. It's also a very strong example on how to build true HTML games for the web at scale entirely with the JS ecosystem. Have fun!
For building the project yourself / contributing, see [Development, Debugging & Contributing](#development-debugging--contributing). For reference at what and how web technologies / frameworks are used, see [TECH.md](./TECH.md) (also for comparison with Eaglercraft).
> **Note**: You can deploy it on your own server in less than a minute using a one-liner script from [Minecraft Everywhere repo](https://github.com/zardoy/minecraft-everywhere)
### Big Features
- Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely.
- Open any zip world file or even folder in read-write mode!
- Connect to cracked servers* (it's possible because of proxy servers, see below)
- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below)
- Integrated JS server clone capable of opening Java world saves in any way (folders, zip, web chunks streaming, etc)
- Singleplayer mode with simple world generations!
- Works offline
- Play with friends over internet! (P2P is powered by Peer.js discovery servers)
- First-class touch (mobile) & controller support
- Resource pack support
- Builtin JEI with recipes & guides for every item (also replaces creative inventory)
- First-class keybindings configuration
- Advanced Resource pack support: Custom GUI, all textures. Server resource packs are supported with proper CORS configuration.
- Builtin JEI with recipes & descriptions for almost every item (JEI is creative inventory replacement)
- Custom protocol channel extensions (eg for custom block models in the world)
- Play with friends over internet! (P2P is powered by Peer.js discovery servers)
- ~~Google Drive support for reading / saving worlds back to the cloud~~
- Support for custom rendering 3D engines. Modular architecture.
- even even more!
All components that are in [Storybook](https://mcraft.fun/storybook) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react)
All components that are in [Storybook](https://minimap.mcraft.fun/storybook/) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react)
### Recommended Settings
- Controls -> **Raw Input** -> **On** - This will make the controls more precise
- Controls -> **Touch Controls Type** -> **Joystick**
- Controls -> **Auto Full Screen** -> **On** - To avoid ctrl+w issue
- Interface -> **Chat Select** -> **On** - To select chat messages
- Interface -> **Enable Minimap** -> **Always** - To enable useful minimap (why not?)
- Controls -> **Raw Input** -> **On** - This will make the controls more precise (UPD: already enabled by default)
- Interface -> **Chat Select** -> **On** - To select chat messages (UPD: already enabled by default)
### Browser Notes
This project is tested with BrowserStack. Special thanks to [BrowserStack](https://www.browserstack.com/) for providing testing infrastructure!
Howerver, it's known that these browsers have issues:
**Opera Mini**: Disable *mouse gestures* in browsre settings to avoid opening new tab on right click hold
**Vivaldi**: Disable Controls -> *Raw Input* in game settings if experiencing issues
### Versions Support
Server versions 1.8 - 1.21.5 are supported.
First class versions (most of the features are tested on these versions):
- 1.19.4
- 1.21.4
Versions below 1.13 are not tested currently and may not work correctly.
### World Loading
@ -37,12 +70,37 @@ Whatever offline mode you used (zip, folder, just single player), you can always
![docs-assets/singleplayer-future-city-1-10-2.jpg](./docs-assets/singleplayer-future-city-1-10-2.jpg)
### Servers
### Servers & Proxy
You can play almost on any server, supporting offline connections.
You can play almost on any Java server, vanilla servers are fully supported.
See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the list of supported versions (should support majority of versions).
There is a builtin proxy, but you can also host a your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field.
MS account authentication will be supported soon.
There is a builtin proxy, but you can also host your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. Or you can deploy it to the cloud service:
[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F)
> **Note**: If you want to make **your own** Minecraft server accessible to web clients (without our proxies), you can use [mwc-proxy](https://github.com/zardoy/mwc-proxy) - a lightweight JS WebSocket proxy that runs on the same server as your Minecraft server, allowing players to connect directly via `wss://play.example.com`. `?client_mcraft` is added to the URL, so the proxy will know that it's this client.
Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser.
```mermaid
graph LR
A[Web App - Client] --> C[Proxy Server]
C --> B[Minecraft Server]
style A fill:#f9d,stroke:#333,stroke-width:2px
style B fill:#fc0,stroke:#333,stroke-width:2px
style C fill:#fff,stroke:#333,stroke-width:2px
```
So if the server is located in Europe and you are connecting from Europe, you will have ~40ms ping (~180ms with residential proxy version), however if you are in the US and connecting to the server located in US, you will have >200ms ping, which is the worst case scenario.
Again, the proxy server is not a part of the client, it is a separate service that you can host yourself.
### Docker Files
- [Main Dockerfile](./Dockerfile) - for production build & offline/private usage. Includes full web-app + proxy server for connecting to Minecraft servers.
- [Proxy Dockerfile](./Dockerfile.proxy) - for proxy server only - that one you would be able to specify in the proxy field on the client (`docker build . -f Dockerfile.proxy -t minecraft-web-proxy`)
In case of using docker, you don't have to follow preparation steps from CONTRIBUTING.MD, like installing Node.js, pnpm, etc.
### Rendering
@ -53,42 +111,37 @@ MS account authentication will be supported soon.
- Supports resource packs
- Doesn't support occlusion culling
<!-- TODO proxy server communication graph -->
### Things that are not planned yet
- Mods, plugins (basically JARs) support, shaders - since they all are related to specific game pipelines
### Advanced Settings
There are many many settings, that are not exposed in the UI yet. You can find or change them by opening the browser console and typing `options`. You can also change them by typing `options.<setting_name> = <value>`.
### Console
To open the console, press `F12`, or if you are on mobile, you can type `#debug` in the URL (browser address bar), it wont't reload the page, but you will see a button to open the console. This way you can change advanced settings and see all errors or warnings. Also this way you can access global variables (described below).
To open the console, press `F12`, or if you are on mobile, you can type `#dev` in the URL (browser address bar), it wont't reload the page, but you will see a button to open the console. This way you can change advanced settings and see all errors or warnings. Also this way you can access global variables (described below).
### Debugging
### Development, Debugging & Contributing
It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info.
It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. Also you can look at Dockerfile for reference.
There is world renderer playground ([link](https://mcon.vercel.app/playground.html)).
There is world renderer playground ([link](https://mcon.vercel.app/playground/)).
However, there are many things that can be done in online version. You can access some global variables in the console and useful examples:
However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples:
- `localStorage.debug = '*'` - Enables all debug messages! Warning: this will start all packets spam.
- If you type `debugToggle`, press enter in console - It will enables all debug messages! Warning: this will start all packets spam.
Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can use `debugTopPackets` (with JSON.stringify) to see what packets were received/sent by name
- `bot` - Mineflayer bot instance. See Mineflayer documentation for more.
- `viewer` - Three.js viewer instance, basically does all the rendering.
- `viewer.world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group.
- `world` - Three.js world instance, basically does all the rendering (part of renderer backend).
- `world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group.
- `debugSceneChunks` - The same as above, but relative to current bot position (e.g. 0,0 is the current chunk).
- `debugChangedOptions` - See what options are changed. Don't change options here.
- `localServer`/`server` - Only for singleplayer mode/host. Flying Squid server instance, see it's documentation for more.
- `localServer.overworld.storageProvider.regions` - See ALL LOADED region files with all raw data.
- `localServer.levelData.LevelName = 'test'; localServer.writeLevelDat()` - Change name of the world
- `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read.
The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `viewer.camera.position` to see the camera position and so on.
The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `world.getCameraPosition()` to see the camera position and so on.
<img src="./docs-assets/watch-expr.png" alt="Watch expression" width="480"/>
@ -107,19 +160,62 @@ world chunks have a *yellow* border, hostile mobs have a *red* outline, passive
Press `Y` to set query parameters to url of your current game state.
- `?ip=<server_address>` - Display connect screen to the server on load
- `?username=<username>` - Set the username for server
- `?proxy=<proxy_address>` - Set the proxy server address to use for server
- `?version=<version>` - Set the version for server
- `?lockConnect=true` - Disable cancel / save buttons, useful for integrates iframes
- `?reconnect=true` - Reconnect to the server on page reloads. Available in **dev mode only** and very useful on server testing.
<!-- - `?password=<password>` - Set the password on load -->
- `?loadSave=<save_name>` - Load the save on load with the specified folder name (not title)
- `?singleplayer=1` - Create empty world on load. Nothing will be saved
- `?noSave=true` - Disable auto save on unload / disconnect / export. Only manual save with `/save` command will work
There are some parameters you can set in the url to archive some specific behaviors:
- `?map=<map_url>` - Load the map from ZIP. You can use any url, but it must be CORS enabled.
- `?setting=<setting_name>:<setting_value>` - Set the and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4`
General:
- **`?setting=<setting_name>:<setting_value>`** - Set and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4`
- `?modal=<modal>` - Open specific modal on page load eg `keybindings`. Very useful on UI changes testing during dev. For path use `,` as separator. To get currently opened modal type this in the console: `activeModalStack.at(-1).reactType`
- `?replayFileUrl=<url>` - Load and start a packet replay session from a URL with a integrated server. For debugging / previewing recorded sessions. The file must be CORS enabled.
Server specific:
- `?ip=<server_address>` - Display connect screen to the server on load with predefined server ip. `:<port>` is optional and can be added to the ip.
- `?name=<name>` - Set the server name for saving to the server list
- `?version=<version>` - Set the version for the server
- `?proxy=<proxy_address>` - Set the proxy server address to use for the server
- `?username=<username>` - Set the username for the server
- `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes.
- `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes.
- `?serversList=<list_or_url>` - `<list_or_url>` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs.
- `?addPing=<ping>` - Add a latency to both sides of the connection. Useful for testing ping issues. For example `?addPing=100` will add 200ms to your ping.
Single player specific:
- `?loadSave=<save_name>` - Load the save on load with the specified folder name (not title)
- `?singleplayer=1` or `?sp=1` - Create empty world on load. Nothing will be saved
- `?version=<version>` - Set the version for the singleplayer world (when used with `?singleplayer=1`)
- `?noSave=true` - Disable auto save on unload / disconnect / export whenever a world is loaded. Only manual save with `/save` command will work.
- `?serverSetting=<key>:<value>` - Set local server [options](https://github.com/zardoy/space-squid/tree/everything/src/modules.ts#L51). For example `?serverSetting=noInitialChunksSend:true` will disable initial chunks loading on the loading screen.
- `?map=<map_url>` - Load the map from ZIP. You can use any url, but it must be **CORS enabled**.
- `?mapDir=<index_file_url>` - Load the map from a file descriptor. It's recommended and the fastest way to load world but requires additional setup. The file must be in the following format:
```json
{
"baseUrl": "<url>",
"index": {
"level.dat": null,
"region": {
"r.-1.-1.mca": null,
"r.-1.0.mca": null,
"r.0.-1.mca": null,
"r.0.0.mca": null,
}
}
}
```
Note that `mapDir` also accepts base64 encoded JSON like so:
`?mapDir=data:application/json;base64,...` where `...` is the base64 encoded JSON of the index file.
In this case you must use `?mapDirBaseUrl` to specify the base URL to fetch the files from index.
- `?mapDirBaseUrl` - See above.
Only during development:
- `?reconnect=true` - Reconnect to the server on page reloads. Very useful on server testing.
<!-- - `?mapDirGuess=<base_url>` - Load the map from the provided URL and paths will be guessed with a few additional fetch requests. -->
### Notable Things that Power this Project
@ -131,6 +227,12 @@ Press `Y` to set query parameters to url of your current game state.
- [Peer.js](https://peerjs.com/) - P2P networking (when you open to wan)
- [Three.js](https://threejs.org/) - Helping in 3D rendering
### Things that are not planned yet
- Mods, plugins (basically JARs) support, shaders - since they all are related to specific game pipelines
### Alternatives
- [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true)
- [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser)
- [js-minecraft](https://github.com/LabyStudio/js-minecraft) - An insanely well done clone from the graphical side that inspired many features here

View file

@ -1,9 +1,13 @@
# Minecraft React
Minecraft UI components for React extracted from [mcraft.fun](https://mcraft.fun) project.
```bash
yarn add minecraft-react
pnpm i minecraft-react
```
![demo](./docs-assets/npm-banner.jpeg)
## Usage
```jsx

58
TECH.md Normal file
View file

@ -0,0 +1,58 @@
### Eaglercraft Comparison
This project uses proxies so you can connect to almost any vanilla server. Though proxies have some limitations such as increased latency and servers will complain about using VPN (though we have a workaround for that, but ping will be much higher).
This client generally has better performance but some features reproduction might be inaccurate eg its less stable and more buggy in some cases.
| Feature | This project | Eaglercraft | Description |
| --------------------------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| General | | | |
| Mobile Support (touch) | ✅(+) | ✅ | |
| Gamepad Support | ✅ | ❌ | |
| A11Y | ✅ | ❌ | We have DOM for almost all UI so your extensions and other browser features will work natively like on any other web page (but maybe it's not needed) |
| Game Features | | | |
| Servers Support (quality) | ❌(+) | ✅ | Eaglercraft is vanilla Minecraft, while this project tries to emulate original game behavior at protocol level (Mineflayer is used) |
| Servers Support (any version, ip) | ✅ | ❌ | We support almost all Minecraft versions, only important if you connect to a server where you need new content like blocks or if you play with friends. And you can connect to almost any server using proxy servers! |
| Servers Support (online mode) | ✅ | ❌ | Join to online servers like Hypixel using your Microsoft account without additional proxies |
| Singleplayer Survival Features | ❌ | ✅ | Just like Eaglercraft this project can generate and save worlds, but generator is simple and only a few survival features are supported (look here for [supported features list](https://github.com/zardoy/space-squid)) |
| Singleplayer Maps | ✅ | ✅ | We support any version, but adventure maps won't work, but simple parkour and build maps might be interesting to explore... |
| Singleplayer Maps World Streaming | ✅ | ❌ | Thanks to Browserfs, saves can be loaded to local singleplayer server using multiple ways: from local folder, server directory (not zip), dropbox or other cloud *backend* etc... |
| P2P Multiplayer | ✅ | ✅ | A way to connect to other browser running the project. But it's almost useless here since many survival features are not implemented. Maybe only to build / explore maps together... |
| Voice Chat | ❌(+) | ✅ | Eaglercraft has custom WebRTC voice chat implementation, though it could also be easily implemented there |
| Online Servers | ✅ | ❌ | We have custom implementation (including integration on proxy side) for joining to servers |
| Plugin Features | ✅ | ❌ | We have Mineflayer plugins support, like Auto Jump & Auto Parkour was added here that way |
| Direct Connection | ✅ | ✅ | We have DOM for almost all UI so your extensions and other browser features will work natively like on any other web page |
| Moding | ✅(own js mods) | ❌ | This project will support mods for singleplayer. In theory its possible to implement support for modded servers on protocol level (including all needed mods) |
| Video Recording | ❌ | ✅ | Doesn't feel needed |
| Metaverse Features | ✅(50%) | ❌ | We have videos / images support inside world, but not iframes (custom protocol channel) |
| Sounds | ✅ | ✅ | |
| Resource Packs | ✅(+extras) | ✅ | This project has very limited support for them (only textures images are loadable for now) |
| Assets Compressing & Splitting | ✅ | ❌ | We have advanced Minecraft data processing and good code chunk splitting so the web app will open much faster and use less memory |
| Graphics | | | |
| Fancy Graphics | ❌ | ✅ | While Eaglercraft has top-level shaders we don't even support lighting |
| Fast & Efficient Graphics | ❌(+) | ❌ | Feels like no one needs to have 64 rendering distance work smoothly |
| VR | ✅(-) | ❌ | Feels like not needed feature. UI is missing in this project since DOM can't be rendered in VR so Eaglercraft could be better in that aspect |
| AR | ❌ | ❌ | Would be the most useless feature |
| Minimap & Waypoints | ✅(-) | ❌ | We have buggy minimap, which can be enabled in settings and full map is opened by pressing `M` key |
Features available to only this project:
- CSS & JS Customization
- JS Real Time Debugging & Console Scripting (eg Devtools)
### Tech Stack
Bundler: Rsbuild!
UI: powered by React and css modules. Storybook helps with UI development.
### Rare WEB Features
There are a number of web features that are not commonly used but you might be interested in them if you decide to build your own game in the web.
TODO
| API | Usage & Description |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `Crypto` API | Used to make chat features work when joining online servers with authentication. |
| `requestPointerLock({ unadjustedMovement: true })` API | Required for games. Disables system mouse acceleration (important for Mac users). Aka mouse raw input |
| `navigator.keyboard.lock()` | (only in Chromium browsers) When entering fullscreen it allows to use any key combination like ctrl+w in the game |
| `navigator.keyboard.getLayoutMap()` | (only in Chromium browsers) To display the right keyboard symbol for the key keybinding on different keyboard layouts (e.g. QWERTY vs AZERTY) |

View file

Before

Width:  |  Height:  |  Size: 859 KiB

After

Width:  |  Height:  |  Size: 859 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 952 KiB

After

Width:  |  Height:  |  Size: 952 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 704 KiB

After

Width:  |  Height:  |  Size: 704 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 684 KiB

After

Width:  |  Height:  |  Size: 684 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

39
assets/config.html Normal file
View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configure client</title>
<script>
function removeSettings() {
if (confirm('Are you sure you want to RESET ALL SETTINGS?')) {
localStorage.setItem('options', '{}');
location.reload();
}
}
function removeAllData() {
localStorage.removeItem('serversList')
localStorage.removeItem('serversHistory')
localStorage.removeItem('authenticatedAccounts')
localStorage.removeItem('modsAutoUpdateLastCheck')
localStorage.removeItem('firstModsPageVisit')
localStorage.removeItem('proxiesData')
localStorage.removeItem('keybindings')
localStorage.removeItem('username')
localStorage.removeItem('customCommands')
localStorage.removeItem('options')
}
</script>
</head>
<body>
<div style="display: flex;gap: 10px;">
<button onclick="removeSettings()">Reset all settings</button>
<button onclick="removeAllData()">Remove all user data (but not mods or worlds)</button>
<!-- <button>Remove all user data (worlds, resourcepacks)</button> -->
<!-- <button>Remove all mods</button> -->
<!-- <button>Remove all mod repositories</button> -->
</div>
<input />
</body>
</html>

View file

@ -0,0 +1,2 @@
here you can place custom textures for bundled files (blocks/items) e.g. blocks/stone.png
get file names from here (blocks/items) https://zardoy.github.io/mc-assets/

237
assets/debug-inputs.html Normal file
View file

@ -0,0 +1,237 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Input Debugger</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background: #f0f0f0;
}
.key-container {
display: grid;
grid-template-columns: repeat(3, 60px);
gap: 5px;
margin: 20px 0;
}
.key {
width: 60px;
height: 60px;
border: 2px solid #333;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
background: white;
position: relative;
user-select: none;
}
.key.pressed {
background: #90EE90;
}
.key .duration {
position: absolute;
bottom: 2px;
font-size: 10px;
}
.key .count {
position: absolute;
top: 2px;
right: 2px;
font-size: 10px;
}
.controls {
margin: 20px 0;
padding: 10px;
background: white;
border-radius: 5px;
}
.wasd-container {
position: relative;
width: 190px;
height: 130px;
}
#KeyW {
position: absolute;
left: 65px;
top: 0;
}
#KeyA {
position: absolute;
left: 0;
top: 65px;
}
#KeyS {
position: absolute;
left: 65px;
top: 65px;
}
#KeyD {
position: absolute;
left: 130px;
top: 65px;
}
.space-container {
margin-top: 20px;
}
#Space {
width: 190px;
}
</style>
</head>
<body>
<div class="controls">
<label>
<input type="checkbox" id="repeatMode"> Use keydown repeat mode (auto key-up after 150ms of no repeat)
</label>
</div>
<div class="wasd-container">
<div id="KeyW" class="key" data-code="KeyW">W</div>
<div id="KeyA" class="key" data-code="KeyA">A</div>
<div id="KeyS" class="key" data-code="KeyS">S</div>
<div id="KeyD" class="key" data-code="KeyD">D</div>
</div>
<div class="key-container">
<div id="ControlLeft" class="key" data-code="ControlLeft">Ctrl</div>
</div>
<div class="space-container">
<div id="Space" class="key" data-code="Space">Space</div>
</div>
<script>
const keys = {};
const keyStats = {};
const pressStartTimes = {};
const keyTimeouts = {};
function initKeyStats(code) {
if (!keyStats[code]) {
keyStats[code] = {
pressCount: 0,
duration: 0,
startTime: 0
};
}
}
function updateKeyVisuals(code) {
const element = document.getElementById(code);
if (!element) return;
const stats = keyStats[code];
if (keys[code]) {
element.classList.add('pressed');
const currentDuration = ((Date.now() - stats.startTime) / 1000).toFixed(1);
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="duration">${currentDuration}s</span><span class="count">${stats.pressCount}</span>`;
} else {
element.classList.remove('pressed');
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="count">${stats.pressCount}</span>`;
}
}
function releaseKey(code) {
keys[code] = false;
if (pressStartTimes[code]) {
keyStats[code].duration += (Date.now() - pressStartTimes[code]) / 1000;
delete pressStartTimes[code];
}
updateKeyVisuals(code);
}
function handleKeyDown(event) {
const code = event.code;
const isRepeatMode = document.getElementById('repeatMode').checked;
initKeyStats(code);
// Clear any existing timeout for this key
if (keyTimeouts[code]) {
clearTimeout(keyTimeouts[code]);
delete keyTimeouts[code];
}
if (isRepeatMode) {
// In repeat mode, always handle the keydown
if (!keys[code] || event.repeat) {
keys[code] = true;
if (!event.repeat) {
// Only increment count on initial press, not repeats
keyStats[code].pressCount++;
keyStats[code].startTime = Date.now();
pressStartTimes[code] = Date.now();
}
}
// Set timeout to release key if no repeat events come
keyTimeouts[code] = setTimeout(() => {
releaseKey(code);
}, 150);
} else {
// In normal mode, only handle keydown if key is not already pressed
if (!keys[code]) {
keys[code] = true;
keyStats[code].pressCount++;
keyStats[code].startTime = Date.now();
pressStartTimes[code] = Date.now();
}
}
updateKeyVisuals(code);
event.preventDefault();
}
function handleKeyUp(event) {
const code = event.code;
const isRepeatMode = document.getElementById('repeatMode').checked;
if (!isRepeatMode) {
releaseKey(code);
}
event.preventDefault();
}
// Initialize all monitored keys
const monitoredKeys = ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'ControlLeft', 'Space'];
monitoredKeys.forEach(code => {
initKeyStats(code);
const element = document.getElementById(code);
if (element) {
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="count">0</span>`;
}
});
// Start visual updates
setInterval(() => {
monitoredKeys.forEach(code => {
if (keys[code]) {
updateKeyVisuals(code);
}
});
}, 100);
// Event listeners
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
// Handle mode changes
document.getElementById('repeatMode').addEventListener('change', () => {
// Release all keys when switching modes
monitoredKeys.forEach(code => {
if (keys[code]) {
releaseKey(code);
}
if (keyTimeouts[code]) {
clearTimeout(keyTimeouts[code]);
delete keyTimeouts[code];
}
});
});
</script>
</body>
</html>

BIN
assets/destroy_stage_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

BIN
assets/destroy_stage_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

BIN
assets/destroy_stage_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

BIN
assets/destroy_stage_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

BIN
assets/destroy_stage_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

BIN
assets/destroy_stage_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

BIN
assets/destroy_stage_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

BIN
assets/destroy_stage_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

BIN
assets/destroy_stage_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

BIN
assets/destroy_stage_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

View file

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 433 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 KiB

After

Width:  |  Height:  |  Size: 513 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

View file

@ -1,6 +1,6 @@
{
"name": "Prismarine Web Client",
"short_name": "Prismarine Web Client",
"name": "Minecraft Web Client",
"short_name": "Minecraft Web Client",
"scope": "./",
"start_url": "./",
"icons": [

4
assets/playground.html Normal file
View file

@ -0,0 +1,4 @@
<!-- just redirect to /playground/ -->
<script>
window.location.href = `/playground/${window.location.search}`
</script>

View file

@ -1,23 +1,80 @@
{
"version": 1,
"defaultHost": "<from-proxy>",
"defaultProxy": "proxy.mcraft.fun",
"defaultProxy": "https://proxy.mcraft.fun",
"mapsProvider": "https://maps.mcraft.fun/",
"skinTexturesProxy": "",
"peerJsServer": "",
"peerJsServerFallback": "https://p2p.mcraft.fun",
"promoteServers": [
{
"ip": "wss://play.mcraft.fun"
},
{
"ip": "wss://play.webmc.fun",
"name": "WebMC"
},
{
"ip": "wss://ws.fuchsmc.net"
},
{
"ip": "wss://play2.mcraft.fun"
},
{
"ip": "wss://play-creative.mcraft.fun",
"description": "Might be available soon, stay tuned!"
},
{
"ip": "kaboom.pw",
"version": "1.18.2",
"description": "Chaos and destruction server. Free for everyone."
"version": "1.20.3",
"description": "Very nice a polite server. Must try for everyone!"
}
],
"rightSideText": "A Minecraft client clone in the browser!",
"splashText": "The sunset is coming!",
"splashTextFallback": "Welcome!",
"pauseLinks": [
[
{
"type": "github"
},
{
"type": "discord"
}
]
],
"defaultUsername": "mcrafter{0-9999}",
"mobileButtons": [
{
"action": "general.drop",
"actionHold": "general.dropStack",
"label": "Q"
},
{
"ip": "go.mineberry.org",
"version": "1.18.2",
"description": "One of the best servers here. Join now!"
"action": "general.selectItem",
"actionHold": "",
"label": "S"
},
{
"ip": "sus.shhnowisnottheti.me",
"version": "1.18.2",
"description": "Creative, your own 'boxes' (islands)"
"action": "general.debugOverlay",
"actionHold": "general.debugOverlayHelpMenu",
"label": "F3"
},
{
"action": "general.playersList",
"actionHold": "",
"icon": "pixelarticons:users",
"label": "TAB"
},
{
"action": "general.chat",
"actionHold": "",
"label": ""
},
{
"action": "ui.pauseMenu",
"actionHold": "",
"label": ""
}
]
}

5
config.mcraft-only.json Normal file
View file

@ -0,0 +1,5 @@
{
"alwaysReconnectButton": true,
"reportBugButtonWithReconnect": true,
"allowAutoConnect": true
}

View file

@ -1,8 +1,11 @@
import { defineConfig } from 'cypress'
const isPerformanceTest = process.env.PERFORMANCE_TEST === 'true'
export default defineConfig({
video: false,
chromeWebSecurity: false,
screenshotOnRunFailure: true, // Enable screenshots on test failures
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
@ -31,7 +34,7 @@ export default defineConfig({
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://localhost:8080',
specPattern: 'cypress/e2e/**/*.spec.ts',
specPattern: !isPerformanceTest ? 'cypress/e2e/smoke.spec.ts' : 'cypress/e2e/rendering_performance.spec.ts',
excludeSpecPattern: ['**/__snapshots__/*', '**/__image_snapshots__/*'],
},
})

View file

@ -0,0 +1,32 @@
/// <reference types="cypress" />
import { BenchmarkAdapterInfo, getAllInfoLines } from '../../src/benchmarkAdapter'
import { cleanVisit } from './shared'
it('Benchmark rendering performance', () => {
cleanVisit('/?openBenchmark=true&renderDistance=5')
// wait for render end event
return cy.document().then({ timeout: 180_000 }, doc => {
return new Cypress.Promise(resolve => {
cy.log('Waiting for world to load')
doc.addEventListener('cypress-world-ready', resolve)
}).then(() => {
cy.log('World loaded')
})
}).then(() => {
cy.window().then(win => {
const adapter = win.benchmarkAdapter as BenchmarkAdapterInfo
const messages = getAllInfoLines(adapter)
// wait for 10 seconds
cy.wait(10_000)
const messages2 = getAllInfoLines(adapter, true)
for (const message of messages) {
cy.log(message)
}
for (const message of messages2) {
cy.log(message)
}
cy.writeFile('benchmark.txt', [...messages, ...messages2].join('\n'))
})
})
})

View file

@ -14,7 +14,7 @@ const compareRenderedFlatWorld = () => {
}
const testWorldLoad = () => {
return cy.document().then({ timeout: 20_000 }, doc => {
return cy.document().then({ timeout: 35_000 }, doc => {
return new Cypress.Promise(resolve => {
doc.addEventListener('cypress-world-ready', resolve)
})
@ -38,18 +38,18 @@ it('Loads & renders singleplayer', () => {
testWorldLoad()
})
it('Joins to local flying-squid server', () => {
it.skip('Joins to local flying-squid server', () => {
visit('/?ip=localhost&version=1.16.1')
window.localStorage.version = ''
// todo replace with data-test
// cy.get('[data-test-id="servers-screen-button"]').click()
// cy.get('[data-test-id="server-ip"]').clear().focus().type('localhost')
// cy.get('[data-test-id="version"]').clear().focus().type('1.16.1') // todo needs to fix autoversion
cy.get('[data-test-id="connect-qs"]').click()
cy.get('[data-test-id="connect-qs"]').click() // todo! cypress sometimes doesn't click
testWorldLoad()
})
it('Joins to local latest Java vanilla server', () => {
it.skip('Joins to local latest Java vanilla server', () => {
const version = supportedVersions.at(-1)!
cy.task('startServer', [version, 25_590]).then(() => {
visit('/?ip=localhost:25590&username=bot')

View file

@ -1,6 +1,6 @@
//@ts-check
import mcServer from 'flying-squid'
import defaultOptions from 'flying-squid/config/default-settings.json' assert { type: 'json' }
import defaultOptions from 'flying-squid/config/default-settings.json' with { type: 'json' }
/** @type {Options} */
const serverOptions = {

View file

@ -0,0 +1,169 @@
# Handled Packets
## Server -> Client
❌ statistics
❌ advancements
❌ face_player
❌ nbt_query_response
❌ chat_suggestions
❌ trade_list
❌ vehicle_move
❌ open_book
❌ craft_recipe_response
❌ end_combat_event
❌ enter_combat_event
❌ unlock_recipes
❌ camera
❌ update_view_position
❌ update_view_distance
❌ entity_sound_effect
❌ stop_sound
❌ feature_flags
❌ select_advancement_tab
❌ declare_recipes
❌ tags
❌ acknowledge_player_digging
❌ initialize_world_border
❌ world_border_center
❌ world_border_lerp_size
❌ world_border_size
❌ world_border_warning_delay
❌ world_border_warning_reach
❌ simulation_distance
❌ chunk_biomes
❌ hurt_animation
✅ damage_event
✅ spawn_entity
✅ spawn_entity_experience_orb
✅ named_entity_spawn
✅ animation
✅ block_break_animation
✅ tile_entity_data
✅ block_action
✅ block_change
✅ boss_bar
✅ difficulty
✅ tab_complete
✅ declare_commands
✅ multi_block_change
✅ close_window
✅ open_window
✅ window_items
✅ craft_progress_bar
✅ set_slot
✅ set_cooldown
✅ custom_payload
✅ hide_message
✅ kick_disconnect
✅ profileless_chat
✅ entity_status
✅ explosion
✅ unload_chunk
✅ game_state_change
✅ open_horse_window
✅ keep_alive
✅ map_chunk
✅ world_event
✅ world_particles
✅ update_light
✅ login
✅ map
✅ rel_entity_move
✅ entity_move_look
✅ entity_look
✅ open_sign_entity
✅ abilities
✅ player_chat
✅ death_combat_event
✅ player_remove
✅ player_info
✅ position
✅ entity_destroy
✅ remove_entity_effect
✅ resource_pack_send
✅ respawn
✅ entity_head_rotation
✅ held_item_slot
✅ scoreboard_display_objective
✅ entity_metadata
✅ attach_entity
✅ entity_velocity
✅ entity_equipment
✅ experience
✅ update_health
✅ scoreboard_objective
✅ set_passengers
✅ teams
✅ scoreboard_score
✅ spawn_position
✅ update_time
✅ sound_effect
✅ system_chat
✅ playerlist_header
✅ collect
✅ entity_teleport
✅ entity_update_attributes
✅ entity_effect
✅ server_data
✅ clear_titles
✅ action_bar
✅ ping
✅ set_title_subtitle
✅ set_title_text
✅ set_title_time
✅ packet
## Client -> Server
❌ query_block_nbt
❌ set_difficulty
❌ query_entity_nbt
❌ pick_item
❌ set_beacon_effect
❌ update_command_block_minecart
❌ update_structure_block
❌ generate_structure
❌ lock_difficulty
❌ craft_recipe_request
❌ displayed_recipe
❌ recipe_book
❌ update_jigsaw_block
❌ spectate
❌ advancement_tab
✅ teleport_confirm
✅ chat_command
✅ chat_message
✅ message_acknowledgement
✅ edit_book
✅ name_item
✅ select_trade
✅ update_command_block
✅ tab_complete
✅ client_command
✅ settings
✅ enchant_item
✅ window_click
✅ close_window
✅ custom_payload
✅ use_entity
✅ keep_alive
✅ position
✅ position_look
✅ look
✅ flying
✅ vehicle_move
✅ steer_boat
✅ abilities
✅ block_dig
✅ entity_action
✅ steer_vehicle
✅ resource_pack_receive
✅ held_item_slot
✅ set_creative_slot
✅ update_sign
✅ arm_animation
✅ block_place
✅ use_item
✅ pong
✅ chat_session_update

BIN
docs-assets/npm-banner.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View file

@ -1,135 +0,0 @@
//@ts-check
import * as esbuild from 'esbuild'
import fs from 'fs'
// import htmlPlugin from '@chialab/esbuild-plugin-html'
import server from './server.js'
import { clients, plugins, startWatchingHmr } from './scripts/esbuildPlugins.mjs'
import { generateSW } from 'workbox-build'
import { getSwAdditionalEntries } from './scripts/build.js'
import { build } from 'esbuild'
//@ts-ignore
try { await import('./localSettings.mjs') } catch { }
const entrypoint = 'index.ts'
fs.writeFileSync('dist/index.html', fs.readFileSync('index.html', 'utf8').replace('<!-- inject script -->', `<script src="${entrypoint.replace(/\.tsx?/, '.js')}"></script>`), 'utf8')
const watch = process.argv.includes('--watch') || process.argv.includes('-w')
const prod = process.argv.includes('--prod')
if (prod) process.env.PROD = 'true'
const dev = !prod
const banner = [
'window.global = globalThis;',
]
const buildingVersion = new Date().toISOString().split(':')[0]
/** @type {import('esbuild').BuildOptions} */
const buildOptions = {
bundle: true,
entryPoints: [`src/${entrypoint}`],
target: ['es2020'],
jsx: 'automatic',
jsxDev: dev,
// logLevel: 'debug',
logLevel: 'info',
platform: 'browser',
sourcemap: prod ? true : 'linked',
outdir: 'dist',
mainFields: [
'browser', 'module', 'main'
],
keepNames: true,
banner: {
// using \n breaks sourcemaps!
js: banner.join(';'),
},
external: [
'sharp'
],
alias: {
events: 'events', // make explicit
buffer: 'buffer',
'fs': 'browserfs/dist/shims/fs.js',
http: 'http-browserify',
perf_hooks: './src/perf_hooks_replacement.js',
crypto: './src/crypto.js',
stream: 'stream-browserify',
net: 'net-browserify',
assert: 'assert',
dns: './src/dns.js',
// todo write advancedAliases plugin
},
inject: [
'./src/shims.js'
],
metafile: true,
plugins,
sourcesContent: !process.argv.includes('--no-sources'),
minify: process.argv.includes('--minify'),
define: {
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'),
'process.env.BUILD_VERSION': JSON.stringify(!dev ? buildingVersion : 'undefined'),
'process.env.GITHUB_URL':
JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}`}`),
'process.env.DEPS_VERSIONS': JSON.stringify({})
},
loader: {
// todo use external or resolve issues with duplicating
'.png': 'dataurl',
'.svg': 'dataurl',
'.map': 'empty',
'.vert': 'text',
'.frag': 'text',
'.obj': 'text',
},
write: false,
// todo would be better to enable?
// preserveSymlinks: true,
}
if (watch) {
const ctx = await esbuild.context(buildOptions)
await ctx.watch()
startWatchingHmr()
server.app.get('/esbuild', (req, res, next) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
})
// Send a comment to keep the connection alive
res.write(': ping\n\n')
// Add the client response to the clients array
clients.push(res)
// Handle any client disconnection logic
res.on('close', () => {
const index = clients.indexOf(res)
if (index !== -1) {
clients.splice(index, 1)
}
})
})
} else {
const result = await build(buildOptions)
// console.log(await esbuild.analyzeMetafile(result.metafile))
if (prod) {
fs.writeFileSync('dist/version.txt', buildingVersion, 'utf-8')
const { count, size, warnings } = await generateSW({
// dontCacheBustURLsMatching: [new RegExp('...')],
globDirectory: 'dist',
skipWaiting: true,
clientsClaim: true,
additionalManifestEntries: getSwAdditionalEntries(),
globPatterns: [],
swDest: 'dist/service-worker.js',
})
}
}

1
experiments/decode.html Normal file
View file

@ -0,0 +1 @@
<script src="decode.ts" type="module"></script>

26
experiments/decode.ts Normal file
View file

@ -0,0 +1,26 @@
// Include the pako library
import pako from 'pako';
import compressedJsRaw from './compressed.js?raw'
function decompressFromBase64(input) {
// Decode the Base64 string
const binaryString = atob(input);
const len = binaryString.length;
const bytes = new Uint8Array(len);
// Convert the binary string to a byte array
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// Decompress the byte array
const decompressedData = pako.inflate(bytes, { to: 'string' });
return decompressedData;
}
// Use the function
console.time('decompress');
const decompressedData = decompressFromBase64(compressedJsRaw);
console.timeEnd('decompress')
console.log(decompressedData)

1
experiments/state.html Normal file
View file

@ -0,0 +1 @@
<script src="state.ts" type="module"></script>

37
experiments/state.ts Normal file
View file

@ -0,0 +1,37 @@
import { SmoothSwitcher } from '../renderer/viewer/lib/smoothSwitcher'
const div = document.createElement('div')
div.style.width = '100px'
div.style.height = '100px'
div.style.backgroundColor = 'red'
document.body.appendChild(div)
const pos = {x: 0, y: 0}
const positionSwitcher = new SmoothSwitcher(() => pos, (key, value) => {
pos[key] = value
})
globalThis.positionSwitcher = positionSwitcher
document.body.addEventListener('keydown', e => {
if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
const to = {
x: e.code === 'ArrowLeft' ? -100 : 100
}
console.log(pos, to)
positionSwitcher.transitionTo(to, e.code === 'ArrowLeft' ? 'Left' : 'Right', () => {
console.log('Switched to ', e.code === 'ArrowLeft' ? 'Left' : 'Right')
})
}
if (e.code === 'Space') {
pos.x = 200
}
})
const render = () => {
positionSwitcher.update()
div.style.transform = `translate(${pos.x}px, ${pos.y}px)`
requestAnimationFrame(render)
}
render()

View file

@ -1,60 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
//@ts-check
import blockImg from '../prismarine-viewer/public/textures/1.16.1.png'
import blocksStates from '../prismarine-viewer/public/blocksStates/1.16.1.json'
// const block = {
// name: 'oak_sign',
// variant: 0,
// elem: 1,
// face: 'up'
// }
const block = {
name: 'light_gray_stained_glass',
variant: 0,
elem: 0,
face: 'north'
}
//@ts-ignore
const model = Object.entries(blocksStates[block.name].variants).find((a, i) => typeof block.variant === 'number' ? i === block.variant : a === block.variant)[1].model.elements[block.elem]
console.log(model)
const textureUv = model.faces[block.face].texture
const canvas = document.createElement('canvas')
canvas.style.imageRendering = 'pixelated'
document.body.appendChild(canvas)
const factor = 50
const modelWidth = model.to[0] - model.from[0]
const modelHeight = model.to[1] - model.from[1]
canvas.width = modelWidth * factor
canvas.height = modelHeight * factor
// canvas.width = 16 * factor
// canvas.height = 16 * factor * 2
const ctx = canvas.getContext('2d')
//@ts-ignore
ctx.imageSmoothingEnabled = false
const img = new Image()
const img2 = new Image()
img.src = blockImg
img.onload = () => {
//@ts-ignore
ctx.drawImage(img, img.width * textureUv.u, img.height * textureUv.v, img.width * textureUv.su, img.height * textureUv.sv, 0, 0, canvas.width, canvas.height)
// ctx.drawImage(img, 0, 0, canvas.width, canvas.height / 2)
console.log('width;height texture', img.width * textureUv.su, img.height * textureUv.sv)
console.log('base su=sv', 16 / img.width)
}
</script>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>Minecraft Item Viewer</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<script type="module" src="./three-item.ts"></script>
</body>
</html>

108
experiments/three-item.ts Normal file
View file

@ -0,0 +1,108 @@
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import itemsAtlas from 'mc-assets/dist/itemsAtlasLegacy.png'
import { createItemMeshFromCanvas, createItemMesh } from '../renderer/viewer/three/itemMesh'
// Create scene, camera and renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Setup camera and controls
camera.position.set(0, 0, 3)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
// Background and lights
scene.background = new THREE.Color(0x333333)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7)
scene.add(ambientLight)
// Animation loop
function animate () {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene, camera)
}
async function setupItemMesh () {
try {
const loader = new THREE.TextureLoader()
const atlasTexture = await loader.loadAsync(itemsAtlas)
// Pixel-art configuration
atlasTexture.magFilter = THREE.NearestFilter
atlasTexture.minFilter = THREE.NearestFilter
atlasTexture.generateMipmaps = false
atlasTexture.wrapS = atlasTexture.wrapT = THREE.ClampToEdgeWrapping
// Extract the tile at x=2, y=0 (16x16)
const tileSize = 16
const tileX = 2
const tileY = 0
const canvas = document.createElement('canvas')
canvas.width = tileSize
canvas.height = tileSize
const ctx = canvas.getContext('2d')!
ctx.imageSmoothingEnabled = false
ctx.drawImage(
atlasTexture.image,
tileX * tileSize,
tileY * tileSize,
tileSize,
tileSize,
0,
0,
tileSize,
tileSize
)
// Test both approaches - working manual extraction:
const meshOld = createItemMeshFromCanvas(canvas, { depth: 0.1 })
meshOld.position.x = -1
meshOld.rotation.x = -Math.PI / 12
meshOld.rotation.y = Math.PI / 12
scene.add(meshOld)
// And new unified function:
const atlasWidth = atlasTexture.image.width
const atlasHeight = atlasTexture.image.height
const u = (tileX * tileSize) / atlasWidth
const v = (tileY * tileSize) / atlasHeight
const sizeX = tileSize / atlasWidth
const sizeY = tileSize / atlasHeight
console.log('Debug texture coords:', {u, v, sizeX, sizeY, atlasWidth, atlasHeight})
const resultNew = createItemMesh(atlasTexture, {
u, v, sizeX, sizeY
}, {
faceCamera: false,
use3D: true,
depth: 0.1
})
resultNew.mesh.position.x = 1
resultNew.mesh.rotation.x = -Math.PI / 12
resultNew.mesh.rotation.y = Math.PI / 12
scene.add(resultNew.mesh)
animate()
} catch (err) {
console.error('Failed to create item mesh:', err)
}
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
// Start
setupItemMesh()

View file

@ -0,0 +1,5 @@
<script type="module" src="three-labels.ts"></script>
<style>
body { margin: 0; }
canvas { display: block; }
</style>

View file

@ -0,0 +1,67 @@
import * as THREE from 'three'
import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'
import { createWaypointSprite, WAYPOINT_CONFIG } from '../renderer/viewer/three/waypointSprite'
// Create scene, camera and renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Add FirstPersonControls
const controls = new FirstPersonControls(camera, renderer.domElement)
controls.lookSpeed = 0.1
controls.movementSpeed = 10
controls.lookVertical = true
controls.constrainVertical = true
controls.verticalMin = 0.1
controls.verticalMax = Math.PI - 0.1
// Position camera
camera.position.y = 1.6 // Typical eye height
camera.lookAt(0, 1.6, -1)
// Create a helper grid and axes
const grid = new THREE.GridHelper(20, 20)
scene.add(grid)
const axes = new THREE.AxesHelper(5)
scene.add(axes)
// Create waypoint sprite via utility
const waypoint = createWaypointSprite({
position: new THREE.Vector3(0, 0, -5),
color: 0xff0000,
label: 'Target',
})
scene.add(waypoint.group)
// Use built-in offscreen arrow from utils
waypoint.enableOffscreenArrow(true)
waypoint.setArrowParent(scene)
// Animation loop
function animate() {
requestAnimationFrame(animate)
const delta = Math.min(clock.getDelta(), 0.1)
controls.update(delta)
// Unified camera update (size, distance text, arrow, visibility)
const sizeVec = renderer.getSize(new THREE.Vector2())
waypoint.updateForCamera(camera.position, camera, sizeVec.width, sizeVec.height)
renderer.render(scene, camera)
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
// Add clock for controls
const clock = new THREE.Clock()
animate()

1
experiments/three.html Normal file
View file

@ -0,0 +1 @@
<script type="module" src="three.ts"></script>

60
experiments/three.ts Normal file
View file

@ -0,0 +1,60 @@
import * as THREE from 'three'
// Create scene, camera and renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Position camera
camera.position.z = 5
// Create a canvas with some content
const canvas = document.createElement('canvas')
canvas.width = 256
canvas.height = 256
const ctx = canvas.getContext('2d')
scene.background = new THREE.Color(0x444444)
// Draw something on the canvas
ctx.fillStyle = '#444444'
// ctx.fillRect(0, 0, 256, 256)
ctx.fillStyle = 'red'
ctx.font = '48px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText('Hello!', 128, 128)
// Create bitmap and texture
async function createTexturedBox() {
const canvas2 = new OffscreenCanvas(256, 256)
const ctx2 = canvas2.getContext('2d')!
ctx2.drawImage(canvas, 0, 0)
const texture = new THREE.Texture(canvas2)
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.NearestFilter
texture.needsUpdate = true
texture.flipY = false
// Create box with texture
const geometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
premultipliedAlpha: false,
})
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)
}
// Create the textured box
createTexturedBox()
// Animation loop
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()

View file

@ -1,12 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta name="darkreader-lock">
<script>
window.startLoad = Date.now()
// g663 fix: forbid change of string prototype
Object.defineProperty(String.prototype, 'format', {
writable: false,
configurable: false
});
Object.defineProperty(String.prototype, 'replaceAll', {
writable: false,
configurable: false
});
</script>
<!-- // #region initial loader -->
<script async>
const loadingDiv = `
const loadingDiv = /* html */ `
<div class="initial-loader" style="position: fixed;transition:opacity 0.2s;inset: 0;background:black;display: flex;justify-content: center;align-items: center;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', Arial, Helvetica, sans-serif;gap: 15px;" ontransitionend="this.remove()">
<div>
<img src="./loading-bg.jpg" alt="Prismarine Web Client" style="position:fixed;inset:0;width:100%;height:100%;z-index: -2;object-fit: cover;filter: blur(3px);">
@ -15,25 +25,68 @@
<div>
<div style="font-size: calc(var(--font-size) * 1.8);color: lightgray;" class="title">Loading...</div>
<div style="font-size: var(--font-size);color: rgb(176, 176, 176);margin-top: 3px;text-align: center" class="subtitle">A true Minecraft client in your browser!</div>
<!-- small text pre -->
<div style="font-size: calc(var(--font-size) * 0.6);color: rgb(150, 150, 150);margin-top: 3px;text-align: center;white-space: pre-line;" class="advanced-info"></div>
<div style="font-size: calc(var(--font-size) * 0.6);color: rgb(255, 100, 100);margin-top: 10px;text-align: center;display: none;" class="ios-warning">Only iOS 15+ is supported due to performance optimizations</div>
</div>
</div>
`
const loadingDivElem = document.createElement('div')
loadingDivElem.innerHTML = loadingDiv
if (!window.pageLoaded) {
document.documentElement.appendChild(loadingDivElem)
}
// load error handling
const onError = (message) => {
console.log(message)
if (document.querySelector('.initial-loader') && document.querySelector('.initial-loader').querySelector('.title').textContent !== 'Error') {
document.querySelector('.initial-loader').querySelector('.title').textContent = 'Error'
document.querySelector('.initial-loader').querySelector('.subtitle').textContent = message
if (window.navigator.maxTouchPoints > 1) window.location.hash = '#dev' // show eruda
const insertLoadingDiv = () => {
const loadingDivElem = document.createElement('div')
loadingDivElem.innerHTML = loadingDiv
if (!window.pageLoaded) {
document.documentElement.appendChild(loadingDivElem)
}
// iOS version detection
const getIOSVersion = () => {
const match = navigator.userAgent.match(/OS (\d+)_(\d+)_?(\d+)?/);
return match ? parseInt(match[1], 10) : null;
}
// load error handling
const onError = (errorOrMessage, log = false) => {
let message = errorOrMessage instanceof Error ? (errorOrMessage.stack ?? errorOrMessage.message) : errorOrMessage
if (log) console.log(message)
if (typeof message !== 'string') message = String(message)
if (document.querySelector('.initial-loader') && document.querySelector('.initial-loader').querySelector('.title').textContent !== 'Error') {
document.querySelector('.initial-loader').querySelector('.title').textContent = 'Error'
const [errorMessage, ...errorStack] = message.split('\n')
document.querySelector('.initial-loader').querySelector('.subtitle').textContent = errorMessage
document.querySelector('.initial-loader').querySelector('.advanced-info').textContent = errorStack.join('\n')
// Show iOS warning if applicable
const iosVersion = getIOSVersion();
if (iosVersion !== null && iosVersion < 15) {
document.querySelector('.initial-loader').querySelector('.ios-warning').style.display = 'block';
}
if (window.navigator.maxTouchPoints > 1) window.location.hash = '#dev' // show eruda
// unregister all sw
if (window.navigator.serviceWorker && document.querySelector('.initial-loader').style.opacity !== 0) {
console.log('got worker')
window.navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => {
console.log('got registration')
registration.unregister().then(() => {
console.log('worker unregistered')
})
})
})
}
window.lastError = errorOrMessage instanceof Error ? errorOrMessage : new Error(errorOrMessage)
}
}
window.addEventListener('unhandledrejection', (e) => onError(e.reason, true))
window.addEventListener('error', (e) => onError(e.error ?? e.message))
}
window.addEventListener('unhandledrejection', (e) => onError(e.reason))
window.addEventListener('error', (e) => onError(e.message))
insertLoadingDiv()
document.addEventListener('DOMContentLoaded', () => {
// move loading div to the end of body
const loadingDivElem = document.querySelector('.initial-loader');
const newContainer = document.body; // replace with your new container
if (loadingDivElem) newContainer.appendChild(loadingDivElem);
})
</script>
<script type="module" async>
const checkLoadEruda = () => {
@ -42,6 +95,25 @@
import('https://cdn.skypack.dev/eruda').then(({ default: eruda }) => {
eruda.init()
})
Promise.all([import('https://cdn.skypack.dev/stacktrace-gps'), import('https://cdn.skypack.dev/error-stack-parser')]).then(async ([{ default: StackTraceGPS }, { default: ErrorStackParser }]) => {
if (!window.lastError) return
let stackFrames = [];
if (window.lastError instanceof Error) {
stackFrames = ErrorStackParser.parse(window.lastError);
}
console.log('stackFrames', stackFrames)
const gps = new StackTraceGPS()
const mappedFrames = await Promise.all(
stackFrames.map(frame => gps.pinpoint(frame))
);
console.log('mappedFrames', mappedFrames)
const stackTrace = mappedFrames
.map(frame => `at ${frame.functionName} (${frame.fileName}:${frame.lineNumber}:${frame.columnNumber})`)
.join('\n');
console.log('stackTrace', stackTrace)
})
}
}
checkLoadEruda()
@ -75,22 +147,17 @@
window.loadedPlugins[pluginName] = await import(script)
}
</script> -->
<title>Prismarine Web Client</title>
<link rel="stylesheet" href="index.css">
<link rel="favicon" href="favicon.png">
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="canonical" href="https://mcraft.fun">
<meta name="description" content="Minecraft web client running in your browser">
<title>Minecraft Web Client</title>
<!-- <link rel="canonical" href="https://mcraft.fun"> -->
<meta name="description" content="Minecraft Java Edition Client in Browser — Full Multiplayer Support, Server Connect, Offline Play — Join real Minecraft servers">
<meta name="keywords" content="Play, Minecraft, Online, Web, Java, Server, Single player, Javascript, PrismarineJS, Voxel, WebGL, Three.js">
<meta name="date" content="2023-09-11" scheme="YYYY-MM-DD">
<meta name="date" content="2024-07-11" scheme="YYYY-MM-DD">
<meta name="language" content="English">
<meta name="theme-color" content="#349474">
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'>
<meta property="og:title" content="Prismarine Web Client" />
<meta property="og:title" content="Minecraft Web Client" />
<meta property="og:type" content="website" />
<meta property="og:image" content="favicon.png" />
<meta name="format-detection" content="telephone=no">
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
</head>
<body>
<div id="react-root"></div>

View file

@ -3,32 +3,47 @@
"version": "0.0.0-dev",
"description": "A minecraft client running in a browser",
"scripts": {
"start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --watch",
"start-watch-script": "nodemon -w esbuild.mjs --watch",
"build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod",
"check-build": "tsc && pnpm build",
"dev-rsbuild": "rsbuild dev",
"dev-proxy": "node server.js",
"start": "run-p dev-proxy dev-rsbuild watch-mesher",
"start2": "run-p dev-rsbuild watch-mesher",
"start-metrics": "ENABLE_METRICS=true rsbuild dev",
"build": "pnpm build-other-workers && rsbuild build",
"build-analyze": "BUNDLE_ANALYZE=true rsbuild build && pnpm build-other-workers",
"build-single-file": "SINGLE_FILE_BUILD=true rsbuild build",
"prepare-project": "tsx scripts/genShims.ts && tsx scripts/makeOptimizedMcData.mjs && tsx scripts/genLargeDataAliases.ts",
"check-build": "pnpm prepare-project && tsc && pnpm build",
"test:cypress": "cypress run",
"test:benchmark": "PERFORMANCE_TEST=true cypress run",
"test:cypress:open": "cypress open",
"test-unit": "vitest",
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
"prod-start": "node server.js",
"postinstall": "node scripts/gen-texturepack-files.mjs && tsx scripts/optimizeBlockCollisions.ts",
"prod-start": "node server.js --prod",
"test-mc-server": "tsx cypress/minecraft-server.mjs",
"lint": "eslint \"{src,cypress}/**/*.{ts,js,jsx,tsx}\"",
"lint": "eslint \"{src,cypress,renderer}/**/*.{ts,js,jsx,tsx}\"",
"lint-fix": "pnpm lint --fix",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build && node scripts/build.js moveStorybookFiles",
"start-experiments": "vite --config experiments/vite.config.ts --host",
"watch-other-workers": "echo NOT IMPLEMENTED",
"watch-mesher": "node prismarine-viewer/buildMesherWorker.mjs -w",
"run-playground": "run-p watch-mesher watch-other-workers playground-server watch-playground",
"build-other-workers": "echo NOT IMPLEMENTED",
"build-mesher": "node renderer/buildMesherWorker.mjs",
"watch-mesher": "pnpm build-mesher -w",
"run-playground": "run-p watch-mesher watch-other-workers watch-playground",
"run-all": "run-p start run-playground",
"playground-server": "live-server --port=9090 prismarine-viewer/public",
"watch-playground": "node prismarine-viewer/esbuild.mjs -w"
"build-playground": "rsbuild build --config renderer/rsbuild.config.ts",
"watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts",
"update-git-deps": "tsx scripts/updateGitDeps.ts",
"request-data": "tsx scripts/requestData.ts"
},
"keywords": [
"prismarine",
"web",
"client"
],
"release": {
"attachReleaseFiles": "{self-host.zip,minecraft.html}"
},
"publish": {
"preset": {
"publishOnlyIfChanged": true,
@ -39,16 +54,17 @@
"dependencies": {
"@dimaka/interface": "0.0.3-alpha.0",
"@floating-ui/react": "^0.26.1",
"@mui/base": "5.0.0-beta.40",
"@nxg-org/mineflayer-auto-jump": "^0.7.7",
"@nxg-org/mineflayer-tracker": "^1.2.1",
"@monaco-editor/react": "^4.7.0",
"@nxg-org/mineflayer-auto-jump": "^0.7.18",
"@nxg-org/mineflayer-tracker": "1.3.0",
"@react-oauth/google": "^0.12.1",
"@stylistic/eslint-plugin": "^2.6.1",
"@types/gapi": "^0.0.47",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/wicg-file-system-access": "^2023.10.2",
"@xmcl/text-component": "^2.1.3",
"@zardoy/react-util": "^0.2.0",
"@zardoy/react-util": "^0.2.4",
"@zardoy/utils": "^0.0.11",
"adm-zip": "^0.5.12",
"browserfs": "github:zardoy/browserfs#build",
@ -56,27 +72,29 @@
"classnames": "^2.5.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cypress-plugin-snapshots": "^1.4.4",
"debug": "^4.3.4",
"deepslate": "^0.23.5",
"diff-match-patch": "^1.0.5",
"eruda": "^3.0.1",
"esbuild": "^0.19.3",
"esbuild-plugin-polyfill-node": "^0.3.0",
"express": "^4.18.2",
"filesize": "^10.0.12",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.29",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.104",
"framer-motion": "^12.9.2",
"fs-extra": "^11.1.1",
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
"iconify-icon": "^1.0.8",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
"minecraft-assets": "^1.12.2",
"minecraft-data": "3.65.0",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol",
"mcraft-fun-mineflayer": "^0.1.23",
"minecraft-data": "3.98.0",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
"mojangson": "^2.0.4",
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
"node-gzip": "^1.1.2",
"peerjs": "^1.5.0",
"pixelarticons": "^1.8.1",
"pretty-bytes": "^6.1.1",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"prosemirror-example-setup": "^1.2.2",
@ -87,7 +105,8 @@
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-transition-group": "^4.4.5",
"react-select": "^5.8.0",
"react-zoom-pan-pinch": "3.4.4",
"remark": "^15.0.1",
"sanitize-filename": "^1.6.3",
"skinview3d": "^3.0.1",
@ -104,13 +123,18 @@
"workbox-build": "^7.0.0"
},
"devDependencies": {
"@rsbuild/core": "1.3.5",
"@rsbuild/plugin-node-polyfill": "1.3.0",
"@rsbuild/plugin-react": "1.2.0",
"@rsbuild/plugin-type-check": "1.2.1",
"@rsbuild/plugin-typed-css-modules": "1.0.2",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/blocks": "^7.4.6",
"@storybook/react": "^7.4.6",
"@storybook/react-vite": "^7.4.6",
"@types/diff-match-patch": "^1.0.36",
"@types/lodash-es": "^4.17.9",
"@types/react-transition-group": "^4.4.7",
"@types/stats.js": "^0.17.1",
"@types/three": "0.154.0",
"@types/ua-parser-js": "^0.7.39",
@ -120,57 +144,99 @@
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"constants-browserify": "^1.0.0",
"contro-max": "^0.1.8",
"contro-max": "^0.1.9",
"crypto-browserify": "^3.12.0",
"cypress": "^10.11.0",
"cypress-esbuild-preprocessor": "^1.0.2",
"eslint": "^8.50.0",
"eslint-config-zardoy": "^0.2.17",
"events": "^3.3.0",
"gzip-size": "^7.0.0",
"http-browserify": "^1.7.0",
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"mc-assets": "^0.2.62",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer",
"mineflayer-pathfinder": "^2.4.4",
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
"mineflayer-mouse": "^0.1.21",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",
"path-exists-cli": "^2.0.0",
"prismarine-viewer": "link:prismarine-viewer",
"process": "github:PrismarineJS/node-process",
"renderer": "link:renderer",
"rimraf": "^5.0.1",
"storybook": "^7.4.6",
"stream-browserify": "^3.0.0",
"three": "0.154.0",
"timers-browserify": "^2.0.12",
"typescript": "5.5.0-beta",
"typescript": "5.5.4",
"vitest": "^0.34.6",
"yaml": "^2.3.2"
},
"optionalDependencies": {
"cypress": "^10.11.0",
"cypress-plugin-snapshots": "^1.4.4",
"sharp": "^0.33.5",
"systeminformation": "^5.21.22"
},
"browserslist": {
"production": [
"iOS >= 14",
"Android >= 13",
"Chrome >= 103",
"not dead",
"not ie <= 11",
"not op_mini all",
"> 0.5%"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"pnpm": {
"overrides": {
"@nxg-org/mineflayer-physics-util": "1.5.8",
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
"@nxg-org/mineflayer-physics-util": "1.8.10",
"buffer": "^6.0.3",
"vec3": "0.1.10",
"three": "0.154.0",
"diamond-square": "github:zardoy/diamond-square",
"prismarine-block": "github:zardoy/prismarine-block#next-era",
"prismarine-world": "github:zardoy/prismarine-world#next-era",
"minecraft-data": "3.65.0",
"minecraft-data": "3.98.0",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol",
"prismarine-physics": "github:zardoy/prismarine-physics",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"react": "^18.2.0",
"prismarine-chunk": "github:zardoy/prismarine-chunk"
"prismarine-chunk": "github:zardoy/prismarine-chunk#master",
"prismarine-item": "latest"
},
"updateConfig": {
"ignoreDependencies": []
"ignoreDependencies": [
"browserfs",
"google-drive-browserfs"
]
},
"patchedDependencies": {
"minecraft-protocol@1.47.0": "patches/minecraft-protocol@1.47.0.patch",
"three@0.154.0": "patches/three@0.154.0.patch"
}
"pixelarticons@1.8.1": "patches/pixelarticons@1.8.1.patch",
"mineflayer-item-map-downloader@1.2.0": "patches/mineflayer-item-map-downloader@1.2.0.patch",
"minecraft-protocol": "patches/minecraft-protocol.patch"
},
"ignoredBuiltDependencies": [
"canvas",
"core-js",
"gl"
],
"onlyBuiltDependencies": [
"sharp",
"cypress",
"esbuild",
"fsevents"
],
"ignorePatchFailures": false,
"allowUnusedPatches": false
},
"packageManager": "pnpm@9.0.4"
"packageManager": "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971"
}

View file

@ -3,7 +3,13 @@
"description": "A Minecraft-like React UI library",
"keywords": [
"minecraft",
"minecraft style"
"minecraft style",
"minecraft ui",
"minecraft components",
"minecraft react",
"minecraft library",
"minecraft web",
"minecraft browser"
],
"license": "MIT",
"sideEffects": false,

View file

@ -0,0 +1,138 @@
diff --git a/src/client/chat.js b/src/client/chat.js
index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b43cb102a 100644
--- a/src/client/chat.js
+++ b/src/client/chat.js
@@ -116,7 +116,7 @@ module.exports = function (client, options) {
for (const player of packet.data) {
if (player.chatSession) {
client._players[player.uuid] = {
- publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
+ // publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
publicKeyDER: player.chatSession.publicKey.keyBytes,
sessionUuid: player.chatSession.uuid
}
@@ -126,7 +126,7 @@ module.exports = function (client, options) {
if (player.crypto) {
client._players[player.uuid] = {
- publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
+ // publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
publicKeyDER: player.crypto.publicKey,
signature: player.crypto.signature,
displayName: player.displayName || player.name
@@ -196,7 +196,7 @@ module.exports = function (client, options) {
if (mcData.supportFeature('useChatSessions')) {
const tsDelta = BigInt(Date.now()) - packet.timestamp
const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0
- const verified = !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired
+ const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired
if (verified) client._signatureCache.push(packet.signature)
client.emit('playerChat', {
globalIndex: packet.globalIndex,
@@ -362,7 +362,7 @@ module.exports = function (client, options) {
}
}
- client._signedChat = (message, options = {}) => {
+ client._signedChat = async (message, options = {}) => {
options.timestamp = options.timestamp || BigInt(Date.now())
options.salt = options.salt || 1n
@@ -407,7 +407,7 @@ module.exports = function (client, options) {
message,
timestamp: options.timestamp,
salt: options.salt,
- signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
+ signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
offset: client._lastSeenMessages.pending,
checksum: computeChatChecksum(client._lastSeenMessages), // 1.21.5+
acknowledged
@@ -422,7 +422,7 @@ module.exports = function (client, options) {
message,
timestamp: options.timestamp,
salt: options.salt,
- signature: client.profileKeys ? client.signMessage(message, options.timestamp, options.salt, options.preview) : Buffer.alloc(0),
+ signature: client.profileKeys ? await client.signMessage(message, options.timestamp, options.salt, options.preview) : Buffer.alloc(0),
signedPreview: options.didPreview,
previousMessages: client._lastSeenMessages.map((e) => ({
messageSender: e.sender,
diff --git a/src/client/encrypt.js b/src/client/encrypt.js
index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f85410eaef 100644
--- a/src/client/encrypt.js
+++ b/src/client/encrypt.js
@@ -25,7 +25,11 @@ module.exports = function (client, options) {
if (packet.serverId !== '-') {
debug('This server appears to be an online server and you are providing no password, the authentication will probably fail')
}
- sendEncryptionKeyResponse()
+ client.end('This server appears to be an online server and you are providing no authentication. Try authenticating first.')
+ // sendEncryptionKeyResponse()
+ // client.once('set_compression', () => {
+ // clearTimeout(loginTimeout)
+ // })
}
function onJoinServerResponse (err) {
diff --git a/src/client/pluginChannels.js b/src/client/pluginChannels.js
index 671eb452f31e6b5fcd57d715f1009d010160c65f..7f69f511c8fb97d431ec5125c851b49be8e2ab76 100644
--- a/src/client/pluginChannels.js
+++ b/src/client/pluginChannels.js
@@ -57,7 +57,7 @@ module.exports = function (client, options) {
try {
packet.data = proto.parsePacketBuffer(channel, packet.data).data
} catch (error) {
- client.emit('error', error)
+ client.emit('error', error, { customPayload: packet })
return
}
}
diff --git a/src/client.js b/src/client.js
index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875989cdf0c 100644
--- a/src/client.js
+++ b/src/client.js
@@ -111,7 +111,13 @@ class Client extends EventEmitter {
this._hasBundlePacket = false
}
} else {
- emitPacket(parsed)
+ try {
+ emitPacket(parsed)
+ } catch (err) {
+ console.log('Client incorrectly handled packet ' + parsed.metadata.name)
+ console.error(err)
+ // todo investigate why it doesn't close the stream even if unhandled there
+ }
}
})
}
@@ -169,7 +175,10 @@ class Client extends EventEmitter {
}
const onFatalError = (err) => {
- this.emit('error', err)
+ // todo find out what is trying to write after client disconnect
+ if(err.code !== 'ECONNABORTED') {
+ this.emit('error', err)
+ }
endSocket()
}
@@ -198,6 +207,10 @@ class Client extends EventEmitter {
serializer -> framer -> socket -> splitter -> deserializer */
if (this.serializer) {
this.serializer.end()
+ setTimeout(() => {
+ this.socket?.end()
+ this.socket?.emit('end')
+ }, 2000) // allow the serializer to finish writing
} else {
if (this.socket) this.socket.end()
}
@@ -243,6 +256,7 @@ class Client extends EventEmitter {
debug('writing packet ' + this.state + '.' + name)
debug(params)
}
+ this.emit('writePacket', name, params)
this.serializer.write({ name, params })
}

View file

@ -1,130 +0,0 @@
diff --git a/src/client/autoVersion.js b/src/client/autoVersion.js
index c437ecf3a0e4ab5758a48538c714b7e9651bb5da..d9c9895ae8614550aa09ad60a396ac32ffdf1287 100644
--- a/src/client/autoVersion.js
+++ b/src/client/autoVersion.js
@@ -9,7 +9,7 @@ module.exports = function (client, options) {
client.wait_connect = true // don't let src/client/setProtocol proceed on socket 'connect' until 'connect_allowed'
debug('pinging', options.host)
// TODO: use 0xfe ping instead for better compatibility/performance? https://github.com/deathcap/node-minecraft-ping
- ping(options, function (err, response) {
+ ping(options, async function (err, response) {
if (err) { return client.emit('error', err) }
debug('ping response', response)
// TODO: could also use ping pre-connect to save description, type, max players, etc.
@@ -40,6 +40,7 @@ module.exports = function (client, options) {
// Reinitialize client object with new version TODO: move out of its constructor?
client.version = minecraftVersion
+ await options.versionSelectedHook?.(client)
client.state = states.HANDSHAKING
// Let other plugins such as Forge/FML (modinfo) respond to the ping response
diff --git a/src/client/encrypt.js b/src/client/encrypt.js
index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644
--- a/src/client/encrypt.js
+++ b/src/client/encrypt.js
@@ -25,7 +25,11 @@ module.exports = function (client, options) {
if (packet.serverId !== '-') {
debug('This server appears to be an online server and you are providing no password, the authentication will probably fail')
}
- sendEncryptionKeyResponse()
+ client.end('This server appears to be an online server and you are providing no authentication. Try authenticating first.')
+ // sendEncryptionKeyResponse()
+ // client.once('set_compression', () => {
+ // clearTimeout(loginTimeout)
+ // })
}
function onJoinServerResponse (err) {
diff --git a/src/client.js b/src/client.js
index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590a3ad8b8a 100644
--- a/src/client.js
+++ b/src/client.js
@@ -88,10 +88,12 @@ class Client extends EventEmitter {
parsed.metadata.name = parsed.data.name
parsed.data = parsed.data.params
parsed.metadata.state = state
- debug('read packet ' + state + '.' + parsed.metadata.name)
- if (debug.enabled) {
- const s = JSON.stringify(parsed.data, null, 2)
- debug(s && s.length > 10000 ? parsed.data : s)
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) {
+ debug('read packet ' + state + '.' + parsed.metadata.name)
+ if (debug.enabled) {
+ const s = JSON.stringify(parsed.data, null, 2)
+ debug(s && s.length > 10000 ? parsed.data : s)
+ }
}
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
if (this._mcBundle.length) { // End bundle
@@ -109,7 +111,13 @@ class Client extends EventEmitter {
this._hasBundlePacket = false
}
} else {
- emitPacket(parsed)
+ try {
+ emitPacket(parsed)
+ } catch (err) {
+ console.log('Client incorrectly handled packet ' + parsed.metadata.name)
+ console.error(err)
+ // todo investigate why it doesn't close the stream even if unhandled there
+ }
}
})
}
@@ -166,7 +174,10 @@ class Client extends EventEmitter {
}
const onFatalError = (err) => {
- this.emit('error', err)
+ // todo find out what is trying to write after client disconnect
+ if(err.code !== 'ECONNABORTED') {
+ this.emit('error', err)
+ }
endSocket()
}
@@ -195,6 +206,8 @@ class Client extends EventEmitter {
serializer -> framer -> socket -> splitter -> deserializer */
if (this.serializer) {
this.serializer.end()
+ this.socket?.end()
+ this.socket?.emit('end')
} else {
if (this.socket) this.socket.end()
}
@@ -236,8 +249,11 @@ class Client extends EventEmitter {
write (name, params) {
if (!this.serializer.writable) { return }
- debug('writing packet ' + this.state + '.' + name)
- debug(params)
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) {
+ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name)
+ debug(params)
+ }
+ this.emit('writePacket', name, params)
this.serializer.write({ name, params })
}
diff --git a/src/index.d.ts b/src/index.d.ts
index 0a5821c32d735e11205a280aa5a503c13533dc14..94a49f661d922478b940d853169b6087e6ec3df5 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -121,6 +121,7 @@ declare module 'minecraft-protocol' {
sessionServer?: string
keepAlive?: boolean
closeTimeout?: number
+ closeTimeout?: number
noPongTimeout?: number
checkTimeoutInterval?: number
version?: string
@@ -141,6 +142,8 @@ declare module 'minecraft-protocol' {
disableChatSigning?: boolean
/** Pass custom client implementation if needed. */
Client?: Client
+ /** Can be used to prepare mc data on autoVersion (client.version has selected version) */
+ versionSelectedHook?: (client: Client) => Promise<void> | void
}
export class Server extends EventEmitter {

View file

@ -0,0 +1,16 @@
diff --git a/package.json b/package.json
index 2a7aff75a9f1c7fe4eebb657002e58f4581dad0e..cd3490983353336efeb13f24f0af69c6c1d16444 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,7 @@
"keywords": [],
"author": "Ic3Tank",
"license": "ISC",
- "dependencies": {
- "mineflayer": "^4.3.0",
- "sharp": "^0.30.6"
- },
+ "dependencies": {},
"devDependencies": {
"mineflayer-item-map-downloader": "file:./"
}

View file

@ -0,0 +1,28 @@
diff --git a/fonts/pixelart-icons-font.css b/fonts/pixelart-icons-font.css
index 3b2ebe839370d96bf93ef5ca94a827f07e49378d..4f8d76be2ca6e4ddc43c68d0a6f0f69979165ab4 100644
--- a/fonts/pixelart-icons-font.css
+++ b/fonts/pixelart-icons-font.css
@@ -1,16 +1,13 @@
@font-face {
font-family: "pixelart-icons-font";
- src: url('pixelart-icons-font.eot?t=1711815892278'); /* IE9*/
- src: url('pixelart-icons-font.eot?t=1711815892278#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ src:
url("pixelart-icons-font.woff2?t=1711815892278") format("woff2"),
url("pixelart-icons-font.woff?t=1711815892278") format("woff"),
- url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
- url('pixelart-icons-font.svg?t=1711815892278#pixelart-icons-font') format('svg'); /* iOS 4.1- */
+ url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'); /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
}
[class^="pixelart-icons-font-"], [class*=" pixelart-icons-font-"] {
font-family: 'pixelart-icons-font' !important;
- font-size:24px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -503,4 +500,3 @@
.pixelart-icons-font-zap:before { content: "\ebe4"; }
.pixelart-icons-font-zoom-in:before { content: "\ebe5"; }
.pixelart-icons-font-zoom-out:before { content: "\ebe6"; }
-

View file

@ -1,16 +0,0 @@
diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js
index 6856a21b17aa45d7922bbf776fd2d7e63c7a9b4e..0925b706f7629bd52f0bb5af469536af8f5fce2c 100644
--- a/examples/jsm/webxr/VRButton.js
+++ b/examples/jsm/webxr/VRButton.js
@@ -62,7 +62,10 @@ class VRButton {
// ('local' is always available for immersive sessions and doesn't need to
// be requested separately.)
- const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] };
+ const sessionInit = {
+ optionalFeatures: ['local-floor', 'bounded-floor', 'layers'],
+ domOverlay: { root: document.body },
+ };
navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
} else {

14498
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
packages:
- "."
- "prismarine-viewer"
- "prismarine-viewer/viewer/sign-renderer/"
- "renderer"
- "renderer/viewer/sign-renderer/"

View file

@ -0,0 +1,5 @@
# Prismarine Viewer
Renamed to `renderer`.
For more info see [CONTRIBUTING.md](../CONTRIBUTING.md).

View file

@ -1,96 +0,0 @@
import * as fs from 'fs'
import fsExtra from 'fs-extra'
//@ts-check
import * as esbuild from 'esbuild'
import { polyfillNode } from 'esbuild-plugin-polyfill-node'
import path, { dirname, join } from 'path'
import { fileURLToPath } from 'url'
const dev = process.argv.includes('-w')
const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))
const mcDataPath = join(__dirname, '../dist/mc-data')
if (!fs.existsSync(mcDataPath)) {
// shouldn't it be in the viewer instead?
await import('../scripts/prepareData.mjs')
}
fs.mkdirSync(join(__dirname, 'public'), { recursive: true })
fs.copyFileSync(join(__dirname, 'playground.html'), join(__dirname, 'public/index.html'))
fsExtra.copySync(mcDataPath, join(__dirname, 'public/mc-data'))
const availableVersions = fs.readdirSync(mcDataPath).map(ver => ver.replace('.js', ''))
/** @type {import('esbuild').BuildOptions} */
const buildOptions = {
bundle: true,
entryPoints: [join(__dirname, './examples/playground.ts')],
// target: ['es2020'],
// logLevel: 'debug',
logLevel: 'info',
platform: 'browser',
sourcemap: dev ? 'inline' : false,
minify: !dev,
outfile: join(__dirname, 'public/playground.js'),
mainFields: [
'browser', 'module', 'main'
],
keepNames: true,
banner: {
js: `globalThis.global = globalThis;globalThis.includedVersions = ${JSON.stringify(availableVersions)};`,
},
alias: {
events: 'events',
buffer: 'buffer',
'fs': 'browserfs/dist/shims/fs.js',
http: 'http-browserify',
stream: 'stream-browserify',
net: 'net-browserify',
},
inject: [],
metafile: true,
loader: {
'.png': 'dataurl',
'.obj': 'text',
},
plugins: [
{
name: 'minecraft-data',
setup (build) {
build.onLoad({
filter: /minecraft-data[\/\\]data.js$/,
}, () => {
const defaultVersionsObj = {}
return {
contents: `window.mcData ??= ${JSON.stringify(defaultVersionsObj)};module.exports = { pc: window.mcData }`,
loader: 'js',
}
})
build.onEnd((e) => {
if (e.errors.length) return
fs.writeFileSync(join(__dirname, 'public/metafile.json'), JSON.stringify(e.metafile), 'utf8')
})
}
},
polyfillNode({
polyfills: {
fs: false,
crypto: false,
events: false,
http: false,
stream: false,
buffer: false,
perf_hooks: false,
net: false,
},
})
],
}
if (dev) {
(await esbuild.context(buildOptions)).watch()
} else {
await esbuild.build(buildOptions)
}
// await ctx.rebuild()

View file

@ -1,478 +0,0 @@
import _ from 'lodash'
import { WorldDataEmitter, Viewer } from '../viewer'
import { Vec3 } from 'vec3'
import BlockLoader from 'prismarine-block'
import ChunkLoader from 'prismarine-chunk'
import WorldLoader from 'prismarine-world'
import * as THREE from 'three'
import { GUI } from 'lil-gui'
import { toMajor } from '../viewer/lib/version'
import { loadScript } from '../viewer/lib/utils'
import JSZip from 'jszip'
import { TWEEN_DURATION } from '../viewer/lib/entities'
import { EntityMesh } from '../viewer/lib/entity/EntityMesh'
globalThis.THREE = THREE
//@ts-ignore
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
const gui = new GUI()
// initial values
const params = {
skip: '',
version: globalThis.includedVersions.sort((a, b) => {
const s = (x) => {
const parts = x.split('.')
return +parts[0] + (+parts[1])
}
return s(a) - s(b)
}).at(-1),
block: '',
metadata: 0,
supportBlock: false,
entity: '',
removeEntity () {
this.entity = ''
},
entityRotate: false,
camera: '',
playSound () { },
blockIsomorphicRenderBundle () { }
}
const qs = new URLSearchParams(window.location.search)
qs.forEach((value, key) => {
const parsed = value.match(/^-?\d+$/) ? parseInt(value) : value === 'true' ? true : value === 'false' ? false : value
params[key] = parsed
})
const setQs = () => {
const newQs = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (!value || typeof value === 'function' || params.skip.includes(key)) continue
//@ts-ignore
newQs.set(key, value)
}
window.history.replaceState({}, '', `${window.location.pathname}?${newQs}`)
}
let ignoreResize = false
async function main () {
let continuousRender = false
const { version } = params
// temporary solution until web worker is here, cache data for faster reloads
const globalMcData = window['mcData']
if (!globalMcData['version']) {
const major = toMajor(version)
const sessionKey = `mcData-${major}`
if (sessionStorage[sessionKey]) {
Object.assign(globalMcData, JSON.parse(sessionStorage[sessionKey]))
} else {
if (sessionStorage.length > 1) sessionStorage.clear()
await loadScript(`./mc-data/${major}.js`)
try {
sessionStorage[sessionKey] = JSON.stringify(Object.fromEntries(Object.entries(globalMcData).filter(([ver]) => ver.startsWith(major))))
} catch { }
}
}
const mcData = require('minecraft-data')(version)
window['loadedData'] = mcData
gui.add(params, 'version', globalThis.includedVersions)
gui.add(params, 'block', mcData.blocksArray.map(b => b.name).sort((a, b) => a.localeCompare(b)))
const metadataGui = gui.add(params, 'metadata')
gui.add(params, 'supportBlock')
gui.add(params, 'entity', mcData.entitiesArray.map(b => b.name).sort((a, b) => a.localeCompare(b))).listen()
gui.add(params, 'removeEntity')
gui.add(params, 'entityRotate')
gui.add(params, 'skip')
gui.add(params, 'playSound')
gui.add(params, 'blockIsomorphicRenderBundle')
gui.open(false)
let metadataFolder = gui.addFolder('metadata')
// let entityRotationFolder = gui.addFolder('entity metadata')
const Chunk = ChunkLoader(version)
const Block = BlockLoader(version)
// const data = await fetch('smallhouse1.schem').then(r => r.arrayBuffer())
// const schem = await Schematic.read(Buffer.from(data), version)
const viewDistance = 0
const targetPos = new Vec3(2, 90, 2)
const World = WorldLoader(version)
// const diamondSquare = require('diamond-square')({ version, seed: Math.floor(Math.random() * Math.pow(2, 31)) })
//@ts-ignore
const chunk1 = new Chunk()
//@ts-ignore
const chunk2 = new Chunk()
chunk1.setBlockStateId(targetPos, 34)
chunk2.setBlockStateId(targetPos.offset(1, 0, 0), 34)
const world = new World((chunkX, chunkZ) => {
// if (chunkX === 0 && chunkZ === 0) return chunk1
// if (chunkX === 1 && chunkZ === 0) return chunk2
//@ts-ignore
const chunk = new Chunk()
return chunk
})
// await schem.paste(world, new Vec3(0, 60, 0))
const worldView = new WorldDataEmitter(world, viewDistance, targetPos)
// Create three.js context, add to page
const renderer = new THREE.WebGLRenderer({ alpha: true, ...localStorage['renderer'] })
renderer.setPixelRatio(window.devicePixelRatio || 1)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Create viewer
const viewer = new Viewer(renderer, { numWorkers: 1, showChunkBorders: false })
viewer.entities.setDebugMode('basic')
viewer.setVersion(version)
viewer.entities.onSkinUpdate = () => {
viewer.render()
}
viewer.world.mesherConfig.enableLighting = false
viewer.listen(worldView)
// Load chunks
await worldView.init(targetPos)
window['worldView'] = worldView
window['viewer'] = viewer
params.blockIsomorphicRenderBundle = () => {
const canvas = renderer.domElement
const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one')
const sizeRaw = prompt('Size', '512')
if (!sizeRaw) return
const size = parseInt(sizeRaw)
// const size = 512
ignoreResize = true
canvas.width = size
canvas.height = size
renderer.setSize(size, size)
//@ts-ignore
viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10)
viewer.scene.background = null
const rad = THREE.MathUtils.degToRad(-120)
viewer.directionalLight.position.set(
Math.cos(rad),
Math.sin(rad),
0.2
).normalize()
viewer.directionalLight.intensity = 1
const cameraPos = targetPos.offset(2, 2, 2)
const pitch = THREE.MathUtils.degToRad(-30)
const yaw = THREE.MathUtils.degToRad(45)
viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX')
// viewer.camera.lookAt(center.x + 0.5, center.y + 0.5, center.z + 0.5)
viewer.camera.position.set(cameraPos.x + 1, cameraPos.y + 0.5, cameraPos.z + 1)
const allBlocks = mcData.blocksArray.map(b => b.name)
// const allBlocks = ['stone', 'warped_slab']
let blockCount = 1
let blockName = allBlocks[0]
const updateBlock = () => {
//@ts-ignore
// viewer.setBlockStateId(targetPos, mcData.blocksByName[blockName].minStateId)
params.block = blockName
// todo cleanup (introduce getDefaultState)
onUpdate.block()
applyChanges(false, true)
}
viewer.waitForChunksToRender().then(async () => {
// wait for next macro task
await new Promise(resolve => {
setTimeout(resolve, 0)
})
if (onlyCurrent) {
viewer.render()
onWorldUpdate()
} else {
// will be called on every render update
viewer.world.renderUpdateEmitter.addListener('update', onWorldUpdate)
updateBlock()
}
})
const zip = new JSZip()
zip.file('description.txt', 'Generated with prismarine-viewer')
const end = async () => {
// download zip file
const a = document.createElement('a')
const blob = await zip.generateAsync({ type: 'blob' })
const dataUrlZip = URL.createObjectURL(blob)
a.href = dataUrlZip
a.download = 'blocks_render.zip'
a.click()
URL.revokeObjectURL(dataUrlZip)
console.log('end')
viewer.world.renderUpdateEmitter.removeListener('update', onWorldUpdate)
}
async function onWorldUpdate () {
// await new Promise(resolve => {
// setTimeout(resolve, 50)
// })
const dataUrl = canvas.toDataURL('image/png')
zip.file(`${blockName}.png`, dataUrl.split(',')[1], { base64: true })
if (onlyCurrent) {
end()
} else {
nextBlock()
}
}
const nextBlock = async () => {
blockName = allBlocks[blockCount++]
console.log(allBlocks.length, '/', blockCount, blockName)
if (blockCount % 5 === 0) {
await new Promise(resolve => {
setTimeout(resolve, 100)
})
}
if (blockName) {
updateBlock()
} else {
end()
}
}
}
//@ts-ignore
const controls = new OrbitControls(viewer.camera, renderer.domElement)
controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
const cameraPos = targetPos.offset(2, 2, 2)
const pitch = THREE.MathUtils.degToRad(-45)
const yaw = THREE.MathUtils.degToRad(45)
viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX')
viewer.camera.lookAt(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
viewer.camera.position.set(cameraPos.x + 0.5, cameraPos.y + 0.5, cameraPos.z + 0.5)
controls.update()
let blockProps = {}
let entityOverrides = {}
const getBlock = () => {
return mcData.blocksByName[params.block || 'air']
}
const entityUpdateShared = () => {
viewer.entities.clear()
if (!params.entity) return
worldView.emit('entity', {
id: 'id', name: params.entity, pos: targetPos.offset(0.5, 1, 0.5), width: 1, height: 1, username: localStorage.testUsername, yaw: Math.PI, pitch: 0
})
const enableSkeletonDebug = (obj) => {
const { children, isSkeletonHelper } = obj
if (!Array.isArray(children)) return
if (isSkeletonHelper) {
obj.visible = true
return
}
for (const child of children) {
if (typeof child === 'object') enableSkeletonDebug(child)
}
}
enableSkeletonDebug(viewer.entities.entities['id'])
setTimeout(() => {
viewer.render()
}, TWEEN_DURATION)
}
const onUpdate = {
block () {
metadataFolder.destroy()
const block = mcData.blocksByName[params.block]
if (!block) return
const props = new Block(block.id, 0, 0).getProperties()
//@ts-ignore
const { states } = mcData.blocksByStateId[getBlock()?.minStateId] ?? {}
metadataFolder = gui.addFolder('metadata')
if (states) {
for (const state of states) {
let defaultValue
switch (state.type) {
case 'enum':
defaultValue = state.values[0]
break
case 'bool':
defaultValue = false
break
case 'int':
defaultValue = 0
break
case 'direction':
defaultValue = 'north'
break
default:
continue
}
blockProps[state.name] = defaultValue
if (state.type === 'enum') {
metadataFolder.add(blockProps, state.name, state.values)
} else {
metadataFolder.add(blockProps, state.name)
}
}
} else {
for (const [name, value] of Object.entries(props)) {
blockProps[name] = value
metadataFolder.add(blockProps, name)
}
}
metadataFolder.open()
},
entity () {
continuousRender = params.entity === 'player'
entityUpdateShared()
if (!params.entity) return
if (params.entity === 'player') {
viewer.entities.updatePlayerSkin('id', viewer.entities.entities.id.username, true, true)
viewer.entities.playAnimation('id', 'running')
}
// let prev = false
// setInterval(() => {
// viewer.entities.playAnimation('id', prev ? 'running' : 'idle')
// prev = !prev
// }, 1000)
EntityMesh.getStaticData(params.entity)
// entityRotationFolder.destroy()
// entityRotationFolder = gui.addFolder('entity metadata')
// entityRotationFolder.add(params, 'entityRotate')
// entityRotationFolder.open()
},
supportBlock () {
viewer.setBlockStateId(targetPos.offset(0, -1, 0), params.supportBlock ? 1 : 0)
}
}
const applyChanges = (metadataUpdate = false, skipQs = false) => {
const blockId = getBlock()?.id
let block: BlockLoader.Block
if (metadataUpdate) {
block = new Block(blockId, 0, params.metadata)
Object.assign(blockProps, block.getProperties())
for (const _child of metadataFolder.children) {
const child = _child as import('lil-gui').Controller
child.updateDisplay()
}
} else {
try {
//@ts-ignore
block = Block.fromProperties(blockId ?? -1, blockProps, 0)
} catch (err) {
console.error(err)
block = Block.fromStateId(0, 0)
}
}
//@ts-ignore
viewer.setBlockStateId(targetPos, block.stateId)
console.log('up stateId', block.stateId)
params.metadata = block.metadata
metadataGui.updateDisplay()
if (!skipQs) {
setQs()
}
}
gui.onChange(({ property, object }) => {
if (object === params) {
if (property === 'camera') return
onUpdate[property]?.()
applyChanges(property === 'metadata')
} else {
applyChanges()
}
})
viewer.waitForChunksToRender().then(async () => {
for (const update of Object.values(onUpdate)) {
update()
}
applyChanges(true)
gui.openAnimated()
})
const animate = () => {
// if (controls) controls.update()
// worldView.updatePosition(controls.target)
viewer.render()
// window.requestAnimationFrame(animate)
}
viewer.world.renderUpdateEmitter.addListener('update', () => {
animate()
})
animate()
// #region camera rotation param
if (params.camera) {
const [x, y] = params.camera.split(',')
viewer.camera.rotation.set(parseFloat(x), parseFloat(y), 0, 'ZYX')
controls.update()
console.log(viewer.camera.rotation.x, parseFloat(x))
}
const throttledCamQsUpdate = _.throttle(() => {
const { camera } = viewer
// params.camera = `${camera.rotation.x.toFixed(2)},${camera.rotation.y.toFixed(2)}`
setQs()
}, 200)
controls.addEventListener('change', () => {
throttledCamQsUpdate()
animate()
})
// #endregion
const continuousUpdate = () => {
if (continuousRender) {
animate()
}
requestAnimationFrame(continuousUpdate)
}
continuousUpdate()
window.onresize = () => {
if (ignoreResize) return
// const vec3 = new THREE.Vector3()
// vec3.set(-1, -1, -1).unproject(viewer.camera)
// console.log(vec3)
// box.position.set(vec3.x, vec3.y, vec3.z-1)
const { camera } = viewer
viewer.camera.aspect = window.innerWidth / window.innerHeight
viewer.camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
animate()
}
window.dispatchEvent(new Event('resize'))
params.playSound = () => {
viewer.playSound(targetPos, 'button_click.mp3')
}
addEventListener('keydown', (e) => {
if (e.code === 'KeyE') {
params.playSound()
}
}, { capture: true })
}
main()

View file

@ -1,38 +0,0 @@
import {Bot} from "mineflayer";
export function mineflayer(bot: Bot, settings: {
viewDistance?: number;
firstPerson?: boolean;
port?: number;
prefix?: string;
});
export function standalone(options: {
version: versions;
world: (x: number, y: number, z: number) => 0 | 1;
center?: Vec3;
viewDistance?: number;
port?: number;
prefix?: string;
});
export function headless(bot: Bot, settings: {
viewDistance?: number;
output?: string;
frames?: number;
width?: number;
height?: number;
logFFMPEG?: boolean;
jpegOption: any;
});
export const viewer: {
Viewer: any;
WorldDataEmitter: any;
MapControls: any;
Entitiy: any;
getBufferFromStream: (stream: any) => Promise<Buffer>;
};
export const supportedVersions: versions[];
export type versions = '1.8.8' | '1.9.4' | '1.10.2' | '1.11.2' | '1.12.2' | '1.13.2' | '1.14.4' | '1.15.2' | '1.16.1' | '1.16.4' | '1.17.1' | '1.18.1';

View file

@ -1,7 +0,0 @@
module.exports = {
mineflayer: require('./lib/mineflayer'),
standalone: require('./lib/standalone'),
headless: require('./lib/headless'),
viewer: require('./viewer'),
supportedVersions: require('./viewer/supportedVersions.json')
}

View file

@ -1,5 +0,0 @@
module.exports = {
launch: {
args: ['--no-sandbox', '--disable-setuid-sandbox']
}
}

View file

@ -1,4 +0,0 @@
module.exports = {
preset: 'jest-puppeteer',
testRegex: './*\\.test\\.js$'
}

View file

@ -1,12 +0,0 @@
const path = require('path')
const compression = require('compression')
const express = require('express')
function setupRoutes (app, prefix = '') {
app.use(compression())
app.use(prefix + '/', express.static(path.join(__dirname, '../public')))
}
module.exports = {
setupRoutes
}

View file

@ -1,135 +0,0 @@
/* global THREE */
function safeRequire (path) {
try {
return require(path)
} catch (e) {
return {}
}
}
const { spawn } = require('child_process')
const net = require('net')
global.THREE = require('three')
global.Worker = require('worker_threads').Worker
const { createCanvas } = safeRequire('node-canvas-webgl/lib')
const { WorldDataEmitter, Viewer, getBufferFromStream } = require('../viewer')
module.exports = (bot, { viewDistance = 6, output = 'output.mp4', frames = -1, width = 512, height = 512, logFFMPEG = false, jpegOptions }) => {
const canvas = createCanvas(width, height)
const renderer = new THREE.WebGLRenderer({ canvas })
const viewer = new Viewer(renderer)
viewer.setVersion(bot.version)
viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
// Load world
const worldView = new WorldDataEmitter(bot.world, viewDistance, bot.entity.position)
viewer.listen(worldView)
worldView.init(bot.entity.position)
function botPosition () {
viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
worldView.updatePosition(bot.entity.position)
}
// Render loop streaming
const rtmpOutput = output.startsWith('rtmp://')
const ffmpegOutput = output.endsWith('mp4')
let client = null
if (rtmpOutput) {
const fps = 20
const gop = fps * 2
const gopMin = fps
const probesize = '42M'
const cbr = '1000k'
const threads = 4
const args = `-y -r ${fps} -probesize ${probesize} -i pipe:0 -f flv -ac 2 -ar 44100 -vcodec libx264 -g ${gop} -keyint_min ${gopMin} -b:v ${cbr} -minrate ${cbr} -maxrate ${cbr} -pix_fmt yuv420p -s 1280x720 -preset ultrafast -tune film -threads ${threads} -strict normal -bufsize ${cbr} ${output}`.split(' ')
client = spawn('ffmpeg', args)
if (logFFMPEG) {
client.stdout.on('data', (data) => {
console.log(`stdout: ${data}`)
})
client.stderr.on('data', (data) => {
console.error(`stderr: ${data}`)
})
}
update()
} else if (ffmpegOutput) {
client = spawn('ffmpeg', ['-y', '-i', 'pipe:0', output])
if (logFFMPEG) {
client.stdout.on('data', (data) => {
console.log(`stdout: ${data}`)
})
client.stderr.on('data', (data) => {
console.error(`stderr: ${data}`)
})
}
update()
} else {
const [host, port] = output.split(':')
client = new net.Socket()
client.connect(parseInt(port, 10), host, () => {
update()
})
}
// Force end of stream
bot.on('end', () => { frames = 0 })
let idx = 0
function update () {
viewer.update()
renderer.render(viewer.scene, viewer.camera)
const imageStream = canvas.createJPEGStream({
bufsize: 4096,
quality: 1,
progressive: false,
...jpegOptions
})
if (rtmpOutput || ffmpegOutput) {
imageStream.on('data', (chunk) => {
if (client.stdin.writable) {
client.stdin.write(chunk)
} else {
console.log('Error: ffmpeg stdin closed!')
}
})
imageStream.on('end', () => {
idx++
if (idx < frames || frames < 0) {
setTimeout(update, 16)
} else {
console.log('done streaming')
client.stdin.end()
}
})
imageStream.on('error', () => { })
} else {
getBufferFromStream(imageStream).then((buffer) => {
const sizebuff = new Uint8Array(4)
const view = new DataView(sizebuff.buffer, 0)
view.setUint32(0, buffer.length, true)
client.write(sizebuff)
client.write(buffer)
idx++
if (idx < frames || frames < 0) {
setTimeout(update, 16)
} else {
client.end()
}
}).catch(() => {})
}
}
// Register events
bot.on('move', botPosition)
worldView.listenToBot(bot)
return client
}

View file

@ -1,71 +0,0 @@
/* global THREE */
global.THREE = require('three')
const TWEEN = require('@tweenjs/tween.js')
require('three/examples/js/controls/OrbitControls')
const { Viewer, Entity } = require('../viewer')
const io = require('socket.io-client')
const socket = io()
let firstPositionUpdate = true
const renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio(window.devicePixelRatio || 1)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const viewer = new Viewer(renderer)
let controls = new THREE.OrbitControls(viewer.camera, renderer.domElement)
function animate () {
window.requestAnimationFrame(animate)
if (controls) controls.update()
viewer.update()
renderer.render(viewer.scene, viewer.camera)
}
animate()
window.addEventListener('resize', () => {
viewer.camera.aspect = window.innerWidth / window.innerHeight
viewer.camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
socket.on('version', (version) => {
viewer.setVersion(version)
firstPositionUpdate = true
viewer.listen(socket)
let botMesh
socket.on('position', ({ pos, addMesh, yaw, pitch }) => {
if (yaw !== undefined && pitch !== undefined) {
if (controls) {
controls.dispose()
controls = null
}
viewer.setFirstPersonCamera(pos, yaw, pitch)
return
}
if (pos.y > 0 && firstPositionUpdate) {
controls.target.set(pos.x, pos.y, pos.z)
viewer.camera.position.set(pos.x, pos.y + 20, pos.z + 20)
controls.update()
firstPositionUpdate = false
}
if (addMesh) {
if (!botMesh) {
botMesh = new Entity('1.16.4', 'player', viewer.scene).mesh
viewer.scene.add(botMesh)
}
new TWEEN.Tween(botMesh.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
const da = (yaw - botMesh.rotation.y) % (Math.PI * 2)
const dy = 2 * da % (Math.PI * 2) - da
new TWEEN.Tween(botMesh.rotation).to({ y: botMesh.rotation.y + dy }, 50).start()
}
})
})

View file

@ -1,91 +0,0 @@
const EventEmitter = require('events')
const { WorldDataEmitter } = require('../viewer')
module.exports = (bot, { viewDistance = 6, firstPerson = false, port = 3000, prefix = '' }) => {
const express = require('express')
const app = express()
const http = require('http').createServer(app)
const io = require('socket.io')(http, { path: prefix + '/socket.io' })
const { setupRoutes } = require('./common')
setupRoutes(app, prefix)
const sockets = []
const primitives = {}
bot.viewer = new EventEmitter()
bot.viewer.erase = (id) => {
delete primitives[id]
for (const socket of sockets) {
socket.emit('primitive', { id })
}
}
bot.viewer.drawBoxGrid = (id, start, end, color = 'aqua') => {
primitives[id] = { type: 'boxgrid', id, start, end, color }
for (const socket of sockets) {
socket.emit('primitive', primitives[id])
}
}
bot.viewer.drawLine = (id, points, color = 0xff0000) => {
primitives[id] = { type: 'line', id, points, color }
for (const socket of sockets) {
socket.emit('primitive', primitives[id])
}
}
bot.viewer.drawPoints = (id, points, color = 0xff0000, size = 5) => {
primitives[id] = { type: 'points', id, points, color, size }
for (const socket of sockets) {
socket.emit('primitive', primitives[id])
}
}
io.on('connection', (socket) => {
socket.emit('version', bot.version)
sockets.push(socket)
const worldView = new WorldDataEmitter(bot.world, viewDistance, bot.entity.position, socket)
worldView.init(bot.entity.position)
worldView.on('blockClicked', (block, face, button) => {
bot.viewer.emit('blockClicked', block, face, button)
})
for (const id in primitives) {
socket.emit('primitive', primitives[id])
}
function botPosition () {
const packet = { pos: bot.entity.position, yaw: bot.entity.yaw, addMesh: true }
if (firstPerson) {
packet.pitch = bot.entity.pitch
}
socket.emit('position', packet)
worldView.updatePosition(bot.entity.position)
}
bot.on('move', botPosition)
worldView.listenToBot(bot)
socket.on('disconnect', () => {
bot.removeListener('move', botPosition)
worldView.removeListenersFromBot(bot)
sockets.splice(sockets.indexOf(socket), 1)
})
})
http.listen(port, () => {
console.log(`Prismarine viewer web server running on *:${port}`)
})
bot.viewer.close = () => {
http.close()
for (const socket of sockets) {
socket.disconnect()
}
}
}

View file

@ -1,52 +0,0 @@
const { Vec3 } = require('vec3')
module.exports = ({ version, world, center = new Vec3(0, 0, 0), viewDistance = 4, port = 3000, prefix = '' }) => {
const express = require('express')
const app = express()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
const { setupRoutes } = require('./common')
setupRoutes(app, prefix)
const sockets = []
const viewer = { world }
async function sendChunks (sockets) {
const cx = Math.floor(center.x / 16)
const cz = Math.floor(center.z / 16)
for (let x = cx - viewDistance; x <= cx + viewDistance; x++) {
for (let z = cz - viewDistance; z <= cz + viewDistance; z++) {
const chunk = (await viewer.world.getColumn(x, z)).toJson()
for (const socket of sockets) {
socket.emit('loadChunk', { x: x * 16, z: z * 16, chunk })
}
}
}
}
viewer.update = () => {
sendChunks(sockets)
}
io.on('connection', (socket) => {
socket.emit('version', version)
sockets.push(socket)
sendChunks([socket])
socket.emit('position', { pos: center, addMesh: false })
socket.on('disconnect', () => {
sockets.splice(sockets.indexOf(socket), 1)
})
})
http.listen(port, () => {
console.log(`Prismarine viewer web server running on *:${port}`)
})
return viewer
}

View file

@ -1,10 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"strictNullChecks": true,
"experimentalDecorators": true
},
"files": [
"index.d.ts"
]
}

View file

@ -1,7 +0,0 @@
module.exports = {
Viewer: require('./lib/viewer').Viewer,
WorldDataEmitter: require('./lib/worldDataEmitter').WorldDataEmitter,
MapControls: require('./lib/controls').MapControls,
Entity: require('./lib/entity/EntityMesh'),
getBufferFromStream: require('./lib/simpleUtils').getBufferFromStream
}

View file

@ -1,923 +0,0 @@
/* eslint-disable */
// Similar to THREE MapControls with more Minecraft-like
// controls.
// Defaults:
// Shift = Move Down, Space = Move Up
// W/Z - north, S - south, A/Q - west, D - east
const STATE = {
NONE: -1,
ROTATE: 0,
DOLLY: 1,
PAN: 2,
TOUCH_ROTATE: 3,
TOUCH_PAN: 4,
TOUCH_DOLLY_PAN: 5,
TOUCH_DOLLY_ROTATE: 6
}
class MapControls {
constructor(camera, domElement) {
this.enabled = true
this.object = camera
this.element = domElement
// Mouse buttons
this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }
// Touch fingers
this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }
this.controlMap = {
MOVE_FORWARD: ['KeyW', 'KeyZ'],
MOVE_BACKWARD: 'KeyS',
MOVE_LEFT: ['KeyA', 'KeyQ'],
MOVE_RIGHT: 'KeyD',
MOVE_DOWN: 'ShiftLeft',
MOVE_UP: 'Space'
}
this.target = new THREE.Vector3()
// How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0
this.maxDistance = Infinity
// How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0
this.maxZoom = Infinity
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0 // radians
this.maxPolarAngle = Math.PI // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
this.minAzimuthAngle = -Infinity // radians
this.maxAzimuthAngle = Infinity // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false
this.dampingFactor = 0.01
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
// Set to false to disable zooming
this.enableZoom = true
this.enableTouchZoom = true
this.zoomSpeed = 1.0
// Set to false to disable rotating
this.enableRotate = true
this.enableTouchRotate = true
this.rotateSpeed = 1.0
// Set to false to disable panning
this.enablePan = true
this.enableTouchPan = true
this.panSpeed = 1.0
this.screenSpacePanning = false // if false, pan orthogonal to world-space direction camera.up
this.keyPanDistance = 32 // how far to pan
this.keyPanSpeed = 10 // pixels moved per arrow key push
this.verticalTranslationSpeed = 0.5 // how much Y increments moving up/down
this.keyDowns = []
// State-related stuff
this.changeEvent = { type: 'change' }
this.startEvent = { type: 'start' }
this.endEvent = { type: 'end' }
this.state = STATE.NONE
this.EPS = 0.000001
this.spherical = new THREE.Spherical()
this.sphericalDelta = new THREE.Spherical()
this.scale = 1
this.panOffset = new THREE.Vector3()
this.zoomChanged = false
this.rotateStart = new THREE.Vector2()
this.rotateEnd = new THREE.Vector2()
this.rotateDelta = new THREE.Vector2()
this.panStart = new THREE.Vector2()
this.panEnd = new THREE.Vector2()
this.panDelta = new THREE.Vector2()
this.dollyStart = new THREE.Vector2()
this.dollyEnd = new THREE.Vector2()
this.dollyDelta = new THREE.Vector2()
// for reset
this.target0 = this.target.clone()
this.position0 = this.object.position.clone()
this.zoom0 = this.object.zoom
this.ticks = 0
// register event handlers
this.onPointerMove = this.onPointerMove.bind(this)
this.onPointerUp = this.onPointerUp.bind(this)
this.onPointerDown = this.onPointerDown.bind(this)
this.onMouseWheel = this.onMouseWheel.bind(this)
this.onTouchStart = this.onTouchStart.bind(this)
this.onTouchEnd = this.onTouchEnd.bind(this)
this.onTouchMove = this.onTouchMove.bind(this)
this.onContextMenu = this.onContextMenu.bind(this)
this.onKeyDown = this.onKeyDown.bind(this)
this.onKeyUp = this.onKeyUp.bind(this)
this.registerHandlers()
}
//#region Public Methods
setRotationOrigin(position) {
this.target = position.clone()
}
unsetRotationOrigin() {
this.target = new THREE.Vector3()
}
getPolarAngle() {
return this.spherical.phi
}
getAzimuthalAngle() {
return this.spherical.theta
}
saveState() {
this.target0.copy(this.target)
this.position0.copy(this.object.position)
this.zoom0 = this.object.zoom
}
reset() {
this.target.copy(this.target0)
this.object.position.copy(this.position0)
this.object.zoom = this.zoom0
this.object.updateProjectionMatrix()
this.dispatchEvent(this.changeEvent)
this.update(true)
this.state = STATE.NONE
}
// this method is exposed, but perhaps it would be better if we can make it private...
update(force) {
// tick controls if called from render loop
if (!force) {
this.tickControls()
}
var offset = new THREE.Vector3()
// so camera.up is the orbit axis
var quat = new THREE.Quaternion().setFromUnitVectors(this.object.up, new THREE.Vector3(0, 1, 0))
var quatInverse = quat.clone().invert()
var lastPosition = new THREE.Vector3()
var lastQuaternion = new THREE.Quaternion()
var twoPI = 2 * Math.PI
var position = this.object.position
offset.copy(position).sub(this.target)
// rotate offset to "y-axis-is-up" space
offset.applyQuaternion(quat)
// angle from z-axis around y-axis
this.spherical.setFromVector3(offset)
if (this.autoRotate && this.state === STATE.NONE) {
this.rotateLeft(this.getAutoRotationAngle())
}
if (this.enableDamping) {
this.spherical.theta += this.sphericalDelta.theta * this.dampingFactor
this.spherical.phi += this.sphericalDelta.phi * this.dampingFactor
} else {
this.spherical.theta += this.sphericalDelta.theta
this.spherical.phi += this.sphericalDelta.phi
}
// restrict theta to be between desired limits
var min = this.minAzimuthAngle
var max = this.maxAzimuthAngle
if (isFinite(min) && isFinite(max)) {
if (min < - Math.PI) min += twoPI; else if (min > Math.PI) min -= twoPI
if (max < - Math.PI) max += twoPI; else if (max > Math.PI) max -= twoPI
if (min < max) {
this.spherical.theta = Math.max(min, Math.min(max, this.spherical.theta))
} else {
this.spherical.theta = (this.spherical.theta > (min + max) / 2) ?
Math.max(min, this.spherical.theta) :
Math.min(max, this.spherical.theta)
}
}
// restrict phi to be between desired limits
this.spherical.phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.spherical.phi))
this.spherical.makeSafe()
this.spherical.radius *= this.scale
// restrict radius to be between desired limits
this.spherical.radius = Math.max(this.minDistance, Math.min(this.maxDistance, this.spherical.radius))
// move target to panned location
if (this.enableDamping === true) {
this.target.addScaledVector(this.panOffset, this.dampingFactor)
} else {
this.target.add(this.panOffset)
}
offset.setFromSpherical(this.spherical)
// rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion(quatInverse)
position.copy(this.target).add(offset)
this.object.lookAt(this.target)
if (this.enableDamping === true) {
this.sphericalDelta.theta *= (1 - this.dampingFactor)
this.sphericalDelta.phi *= (1 - this.dampingFactor)
this.panOffset.multiplyScalar(1 - this.dampingFactor)
} else {
this.sphericalDelta.set(0, 0, 0)
this.panOffset.set(0, 0, 0)
}
this.scale = 1
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if (this.zoomChanged ||
lastPosition.distanceToSquared(this.object.position) > this.EPS ||
8 * (1 - lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
this.dispatchEvent(this.changeEvent)
lastPosition.copy(this.object.position)
lastQuaternion.copy(this.object.quaternion)
this.zoomChanged = false
return true
}
return false
}
//#endregion
//#region Orbit Controls
getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed
}
getZoomScale() {
return Math.pow(0.95, this.zoomSpeed)
}
rotateLeft(angle) {
this.sphericalDelta.theta -= angle
}
rotateUp(angle) {
this.sphericalDelta.phi -= angle
}
panLeft(distance, objectMatrix) {
let v = new THREE.Vector3()
v.setFromMatrixColumn(objectMatrix, 0) // get X column of objectMatrix
v.multiplyScalar(- distance)
this.panOffset.add(v)
}
panUp(distance, objectMatrix) {
let v = new THREE.Vector3()
if (this.screenSpacePanning === true) {
v.setFromMatrixColumn(objectMatrix, 1)
} else {
v.setFromMatrixColumn(objectMatrix, 0)
v.crossVectors(this.object.up, v)
}
v.multiplyScalar(distance)
this.panOffset.add(v)
}
// Patch - translate Y
translateY(delta) {
this.panOffset.y += delta
}
// deltaX and deltaY are in pixels; right and down are positive
pan(deltaX, deltaY, distance) {
let offset = new THREE.Vector3()
if (this.object.isPerspectiveCamera) {
// perspective
var position = this.object.position
offset.copy(position).sub(this.target)
var targetDistance = offset.length()
// half of the fov is center to top of screen
targetDistance *= Math.tan((this.object.fov / 2) * Math.PI / 180.0)
targetDistance = distance || targetDistance
// we use only clientHeight here so aspect ratio does not distort speed
this.panLeft(2 * deltaX * targetDistance / this.element.clientHeight, this.object.matrix)
this.panUp(2 * deltaY * targetDistance / this.element.clientHeight, this.object.matrix)
} else if (this.object.isOrthographicCamera) {
// orthographic
this.panLeft(deltaX * (this.object.right - this.object.left) / this.object.zoom / this.element.clientWidth, this.object.matrix)
this.panUp(deltaY * (this.object.top - this.object.bottom) / this.object.zoom / this.element.clientHeight, this.object.matrix)
} else {
// camera neither orthographic nor perspective
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.')
this.enablePan = false
}
}
dollyOut(dollyScale) {
if (this.object.isPerspectiveCamera) {
this.scale /= dollyScale
} else if (this.object.isOrthographicCamera) {
this.object.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.object.zoom * dollyScale))
this.object.updateProjectionMatrix()
this.zoomChanged = true
} else {
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.')
this.enableZoom = false
}
}
dollyIn(dollyScale) {
if (this.object.isPerspectiveCamera) {
this.scale *= dollyScale
} else if (this.object.isOrthographicCamera) {
this.object.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.object.zoom / dollyScale))
this.object.updateProjectionMatrix()
this.zoomChanged = true
} else {
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.')
this.enableZoom = false
}
}
//#endregion
//#region Event Callbacks - update the object state
handleMouseDownRotate(event) {
this.rotateStart.set(event.clientX, event.clientY)
}
handleMouseDownDolly(event) {
this.dollyStart.set(event.clientX, event.clientY)
}
handleMouseDownPan(event) {
this.panStart.set(event.clientX, event.clientY)
}
handleMouseMoveRotate(event) {
this.rotateEnd.set(event.clientX, event.clientY)
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart).multiplyScalar(this.rotateSpeed)
this.rotateLeft(2 * Math.PI * this.rotateDelta.x / this.element.clientHeight) // yes, height
this.rotateUp(2 * Math.PI * this.rotateDelta.y / this.element.clientHeight)
this.rotateStart.copy(this.rotateEnd)
this.update(true)
}
handleMouseMoveDolly(event) {
this.dollyEnd.set(event.clientX, event.clientY)
this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart)
if (this.dollyDelta.y > 0) {
this.dollyOut(this.getZoomScale())
} else if (this.dollyDelta.y < 0) {
this.dollyIn(this.getZoomScale())
}
this.dollyStart.copy(this.dollyEnd)
this.update(true)
}
handleMouseMovePan(event) {
this.panEnd.set(event.clientX, event.clientY)
this.panDelta.subVectors(this.panEnd, this.panStart).multiplyScalar(this.panSpeed)
this.pan(this.panDelta.x, this.panDelta.y)
this.panStart.copy(this.panEnd)
this.update(true)
}
handleMouseUp(/*event*/) {
// no-op
}
handleMouseWheel(event) {
if (event.deltaY < 0) {
this.dollyIn(this.getZoomScale())
} else if (event.deltaY > 0) {
this.dollyOut(this.getZoomScale())
}
this.update(true)
}
//#endregion
//#region Mouse/Keyboard handlers
// Called when the cursor location has moved
onPointerMove(event) {
if (!this.enabled || (this.state == STATE.NONE)) return
switch (event.pointerType) {
case 'mouse':
case 'pen':
this.onMouseMove(event)
break
// TODO touch
}
}
// Called when the cursor is no longer behind held
onPointerUp(event) {
if (!this.enabled) return
switch (event.pointerType) {
case 'mouse':
case 'pen':
this.onMouseUp(event)
break
// TODO touch
}
}
// On left click or tap
onPointerDown(event) {
if (!this.enabled) return
switch (event.pointerType) {
case 'mouse':
case 'pen':
this.onMouseDown(event)
break
// TODO touch
}
}
onMouseDown(event) {
// Prevent the browser from scrolling.
event.preventDefault()
// Manually set the focus since calling preventDefault above
// prevents the browser from setting it automatically.
this.element.focus ? this.element.focus() : window.focus()
var mouseAction
switch (event.button) {
case 0:
mouseAction = this.mouseButtons.LEFT
break
case 1:
mouseAction = this.mouseButtons.MIDDLE
break
case 2:
mouseAction = this.mouseButtons.RIGHT
break
default:
mouseAction = - 1
}
switch (mouseAction) {
case THREE.MOUSE.DOLLY:
if (this.enableZoom === false) return
this.handleMouseDownDolly(event)
this.state = STATE.DOLLY
break
case THREE.MOUSE.ROTATE:
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if (this.enablePan === false) return
this.handleMouseDownPan(event)
this.state = STATE.PAN
} else {
if (this.enableRotate === false) return
this.handleMouseDownRotate(event)
this.state = STATE.ROTATE
}
break
case THREE.MOUSE.PAN:
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if (this.enableRotate === false) return
this.handleMouseDownRotate(event)
this.state = STATE.ROTATE
} else {
if (this.enablePan === false) return
this.handleMouseDownPan(event)
this.state = STATE.PAN
}
break
default:
this.state = STATE.NONE
}
}
onMouseMove(event) {
if (this.enabled === false) return
event.preventDefault()
switch (this.state) {
case STATE.ROTATE:
if (this.enableRotate === false) return
this.handleMouseMoveRotate(event)
break
case STATE.DOLLY:
if (this.enableZoom === false) return
this.handleMouseMoveDolly(event)
break
case STATE.PAN:
if (this.enablePan === false) return
this.handleMouseMovePan(event)
break
}
}
onMouseUp(event) {
this.state = STATE.NONE
}
onMouseWheel(event) {
if (this.enabled === false || this.enableZoom === false || (this.state !== STATE.NONE && this.state !== STATE.ROTATE)) return
event.preventDefault()
event.stopPropagation()
this.dispatchEvent(this.startEvent)
this.handleMouseWheel(event)
this.dispatchEvent(this.endEvent)
}
//#endregion
//#region Touch handlers
handleTouchStartRotate(event) {
if (event.touches.length == 1) {
this.rotateStart.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.rotateStart.set(x, y)
}
}
handleTouchStartPan(event) {
if (event.touches.length == 1) {
this.panStart.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.panStart.set(x, y)
}
}
handleTouchStartDolly(event) {
var dx = event.touches[0].pageX - event.touches[1].pageX
var dy = event.touches[0].pageY - event.touches[1].pageY
var distance = Math.sqrt(dx * dx + dy * dy)
this.dollyStart.set(0, distance)
}
handleTouchStartDollyPan(event) {
if (this.enableTouchZoom) this.handleTouchStartDolly(event)
if (this.enableTouchPan) this.handleTouchStartPan(event)
}
handleTouchStartDollyRotate(event) {
if (this.enableTouchZoom) this.handleTouchStartDolly(event)
if (this.enableTouchRotate) this.handleTouchStartRotate(event)
}
handleTouchMoveRotate(event) {
if (event.touches.length == 1) {
this.rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.rotateEnd.set(x, y)
}
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart).multiplyScalar(this.rotateSpeed)
this.rotateLeft(2 * Math.PI * this.rotateDelta.x / this.element.clientHeight) // yes, height
this.rotateUp(2 * Math.PI * this.rotateDelta.y / this.element.clientHeight)
this.rotateStart.copy(this.rotateEnd)
}
handleTouchMovePan(event) {
if (event.touches.length == 1) {
this.panEnd.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.panEnd.set(x, y)
}
this.panDelta.subVectors(this.panEnd, this.panStart).multiplyScalar(this.panSpeed)
this.pan(this.panDelta.x, this.panDelta.y)
this.panStart.copy(this.panEnd)
}
handleTouchMoveDolly(event) {
var dx = event.touches[0].pageX - event.touches[1].pageX
var dy = event.touches[0].pageY - event.touches[1].pageY
var distance = Math.sqrt(dx * dx + dy * dy)
this.dollyEnd.set(0, distance)
this.dollyDelta.set(0, Math.pow(this.dollyEnd.y / this.dollyStart.y, this.zoomSpeed))
this.dollyOut(this.dollyDelta.y)
this.dollyStart.copy(this.dollyEnd)
}
handleTouchMoveDollyPan(event) {
if (this.enableTouchZoom) this.handleTouchMoveDolly(event)
if (this.enableTouchPan) this.handleTouchMovePan(event)
}
handleTouchMoveDollyRotate(event) {
if (this.enableTouchZoom) this.handleTouchMoveDolly(event)
if (this.enableTouchRotate) this.handleTouchMoveRotate(event)
}
handleTouchEnd( /*event*/) {
// no-op
}
//#endregion
tickControls() {
const control = this.controlMap
for (var keyCode of this.keyDowns) {
if (control.MOVE_FORWARD.includes(keyCode)) {
this.pan(0, this.keyPanSpeed, this.keyPanDistance)
} else if (control.MOVE_BACKWARD.includes(keyCode)) {
this.pan(0, -this.keyPanSpeed, this.keyPanDistance)
} else if (control.MOVE_LEFT.includes(keyCode)) {
this.pan(this.keyPanSpeed, 0, this.keyPanDistance)
} else if (control.MOVE_RIGHT.includes(keyCode)) {
this.pan(-this.keyPanSpeed, 0, this.keyPanDistance)
} else if (control.MOVE_UP.includes(keyCode)) {
this.translateY(+this.verticalTranslationSpeed)
} else if (control.MOVE_DOWN.includes(keyCode)) {
this.translateY(-this.verticalTranslationSpeed)
}
}
}
onKeyDown(e) {
if (!this.enabled) return
if (e.code && !this.keyDowns.includes(e.code)) {
this.keyDowns.push(e.code)
// console.debug('[control] Key down: ', this.keyDowns)
}
}
onKeyUp(event) {
// console.log('[control] Key up: ', event.code, this.keyDowns)
this.keyDowns = this.keyDowns.filter(code => code != event.code)
}
onTouchStart(event) {
if (this.enabled === false) return
event.preventDefault() // prevent scrolling
switch (event.touches.length) {
case 1:
switch (this.touches.ONE) {
case THREE.TOUCH.ROTATE:
if (this.enableTouchRotate === false) return
this.handleTouchStartRotate(event)
this.state = STATE.TOUCH_ROTATE
break
case THREE.TOUCH.PAN:
if (this.enableTouchPan === false) return
this.handleTouchStartPan(event)
this.state = STATE.TOUCH_PAN
break
default:
this.state = STATE.NONE
}
break
case 2:
switch (this.touches.TWO) {
case THREE.TOUCH.DOLLY_PAN:
if (this.enableTouchZoom === false && this.enableTouchPan === false) return
this.handleTouchStartDollyPan(event)
this.state = STATE.TOUCH_DOLLY_PAN
break
case THREE.TOUCH.DOLLY_ROTATE:
if (this.enableTouchZoom === false && this.enableTouchRotate === false) return
this.handleTouchStartDollyRotate(event)
this.state = STATE.TOUCH_DOLLY_ROTATE
break
default:
this.state = STATE.NONE
}
break
default:
this.state = STATE.NONE
}
if (this.state !== STATE.NONE) {
this.dispatchEvent(this.startEvent)
}
}
onTouchMove(event) {
if (this.enabled === false) return
event.preventDefault() // prevent scrolling
event.stopPropagation()
switch (this.state) {
case STATE.TOUCH_ROTATE:
if (this.enableTouchRotate === false) return
this.handleTouchMoveRotate(event)
this.update()
break
case STATE.TOUCH_PAN:
if (this.enableTouchPan === false) return
this.handleTouchMovePan(event)
this.update()
break
case STATE.TOUCH_DOLLY_PAN:
if (this.enableTouchZoom === false && this.enableTouchPan === false) return
this.handleTouchMoveDollyPan(event)
this.update()
break
case STATE.TOUCH_DOLLY_ROTATE:
if (this.enableTouchZoom === false && this.enableTouchRotate === false) return
this.handleTouchMoveDollyRotate(event)
this.update()
break
default:
this.state = STATE.NONE
}
}
onTouchEnd(event) {
if (this.enabled === false) return
this.handleTouchEnd(event)
this.dispatchEvent(this.endEvent)
this.state = STATE.NONE
}
onContextMenu(event) {
// Disable context menu
if (this.enabled) event.preventDefault()
}
registerHandlers() {
this.element.addEventListener('pointermove', this.onPointerMove, false, {passive: true})
this.element.addEventListener('pointerup', this.onPointerUp, false, {passive: true})
this.element.addEventListener('pointerdown', this.onPointerDown, false, {passive: true})
this.element.addEventListener('wheel', this.onMouseWheel, true, {passive: true})
this.element.addEventListener('touchstart', this.onTouchStart, false, {passive: true})
this.element.addEventListener('touchend', this.onTouchEnd, false, {passive: true})
this.element.addEventListener('touchmove', this.onTouchMove, false, {passive: true})
this.element.ownerDocument.addEventListener('contextmenu', this.onContextMenu, false, {passive: true})
this.element.ownerDocument.addEventListener('keydown', this.onKeyDown, false, {passive: true})
this.element.ownerDocument.addEventListener('keyup', this.onKeyUp, false, {passive: true})
console.log('[controls] registered handlers', this.element)
}
unregisterHandlers() {
this.element.removeEventListener('pointermove', this.onPointerMove, false, {passive: true})
this.element.removeEventListener('pointerup', this.onPointerUp, false, {passive: true})
this.element.removeEventListener('pointerdown', this.onPointerDown, false, {passive: true})
this.element.removeEventListener('wheel', this.onMouseWheel, true, {passive: true})
this.element.removeEventListener('touchstart', this.onTouchStart, false, {passive: true})
this.element.removeEventListener('touchend', this.onTouchEnd, false, {passive: true})
this.element.removeEventListener('touchmove', this.onTouchMove, false, {passive: true})
this.element.ownerDocument.removeEventListener('contextmenu', this.onContextMenu, false, {passive: true})
this.element.ownerDocument.removeEventListener('keydown', this.onKeyDown, false, {passive: true})
this.element.ownerDocument.removeEventListener('keyup', this.onKeyUp, false, {passive: true})
console.log('[controls] unregistered handlers', this.element)
}
dispatchEvent() {
// no-op
}
}
module.exports = { MapControls }

View file

@ -1,482 +0,0 @@
//@ts-check
import * as THREE from 'three'
import * as TWEEN from '@tweenjs/tween.js'
import * as Entity from './entity/EntityMesh'
import nbt from 'prismarine-nbt'
import EventEmitter from 'events'
import { PlayerObject, PlayerAnimation } from 'skinview3d'
import { loadSkinToCanvas, loadEarsToCanvasFromSkin, inferModelType, loadCapeToCanvas, loadImage } from 'skinview-utils'
// todo replace with url
import stevePng from 'minecraft-assets/minecraft-assets/data/1.20.2/entity/player/wide/steve.png'
import { WalkingGeneralSwing } from './entity/animations'
import { NameTagObject } from 'skinview3d/libs/nametag'
import { flat, fromFormattedString } from '@xmcl/text-component'
import mojangson from 'mojangson'
import externalTexturesJson from './entity/externalTextures.json'
import { disposeObject } from './threeJsUtils'
export const TWEEN_DURATION = 50 // todo should be 100
function getUsernameTexture(username, { fontFamily = 'sans-serif' }) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Could not get 2d context')
const fontSize = 50
const padding = 5
ctx.font = `${fontSize}px ${fontFamily}`
const textWidth = ctx.measureText(username).width + padding * 2
canvas.width = textWidth
canvas.height = fontSize + padding * 2
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.font = `${fontSize}px ${fontFamily}`
ctx.fillStyle = 'white'
ctx.fillText(username, padding, fontSize)
return canvas
}
const addNametag = (entity, options, mesh) => {
if (entity.username !== undefined) {
if (mesh.children.find(c => c.name === 'nametag')) return // todo update
const canvas = getUsernameTexture(entity.username, options)
const tex = new THREE.Texture(canvas)
tex.needsUpdate = true
const spriteMat = new THREE.SpriteMaterial({ map: tex })
const sprite = new THREE.Sprite(spriteMat)
sprite.renderOrder = 1000
sprite.scale.set(canvas.width * 0.005, canvas.height * 0.005, 1)
sprite.position.y += entity.height + 0.6
sprite.name = 'nametag'
mesh.add(sprite)
}
}
// todo cleanup
const nametags = {}
function getEntityMesh(entity, scene, options, overrides) {
if (entity.name) {
try {
// https://github.com/PrismarineJS/prismarine-viewer/pull/410
const entityName = entity.name.toLowerCase()
const e = new Entity.EntityMesh('1.16.4', entityName, scene, overrides)
if (e.mesh) {
addNametag(entity, options, e.mesh)
return e.mesh
}
} catch (err) {
reportError?.(err)
}
}
const geometry = new THREE.BoxGeometry(entity.width, entity.height, entity.width)
geometry.translate(0, entity.height / 2, 0)
const material = new THREE.MeshBasicMaterial({ color: 0xff_00_ff })
const cube = new THREE.Mesh(geometry, material)
const nametagCount = (nametags[entity.name] = (nametags[entity.name] || 0) + 1)
if (nametagCount < 6) {
addNametag({
username: entity.name,
height: entity.height,
}, options, cube)
}
return cube
}
export class Entities extends EventEmitter {
constructor(scene) {
super()
/** @type {THREE.Scene} */
this.scene = scene
this.entities = {}
this.entitiesOptions = {}
this.debugMode = 'none'
this.onSkinUpdate = () => { }
this.clock = new THREE.Clock()
this.rendering = true
this.itemsTexture = null
this.getItemUv = undefined
}
clear() {
for (const mesh of Object.values(this.entities)) {
this.scene.remove(mesh)
disposeObject(mesh)
}
this.entities = {}
}
setDebugMode(mode, /** @type {THREE.Object3D?} */entity = null) {
this.debugMode = mode
for (const mesh of entity ? [entity] : Object.values(this.entities)) {
const boxHelper = mesh.children.find(c => c.name === 'debug')
boxHelper.visible = false
if (this.debugMode === 'basic') {
boxHelper.visible = true
}
// todo advanced
}
}
setRendering(rendering, /** @type {THREE.Object3D?} */entity = null) {
this.rendering = rendering
for (const ent of entity ? [entity] : Object.values(this.entities)) {
if (rendering) {
if (!this.scene.children.includes(ent)) this.scene.add(ent)
} else {
this.scene.remove(ent)
}
}
}
render() {
const dt = this.clock.getDelta()
for (const entityId of Object.keys(this.entities)) {
const playerObject = this.getPlayerObject(entityId)
if (playerObject?.animation) {
playerObject.animation.update(playerObject, dt)
}
}
}
getPlayerObject(entityId) {
/** @type {(PlayerObject & { animation?: PlayerAnimation }) | undefined} */
const playerObject = this.entities[entityId]?.playerObject
return playerObject
}
// fixme workaround
defaultSteveTexture
// true means use default skin url
updatePlayerSkin(entityId, username, /** @type {string | true} */skinUrl, /** @type {string | true | undefined} */capeUrl = undefined) {
let playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
// const username = this.entities[entityId].username
// or https://mulv.vercel.app/
if (skinUrl === true) {
skinUrl = `https://mulv.tycrek.dev/api/lookup?username=${username}&type=skin`
if (!username) return
}
loadImage(skinUrl).then((image) => {
playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
/** @type {THREE.CanvasTexture} */
let skinTexture
if (skinUrl === stevePng && this.defaultSteveTexture) {
skinTexture = this.defaultSteveTexture
} else {
const skinCanvas = document.createElement('canvas')
loadSkinToCanvas(skinCanvas, image)
skinTexture = new THREE.CanvasTexture(skinCanvas)
if (skinUrl === stevePng) {
this.defaultSteveTexture = skinTexture
}
}
skinTexture.magFilter = THREE.NearestFilter
skinTexture.minFilter = THREE.NearestFilter
skinTexture.needsUpdate = true
//@ts-ignore
playerObject.skin.map = skinTexture
playerObject.skin.modelType = inferModelType(skinTexture.image)
const earsCanvas = document.createElement('canvas')
loadEarsToCanvasFromSkin(earsCanvas, image)
if (!isCanvasBlank(earsCanvas)) {
const earsTexture = new THREE.CanvasTexture(earsCanvas)
earsTexture.magFilter = THREE.NearestFilter
earsTexture.minFilter = THREE.NearestFilter
earsTexture.needsUpdate = true
//@ts-ignore
playerObject.ears.map = earsTexture
playerObject.ears.visible = true
} else {
playerObject.ears.map = null
playerObject.ears.visible = false
}
this.onSkinUpdate?.()
if (capeUrl) {
if (capeUrl === true) capeUrl = `https://mulv.tycrek.dev/api/lookup?username=${username}&type=cape`
loadImage(capeUrl).then((capeImage) => {
playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
const capeCanvas = document.createElement('canvas')
loadCapeToCanvas(capeCanvas, capeImage)
const capeTexture = new THREE.CanvasTexture(capeCanvas)
capeTexture.magFilter = THREE.NearestFilter
capeTexture.minFilter = THREE.NearestFilter
capeTexture.needsUpdate = true
//@ts-ignore
playerObject.cape.map = capeTexture
playerObject.cape.visible = true
//@ts-ignore
playerObject.elytra.map = capeTexture
this.onSkinUpdate?.()
if (!playerObject.backEquipment) {
playerObject.backEquipment = 'cape'
}
}, () => { })
}
}, () => { })
playerObject.cape.visible = false
if (!capeUrl) {
playerObject.backEquipment = null
playerObject.elytra.map = null
if (playerObject.cape.map) {
playerObject.cape.map.dispose()
}
playerObject.cape.map = null
}
function isCanvasBlank(canvas) {
return !canvas.getContext('2d')
.getImageData(0, 0, canvas.width, canvas.height).data
.some(channel => channel !== 0)
}
}
playAnimation(entityPlayerId, /** @type {'walking' | 'running' | 'oneSwing' | 'idle'} */animation) {
const playerObject = this.getPlayerObject(entityPlayerId)
if (!playerObject) return
if (animation === 'oneSwing') {
if (!(playerObject.animation instanceof WalkingGeneralSwing)) throw new Error('Expected WalkingGeneralSwing')
playerObject.animation.swingArm()
return
}
if (playerObject.animation instanceof WalkingGeneralSwing) {
playerObject.animation.switchAnimationCallback = () => {
if (!(playerObject.animation instanceof WalkingGeneralSwing)) throw new Error('Expected WalkingGeneralSwing')
playerObject.animation.isMoving = animation !== 'idle'
playerObject.animation.isRunning = animation === 'running'
}
}
}
parseEntityLabel(jsonLike) {
if (!jsonLike) return
try {
const parsed = typeof jsonLike === 'string' ? mojangson.simplify(mojangson.parse(jsonLike)) : nbt.simplify(jsonLike)
const text = flat(parsed).map(x => x.text)
return text.join('')
} catch (err) {
return jsonLike
}
}
update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) {
let isPlayerModel = entity.name === 'player'
if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') {
isPlayerModel = true
overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}`
}
if (!this.entities[entity.id] && !entity.delete) {
const group = new THREE.Group()
let mesh
if (entity.name === 'item') {
/** @type {any} */
//@ts-ignore
const item = entity.metadata?.find(m => typeof m === 'object' && m !== null && m.itemCount)
if (item) {
const textureUv = this.getItemUv?.(item.itemId ?? item.blockId)
if (textureUv) {
// todo use geometry buffer uv instead!
const { u, v, size, su, sv, texture } = textureUv
const itemsTexture = texture.clone()
itemsTexture.flipY = true
itemsTexture.offset.set(u, 1 - v - (sv ?? size))
itemsTexture.repeat.set(su ?? size, sv ?? size)
itemsTexture.needsUpdate = true
itemsTexture.magFilter = THREE.NearestFilter
itemsTexture.minFilter = THREE.NearestFilter
const itemsTextureFlipped = itemsTexture.clone()
itemsTextureFlipped.repeat.x *= -1
itemsTextureFlipped.needsUpdate = true
itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size))
const material = new THREE.MeshStandardMaterial({
map: itemsTexture,
transparent: true,
alphaTest: 0.1,
})
const materialFlipped = new THREE.MeshStandardMaterial({
map: itemsTextureFlipped,
transparent: true,
alphaTest: 0.1,
})
mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0.0), [
// top left and right bottom are black box materials others are transparent
new THREE.MeshBasicMaterial({ color: 0x000000 }), new THREE.MeshBasicMaterial({ color: 0x000000 }),
new THREE.MeshBasicMaterial({ color: 0x000000 }), new THREE.MeshBasicMaterial({ color: 0x000000 }),
material, materialFlipped
])
mesh.scale.set(0.5, 0.5, 0.5)
mesh.position.set(0, 0.2, 0)
// set faces
// mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5)
// viewer.scene.add(mesh)
const clock = new THREE.Clock()
mesh.onBeforeRender = () => {
const delta = clock.getDelta()
mesh.rotation.y += delta
}
//@ts-ignore
group.additionalCleanup = () => {
// important: avoid texture memory leak and gpu slowdown
itemsTexture.dispose()
itemsTextureFlipped.dispose()
}
}
}
} else if (isPlayerModel) {
// CREATE NEW PLAYER ENTITY
const wrapper = new THREE.Group()
/** @type {PlayerObject & { animation?: PlayerAnimation }} */
const playerObject = new PlayerObject()
playerObject.position.set(0, 16, 0)
//@ts-ignore
wrapper.add(playerObject)
const scale = 1 / 16
wrapper.scale.set(scale, scale, scale)
if (entity.username) {
// todo proper colors
const nameTag = new NameTagObject(fromFormattedString(entity.username).text, {
font: `48px ${this.entitiesOptions.fontFamily}`,
})
nameTag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3
nameTag.renderOrder = 1000
//@ts-ignore
wrapper.add(nameTag)
}
//@ts-ignore
group.playerObject = playerObject
wrapper.rotation.set(0, Math.PI, 0)
mesh = wrapper
playerObject.animation = new WalkingGeneralSwing()
//@ts-ignore
playerObject.animation.isMoving = false
} else {
mesh = getEntityMesh(entity, this.scene, this.entitiesOptions, overrides)
}
if (!mesh) return
mesh.name = 'mesh'
// set initial position so there are no weird jumps update after
group.position.set(entity.pos.x, entity.pos.y, entity.pos.z)
// todo use width and height instead
const boxHelper = new THREE.BoxHelper(mesh,
entity.type === 'hostile' ? 0xff0000 :
entity.type === 'mob' ? 0x00ff00 :
entity.type === "player" ? 0x0000ff :
0xffa500
)
boxHelper.name = 'debug'
group.add(mesh)
group.add(boxHelper)
boxHelper.visible = false
this.scene.add(group)
this.entities[entity.id] = group
this.emit('add', entity)
if (isPlayerModel) {
this.updatePlayerSkin(entity.id, '', overrides?.texture || stevePng)
}
this.setDebugMode(this.debugMode, group)
this.setRendering(this.rendering, group)
}
//@ts-ignore
const isInvisible = entity.metadata?.[0] & 0x20
if (isInvisible) {
for (const child of this.entities[entity.id].children.find(c => c.name === 'mesh').children) {
if (child.name !== 'nametag') {
child.visible = false
}
}
}
// not player
const displayText = entity.metadata?.[3] && this.parseEntityLabel(entity.metadata[2])
if (entity.name !== 'player' && displayText) {
addNametag({ ...entity, username: displayText }, this.entitiesOptions, this.entities[entity.id].children.find(c => c.name === 'mesh'))
}
// todo handle map, map_chunks events
// if (entity.name === 'item_frame' || entity.name === 'glow_item_frame') {
// const example = {
// "present": true,
// "itemId": 847,
// "itemCount": 1,
// "nbtData": {
// "type": "compound",
// "name": "",
// "value": {
// "map": {
// "type": "int",
// "value": 2146483444
// },
// "interactiveboard": {
// "type": "byte",
// "value": 1
// }
// }
// }
// }
// const item = entity.metadata?.[8]
// if (item.nbtData) {
// const nbt = nbt.simplify(item.nbtData)
// }
// }
// this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted)
const e = this.entities[entity.id]
if (entity.username) {
e.username = entity.username
}
if (e?.playerObject && overrides?.rotation?.head) {
/** @type {PlayerObject} */
const playerObject = e.playerObject
const headRotationDiff = overrides.rotation.head.y ? overrides.rotation.head.y - entity.yaw : 0
playerObject.skin.head.rotation.y = -headRotationDiff
playerObject.skin.head.rotation.x = overrides.rotation.head.x ? - overrides.rotation.head.x : 0
}
if (entity.delete && e) {
if (e.additionalCleanup) e.additionalCleanup()
this.emit('remove', entity)
this.scene.remove(e)
disposeObject(e)
// todo dispose textures as well ?
delete this.entities[entity.id]
}
if (entity.pos) {
new TWEEN.Tween(e.position).to({ x: entity.pos.x, y: entity.pos.y, z: entity.pos.z }, TWEEN_DURATION).start()
}
if (entity.yaw) {
const da = (entity.yaw - e.rotation.y) % (Math.PI * 2)
const dy = 2 * da % (Math.PI * 2) - da
new TWEEN.Tween(e.rotation).to({ y: e.rotation.y + dy }, TWEEN_DURATION).start()
}
}
}

View file

@ -1,383 +0,0 @@
//@ts-check
import { OBJLoader } from 'three-stdlib'
import entities from './entities.json'
import { externalModels } from './objModels'
import externalTexturesJson from './externalTextures.json'
// import { loadTexture } from globalThis.isElectron ? '../utils.electron.js' : '../utils';
const { loadTexture } = globalThis.isElectron ? require('../utils.electron.js') : require('../utils')
const elemFaces = {
up: {
dir: [0, 1, 0],
u0: [0, 0, 1],
v0: [0, 0, 0],
u1: [1, 0, 1],
v1: [0, 0, 1],
corners: [
[0, 1, 1, 0, 0],
[1, 1, 1, 1, 0],
[0, 1, 0, 0, 1],
[1, 1, 0, 1, 1]
]
},
down: {
dir: [0, -1, 0],
u0: [1, 0, 1],
v0: [0, 0, 0],
u1: [2, 0, 1],
v1: [0, 0, 1],
corners: [
[1, 0, 1, 0, 0],
[0, 0, 1, 1, 0],
[1, 0, 0, 0, 1],
[0, 0, 0, 1, 1]
]
},
east: {
dir: [1, 0, 0],
u0: [0, 0, 0],
v0: [0, 0, 1],
u1: [0, 0, 1],
v1: [0, 1, 1],
corners: [
[1, 1, 1, 0, 0],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 0],
[1, 0, 0, 1, 1]
]
},
west: {
dir: [-1, 0, 0],
u0: [1, 0, 1],
v0: [0, 0, 1],
u1: [1, 0, 2],
v1: [0, 1, 1],
corners: [
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 1, 1]
]
},
north: {
dir: [0, 0, -1],
u0: [0, 0, 1],
v0: [0, 0, 1],
u1: [1, 0, 1],
v1: [0, 1, 1],
corners: [
[1, 0, 0, 0, 1],
[0, 0, 0, 1, 1],
[1, 1, 0, 0, 0],
[0, 1, 0, 1, 0]
]
},
south: {
dir: [0, 0, 1],
u0: [1, 0, 2],
v0: [0, 0, 1],
u1: [2, 0, 2],
v1: [0, 1, 1],
corners: [
[0, 0, 1, 0, 1],
[1, 0, 1, 1, 1],
[0, 1, 1, 0, 0],
[1, 1, 1, 1, 0]
]
}
}
function dot (a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
function addCube (attr, boneId, bone, cube, texWidth = 64, texHeight = 64) {
const cubeRotation = new THREE.Euler(0, 0, 0)
if (cube.rotation) {
cubeRotation.x = -cube.rotation[0] * Math.PI / 180
cubeRotation.y = -cube.rotation[1] * Math.PI / 180
cubeRotation.z = -cube.rotation[2] * Math.PI / 180
}
for (const { dir, corners, u0, v0, u1, v1 } of Object.values(elemFaces)) {
const ndx = Math.floor(attr.positions.length / 3)
for (const pos of corners) {
const u = (cube.uv[0] + dot(pos[3] ? u1 : u0, cube.size)) / texWidth
const v = (cube.uv[1] + dot(pos[4] ? v1 : v0, cube.size)) / texHeight
const inflate = cube.inflate ? cube.inflate : 0
let vecPos = new THREE.Vector3(
cube.origin[0] + pos[0] * cube.size[0] + (pos[0] ? inflate : -inflate),
cube.origin[1] + pos[1] * cube.size[1] + (pos[1] ? inflate : -inflate),
cube.origin[2] + pos[2] * cube.size[2] + (pos[2] ? inflate : -inflate)
)
vecPos = vecPos.applyEuler(cubeRotation)
vecPos = vecPos.sub(bone.position)
vecPos = vecPos.applyEuler(bone.rotation)
vecPos = vecPos.add(bone.position)
attr.positions.push(vecPos.x, vecPos.y, vecPos.z)
attr.normals.push(...dir)
attr.uvs.push(u, v)
attr.skinIndices.push(boneId, 0, 0, 0)
attr.skinWeights.push(1, 0, 0, 0)
}
attr.indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3
)
}
}
function getMesh (texture, jsonModel, overrides = {}) {
const bones = {}
const geoData = {
positions: [],
normals: [],
uvs: [],
indices: [],
skinIndices: [],
skinWeights: []
}
let i = 0
for (const jsonBone of jsonModel.bones) {
const bone = new THREE.Bone()
if (jsonBone.pivot) {
bone.position.x = jsonBone.pivot[0]
bone.position.y = jsonBone.pivot[1]
bone.position.z = jsonBone.pivot[2]
}
if (jsonBone.bind_pose_rotation) {
bone.rotation.x = -jsonBone.bind_pose_rotation[0] * Math.PI / 180
bone.rotation.y = -jsonBone.bind_pose_rotation[1] * Math.PI / 180
bone.rotation.z = -jsonBone.bind_pose_rotation[2] * Math.PI / 180
} else if (jsonBone.rotation) {
bone.rotation.x = -jsonBone.rotation[0] * Math.PI / 180
bone.rotation.y = -jsonBone.rotation[1] * Math.PI / 180
bone.rotation.z = -jsonBone.rotation[2] * Math.PI / 180
}
if (overrides.rotation?.[jsonBone.name]) {
bone.rotation.x -= (overrides.rotation[jsonBone.name].x ?? 0) * Math.PI / 180
bone.rotation.y -= (overrides.rotation[jsonBone.name].y ?? 0) * Math.PI / 180
bone.rotation.z -= (overrides.rotation[jsonBone.name].z ?? 0) * Math.PI / 180
}
bone.name = `bone_${jsonBone.name}`
bones[jsonBone.name] = bone
if (jsonBone.cubes) {
for (const cube of jsonBone.cubes) {
addCube(geoData, i, bone, cube, jsonModel.texturewidth, jsonModel.textureheight)
}
}
i++
}
const rootBones = []
for (const jsonBone of jsonModel.bones) {
if (jsonBone.parent && bones[jsonBone.parent]) bones[jsonBone.parent].add(bones[jsonBone.name])
else {
rootBones.push(bones[jsonBone.name])
}
}
const skeleton = new THREE.Skeleton(Object.values(bones))
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.Float32BufferAttribute(geoData.positions, 3))
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(geoData.normals, 3))
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(geoData.uvs, 2))
geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(geoData.skinIndices, 4))
geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(geoData.skinWeights, 4))
geometry.setIndex(geoData.indices)
const material = new THREE.MeshLambertMaterial({ transparent: true, alphaTest: 0.1 })
const mesh = new THREE.SkinnedMesh(geometry, material)
mesh.add(...rootBones)
mesh.bind(skeleton)
mesh.scale.set(1 / 16, 1 / 16, 1 / 16)
loadTexture(texture, texture => {
if (material.map) {
// texture is already loaded
return
}
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.NearestFilter
texture.flipY = false
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
material.map = texture
})
return mesh
}
export const knownNotHandled = [
'area_effect_cloud', 'block_display',
'chest_boat', 'end_crystal',
'falling_block', 'furnace_minecart',
'giant', 'glow_item_frame',
'glow_squid', 'illusioner',
'interaction', 'item',
'item_display', 'item_frame',
'lightning_bolt', 'marker',
'painting', 'spawner_minecart',
'spectral_arrow', 'text_display',
'tnt', 'trader_llama', 'zombie_horse'
]
export const temporaryMap = {
'furnace_minecart': 'minecart',
'spawner_minecart': 'minecart',
'chest_minecart': 'minecart',
'hopper_minecart': 'minecart',
'command_block_minecart': 'minecart',
'tnt_minecart': 'minecart',
'glow_squid': 'squid',
'trader_llama': 'llama',
'chest_boat': 'boat',
'spectral_arrow': 'arrow',
'husk': 'zombie',
'zombie_horse': 'horse',
'donkey': 'horse',
'skeleton_horse': 'horse',
'mule': 'horse',
'ocelot': 'cat',
// 'falling_block': 'block',
// 'lightning_bolt': 'lightning',
}
const getEntity = (name) => {
return entities[name]
}
// const externalModelsTextures = {
// allay: 'allay/allay',
// axolotl: 'axolotl/axolotl_blue',
// blaze: 'blaze',
// camel: 'camel/camel',
// cat: 'cat/black',
// chicken: 'chicken',
// cod: 'fish/cod',
// creeper: 'creeper/creeper',
// dolphin: 'dolphin',
// ender_dragon: 'enderdragon/dragon',
// enderman: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAgCAYAAACinX6EAAAABGdBTUEAALGPC/xhBQAAAY5JREFUaN7lWNESgzAI8yv8/z/tXjZPHSShYitb73rXedo1AQJ0WchY17WhudQZ7TS18Qb5AXtY/yUBO8tXIaCRqRNwXlcgwDJgmAALfBUP8AjYEdHnAZUIAGdvPy+CnobJIVw9DVIPEABawuEyyvYx1sMIMP8fAbUO7ukBImZmCCEP2AhglnRip8vio7MIxYEsaVkdeYNjYfbN/BBA1twP9AxpB0qlMwj48gBP5Ji1rXc8nfBImk6A5+KqShNwdTwgKy0xYRzdS4yoY651W8EDRwGVJEDVITGtjiEAaEBq3o4SwGqRVAKsdVYIsAzDCACV6VwCFMBCpqLvgudzQ6CnjL5afmeX4pdE0LIQuYCBzZbQfT4rC6COUQGn9B3MQ28pSIxDSDdNrKdQSZJ7lDurMeZm6iEjKVENh8cQgBowBFK5gEHhsO3xFA/oKXp6vg8RoHaD2QRkiaDnAYcZAcB+E6GTRVAhQCVJyVImKOUiBLW3KL4jzU2POHp64RIQ/ADO6D6Ry1gl9tlN1Xm+AK8s2jHadDijAAAAAElFTkSuQmCC',
// endermite: 'endermite',
// fox: 'fox/fox',
// frog: 'frog/cold_frog',
// ghast: 'ghast/ghast',
// goat: 'goat/goat',
// guardian: 'guardian',
// horse: 'horse/horse_brown',
// llama: 'llama/creamy',
// minecart: 'minecart',
// parrot: 'parrot/parrot_grey',
// piglin: 'piglin/piglin',
// pillager: 'illager/pillager',
// rabbit: 'rabbit/brown',
// sheep: 'sheep/sheep',
// shulker: 'shulker/shulker',
// sniffer: 'sniffer/sniffer',
// spider: 'spider/spider',
// tadpole: 'tadpole/tadpole',
// turtle: 'turtle/big_sea_turtle',
// vex: 'illager/vex',
// villager: 'villager/villager',
// warden: 'warden/warden',
// witch: 'witch',
// wolf: 'wolf/wolf',
// zombie_villager: 'zombie_villager/zombie_villager'
// }
export class EntityMesh {
constructor (version, type, scene, /** @type {{textures?, rotation?: Record<string, {x,y,z}>}} */overrides = {}) {
let originalType = type
const mappedValue = temporaryMap[type]
if (mappedValue) type = mappedValue
if (externalModels[type]) {
const objLoader = new OBJLoader()
let texturePath = externalTexturesJson[type]
if (originalType === 'zombie_horse') {
texturePath = `textures/${version}/entity/horse/horse_zombie.png`
}
if (originalType === 'skeleton_horse') {
texturePath = `textures/${version}/entity/horse/horse_skeleton.png`
}
if (originalType === 'donkey') {
texturePath = `textures/${version}/entity/horse/donkey.png`
}
if (originalType === 'mule') {
texturePath = `textures/${version}/entity/horse/mule.png`
}
if (originalType === 'ocelot') {
texturePath = `textures/${version}/entity/cat/ocelot.png`
}
if (!texturePath) throw new Error(`No texture for ${type}`)
const texture = new THREE.TextureLoader().load(texturePath)
texture.minFilter = THREE.NearestFilter
texture.magFilter = THREE.NearestFilter
const material = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
alphaTest: 0.1
})
const obj = objLoader.parse(externalModels[type])
if (type === 'boat') obj.position.y = -1 // todo, should not be hardcoded
obj.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.material = material
// todo
if (child.name === 'Head layer') child.visible = false
if (child.name === 'Head' && overrides.rotation?.head) { // todo
child.rotation.x -= (overrides.rotation.head.x ?? 0) * Math.PI / 180
child.rotation.y -= (overrides.rotation.head.y ?? 0) * Math.PI / 180
child.rotation.z -= (overrides.rotation.head.z ?? 0) * Math.PI / 180
}
}
})
this.mesh = obj
return
}
const e = getEntity(type)
if (!e) {
if (knownNotHandled.includes(type)) return
throw new Error(`Unknown entity ${type}`)
}
this.mesh = new THREE.Object3D()
for (const [name, jsonModel] of Object.entries(e.geometry)) {
const texture = overrides.textures?.[name] ?? e.textures[name]
if (!texture) continue
// console.log(JSON.stringify(jsonModel, null, 2))
const mesh = getMesh(texture.replace('textures', 'textures/' + version) + '.png', jsonModel, overrides)
mesh.name = `geometry_${name}`
this.mesh.add(mesh)
const skeletonHelper = new THREE.SkeletonHelper(mesh)
//@ts-ignore
skeletonHelper.material.linewidth = 2
skeletonHelper.visible = false
this.mesh.add(skeletonHelper)
}
}
static getStaticData (name) {
name = temporaryMap[name] || name
if (externalModels[name]) {
return {
boneNames: [] // todo
}
}
const e = getEntity(name)
if (!e) throw new Error(`Unknown entity ${name}`)
return {
boneNames: Object.values(e.geometry).flatMap(x => x.name)
}
}
}

View file

@ -1,103 +0,0 @@
import { PlayerAnimation } from 'skinview3d'
export class WalkingGeneralSwing extends PlayerAnimation {
switchAnimationCallback
isRunning = false
isMoving = true
_startArmSwing
swingArm () {
this._startArmSwing = this.progress
}
animate (player) {
// Multiply by animation's natural speed
let t
const updateT = () => {
if (!this.isMoving) {
t = 0
return
}
if (this.isRunning) {
t = this.progress * 10 + Math.PI * 0.5
} else {
t = this.progress * 8
}
}
updateT()
let reset = false
if ((this.isRunning ? Math.cos(t) : Math.sin(t)) < 0.01) {
if (this.switchAnimationCallback) {
reset = true
this.progress = 0
updateT()
}
}
if (this.isRunning) {
// Leg swing with larger amplitude
player.skin.leftLeg.rotation.x = Math.cos(t + Math.PI) * 1.3
player.skin.rightLeg.rotation.x = Math.cos(t) * 1.3
} else {
// Leg swing
player.skin.leftLeg.rotation.x = Math.sin(t) * 0.5
player.skin.rightLeg.rotation.x = Math.sin(t + Math.PI) * 0.5
}
if (this._startArmSwing) {
let tHand = (this.progress - this._startArmSwing) * 18 + Math.PI * 0.5
player.skin.rightArm.rotation.x = Math.cos(tHand) * 1.5
const basicArmRotationZ = Math.PI * 0.1
player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.3 - basicArmRotationZ
if (tHand > Math.PI + Math.PI * 0.5) {
this._startArmSwing = null
player.skin.rightArm.rotation.z = 0
}
}
if (this.isRunning) {
player.skin.leftArm.rotation.x = Math.cos(t) * 1.5
if (!this._startArmSwing) {
player.skin.rightArm.rotation.x = Math.cos(t + Math.PI) * 1.5
}
const basicArmRotationZ = Math.PI * 0.1
player.skin.leftArm.rotation.z = Math.cos(t) * 0.1 + basicArmRotationZ
if (!this._startArmSwing) {
player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.1 - basicArmRotationZ
}
} else {
// Arm swing
player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.5
if (!this._startArmSwing) {
player.skin.rightArm.rotation.x = Math.sin(t) * 0.5
}
const basicArmRotationZ = Math.PI * 0.02
player.skin.leftArm.rotation.z = Math.cos(t) * 0.03 + basicArmRotationZ
if (!this._startArmSwing) {
player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.03 - basicArmRotationZ
}
}
if (this.isRunning) {
player.rotation.z = Math.cos(t + Math.PI) * 0.01
}
if (this.isRunning) {
const basicCapeRotationX = Math.PI * 0.3
player.cape.rotation.x = Math.sin(t * 2) * 0.1 + basicCapeRotationX
} else {
// Always add an angle for cape around the x axis
const basicCapeRotationX = Math.PI * 0.06
player.cape.rotation.x = Math.sin(t / 1.5) * 0.06 + basicCapeRotationX
}
if (reset) {
this.switchAnimationCallback()
this.switchAnimationCallback = null
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,325 +0,0 @@
# Made in Blockbench 4.9.4
mtllib materials.mtl
o Body
v 0.25 1.5 0.125
v 0.25 1.5 -0.125
v 0.25 0.75 0.125
v 0.25 0.75 -0.125
v -0.25 1.5 -0.125
v -0.25 1.5 0.125
v -0.25 0.75 -0.125
v -0.25 0.75 0.125
vt 0.3125 0.375
vt 0.4375 0.375
vt 0.4375 0
vt 0.3125 0
vt 0.25 0.375
vt 0.3125 0.375
vt 0.3125 0
vt 0.25 0
vt 0.5 0.375
vt 0.625 0.375
vt 0.625 0
vt 0.5 0
vt 0.4375 0.375
vt 0.5 0.375
vt 0.5 0
vt 0.4375 0
vt 0.4375 0.375
vt 0.3125 0.375
vt 0.3125 0.5
vt 0.4375 0.5
vt 0.5625 0.5
vt 0.4375 0.5
vt 0.4375 0.375
vt 0.5625 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 4/4/1 7/3/1 5/2/1 2/1/1
f 3/8/2 4/7/2 2/6/2 1/5/2
f 8/12/3 3/11/3 1/10/3 6/9/3
f 7/16/4 8/15/4 6/14/4 5/13/4
f 6/20/5 1/19/5 2/18/5 5/17/5
f 7/24/6 4/23/6 3/22/6 8/21/6
o Head
v 0.25 2 0.25
v 0.25 2 -0.25
v 0.25 1.5 0.25
v 0.25 1.5 -0.25
v -0.25 2 -0.25
v -0.25 2 0.25
v -0.25 1.5 -0.25
v -0.25 1.5 0.25
vt 0.125 0.75
vt 0.25 0.75
vt 0.25 0.5
vt 0.125 0.5
vt 0 0.75
vt 0.125 0.75
vt 0.125 0.5
vt 0 0.5
vt 0.375 0.75
vt 0.5 0.75
vt 0.5 0.5
vt 0.375 0.5
vt 0.25 0.75
vt 0.375 0.75
vt 0.375 0.5
vt 0.25 0.5
vt 0.25 0.75
vt 0.125 0.75
vt 0.125 1
vt 0.25 1
vt 0.375 1
vt 0.25 1
vt 0.25 0.75
vt 0.375 0.75
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 12/28/7 15/27/7 13/26/7 10/25/7
f 11/32/8 12/31/8 10/30/8 9/29/8
f 16/36/9 11/35/9 9/34/9 14/33/9
f 15/40/10 16/39/10 14/38/10 13/37/10
f 14/44/11 9/43/11 10/42/11 13/41/11
f 15/48/12 12/47/12 11/46/12 16/45/12
o Hat Layer
v 0.28125 2.03125 0.28125
v 0.28125 2.03125 -0.28125
v 0.28125 1.46875 0.28125
v 0.28125 1.46875 -0.28125
v -0.28125 2.03125 -0.28125
v -0.28125 2.03125 0.28125
v -0.28125 1.46875 -0.28125
v -0.28125 1.46875 0.28125
vt 0.625 0.75
vt 0.75 0.75
vt 0.75 0.5
vt 0.625 0.5
vt 0.5 0.75
vt 0.625 0.75
vt 0.625 0.5
vt 0.5 0.5
vt 0.875 0.75
vt 1 0.75
vt 1 0.5
vt 0.875 0.5
vt 0.75 0.75
vt 0.875 0.75
vt 0.875 0.5
vt 0.75 0.5
vt 0.75 0.75
vt 0.625 0.75
vt 0.625 1
vt 0.75 1
vt 0.875 1
vt 0.75 1
vt 0.75 0.75
vt 0.875 0.75
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 20/52/13 23/51/13 21/50/13 18/49/13
f 19/56/14 20/55/14 18/54/14 17/53/14
f 24/60/15 19/59/15 17/58/15 22/57/15
f 23/64/16 24/63/16 22/62/16 21/61/16
f 22/68/17 17/67/17 18/66/17 21/65/17
f 23/72/18 20/71/18 19/70/18 24/69/18
o RightArm
v 0.5 1.5 0.125
v 0.5 1.5 -0.125
v 0.5 0.75 0.125
v 0.5 0.75 -0.125
v 0.25 1.5 -0.125
v 0.25 1.5 0.125
v 0.25 0.75 -0.125
v 0.25 0.75 0.125
vt 0.6875 0.375
vt 0.75 0.375
vt 0.75 0
vt 0.6875 0
vt 0.625 0.375
vt 0.6875 0.375
vt 0.6875 0
vt 0.625 0
vt 0.8125 0.375
vt 0.875 0.375
vt 0.875 0
vt 0.8125 0
vt 0.75 0.375
vt 0.8125 0.375
vt 0.8125 0
vt 0.75 0
vt 0.75 0.375
vt 0.6875 0.375
vt 0.6875 0.5
vt 0.75 0.5
vt 0.8125 0.5
vt 0.75 0.5
vt 0.75 0.375
vt 0.8125 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 28/76/19 31/75/19 29/74/19 26/73/19
f 27/80/20 28/79/20 26/78/20 25/77/20
f 32/84/21 27/83/21 25/82/21 30/81/21
f 31/88/22 32/87/22 30/86/22 29/85/22
f 30/92/23 25/91/23 26/90/23 29/89/23
f 31/96/24 28/95/24 27/94/24 32/93/24
o LeftArm
v -0.25 1.5 0.125
v -0.25 1.5 -0.125
v -0.25 0.75 0.125
v -0.25 0.75 -0.125
v -0.5 1.5 -0.125
v -0.5 1.5 0.125
v -0.5 0.75 -0.125
v -0.5 0.75 0.125
vt 0.75 0.375
vt 0.6875 0.375
vt 0.6875 0
vt 0.75 0
vt 0.8125 0.375
vt 0.75 0.375
vt 0.75 0
vt 0.8125 0
vt 0.875 0.375
vt 0.8125 0.375
vt 0.8125 0
vt 0.875 0
vt 0.6875 0.375
vt 0.625 0.375
vt 0.625 0
vt 0.6875 0
vt 0.6875 0.375
vt 0.75 0.375
vt 0.75 0.5
vt 0.6875 0.5
vt 0.75 0.5
vt 0.8125 0.5
vt 0.8125 0.375
vt 0.75 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 36/100/25 39/99/25 37/98/25 34/97/25
f 35/104/26 36/103/26 34/102/26 33/101/26
f 40/108/27 35/107/27 33/106/27 38/105/27
f 39/112/28 40/111/28 38/110/28 37/109/28
f 38/116/29 33/115/29 34/114/29 37/113/29
f 39/120/30 36/119/30 35/118/30 40/117/30
o RightLeg
v 0.24375000000000002 0.75 0.125
v 0.24375000000000002 0.75 -0.125
v 0.24375000000000002 0 0.125
v 0.24375000000000002 0 -0.125
v -0.006249999999999978 0.75 -0.125
v -0.006249999999999978 0.75 0.125
v -0.006249999999999978 0 -0.125
v -0.006249999999999978 0 0.125
vt 0.0625 0.375
vt 0.125 0.375
vt 0.125 0
vt 0.0625 0
vt 0 0.375
vt 0.0625 0.375
vt 0.0625 0
vt 0 0
vt 0.1875 0.375
vt 0.25 0.375
vt 0.25 0
vt 0.1875 0
vt 0.125 0.375
vt 0.1875 0.375
vt 0.1875 0
vt 0.125 0
vt 0.125 0.375
vt 0.0625 0.375
vt 0.0625 0.5
vt 0.125 0.5
vt 0.1875 0.5
vt 0.125 0.5
vt 0.125 0.375
vt 0.1875 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 44/124/31 47/123/31 45/122/31 42/121/31
f 43/128/32 44/127/32 42/126/32 41/125/32
f 48/132/33 43/131/33 41/130/33 46/129/33
f 47/136/34 48/135/34 46/134/34 45/133/34
f 46/140/35 41/139/35 42/138/35 45/137/35
f 47/144/36 44/143/36 43/142/36 48/141/36
o LeftLeg
v 0.006249999999999978 0.75 0.125
v 0.006249999999999978 0.75 -0.125
v 0.006249999999999978 0 0.125
v 0.006249999999999978 0 -0.125
v -0.24375000000000002 0.75 -0.125
v -0.24375000000000002 0.75 0.125
v -0.24375000000000002 0 -0.125
v -0.24375000000000002 0 0.125
vt 0.125 0.375
vt 0.0625 0.375
vt 0.0625 0
vt 0.125 0
vt 0.1875 0.375
vt 0.125 0.375
vt 0.125 0
vt 0.1875 0
vt 0.25 0.375
vt 0.1875 0.375
vt 0.1875 0
vt 0.25 0
vt 0.0625 0.375
vt 0 0.375
vt 0 0
vt 0.0625 0
vt 0.0625 0.375
vt 0.125 0.375
vt 0.125 0.5
vt 0.0625 0.5
vt 0.125 0.5
vt 0.1875 0.5
vt 0.1875 0.375
vt 0.125 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24
f 52/148/37 55/147/37 53/146/37 50/145/37
f 51/152/38 52/151/38 50/150/38 49/149/38
f 56/156/39 51/155/39 49/154/39 54/153/39
f 55/160/40 56/159/40 54/158/40 53/157/40
f 54/164/41 49/163/41 50/162/41 53/161/41
f 55/168/42 52/167/42 51/166/42 56/165/42

View file

@ -1,3 +0,0 @@
import * as externalModels from './exportedModels'
export { externalModels }

Some files were not shown because too many files have changed in this diff Show more