diff --git a/app-dev.html b/app-dev.html index 529750d..155fc30 100755 --- a/app-dev.html +++ b/app-dev.html @@ -10,8 +10,5 @@ - diff --git a/app-prod.html b/app-prod.html index c46e5cc..5171e85 100755 --- a/app-prod.html +++ b/app-prod.html @@ -10,8 +10,5 @@ - diff --git a/assets/blocks.webp b/assets/blocks.webp deleted file mode 100644 index c5ecda3..0000000 Binary files a/assets/blocks.webp and /dev/null differ diff --git a/build.js b/build.js index e7c7881..b12ba35 100644 --- a/build.js +++ b/build.js @@ -48,14 +48,14 @@ async function lang_generate(lang) { return output; } -const env_set = async (version, debug, lang, api, api_download) => Promise.all([ +const env_set = async (version, debug, lang, api, api_data) => Promise.all([ writeFile( './src/etc/env.js', `export const VERSION = '${version}'; export const DEBUG = ${debug}; export const LANG = '${lang}'; export const API = '${api}'; -export const API_DOWNLOAD = '${api_download}'; +export const API_DATA = '${api_data}'; `, 'utf8' ), @@ -92,7 +92,7 @@ async function build_js(lang) { false, lang, prod ? '/api/minicraft/' : '//l3p3.de/api/minicraft/', - prod ? '/static/minicraft/worlds/' : '//l3p3.de/static/minicraft/worlds/' + prod ? '/static/minicraft/' : '//l3p3.de/static/minicraft/' ); console.log('js pass 1...'); @@ -170,7 +170,7 @@ try { for (const lang of languages) await build_js(lang); } finally { - await env_set('dev', true, 'en', '//l3p3.de/api/minicraft/', '//l3p3.de/static/minicraft/worlds/'); + await env_set('dev', true, 'en', '//l3p3.de/api/minicraft/', '//l3p3.de/static/minicraft/'); } console.log('done.'); diff --git a/locales/de.csv b/locales/de.csv index 9d97735..220182e 100644 --- a/locales/de.csv +++ b/locales/de.csv @@ -1,6 +1,7 @@ amount: Anzahl ask_world_delete_1: Welt " ask_world_delete_2: " wirklich löschen? +back: Zurück back_to_game: Zurück zum Spiel change_world_name: Welt-Namen ändern chunk_regenerated: Chunk neu generiert. @@ -78,7 +79,6 @@ settings: Einstellungen show_world_settings: Aktionen/Einstellungen zur Welt anzeigen spawn_updated: Startpunkt aktualisiert. surfaces_colored: Einfarbig -surfaces_textured: Texturiert surfaces: Oberflächen teleported_to_spawn: Zum Startpunkt teleportiert. teleported_to: Teleportiert zu diff --git a/locales/en.csv b/locales/en.csv index 2458f82..ff6cd18 100644 --- a/locales/en.csv +++ b/locales/en.csv @@ -1,6 +1,7 @@ amount: Amount ask_world_delete_1: Really delete world " ask_world_delete_2: "? +back: Back back_to_game: Back to game change_world_name: Change world name chunk_regenerated: Chunk regenerated. @@ -78,7 +79,6 @@ settings: Settings show_world_settings: Show world settings spawn_updated: Spawn updated. surfaces_colored: Plain -surfaces_textured: Textured surfaces: Surfaces teleported_to_spawn: Teleported to spawn. teleported_to: Teleported to diff --git a/locales/ru.csv b/locales/ru.csv index 3e7e989..b29c711 100644 --- a/locales/ru.csv +++ b/locales/ru.csv @@ -1,6 +1,7 @@ amount: Число ask_world_delete_1: Мир " ask_world_delete_2: " точно удалить? +back: Вернуться back_to_game: Вернуться в игру change_world_name: Изменит имя миру chunk_regenerated: Чанки перезагружается @@ -78,7 +79,6 @@ settings: Настройки show_world_settings: Акции/Показать настройки мира spawn_updated: Началная точка обновлена surfaces_colored: Одноцветный -surfaces_textured: С текстурами surfaces: Поверхность teleported_to_spawn: Переместится к стартовой точки teleported_to: Переместится к diff --git a/locales/tr.csv b/locales/tr.csv index 1f5f6d7..e76193a 100644 --- a/locales/tr.csv +++ b/locales/tr.csv @@ -1,6 +1,7 @@ amount: Miktar ask_world_delete_1: " ask_world_delete_2: " isimli dünya gerçekten silinsin mi? +back: Geri back_to_game: Oyuna geri dön change_world_name: Dünyayı değiştir chunk_regenerated: Parça yenilendi. @@ -78,7 +79,6 @@ settings: Ayarlar show_world_settings: Dünya ayarlarını göster spawn_updated: Spawn güncellendi. surfaces_colored: Sade -surfaces_textured: Dokulu surfaces: Yüzeyler teleported_to_spawn: Spawn\'a ışınlandı. teleported_to: Şuraya ışınlandı: diff --git a/locales/uk.csv b/locales/uk.csv index 0a17e8f..ab9d074 100644 --- a/locales/uk.csv +++ b/locales/uk.csv @@ -1,6 +1,7 @@ amount: Номер ask_world_delete_1: Світ " ask_world_delete_2: " дійсно видалити? +back: Повернуться back_to_game: Повернуться в гру change_world_name: Змінити ім\'я світу chunk_regenerated: Участки відновлені. @@ -77,8 +78,7 @@ selection_secondary: Друга крапка вибору settings: Налаштування show_world_settings: Акції/Показати налаштування світу. spawn_updated: Початкова крапка оновлена. -surfaces_colored: Однотонні -surfaces_textured: Текстурований +surfaces_colored: Однотонні surfaces: Поверхности teleported_to_spawn: Перемістився до початкової. teleported_to: Переміститися до diff --git a/package.json b/package.json index 173150a..6fb9799 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minicraft", - "version": "0.10.7", + "version": "0.11.0", "description": "voxel-based 3d game, written in javascript", "homepage": "https://l3p3.de/minicraft", "repository": { diff --git a/src/app.css b/src/app.css index 6ea4ed1..4a1ef40 100644 --- a/src/app.css +++ b/src/app.css @@ -82,7 +82,6 @@ button { } .game .bitmap { background-size: 100% auto; - transform: scaleY(-1); } .game > .diagnostics { position: absolute; @@ -266,9 +265,11 @@ button { margin: 1rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); - grid-template-rows: 3rem; gap: 1rem; } +.game .menu > .settings > * { + min-height: 2rem; +} .game .menu.inventory, .game .menu.advanced { display: flex; align-items: center; diff --git a/src/app.js b/src/app.js index 744f1b1..ccd5d36 100755 --- a/src/app.js +++ b/src/app.js @@ -38,6 +38,9 @@ import { game_key, game_save, } from './game/m_game.js'; +import { + tiles_set, +} from './game/m_renderer.js'; import App from './game/c_app.js'; @@ -121,6 +124,7 @@ function Root() { addEventListener_('touchstart', handler_touch, true); } }, [flag_touch]); + hook_effect(tiles_set, [state.config.textures]); hook_dom('', { onkeydown: handler_key, diff --git a/src/etc/constants.js b/src/etc/constants.js index da3c872..1856714 100644 --- a/src/etc/constants.js +++ b/src/etc/constants.js @@ -5,8 +5,9 @@ import { locale_item_labels, } from './locale.js'; -export const APP_VIEW_WORLDS = 0; -export const APP_VIEW_GAME = 1; +export const APP_VIEW_GAME = 0; +export const APP_VIEW_SETTINGS = 1; +export const APP_VIEW_WORLDS = 2; export const CHUNK_WIDTH_L2 = 4; export const CHUNK_WIDTH = 1 << CHUNK_WIDTH_L2; diff --git a/src/etc/env.js b/src/etc/env.js index fa95b70..3b06346 100644 --- a/src/etc/env.js +++ b/src/etc/env.js @@ -2,4 +2,4 @@ export const VERSION = 'dev'; export const DEBUG = true; export const LANG = 'en'; export const API = '//l3p3.de/api/minicraft/'; -export const API_DOWNLOAD = '//l3p3.de/static/minicraft/worlds/'; +export const API_DATA = '//l3p3.de/static/minicraft/'; diff --git a/src/etc/helpers.js b/src/etc/helpers.js index 792f2a4..495e0df 100644 --- a/src/etc/helpers.js +++ b/src/etc/helpers.js @@ -33,8 +33,11 @@ export const localStorage_getItem = key => localStorage_.getItem(key); export const localStorage_setItem = localStorage_.setItem.bind(localStorage_); export const localStorage_removeItem = localStorage_.removeItem.bind(localStorage_); export const indexedDB_ = window_.indexedDB; +export const fetch_ = fetch; +export const Error_ = msg => new Error(msg); export const Uint8Array_ = Uint8Array; export const Uint32Array_ = Uint32Array; +export const Set_ = Set; export const Map_ = Map; export const Number_ = Number; export const Object_ = Object; diff --git a/src/etc/locale.js b/src/etc/locale.js index 9fe84b2..0643189 100644 --- a/src/etc/locale.js +++ b/src/etc/locale.js @@ -1,6 +1,7 @@ export const locale_amount = 'Amount'; export const locale_ask_world_delete_1 = 'Really delete world "'; export const locale_ask_world_delete_2 = '"?'; +export const locale_back = 'Back'; export const locale_back_to_game = 'Back to game'; export const locale_change_world_name = 'Change world name'; export const locale_chunk_regenerated = 'Chunk regenerated.'; @@ -78,7 +79,6 @@ export const locale_settings = 'Settings'; export const locale_show_world_settings = 'Show world settings'; export const locale_spawn_updated = 'Spawn updated.'; export const locale_surfaces_colored = 'Plain'; -export const locale_surfaces_textured = 'Textured'; export const locale_surfaces = 'Surfaces'; export const locale_teleported_to_spawn = 'Teleported to spawn.'; export const locale_teleported_to = 'Teleported to'; diff --git a/src/etc/state.js b/src/etc/state.js index aa617f6..9633079 100644 --- a/src/etc/state.js +++ b/src/etc/state.js @@ -6,6 +6,7 @@ import { JSON_parse, JSON_stringify, Object_keys, + Set_, localStorage_, localStorage_getItem, localStorage_removeItem, @@ -25,7 +26,7 @@ if ( config_loaded['worlds'] && config_loaded['version'].startsWith('0.9.') ) { - const prefixes_keep = new Set( + const prefixes_keep = new Set_( config_loaded['worlds'] .map(world => 'minicraft.world.' + world.id) ); @@ -42,11 +43,11 @@ export const reducers = { init: () => { let needs_save = false; const config = { - flag_textures: true, flag_touch: false, // not saved pixel_grouping: 1, mouse_sensitivity: 3, resolution_scaling: 4, + textures: 1, view_angle: 120, view_distance: 64, world_last: 0, @@ -54,13 +55,8 @@ export const reducers = { worlds: [], }; if (config_loaded) { - let tmp = config_loaded['flag_textures']; + let tmp = config_loaded['pixel_grouping']; if (tmp != null) { - config.flag_textures = tmp; - } - if (( - tmp = config_loaded['pixel_grouping'] - ) != null) { config.pixel_grouping = tmp; } if (( @@ -69,6 +65,18 @@ export const reducers = { config.mouse_sensitivity = tmp; } config.resolution_scaling = config_loaded['resolution_scaling']; + if (( + tmp = config_loaded['textures'] + ) != null) { + config.textures = tmp; + } + else { + config.textures = ( + config_loaded['flag_textures'] + ? 1 + : 0 + ); + } config.view_angle = config_loaded['view_angle']; config.view_distance = config_loaded['view_distance']; if (( @@ -117,10 +125,10 @@ export const reducers = { if (config === state.config_saved) return state; localStorage_setItem('minicraft.config', JSON_stringify({ 'version': VERSION, - 'flag_textures': config.flag_textures, 'pixel_grouping': config.pixel_grouping, 'mouse_sensitivity': config.mouse_sensitivity, 'resolution_scaling': config.resolution_scaling, + 'textures': config.textures, 'view_angle': config.view_angle, 'view_distance': config.view_distance, 'world_last': config.world_last, diff --git a/src/etc/textures.js b/src/etc/textures.js deleted file mode 120000 index 2cecc10..0000000 --- a/src/etc/textures.js +++ /dev/null @@ -1 +0,0 @@ -../../tools/textures/textures.js \ No newline at end of file diff --git a/src/etc/textures.js b/src/etc/textures.js new file mode 100644 index 0000000..2d10f04 --- /dev/null +++ b/src/etc/textures.js @@ -0,0 +1,30 @@ +export const TILES_COUNT = 26; +export const TILES_RESOLUTION_LOG2 = 4; +export const TILES_RESOLUTION = 1 << TILES_RESOLUTION_LOG2; + +export const TILE_STONE = 0; +export const TILE_GRASS_TOP = 1; +export const TILE_DIRT = 2; +export const TILE_COBBLE = 3; +export const TILE_PLANKS = 4; +export const TILE_BEDROCK = 5; +export const TILE_LOG_SIDE = 6; +export const TILE_LEAVES = 7; +export const TILE_BRICKS = 8; +export const TILE_WOOL = 9; +export const TILE_SAND = 10; +export const TILE_GRAVEL = 11; +export const TILE_GLASS = 12; +export const TILE_BOOKSHELF = 13; +export const TILE_OBSIDIAN = 14; +export const TILE_STONE_BRICKS = 15; +export const TILE_SANDSTONE = 16; +export const TILE_LAPIS_BLOCK = 17; +export const TILE_IRON_BLOCK = 18; +export const TILE_GOLD_BLOCK = 19; +export const TILE_DIAMOND_BLOCK = 20; +export const TILE_EMERALD_BLOCK = 21; +export const TILE_REDSTONE_BLOCK = 22; +export const TILE_QUARTZ_BLOCK = 23; +export const TILE_GRASS_SIDE = 24; +export const TILE_LOG_TOP = 25; diff --git a/src/externs.js b/src/externs.js index 58f296e..e583693 100644 --- a/src/externs.js +++ b/src/externs.js @@ -262,11 +262,6 @@ var onmousedown; */ var onmouseup; -/** - @type {string} -*/ -var ASSETS; - /** @type {boolean} */ @@ -327,6 +322,15 @@ var TYPE_WORLD_API; */ var TYPE_CHAT_API; +/** + @typedef {{ + id: number, + label: string, + owner: string, + }} +*/ +var TYPE_TEXTURES_ITEM; + /** @typedef {{ account: { diff --git a/src/game/c_app.js b/src/game/c_app.js index e80126c..3b49caa 100644 --- a/src/game/c_app.js +++ b/src/game/c_app.js @@ -6,11 +6,13 @@ import { import { APP_VIEW_GAME, + APP_VIEW_SETTINGS, APP_VIEW_WORLDS, } from '../etc/constants.js'; import Game from './c_game.js'; import MenuStart from './c_menu_start.js'; +import Settings from './c_settings.js'; export default function App({ account, @@ -23,13 +25,23 @@ export default function App({ const frame = hook_dom('div[className=game]'); return [ - view === APP_VIEW_WORLDS && + ( + view === APP_VIEW_WORLDS || + view === APP_VIEW_SETTINGS + ) && node(MenuStart, { account, actions, config, view_set, }), + view === APP_VIEW_SETTINGS && + node(Settings, { + actions, + config, + game: null, + view_set, + }), view === APP_VIEW_GAME && node(Game, { account, diff --git a/src/game/c_bar.js b/src/game/c_bar.js index 10e9eb5..4931ec1 100644 --- a/src/game/c_bar.js +++ b/src/game/c_bar.js @@ -13,14 +13,11 @@ import { Math_min, } from '../etc/helpers.js'; -import { - tiles_data, -} from './m_renderer.js'; - import Stack from './c_stack.js'; export default function Bar({ player, + textures_id, time_now, }) { hook_dom('div[className=bar]', { @@ -44,7 +41,6 @@ export default function Bar({ const {gamemode} = player; return ( - tiles_data && player.inventory .slice(0, PLAYER_SLOTS) .map(({content}, index) => ( @@ -62,6 +58,7 @@ export default function Bar({ data: content.data, gamemode, id: content.id, + textures_id, }), ]) )) diff --git a/src/game/c_game.js b/src/game/c_game.js index 0c21c6d..0d32011 100644 --- a/src/game/c_game.js +++ b/src/game/c_game.js @@ -187,6 +187,9 @@ export default function Game({ hook_rerender(); + const textures_id_ref = hook_static({val: 1}); + if (config.textures) textures_id_ref.val = config.textures; + return [ node_dom('canvas', { R: hook_static(canvas_element => ( @@ -214,11 +217,13 @@ export default function Game({ model.player.gamemode !== GAMEMODE_SPECTATOR && node(Bar, { player: model.player, + textures_id: textures_id_ref.val, time_now, }), model.menu === MENU_INVENTORY && node(Inventory, { game: model, + textures_id: textures_id_ref.val, time_now, }), model.menu === MENU_SETTINGS && diff --git a/src/game/c_inventory.js b/src/game/c_inventory.js index 47481d4..658f37b 100644 --- a/src/game/c_inventory.js +++ b/src/game/c_inventory.js @@ -22,9 +22,6 @@ import { import { game_menu_close, } from './m_game.js'; -import { - tiles_data, -} from './m_renderer.js'; import { slot_create, slot_transfer, @@ -37,6 +34,7 @@ import Stack from './c_stack.js'; function Palette({ slot_hand, + textures_id, }) { hook_dom('div[className=grid]', { onclick: ({ @@ -69,6 +67,7 @@ function Palette({ data: null, gamemode: GAMEMODE_CREATIVE, id, + textures_id, }), ]) )) @@ -77,10 +76,11 @@ function Palette({ export default function Inventory({ game, + textures_id, }) { const slot_hand = hook_memo(() => slot_create(null)); - const gamemode = game.player.gamemode; + const {gamemode} = game.player; hook_dom('div[className=menu overlay inventory]', hook_memo(() => ({ onclick: ({ @@ -137,12 +137,13 @@ export default function Inventory({ }, }))); - return tiles_data && [ + return [ node_dom('div[className=window]', null, [ node_dom(`h2[innerText=${locale_inventory}]`), gamemode === GAMEMODE_CREATIVE && node(Palette, { slot_hand, + textures_id, }), node_dom('div[className=grid]', null, game.player.inventory.map(({content}, index) => @@ -160,6 +161,7 @@ export default function Inventory({ data: content.data, gamemode, id: content.id, + textures_id, }), ]) ) @@ -176,6 +178,7 @@ export default function Inventory({ data: slot_hand.content.data, gamemode: GAMEMODE_SURVIVAL, id: slot_hand.content.id, + textures_id, }), ]), ]; diff --git a/src/game/c_menu_start.js b/src/game/c_menu_start.js index 524ca6c..0086d24 100644 --- a/src/game/c_menu_start.js +++ b/src/game/c_menu_start.js @@ -13,19 +13,22 @@ import { import { APP_VIEW_GAME, + APP_VIEW_SETTINGS, } from '../etc/constants.js'; import { API, - API_DOWNLOAD, + API_DATA, VERSION, } from '../etc/env.js'; import { Date_now, + Error_, JSON_stringify, Math_max, Math_min, Promise_, datify, + fetch_, } from '../etc/helpers.js'; import { locale_ask_world_delete_1, @@ -67,12 +70,12 @@ import { locale_only_local, locale_open, locale_owner, - locale_project_page, locale_public, locale_publish_world, locale_refresh, locale_reload_list, locale_rename, + locale_settings, locale_show_world_settings, locale_transfer, locale_unpublish_world, @@ -146,6 +149,11 @@ function WorldItem({ ]; } +const headers_json_post = { + method: 'POST', + headers: {'Content-Type': 'application/json'}, +}; + export default function MenuStart({ account, actions, @@ -173,8 +181,8 @@ export default function MenuStart({ async () => { try { const initial = !world_list_remote_ref.value && !refreshes; - const response = await fetch(API + `world?what=${initial ? 'initial' : 'meta_all'}`); - if (!response.ok) throw new Error(locale_error_connection); + const response = await fetch_(`${API}world?what=${initial ? 'initial' : 'meta_all'}`); + if (!response.ok) throw Error_(locale_error_connection); const json = await response.json(); if (!initial) return /** @type {!Array} */ (json); const json_initial = /** @type {TYPE_RESPONSE_INITIAL} */ (json); @@ -307,10 +315,9 @@ export default function MenuStart({ let cancelled = false; const world_busy = world_list.find(world => world.id === world_busy_id); - const prefix = `minicraft.world.${world_busy_id}:`; if (world_busy.local < world_busy.remote) { // download - fetch(API_DOWNLOAD + `${world_busy.hash}.json`) + fetch_(`${API_DATA}worlds/${world_busy.hash}.json`) .then(response => response.json()) .then(json => { if (cancelled) return; @@ -342,21 +349,19 @@ export default function MenuStart({ }); return; } - const prefix_length = prefix.length; let id_new = world_busy_id; ( // register new world? world_busy.remote === 1 - ? fetch(API + 'world', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, + ? fetch_(API + 'world', { + ...headers_json_post, body: JSON_stringify({ what: 'meta', label: world_busy.label, }), }) .then(response => { - if (!response.ok) throw new Error( + if (!response.ok) throw Error_( response.status === 403 ? locale_error_no_permission_logged_in : locale_error_connection @@ -374,9 +379,8 @@ export default function MenuStart({ }) .then(json => { if (cancelled) throw null; - return fetch(API + 'world', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, + return fetch_(API + 'world', { + ...headers_json_post, body: JSON_stringify({ what: 'data', world: id_new, @@ -385,7 +389,7 @@ export default function MenuStart({ }); }) .then(response => { - if (!response.ok) throw new Error( + if (!response.ok) throw Error_( response.status === 403 ? locale_error_no_permission_logged_in : locale_error_connection @@ -519,9 +523,9 @@ export default function MenuStart({ }); } }), - node_dom(`button[innerText=${locale_project_page}]`, { + node_dom(`button[innerText=${locale_settings}]`, { onclick: () => { - open('//github.com/L3P3/minicraft'); + view_set(APP_VIEW_SETTINGS); }, }), ]), @@ -584,7 +588,7 @@ export default function MenuStart({ } if (world_selected.remote) { busy_set(true); - fetch(API + 'world', { + fetch_(API + 'world', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON_stringify({ @@ -594,7 +598,7 @@ export default function MenuStart({ }), }) .then(response => { - if (!response.ok) throw new Error( + if (!response.ok) throw Error_( response.status === 403 ? locale_error_no_permission_logged_in : locale_error_connection @@ -636,7 +640,7 @@ export default function MenuStart({ } else { busy_set(true); - fetch(API + 'world', { + fetch_(API + 'world', { method: 'DELETE', headers: {'Content-Type': 'application/json'}, body: JSON_stringify({ @@ -645,7 +649,7 @@ export default function MenuStart({ }), }) .then(response => { - if (!response.ok) throw new Error( + if (!response.ok) throw Error_( response.status === 403 ? locale_error_no_permission_logged_in : locale_error_connection @@ -686,7 +690,7 @@ export default function MenuStart({ }`, onclick: () => { busy_set(true); - fetch(API + 'world', { + fetch_(API + 'world', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON_stringify({ @@ -696,7 +700,7 @@ export default function MenuStart({ }), }) .then(response => { - if (!response.ok) throw new Error( + if (!response.ok) throw Error_( response.status === 403 ? locale_error_no_permission_logged_in : locale_error_connection diff --git a/src/game/c_settings.js b/src/game/c_settings.js index e1325b7..ed305f8 100644 --- a/src/game/c_settings.js +++ b/src/game/c_settings.js @@ -1,21 +1,31 @@ import { + hook_async, hook_dom, hook_effect, - hook_static, + hook_state, node_dom, + node_map, } from '../etc/lui.js'; import { APP_VIEW_WORLDS, } from '../etc/constants.js'; import { + Promise_, + fetch_, +} from '../etc/helpers.js'; +import { + API_DATA, +} from '../etc/env.js'; +import { + locale_back, locale_back_to_game, locale_mouse_sensitivity, locale_pixel_grouping, + locale_project_page, locale_resolution, locale_settings, locale_surfaces_colored, - locale_surfaces_textured, locale_surfaces, locale_view_angle, locale_view_distance, @@ -27,103 +37,159 @@ import { game_save, } from './m_game.js'; +function TextureItem({ + /** TYPE_TEXTURES_ITEM */ I, + config_set, + current, +}) { + hook_dom('button', { + disabled: I.id === current, + innerText: `${I.label} (${I.owner})`, + onclick: () => { + config_set({ + textures: I.id, + }); + }, + }); + return null; +} + export default function Settings({ actions: { - config_reduce, config_set, }, config, game, view_set, }) { - hook_effect(() => ( + game && hook_effect(() => ( game_save(game) )); + + const [textures_opened, textures_opened_set] = hook_state(false); + const textures = hook_async(() => ( + !textures_opened ? Promise_.resolve(null) : + fetch_(`${API_DATA}textures.json`) + .then(response => ( + response.ok + ? response.json() + : null + )) + .catch(e => null) + ), [textures_opened], null); hook_dom('div[className=menu overlay]'); return [ - node_dom(`h1[innerText=${locale_settings}]`), - hook_static(node_dom('center', null, [ - node_dom(`button[innerText=${locale_back_to_game}]`, { + node_dom('h1', { + innerText: ( + textures_opened + ? locale_surfaces + : locale_settings + ), + }), + node_dom('center', null, [ + node_dom('button', { + innerText: ( + game && !textures_opened + ? locale_back_to_game + : locale_back + ), onclick: () => { - game_menu_close(game); + if (textures_opened) textures_opened_set(false); + else if (game) game_menu_close(game); + else view_set(APP_VIEW_WORLDS); }, }), - ])), + ]), + !textures_opened && node_dom('div[className=settings]', null, [ - node_dom('button', { - innerText: ( - locale_surfaces + ':\n' + - ( - config.flag_textures - ? locale_surfaces_textured - : locale_surfaces_colored - ) - ), - onclick: hook_static(() => ( - config_reduce(config => ({ - flag_textures: !config.flag_textures, - })) - )), + node_dom(`button[innerText=${locale_surfaces}...]`, { + onclick: () => { + textures_opened_set(true); + }, }), node_dom(`label[innerText=${locale_resolution}:]`, null, [ node_dom('input[type=range][min=1][max=100][step=1]', { value: 101 - config.resolution_scaling, - onchange: hook_static(event => ( + onchange: event => ( config_set({ resolution_scaling: 101 - Number(event.target.value), }) - )), + ), }), ]), node_dom(`label[innerText=${locale_view_angle}:]`, null, [ node_dom('input[type=range][min=1][max=180][step=1]', { value: config.view_angle, - onchange: hook_static(event => ( + onchange: event => ( config_set({ view_angle: Number(event.target.value), }) - )), + ), }), ]), node_dom(`label[innerText=${locale_view_distance}:]`, null, [ node_dom('input[type=range][min=1][max=128][step=1]', { value: config.view_distance, - onchange: hook_static(event => ( + onchange: event => ( config_set({ view_distance: Number(event.target.value), }) - )), + ), }), ]), node_dom(`label[innerText=${locale_pixel_grouping}:]`, null, [ node_dom('input[type=range][min=1][max=6][step=1]', { value: config.pixel_grouping, - onchange: hook_static(event => ( + onchange: event => ( config_set({ pixel_grouping: Number(event.target.value), }) - )), + ), }), ]), node_dom(`label[innerText=${locale_mouse_sensitivity}:]`, null, [ node_dom('input[type=range][min=1][max=15][step=1]', { value: config.mouse_sensitivity, - onchange: hook_static(event => ( + onchange: event => ( config_set({ mouse_sensitivity: Number(event.target.value), }) - )), + ), }), ]), + node_dom(`button[innerText=${locale_project_page}]`, { + onclick: () => { + open('//github.com/L3P3/minicraft'); + }, + }), ]), - hook_static(node_dom('center', null, [ + game && + !textures_opened && + node_dom('center', null, [ node_dom(`button[innerText=${locale_world_leave}]`, { onclick: () => { view_set(APP_VIEW_WORLDS); }, }), - ])), + ]), + + textures_opened && + node_dom('div[className=settings]', null, [ + node_dom(`button[innerText=${locale_surfaces_colored}]`, { + disabled: config.textures === 0, + onclick: () => { + config_set({ + textures: 0, + }); + }, + }), + textures && + node_map(TextureItem, textures, { + config_set, + current: config.textures, + }), + ]), ]; } diff --git a/src/game/c_stack.js b/src/game/c_stack.js index 7c425bb..ebc0f90 100644 --- a/src/game/c_stack.js +++ b/src/game/c_stack.js @@ -10,14 +10,17 @@ import { ITEM_LABELS, } from '../etc/constants.js'; import { + API_DATA, LANG, } from '../etc/env.js'; const Bitmap = ({ id, + textures_id, }) => ( - hook_dom('#tile', { + hook_dom('div[className=bitmap]', { S: { + backgroundImage: `url(${API_DATA}textures/${textures_id}.png)`, backgroundPositionY: `-${(id - 1) * 2}rem`, }, }), @@ -29,6 +32,7 @@ export default function Stack({ data, gamemode, id, + textures_id, }) { hook_dom('div[className=stack]', { title: ( @@ -41,6 +45,7 @@ export default function Stack({ return [ node(Bitmap, { id, + textures_id, }), amount !== 1 && node_dom('div[className=amount]', { diff --git a/src/game/m_game.js b/src/game/m_game.js index 1008466..a0ac6ef 100644 --- a/src/game/m_game.js +++ b/src/game/m_game.js @@ -58,6 +58,7 @@ import { clearInterval_, clearTimeout_, Date_now, + fetch_, flag_chromium, JSON_stringify, Math_ceil, @@ -66,6 +67,7 @@ import { Math_max, Math_PI, Number_, + Set_, setInterval_, setTimeout_, } from '../etc/helpers.js'; @@ -147,7 +149,7 @@ export const game_create = (actions, frame_element, config, account) => { flag_hud: true, frame_element, frame_last: 0, - keys_active: new Set, + keys_active: new Set_, keys_active_check: '', menu: MENU_NONE, messages: [], @@ -885,7 +887,7 @@ const game_poll = (model, msg) => ( clearTimeout_(model.poll_timeout), ( msg - ? fetch(API_CHAT, { + ? fetch_(API_CHAT, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -894,7 +896,7 @@ const game_poll = (model, msg) => ( msg, })), }) - : fetch(API_CHAT) + : fetch_(API_CHAT) ) .then(res => { if (!res.ok) return; diff --git a/src/game/m_renderer.js b/src/game/m_renderer.js index 1be6ac1..2070aad 100644 --- a/src/game/m_renderer.js +++ b/src/game/m_renderer.js @@ -1,7 +1,3 @@ -import { - dom_define, -} from '../etc/lui.js'; - import { BLOCK_COLORS, BLOCK_TYPE_AIR, @@ -16,6 +12,7 @@ import { SKY_COLOR, } from '../etc/constants.js'; import { + API_DATA, VERSION, } from '../etc/env.js'; import { @@ -32,6 +29,7 @@ import { number_padStart2, number_square, number_toFixed2, + Set_, setInterval_, Uint32Array_, } from '../etc/helpers.js'; @@ -52,32 +50,49 @@ import { } from '../etc/textures.js'; // parse png -export let tiles_data = null; -let tiles_data_onload = null; -let tiles_image = new Image(); -tiles_image.crossOrigin = 'anonymous'; -tiles_image.onload = () => { - const canvas_temp = document_.createElement('canvas'); - canvas_temp.width = 1 << TILES_RESOLUTION_LOG2; - canvas_temp.height = TILES_COUNT << TILES_RESOLUTION_LOG2; - const context = canvas_temp.getContext('2d'); - context.drawImage(tiles_image, 0, 0); - dom_define('tile', 'div[className=bitmap]', { - S: { - backgroundImage: `url(${canvas_temp.toDataURL()})`, - }, - }); - tiles_data = new Uint32Array_( - context.getImageData( - 0, 0, - 1 << TILES_RESOLUTION_LOG2, - TILES_COUNT << TILES_RESOLUTION_LOG2 - ).data.buffer - ); - tiles_data_onload && tiles_data_onload(); - tiles_image = tiles_data_onload = null; +let tiles_data = null; +let tiles_image_loading = null; +const renderer_instances = new Set_; + +export const tiles_set = id => { + if (!id) { + tiles_data = null; + } + else { + const tiles_image = tiles_image_loading = new Image(); + if (VERSION === 'dev') { + tiles_image.crossOrigin = 'anonymous'; + } + tiles_image.onload = () => { + if (tiles_image_loading !== tiles_image) return; + const canvas_temp = document_.createElement('canvas'); + canvas_temp.width = TILES_RESOLUTION; + canvas_temp.height = TILES_COUNT << TILES_RESOLUTION_LOG2; + const context = canvas_temp.getContext('2d'); + // draw image flipped + context.scale(1, -1); + for (let tile = 0; tile < TILES_COUNT; ++tile) { + context.drawImage( + tiles_image, + 0, tile << TILES_RESOLUTION_LOG2, + TILES_RESOLUTION, TILES_RESOLUTION, + 0, -(tile << TILES_RESOLUTION_LOG2) - TILES_RESOLUTION, + TILES_RESOLUTION, TILES_RESOLUTION + ); + } + tiles_data = new Uint32Array_( + context.getImageData( + 0, 0, + TILES_RESOLUTION, + TILES_COUNT << TILES_RESOLUTION_LOG2 + ).data.buffer + ); + for (const model of renderer_instances) model.flag_dirty = true; + tiles_image_loading = null; + } + tiles_image.src = `${API_DATA}textures/${id}.png`; + } } -tiles_image.src = ASSETS + 'blocks.webp'; export const renderer_create = (game, canvas_element) => { const model = { @@ -98,14 +113,13 @@ export const renderer_create = (game, canvas_element) => { ), 1e3), game, }; - if (!tiles_data) { - tiles_data_onload = () => model.flag_dirty = true; - } + renderer_instances.add(model); renderer_canvas_init(model); return model; } export const renderer_destroy = model => ( + renderer_instances.delete(model), clearInterval_(model.fps_interval) ); @@ -153,7 +167,7 @@ export const renderer_render = (model, now) => { blocks, size_l2, } = world; - const flag_textures = config.flag_textures && tiles_data !== null; + const flag_textures = tiles_data !== null; const resolution_x_1d = 1 / resolution_x; const resolution_y_1d = 1 / resolution_y; const resolution_x_h = resolution_x >> 1; diff --git a/tools/textures/blocks.json b/tools/textures/blocks.json deleted file mode 100644 index 60de2b8..0000000 --- a/tools/textures/blocks.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "resolution": 16, - "tiles": [ - "stone", - "grass_top", - "dirt", - "cobble", - "planks", - "bedrock", - "log_side", - "leaves", - "bricks", - "wool", - "sand", - "gravel", - "glass", - "bookshelf", - "obsidian", - "stone_bricks", - "sandstone", - "lapis_block", - "iron_block", - "gold_block", - "diamond_block", - "emerald_block", - "redstone_block", - "quartz_block", - - "grass_side", - "log_top" - ] -} diff --git a/tools/textures/blocks.png b/tools/textures/blocks.png deleted file mode 100644 index 4e561d1..0000000 Binary files a/tools/textures/blocks.png and /dev/null differ diff --git a/tools/textures/package.json b/tools/textures/package.json deleted file mode 100644 index 6cec347..0000000 --- a/tools/textures/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "minicraft-texture-packer", - "version": "2.0.0", - "description": "generates optimized texture data for minicraft", - "main": "./packer.js", - "scripts": { - "pack": "node ./packer.js" - }, - "type": "module", - "keywords": [ - "minecraft", - "texture" - ], - "author": "L3P3 (https://l3p3.de)", - "license": "Zlib", - "devDependencies": { - "pngjs": "latest" - } -} diff --git a/tools/textures/packer.js b/tools/textures/packer.js deleted file mode 100644 index 9b2a53b..0000000 --- a/tools/textures/packer.js +++ /dev/null @@ -1,97 +0,0 @@ -import {execSync} from 'child_process'; -import { - readFileSync, - unlinkSync, - writeFileSync, -} from 'fs'; -import {PNG} from 'pngjs'; - -const input_meta = JSON.parse(readFileSync('./blocks.json', 'utf8')); -const tiles_length = input_meta.tiles.length; -const {resolution} = input_meta; -console.log('metafile read, tiles:', tiles_length); - -const input_resolution_log2 = Math.log2(resolution); -if (input_resolution_log2 % 1) - throw new Error('resolution must be in 1, 2, 4, 8...'); - -const input_data = readFileSync('blocks.png'); -console.log('input read, size:', input_data.length); - -const png_instance = PNG.sync.read(input_data); -console.log('input parsed, resolution:', png_instance.width, png_instance.height); - -if (png_instance.width !== resolution) - throw new Error('wrong input width'); -if (png_instance.height !== resolution * tiles_length) - throw new Error('wrong input height'); - -console.log('input size matches metafile'); - -const image_buffer = png_instance.data; - -const resolution_half = resolution >>> 1; -for (let texture = 0; texture < tiles_length; ++texture) { - const texture_offset = texture << (input_resolution_log2 << 1); - for (let row = 0; row < resolution_half; ++row) { - const row_offset_a = ( - texture_offset | - row << input_resolution_log2 - ); - const row_offset_b = ( - texture_offset | - (resolution - 1 - row) << input_resolution_log2 - ); - for (let column = 0; column < resolution; ++column) { - let index_a = ( - row_offset_a | - column - ) << 2; - let index_b = ( - row_offset_b | - column - ) << 2; - let tmp = image_buffer[index_a]; - image_buffer[index_a] = image_buffer[index_b]; - image_buffer[index_b] = tmp; - tmp = image_buffer[++index_a]; - image_buffer[index_a] = image_buffer[++index_b]; - image_buffer[index_b] = tmp; - tmp = image_buffer[++index_a]; - image_buffer[index_a] = image_buffer[++index_b]; - image_buffer[index_b] = tmp; - tmp = image_buffer[++index_a]; - image_buffer[index_a] = image_buffer[++index_b]; - image_buffer[index_b] = tmp; - } - } -} - -const transformed_data = PNG.sync.write(png_instance); -console.log('transformed png generated, size:', transformed_data.length); - -writeFileSync('/tmp/blocks.png', transformed_data); - -execSync('cwebp -m 6 -near_lossless 40 -sharp_yuv -segments 1 -q 100 /tmp/blocks.png -o ../../assets/blocks.webp'); -console.log('blocks.webp written'); - -unlinkSync('/tmp/blocks.png'); - -let output_text = `// This file was automatically generated by packer.js - -`; - -let tiles_index = 0; -for (const tile of input_meta.tiles) { - output_text += 'export const TILE_' + tile.toUpperCase() + ' = ' + tiles_index + ';\n'; - ++tiles_index; -} - -output_text += ` -export const TILES_COUNT = ${tiles_length}; -export const TILES_RESOLUTION = ${resolution}; -export const TILES_RESOLUTION_LOG2 = ${input_resolution_log2}; -`; - -writeFileSync('textures.js', output_text, 'utf8'); -console.log('textures.js written'); diff --git a/tools/textures/textures.js b/tools/textures/textures.js deleted file mode 100644 index e10297a..0000000 --- a/tools/textures/textures.js +++ /dev/null @@ -1,32 +0,0 @@ -// This file was automatically generated by packer.js - -export const TILE_STONE = 0; -export const TILE_GRASS_TOP = 1; -export const TILE_DIRT = 2; -export const TILE_COBBLE = 3; -export const TILE_PLANKS = 4; -export const TILE_BEDROCK = 5; -export const TILE_LOG_SIDE = 6; -export const TILE_LEAVES = 7; -export const TILE_BRICKS = 8; -export const TILE_WOOL = 9; -export const TILE_SAND = 10; -export const TILE_GRAVEL = 11; -export const TILE_GLASS = 12; -export const TILE_BOOKSHELF = 13; -export const TILE_OBSIDIAN = 14; -export const TILE_STONE_BRICKS = 15; -export const TILE_SANDSTONE = 16; -export const TILE_LAPIS_BLOCK = 17; -export const TILE_IRON_BLOCK = 18; -export const TILE_GOLD_BLOCK = 19; -export const TILE_DIAMOND_BLOCK = 20; -export const TILE_EMERALD_BLOCK = 21; -export const TILE_REDSTONE_BLOCK = 22; -export const TILE_QUARTZ_BLOCK = 23; -export const TILE_GRASS_SIDE = 24; -export const TILE_LOG_TOP = 25; - -export const TILES_COUNT = 26; -export const TILES_RESOLUTION = 16; -export const TILES_RESOLUTION_LOG2 = 4;