PageSourceSearch

https://mixelte.github.io/The-Legend-of-Pirate/worldEditor/out/main.js

js mixelte.github.io collected 2026-10-03 09:37:34 UTC 75,010 bytes, 2,057 lines download raw bytes

1"use strict";
2const inp_width = getInput("inp-width");
3const inp_height = getInput("inp-height");
4const inp_tilesize = getInput("inp-tilesize");
5const btn_up = getButton("btn-up");
6const btn_right = getButton("btn-right");
7const btn_down = getButton("btn-down");
8const btn_left = getButton("btn-left");
9const btn_save = getButton("btn-save");
10const inp_load = getInput("inp-load");
11const btn_new = getButton("btn-new");
12const inp_cameraSpeed = getInput("inp_cameraSpeed");
13const inp_mode_pen = getInput("inp-mode-pen");
14const inp_mode_fill = getInput("inp-mode-fill");
15const inp_mode_view = getInput("inp-mode-view");
16const inp_mode_entity = getInput("inp-mode-entity");
17const inp_mode_decor = getInput("inp-mode-decor");
18const inp_highlight_tiles = getInput("inp-highlight-tiles");
19const inp_highlight_decor = getInput("inp-highlight-decor");
20const inp_highlight_stacked = getInput("inp-highlight-stacked");
21const inp_auto_edge = getInput("inp-auto-edge");
22// const world_map = getTable("world-map")
23const div_viewport = getDiv("viewport");
24const div_palette = getDiv("palette");
25const div_fast_palette = getDiv("fast-palette");
26const div_palette_group = getDiv("palette-group");
27const div_palette_group_imgs = getDiv("palette-group-imgs");
28const canvas = getCanvas("canvas");
29const ctx = getCanvasContext(canvas);
30const localStorageKey = "WorldEditor-world-data";
31const ViewWidth = 20;
32const ViewHeight = 9;
33let TileSize = 16 * 2;
34// let TileSize = 16 * 4; // Temp
35const entity = [];
36for (const key in EntityDict)
37    entity.push(EntityDict[key]);
38const decor = [];
39for (const key in DecorDict)
40    decor.push(DecorDict[key]);
41const tileImages = {};
42const startViews_JSON = localStorage.getItem("WorldEditor-startViews");
43const startViews = startViews_JSON ? JSON.parse(startViews_JSON) : [DefaultNewView];
44let startView = 0;
45let icon_move = null;
46let icon_plus = null;
47const icon_center_rect = () => { const size = TileSize * 2; return { x: (ViewWidth * TileSize - size) / 2, y: (ViewHeight * TileSize - size) / 2, w: size, h: size }; };
48let icon_trash = null;
49const icon_trash_rect = () => { const size = TileSize * 0.9; return { x: (ViewWidth - 0.5) * TileSize - size, y: TileSize / 2, w: size, h: size * 1.5 }; };
50let icon_save = null;
51const icon_save_rect = () => { const size = TileSize * 0.9; return { x: 0.5 * TileSize, y: ((ViewHeight - 0.5) * TileSize - size), w: size, h: size }; };
52inp_tilesize.valueAsNumber = TileSize;
53let camera_x = 0;
54let camera_y = 0;
55const camera_speed = () => {
56    return Math.round(TileSize * inp_cameraSpeed.valueAsNumber / 2);
57};
58let penEntity = null;
59let penDecor = null;
60let penDecorData = null;
61let selectedEntity = null;
62let selectedDecor = null;
63let worldFileName = localStorage.getItem("WorldEditor-world-filename") || "worldData.json";
64let mousePos = { x: 0, y: 0 };
65let mousePosCanvas = { x: 0, y: 0 };
66let selectedEntities = [];
67let selectedDecors = [];
68let copied = { entities: [], decors: [] };
69class World {
70    map = [];
71    width;
72    height;
73    constructor(width, height, world) {
74        this.width = width;
75        this.height = height;
76        inp_width.valueAsNumber = width;
77        inp_height.valueAsNumber = height;
78        this.map = [];
79        if (world) {
80            if (world.width > width) {
81                for (let y = 0; y < world.height; y++) {
82                    for (let x = width; x < world.width; x++) {
83                        if (world.map[y][x]) {
84                            const popup = new Popup();
85                            popup.content.appendChild(Div([], [], "Уменьшение мира нельзя провести без потерь!"));
86                            popup.open();
87                            this.width = world.width;
88                            this.height = world.height;
89                            this.map = world.map;
90                            return;
91                        }
92                    }
93                }
94            }
95            if (world.height > height) {
96                for (let y = height; y < world.height; y++) {
97                    for (let x = 0; x < world.width; x++) {
98                        if (world.map[y][x]) {
99                            const popup = new Popup();
100                            popup.content.appendChild(Div([], [], "Уменьшение мира нельзя провести без потерь!"));
101                            popup.open();
102                            this.width = world.width;
103                            this.height = world.height;
104                            this.map = world.map;
105                            return;
106                        }
107                    }
108                }
109            }
110            for (let y = 0; y < height; y++) {
111                const line = [];
112                for (let x = 0; x < width; x++) {
113                    if (y < world.height && x < world.width) {
114                        line.push(world.map[y][x]);
115                    }
116                    else {
117                        line.push(undefined);
118                    }
119                }
120                this.map.push(line);
121            }
122        }
123        else {
124            for (let y = 0; y < height; y++) {
125                const line = [];
126                for (let x = 0; x < width; x++) {
127                    line.push(undefined);
128                }
129                this.map.push(line);
130            }
131        }
132        // this.createTable();
133    }
134    up() {
135        if (this.height == 0)
136            return;
137        const line = this.map.splice(0, 1);
138        this.map.push(line[0]);
139        this.createTable();
140    }
141    right() {
142        if (this.width == 0)
143            return;
144        for (let y = 0; y < this.height; y++) {
145            this.map[y].unshift(this.map[y].pop());
146        }
147        this.createTable();
148    }
149    down() {
150        if (this.height == 0)
151            return;
152        const line = this.map.splice(this.height - 1, 1);
153        this.map.unshift(line[0]);
154        this.createTable();
155    }
156    left() {
157        if (this.width == 0)
158            return;
159        for (let y = 0; y < this.height; y++) {
160            this.map[y].push(this.map[y].shift());
161        }
162        this.createTable();
163    }
164    createTable() {
165        // world_map.innerHTML = "";
166        for (let y = 0; y < this.height; y++) {
167            const row = document.createElement("tr");
168            // world_map.appendChild(row);
169            for (let x = 0; x < this.width; x++) {
170                const cell = document.createElement("td");
171                row.appendChild(cell);
172                const btn = document.createElement("button");
173                if (this.map[y][x])
174                    btn.classList.add("active");
175                cell.appendChild(btn);
176                btn.addEventListener("click", async () => {
177                    if (this.map[y][x]) {
178                        const popup = new Popup();
179                        popup.focusOn = "cancel";
180                        popup.content.appendChild(Div([], [], "Вы уверены, что хотите удалить экран?"));
181                        const r = await popup.openAsync();
182                        if (r) {
183                            const popup = new Popup();
184                            popup.focusOn = "cancel";
185                            popup.content.appendChild(Div([], [], "Вы точно уверены, что хотите удалить экран?"));
186                            popup.reverse = true;
187                            const r = await popup.openAsync();
188                            if (!r)
189                                return;
190                        }
191                        if (!r)
192                            return;
193                        this.map[y][x] = undefined;
194                        btn.classList.remove("active");
195                    }
196                    else {
197                        this.map[y][x] = new View();
198                        btn.classList.add("active");
199                    }
200                });
201            }
202        }
203    }
204    draw() {
205        const viewWidth = ViewWidth * TileSize;
206        const viewHeight = ViewHeight * TileSize;
207        for (let y = 0; y < this.height; y++) {
208            for (let x = 0; x < this.width; x++) {
209                if (x * viewWidth + camera_x > canvas.width ||
210                    y * viewHeight + camera_y > canvas.height ||
211                    (x + 1) * viewWidth + camera_x < 0 ||
212                    (y + 1) * viewHeight + camera_y < 0) {
213                    continue;
214                }
215                const view = this.map[y][x];
216                ctx.save();
217                ctx.fillStyle = "lightblue";
218                ctx.translate(x * viewWidth, y * viewHeight);
219                ctx.fillRect(0, 0, viewWidth, viewHeight);
220                if (view) {
221                    ctx.save();
222                    if (view_moving && view_moving.vx == x && view_moving.vy == y)
223                        ctx.translate(view_moving.dx, view_moving.dy);
224                    view.draw(x, y);
225                    ctx.restore();
226                }
227                else {
228                    if (inp_mode_view.checked && icon_plus) {
229                        const rect = icon_center_rect();
230                        ctx.drawImage(icon_plus, rect.x, rect.y, rect.w, rect.h);
231                    }
232                }
233                ctx.restore();
234            }
235        }
236        if (view_moving) {
237            ctx.save();
238            ctx.translate(view_moving.vx * viewWidth, view_moving.vy * viewHeight);
239            ctx.translate(view_moving.dx, view_moving.dy);
240            const view = this.map[view_moving.vy][view_moving.vx];
241            if (view)
242                view.draw();
243            ctx.restore();
244        }
245        else if (inp_highlight_tiles.checked && !inp_mode_view.checked) {
246            ctx.save();
247            const { view, X, Y, vx, vy } = this.getView(mousePosCanvas.x - camera_x, mousePosCanvas.y - camera_y);
248            if (vx >= this.width || vy >= this.height)
249                return;
250            const tilex = Math.floor(X / TileSize);
251            const tiley = Math.floor(Y / TileSize);
252            ctx.strokeStyle = "black";
253            ctx.lineWidth = 2;
254            ctx.strokeRect((vx * ViewWidth + tilex) * TileSize, (vy * ViewHeight + tiley) * TileSize, TileSize, TileSize);
255            ctx.strokeStyle = "orange";
256            ctx.strokeRect((vx * ViewWidth + tilex) * TileSize + 1, (vy * ViewHeight + tiley) * TileSize + 1, TileSize - 2, TileSize - 2);
257            ctx.restore();
258        }
259    }
260    getView(x, y) {
261        const width = TileSize * ViewWidth;
262        const height = TileSize * ViewHeight;
263        const X = Math.floor(x / width);
264        const Y = Math.floor(y / height);
265        if (X >= this.width || Y >= this.height)
266            return { view: null, X, Y, vx: X, vy: Y };
267        return { view: this.map[Y][X] || null, X: x - X * width, Y: y - Y * height, vx: X, vy: Y };
268    }
269    findView(obj) {
270        for (let y = 0; y < this.height; y++) {
271            for (let x = 0; x < this.width; x++) {
272                const view = this.map[y][x];
273                if (!view)
274                    continue;
275                let i = -1;
276                if (obj instanceof Decor)
277                    i = view.decor.indexOf(obj);
278                else
279                    i = view.entity.indexOf(obj);
280                if (i > -1)
281                    return { view, i };
282            }
283        }
284        return { view: null, i: -1 };
285    }
286    fill(x, y) {
287        const { view, X, Y } = this.getView(x, y);
288        if (view)
289            view.fill(X, Y);
290    }
291    pen(x, y) {
292        const { view, X, Y } = this.getView(x, y);
293        if (view)
294            view.pen(X, Y);
295    }
296    pick(x, y) {
297        const { view, X, Y } = this.getView(x, y);
298        if (view)
299            view.pick(X, Y);
300    }
301    setCursor(x, y) {
302        const { view, X, Y } = this.getView(x, y);
303        if (view)
304            view.setCursor(X, Y);
305        else
306            setCursor("pointer");
307    }
308    mousedown(x, y) {
309        const { view, X, Y, vx, vy } = this.getView(x - camera_x, y - camera_y);
310        if (!view)
311            return;
312        view.mousedown(X, Y, x, y, vx, vy);
313    }
314    mouseup(x, y) {
315        const { view, X, Y, vx, vy } = this.getView(x - camera_x, y - camera_y);
316        if (view_moving) {
317            if (view)
318                return;
319            if (vx >= this.width || vy >= this.height || vx < 0 || vy < 0)
320                return;
321            this.map[vy][vx] = this.map[view_moving.vy][view_moving.vx];
322            this.map[view_moving.vy][view_moving.vx] = undefined;
323            return;
324        }
325        if (view)
326            view.mouseup(X, Y, vx, vy);
327        else {
328            const data = JSON.parse(startViews[startView]);
329            this.map[vy][vx] = View.fromData(data);
330        }
331    }
332    getEntity(x, y) {
333        const { view, X, Y, vx, vy } = this.getView(x, y);
334        return { entity: view && view.getEntity(X, Y) || null, vx, vy };
335    }
336    getDecor(x, y) {
337        const { view, X, Y, vx, vy } = this.getView(x, y);
338        return { decor: view && view.getDecor(X, Y) || null, vx, vy };
339    }
340    entity(x, y) {
341        const { view, X, Y } = this.getView(x, y);
342        if (view)
343            view.setEntity(X, Y);
344    }
345    decor(x, y) {
346        const { view, X, Y } = this.getView(x, y);
347        if (view)
348            view.setDecor(X, Y);
349    }
350    saveToLocalStarage() {
351        const data = this.getData();
352        const json = JSON.stringify(data);
353        localStorage.setItem(localStorageKey, json);
354    }
355    getData() {
356        const data = {
357            width: this.width,
358            height: this.height,
359            map: [],
360        };
361        for (let y = 0; y < this.height; y++) {
362            const row = [];
363            for (let x = 0; x < this.width; x++) {
364                const view = this.map[y][x];
365                if (view)
366                    row.push(view.getData());
367                else
368                    row.push(undefined);
369            }
370            data.map.push(row);
371        }
372        return data;
373    }
374    static fromData(data) {
375        const world = new World(data.width, data.height);
376        for (let y = 0; y < data.height; y++) {
377            for (let x = 0; x < data.width; x++) {
378                const vData = data.map[y][x];
379                if (vData)
380                    world.map[y][x] = View.fromData(vData);
381            }
382        }
383        return world;
384    }
385    static loadFromLocalStorage() {
386        const json = localStorage.getItem(localStorageKey);
387        if (!json)
388            return;
389        World.loadData(json);
390    }
391    static loadData(json) {
392        try {
393            const data = JSON.parse(json);
394            const newWorld = World.fromData(data);
395            world = newWorld;
396        }
397        catch (error) {
398            console.error(error);
399            const popup = new Popup();
400            popup.cancelBtn = false;
401            popup.content.appendChild(Div([], [], "Произошла ошибка при загрузке данных"));
402            popup.open();
403        }
404    }
405    calcSelected() {
406        if (!selection)
407            return;
408        selectedDecors = [];
409        selectedEntities = [];
410        const selectionWorld = {
411            x: (selection.x - camera_x) / TileSize,
412            y: (selection.y - camera_y) / TileSize,
413            w: (mousePosCanvas.x - selection.x) / TileSize,
414            h: (mousePosCanvas.y - selection.y) / TileSize,
415        };
416        normalizeRect(selectionWorld);
417        for (let y = 0; y < this.height; y++) {
418            for (let x = 0; x < this.width; x++) {
419                const view = this.map[y][x];
420                if (!view)
421                    continue;
422                if (rectIntersect(selectionWorld, { x: x * ViewWidth, y: y * ViewHeight, w: ViewWidth, h: ViewHeight })) {
423                    if (inp_mode_entity.checked) {
424                        for (const entity of view.entity) {
425                            if (rectIntersect(selectionWorld, { x: x * ViewWidth + entity.x, y: y * ViewHeight + entity.y, w: entity.getWidth(), h: entity.getHeight() })) {
426                                selectedEntities.push(entity);
427                            }
428                        }
429                    }
430                    else if (inp_mode_decor.checked) {
431                        for (const decor of view.decor) {
432                            if (rectIntersect(selectionWorld, { x: x * ViewWidth + decor.x, y: y * ViewHeight + decor.y, w: decor.getWidth(), h: decor.getHeight() })) {
433                                selectedDecors.push(decor);
434                            }
435                        }
436                    }
437                }
438            }
439        }
440    }
441}
442class View {
443    tiles = [];
444    entity = [];
445    decor = [];
446    constructor() {
447        for (let y = 0; y < ViewHeight; y++) {
448            const line = [];
449            for (let x = 0; x < ViewWidth; x++) {
450                line.push(new Tile("water_deep"));
451                // if (x == 0 || x == ViewWidth - 1 || y == 0 || y == ViewHeight - 1)
452                // {
453                // 	line.push(new Tile("water_deep"))
454                // }
455                // else
456                // {
457                // 	const random = Math.floor(Math.random() * 3);
458                // 	if (random == 0) line.push(new Tile("sand1"));
459                // 	else if (random == 1) line.push(new Tile("sand2"));
460                // 	else line.push(new Tile("sand3"));
461                // 	if (x == 1 || x == ViewWidth - 2 || y == 1 || y == ViewHeight - 2)
462                // 	{
463                // 		const d = new DecorDict["tileEdge_water_deep"](x, y);
464                // 		this.decor.push(d);
465                // 		if (x == 1 && y == 1) d.objData[0].value = [true, false, false, true];
466                // 		else if (x == 1 && y == ViewHeight - 2)  d.objData[0].value = [false, false, true, true];
467                // 		else if (x == ViewWidth - 2 && y == 1)  d.objData[0].value = [true, true, false, false];
468                // 		else if (x == ViewWidth - 2 && y == ViewHeight - 2)  d.objData[0].value = [false, true, true, false];
469                // 		else if (x == 1)  d.objData[0].value = [false, false, false, true];
470                // 		else if (x == ViewWidth - 2)  d.objData[0].value = [false, true, false, false];
471                // 		else if (y == 1)  d.objData[0].value = [true, false, false, false];
472                // 		else if (y == ViewHeight - 2) d.objData[0].value = [false, false, true, false];
473                // 		d.afterDataSet();
474                // 	}
475                // }
476            }
477            this.tiles.push(line);
478        }
479    }
480    draw(x, y) {
481        for (let y = 0; y < ViewHeight; y++) {
482            for (let x = 0; x < ViewWidth; x++) {
483                let tile = this.tiles[y][x];
484                ctx.save();
485                ctx.translate(x * TileSize, y * TileSize);
486                tile.draw(ctx);
487                ctx.restore();
488            }
489        }
490        ctx.save();
491        if (inp_highlight_decor.checked && !inp_mode_decor.checked)
492            ctx.globalAlpha = 0.2;
493        const decorPlaces = {};
494        this.decor.forEach(d => {
495            d.draw(ctx);
496            if (inp_highlight_stacked.checked && inp_mode_decor.checked) {
497                const key = `${Math.floor(d.x)}:${Math.floor(d.y)}`;
498                if (!decorPlaces[key])
499                    decorPlaces[key] = 1;
500                else
501                    decorPlaces[key] += 1;
502            }
503        });
504        ctx.restore();
505        ctx.save();
506        if (!inp_mode_entity.checked)
507            ctx.globalAlpha = 0.5;
508        const entityPlaces = {};
509        this.entity.forEach(e => {
510            e.draw(ctx);
511            if (inp_highlight_stacked.checked && inp_mode_entity.checked) {
512                const key = `${Math.floor(e.x)}:${Math.floor(e.y)}`;
513                if (!entityPlaces[key])
514                    entityPlaces[key] = 1;
515                else
516                    entityPlaces[key] += 1;
517            }
518        });
519        ctx.restore();
520        const drawStacked = (pointS, count) => {
521            if (count < 2)
522                return;
523            const splited = pointS.split(":");
524            ctx.save();
525            ctx.translate(parseInt(splited[0]) * TileSize, parseInt(splited[1]) * TileSize);
526            ctx.fillStyle = "red";
527            // ctx.fillRect(0.5 * TileSize, 0.1 * TileSize, 0.4 * TileSize, 0.1 * TileSize);
528            // ctx.fillRect(0.5 * TileSize, 0.3 * TileSize, 0.4 * TileSize, 0.16 * TileSize);
529            ctx.font = `${TileSize * 0.6}px Arial`;
530            ctx.fillText(`${count}`, 0.5 * TileSize, 0.5 * TileSize);
531            ctx.restore();
532        };
533        for (const key in decorPlaces)
534            drawStacked(key, decorPlaces[key]);
535        for (const key in entityPlaces)
536            drawStacked(key, entityPlaces[key]);
537        ctx.strokeStyle = "black";
538        ctx.lineWidth = 1;
539        ctx.strokeRect(0, 0, ViewWidth * TileSize, ViewHeight * TileSize);
540        if (inp_mode_view.checked) {
541            ctx.save();
542            ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
543            ctx.fillRect(0, 0, ViewWidth * TileSize, ViewHeight * TileSize);
544            if (icon_move) {
545                const rect = icon_center_rect();
546                ctx.drawImage(icon_move, rect.x, rect.y, rect.w, rect.h);
547            }
548            if (icon_trash) {
549                const rect = icon_trash_rect();
550                ctx.drawImage(icon_trash, rect.x, rect.y, rect.w, rect.h);
551            }
552            if (icon_save) {
553                const rect = icon_save_rect();
554                ctx.drawImage(icon_save, rect.x, rect.y, rect.w, rect.h);
555            }
556            if (x != undefined && y != undefined) {
557                ctx.save();
558                ctx.font = `${TileSize}px Arial`;
559                ctx.fillStyle = "lime";
560                ctx.fillText(`${x}:${y}`, TileSize * 0.3, TileSize);
561                ctx.restore();
562            }
563            ctx.restore();
564        }
565    }
566    drawInd(ctx) {
567        for (let y = 0; y < ViewHeight; y++) {
568            for (let x = 0; x < ViewWidth; x++) {
569                let tile = this.tiles[y][x];
570                ctx.save();
571                ctx.translate(x * TileSize, y * TileSize);
572                tile.draw(ctx);
573                ctx.restore();
574            }
575        }
576        this.decor.forEach(d => d.draw(ctx));
577        this.entity.forEach(e => e.draw(ctx));
578    }
579    fill(x, y) {
580        const X = Math.floor(x / TileSize);
581        const Y = Math.floor(y / TileSize);
582        const tile = this.tiles[Y][X].id;
583        const tiles = [];
584        const group = PenTiles.getGroup(tile);
585        if (group && group.random) {
586            for (const k in tileGroups[group.key].tiles)
587                tiles.push(k);
588        }
589        else {
590            tiles.push(tile);
591        }
592        this.fillR(X, Y, tiles, []);
593    }
594    fillR(x, y, tiles, path) {
595        if (x < 0 || y < 0 || x >= ViewWidth || y >= ViewHeight)
596            return;
597        if (!tiles.includes(this.tiles[y][x].id))
598            return;
599        this.tiles[y][x].id = pen.getTile();
600        const key = (x, y) => y * ViewWidth + x;
601        path.push(key(x, y));
602        if (path.indexOf(key(x + 1, y)) == -1)
603            this.fillR(x + 1, y, tiles, path);
604        if (path.indexOf(key(x - 1, y)) == -1)
605            this.fillR(x - 1, y, tiles, path);
606        if (path.indexOf(key(x, y + 1)) == -1)
607            this.fillR(x, y + 1, tiles, path);
608        if (path.indexOf(key(x, y - 1)) == -1)
609            this.fillR(x, y - 1, tiles, path);
610    }
611    pen(x, y) {
612        const X = Math.floor(x / TileSize);
613        const Y = Math.floor(y / TileSize);
614        this.tiles[Y][X].id = pen.getTile();
615    }
616    pick(x, y) {
617        const X = Math.floor(x / TileSize);
618        const Y = Math.floor(y / TileSize);
619        const tile = this.tiles[Y][X].id;
620        pen.setPen(tile);
621        pen.setGroup(PenTiles.getGroup(tile));
622    }
623    setCursor(x, y) {
624        if (rectPointIntersect(icon_trash_rect(), { x, y })) {
625            setCursor("pointer");
626        }
627        else if (rectPointIntersect(icon_save_rect(), { x, y })) {
628            setCursor("pointer");
629        }
630        else {
631            setCursor("move");
632        }
633    }
634    mousedown(x, y, rx, ry, vx, vy) {
635        if (rectPointIntersect(icon_trash_rect(), { x, y })) { }
636        else if (rectPointIntersect(icon_save_rect(), { x, y })) { }
637        else {
638            view_moving = { x: rx, y: ry, dx: 0, dy: 0, vx, vy };
639        }
640    }
641    async mouseup(x, y, vx, vy) {
642        if (rectPointIntersect(icon_trash_rect(), { x, y })) {
643            const popup = new Popup();
644            popup.focusOn = "cancel";
645            popup.content.appendChild(Div([], [], "Вы уверены, что хотите удалить экран?"));
646            const r = await popup.openAsync();
647            if (r) {
648                const popup = new Popup();
649                popup.focusOn = "cancel";
650                popup.content.appendChild(Div([], [], "Вы точно уверены, что хотите удалить экран?"));
651                popup.reverse = true;
652                const r = await popup.openAsync();
653                if (!r)
654                    return;
655            }
656            if (!r)
657                return;
658            world.map[vy][vx] = undefined;
659        }
660        else if (rectPointIntersect(icon_save_rect(), { x, y })) {
661            const popup = new Popup();
662            popup.focusOn = "cancel";
663            popup.content.appendChild(Div([], [], "Сохранить экран в палитре?"));
664            const r = await popup.openAsync();
665            if (!r)
666                return;
667            const data = JSON.stringify(this.getData());
668            div_palette.children[startView].classList.remove("palette-view-selected");
669            startView = startViews.push(data) - 1;
670            localStorage.setItem("WorldEditor-startViews", JSON.stringify(startViews));
671            setPalete();
672            div_palette.children[startView].classList.add("palette-view-selected");
673        }
674    }
675    getEntity(x, y) {
676        for (let i = 0; i < this.entity.length; i++) {
677            const e = this.entity[i];
678            if (e.intersect(x, y))
679                return e;
680        }
681        return null;
682    }
683    getDecor(x, y) {
684        for (let i = 0; i < this.decor.length; i++) {
685            const e = this.decor[i];
686            if (e.intersect(x, y))
687                return e;
688        }
689        return null;
690    }
691    setEntity(x, y) {
692        if (penEntity && !this.getEntity(x, y)) {
693            const e = new penEntity(x / TileSize, y / TileSize);
694            e.x -= e.getWidth() / 2;
695            e.y -= e.getHeight() / 2;
696            if (shiftKey)
697                e.snapToPixels();
698            else
699                e.center();
700            this.entity.push(e);
701            selectedEntity = e;
702        }
703    }
704    setDecor(x, y) {
705        if (penDecor && !this.getDecor(x, y)) {
706            const d = new penDecor(x / TileSize, y / TileSize);
707            if (penDecorData)
708                d.apllyData(penDecorData);
709            if (shiftKey)
710                d.snapToPixels();
711            else
712                d.center();
713            this.decor.push(d);
714            selectedDecor = d;
715            if (d instanceof Decor_TileEdge && inp_auto_edge.checked) {
716                const x = Math.floor(d.x);
717                const y = Math.floor(d.y);
718                const type = d.constructor.className;
719                const checkTile = (x, y, id) => {
720                    if (x < 0 || x >= ViewWidth || y < 0 || y >= ViewHeight)
721                        return false;
722                    if (typeof id == "string")
723                        return this.tiles[y][x].id == id;
724                    return id(this.tiles[y][x].id);
725                };
726                const setSides = (tileId, startsWith = false) => {
727                    const cond = startsWith ? ((id) => id.startsWith(tileId)) : ((id) => id == tileId);
728                    d.sides[1] = checkTile(x + 1, y, cond);
729                    d.sides[3] = checkTile(x - 1, y, cond);
730                    d.sides[2] = checkTile(x, y + 1, cond);
731                    d.sides[0] = checkTile(x, y - 1, cond);
732                    if (!(d.sides[1] || d.sides[3] || d.sides[2] || d.sides[0])) {
733                        if (penDecorData)
734                            d.apllyData(penDecorData);
735                        else
736                            d.sides[0] = true;
737                    }
738                };
739                const setSidesAndCorners = (tileId, startsWith = false) => {
740                    const cond = startsWith ? ((id) => id.startsWith(tileId)) : ((id) => id == tileId);
741                    d.sides[1] = checkTile(x + 1, y, cond);
742                    d.sides[3] = checkTile(x - 1, y, cond);
743                    d.sides[2] = checkTile(x, y + 1, cond);
744                    d.sides[0] = checkTile(x, y - 1, cond);
745                    d.corners[1] = checkTile(x + 1, y + 1, cond);
746                    d.corners[0] = checkTile(x + 1, y - 1, cond);
747                    d.corners[2] = checkTile(x - 1, y + 1, cond);
748                    d.corners[3] = checkTile(x - 1, y - 1, cond);
749                    if (!(d.sides[1] || d.sides[3] || d.sides[2] || d.sides[0] || d.corners[1] || d.corners[3] || d.corners[2] || d.corners[0])) {
750                        if (penDecorData)
751                            d.apllyData(penDecorData);
752                        else
753                            d.sides[0] = true;
754                    }
755                };
756                if (type == "tileEdge_water")
757                    setSides("water_low");
758                else if (type == "tileEdge_water_deep")
759                    setSides("water_deep");
760                else if (type == "tileEdge_lava")
761                    setSidesAndCorners("lava2");
762                else if (type == "tileEdge_sand")
763                    setSidesAndCorners("sand", true);
764                else if (type == "tileEdge_grass")
765                    setSidesAndCorners("grass", true);
766                d.afterDataSet();
767            }
768            penDecorData = d.objData;
769        }
770    }
771    getData() {
772        const data = {
773            tiles: [],
774            entity: [],
775            decor: [],
776        };
777        for (let y = 0; y < ViewHeight; y++) {
778            const row = [];
779            for (let x = 0; x < ViewWidth; x++) {
780                row.push(this.tiles[y][x].id);
781            }
782            data.tiles.push(row);
783        }
784        this.entity.forEach(e => data.entity.push(e.getData()));
785        this.decor.forEach(d => data.decor.push(d.getData()));
786        return data;
787    }
788    static fromData(data) {
789        const view = new View();
790        view.decor = [];
791        for (let y = 0; y < ViewHeight && y < data.tiles.length; y++) {
792            for (let x = 0; x < ViewWidth && x < data.tiles[y].length; x++) {
793                view.tiles[y][x] = new Tile(data.tiles[y][x]);
794            }
795        }
796        data.entity.forEach(eData => {
797            const entity = Entity.fromData(eData);
798            if (entity)
799                view.entity.push(entity);
800        });
801        if (!data.decor)
802            return view;
803        data.decor.forEach(dData => {
804            const decor = Decor.fromData(dData);
805            if (decor)
806                view.decor.push(decor);
807        });
808        return view;
809    }
810}
811class Tile {
812    id = "sand1";
813    constructor(id) {
814        if (id)
815            this.id = id;
816    }
817    draw(ctx) {
818        const img = tileImages[this.id];
819        if (img) {
820            ctx.drawImage(img, 0, 0, TileSize, TileSize);
821        }
822    }
823}
824class MiniMap {
825    canvas = getCanvas("minimap");
826    ctx = getCanvasContext(this.canvas);
827    size = 20;
828    constructor() {
829        this.canvas.addEventListener("contextmenu", e => e.preventDefault());
830        this.canvas.addEventListener("mousedown", e => {
831            const x = Math.min(Math.floor(e.offsetX / this.size), world.width);
832            const y = Math.min(Math.floor(e.offsetY / this.size), world.height);
833            const X = x * TileSize * ViewWidth;
834            const Y = y * TileSize * ViewHeight;
835            if (e.button == 0) {
836                camera_x = -(X - Math.round((canvas.width - ViewWidth * TileSize) / 2));
837                camera_y = -(Y - Math.round((canvas.height - ViewHeight * TileSize) / 2));
838                normalizeCamera();
839            }
840            else {
841                centerView(X, Y);
842            }
843        });
844    }
845    draw() {
846        this.canvas.width = (this.size + 1) * world.width + 2;
847        this.canvas.height = (this.size + 1) * world.height + 2;
848        for (let y = 0; y < world.height; y++) {
849            for (let x = 0; x < world.width; x++) {
850                if (world.map[y] && world.map[y][x])
851                    this.ctx.fillStyle = "rgb(243, 200, 81)";
852                else
853                    this.ctx.fillStyle = "rgb(177, 225, 255)";
854                this.ctx.fillRect(x * (this.size + 1) + 1, y * (this.size + 1) + 1, this.size, this.size);
855            }
856        }
857        const coefX = (this.canvas.width - 2) / (world.width * ViewWidth * TileSize);
858        const coefY = (this.canvas.height - 2) / (world.height * ViewHeight * TileSize);
859        this.ctx.strokeStyle = "blue";
860        this.ctx.strokeRect(-camera_x * coefX + 1, -camera_y * coefY + 1, canvas.width * coefX, canvas.height * coefY);
861    }
862}
863class FastPalette {
864    opened = false;
865    closedByClick = false;
866    imgs = [];
867    tiles = [pen.getTile()];
868    entity = [penEntity];
869    decor = [penDecor];
870    hovered = null;
871    addtileI = 1;
872    addentityI = 1;
873    adddecorI = 1;
874    constructor() {
875        const imgs = div_fast_palette.querySelectorAll(".fast-palette-part");
876        for (let i = 0; i < imgs.length; i++) {
877            const el = imgs[i];
878            if (el instanceof HTMLDivElement) {
879                this.imgs.push(el);
880                el.addEventListener("mouseover", () => this.hovered = i);
881                el.addEventListener("click", () => {
882                    this.hovered = i;
883                    this.close(true);
884                });
885            }
886        }
887    }
888    open() {
889        if (this.opened)
890            return;
891        this.hovered = null;
892        this.opened = true;
893        this.closedByClick = false;
894        if (inp_mode_entity.checked)
895            this.setPaletteEntity();
896        else if (inp_mode_decor.checked)
897            this.setPaletteDecor();
898        else
899            this.setPaletteTiles();
900        div_fast_palette.style.left = `${mousePos.x}px`;
901        div_fast_palette.style.top = `${mousePos.y}px`;
902        div_fast_palette.classList.add("fast-palette-visible");
903    }
904    setPaletteTiles() {
905        for (let i = 0; i < this.imgs.length; i++) {
906            const imgDiv = this.imgs[i];
907            imgDiv.innerHTML = "";
908            const imgTile = this.tiles.length > i ? tileImages[this.tiles[i]] : tileImages[this.tiles[0]];
909            if (!imgTile)
910                continue;
911            const img = document.createElement("img");
912            img.src = imgTile?.src;
913            imgDiv.appendChild(img);
914        }
915    }
916    setPaletteEntity() {
917        for (let i = 0; i < this.imgs.length; i++) {
918            const imgDiv = this.imgs[i];
919            imgDiv.innerHTML = "";
920            const e = this.entity.length > i ? this.entity[i] : this.entity[0];
921            if (!e) {
922                const img = document.createElement("img");
923                img.src = "imgs/none.png";
924                imgDiv.appendChild(img);
925                continue;
926            }
927            const img = document.createElement("canvas");
928            e.draw(img, 65);
929            imgDiv.appendChild(img);
930        }
931    }
932    setPaletteDecor() {
933        for (let i = 0; i < this.imgs.length; i++) {
934            const imgDiv = this.imgs[i];
935            imgDiv.innerHTML = "";
936            const d = this.decor.length > i ? this.decor[i] : this.decor[0];
937            if (!d) {
938                const img = document.createElement("img");
939                img.src = "imgs/none.png";
940                imgDiv.appendChild(img);
941                continue;
942            }
943            const img = document.createElement("canvas");
944            d.draw(img, 65);
945            imgDiv.appendChild(img);
946        }
947    }
948    close(closedByClick = false) {
949        this.opened = false || this.closedByClick;
950        div_fast_palette.classList.remove("fast-palette-visible");
951        if (this.closedByClick || this.hovered == null)
952            return;
953        this.closedByClick = closedByClick;
954        if (inp_mode_entity.checked)
955            penEntity = this.entity.length > this.hovered ? this.entity[this.hovered] : this.entity[0];
956        else if (inp_mode_decor.checked) {
957            penDecor = this.decor.length > this.hovered ? this.decor[this.hovered] : this.decor[0];
958            penDecorData = null;
959        }
960        else {
961            const tile = this.tiles.length > this.hovered ? this.tiles[this.hovered] : this.tiles[0];
962            pen.setPen(tile);
963            pen.setGroup(PenTiles.getGroup(tile));
964        }
965    }
966    addTile(tileid) {
967        if (this.tiles.indexOf(tileid) != -1)
968            return;
969        this.tiles[this.addtileI] = tileid;
970        this.addtileI = (this.addtileI + 1) % this.imgs.length;
971    }
972    addEntity(entity) {
973        if (this.entity.indexOf(entity) != -1)
974            return;
975        this.entity[this.addentityI] = entity;
976        this.addentityI = (this.addentityI + 1) % this.imgs.length;
977    }
978    addDecor(decor) {
979        if (this.decor.indexOf(decor) != -1)
980            return;
981        this.decor[this.adddecorI] = decor;
982        this.adddecorI = (this.adddecorI + 1) % this.imgs.length;
983    }
984}
985class PenTiles {
986    pen = "sand1";
987    group = null;
988    groupTiles = ["sand1"];
989    constructor() {
990        div_palette_group.addEventListener("click", () => {
991            div_palette_group.classList.remove("fast-palette-visible");
992        });
993    }
994    setPen(tile, resetGroup = true) {
995        if (resetGroup)
996            this.group = null;
997        this.pen = tile;
998        fastPalette.addTile(this.pen);
999    }
1000    getTile() {
1001        if (!this.group?.random)
1002            return this.pen;
1003        if (this.groupTiles.length == 0)
1004            return this.group.key;
1005        return this.groupTiles[Math.floor(Math.random() * this.groupTiles.length)];
1006    }
1007    openGroup(group) {
1008        if (group == undefined) {
1009            if (this.group && !this.group.random) {
1010                div_palette_group.classList.toggle("fast-palette-visible");
1011            }
1012            return;
1013        }
1014        this.setGroup(group);
1015        if (group.random)
1016            return;
1017        div_palette_group.classList.add("fast-palette-visible");
1018    }
1019    setGroup(group) {
1020        if (!group)
1021            return;
1022        if (!group.group)
1023            return;
1024        this.group = group;
1025        this.groupTiles = [];
1026        if (group.random) {
1027            for (const k in tileGroups[group.key].tiles)
1028                this.groupTiles.push(k);
1029        }
1030        div_palette_group_imgs.innerHTML = "";
1031        for (const k in tileGroups[group.key].tiles) {
1032            const key = k;
1033            function addImg() {
1034                if (inp_mode_entity.checked)
1035                    return;
1036                const img = tileImages[key];
1037                if (img) {
1038                    const imgNew = document.createElement("img");
1039                    imgNew.src = img.src;
1040                    imgNew.title = key;
1041                    div_palette_group_imgs.appendChild(imgNew);
1042                    imgNew.addEventListener("click", () => {
1043                        pen.setPen(key, false);
1044                        div_palette_group.classList.remove("fast-palette-visible");
1045                    });
1046                }
1047                else
1048                    setTimeout(addImg, 100);
1049            }
1050            addImg();
1051        }
1052    }
1053    static getGroup(tile) {
1054        for (const group of tileList) {
1055            if (group.key == tile)
1056                return group;
1057            if (group.group) {
1058                const tiles = tileGroups[group.key].tiles;
1059                for (const key in tiles) {
1060                    if (key == tile)
1061                        return group;
1062                }
1063            }
1064        }
1065        return undefined;
1066    }
1067}
1068const pen = new PenTiles();
1069let world = new World(0, 0);
1070const minimap = new MiniMap();
1071const fastPalette = new FastPalette();
1072inp_width.addEventListener("change", () => {
1073    world = new World(inp_width.valueAsNumber, inp_height.valueAsNumber, world);
1074    inp_width.valueAsNumber = world.width;
1075    inp_height.valueAsNumber = world.height;
1076});
1077inp_height.addEventListener("change", () => {
1078    world = new World(inp_width.valueAsNumber, inp_height.valueAsNumber, world);
1079    inp_width.valueAsNumber = world.width;
1080    inp_height.valueAsNumber = world.height;
1081});
1082btn_up.addEventListener("click", () => world.up());
1083btn_right.addEventListener("click", () => world.right());
1084btn_down.addEventListener("click", () => world.down());
1085btn_left.addEventListener("click", () => world.left());
1086btn_save.addEventListener("click", () => {
1087    const text = JSON.stringify(world.getData());
1088    var el = document.createElement('a');
1089    el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
1090    el.setAttribute('download', worldFileName);
1091    el.style.display = 'none';
1092    document.body.appendChild(el);
1093    el.click();
1094    document.body.removeChild(el);
1095});
1096inp_load.addEventListener("input", async () => {
1097    const curFiles = inp_load.files;
1098    if (!curFiles)
1099        return;
1100    if (curFiles.length == 0)
1101        return;
1102    if (world.height != 0 && world.width != 0) {
1103        if (!await askForSure("заменить текущий мир"))
1104            return;
1105    }
1106    const file = curFiles[0];
1107    worldFileName = file.name;
1108    localStorage.setItem("WorldEditor-world-filename", worldFileName);
1109    const data = await file.text();
1110    World.loadData(data);
1111});
1112btn_new.addEventListener("click", async () => {
1113    if (world.height != 0 && world.width != 0) {
1114        if (!await askForSure("создать пустой мир"))
1115            return;
1116    }
1117    world = new World(inp_width.valueAsNumber, inp_height.valueAsNumber);
1118    worldFileName = "worldData.json";
1119    localStorage.setItem("WorldEditor-world-filename", worldFileName);
1120});
1121inp_tilesize.addEventListener("change", () => TileSize = inp_tilesize.valueAsNumber);
1122inp_mode_entity.addEventListener("change", () => {
1123    if (inp_mode_entity.checked)
1124        inp_mode_decor.checked = false;
1125    else
1126        selectedEntity = null;
1127    selectedDecor = null;
1128    setPalete();
1129});
1130inp_mode_decor.addEventListener("change", () => {
1131    if (inp_mode_decor.checked)
1132        inp_mode_entity.checked = false;
1133    else
1134        selectedDecor = null;
1135    selectedEntity = null;
1136    setPalete();
1137});
1138inp_mode_view.addEventListener("change", () => {
1139    setPalete();
1140});
1141canvas.addEventListener("wheel", e => {
1142    const moveSpeed = camera_speed();
1143    if (e.ctrlKey) {
1144        e.preventDefault();
1145        const x = (mousePosCanvas.x - camera_x) / TileSize;
1146        const y = (mousePosCanvas.y - camera_y) / TileSize;
1147        const v = 1.4;
1148        if (e.deltaY > 0)
1149            TileSize /= v;
1150        else
1151            TileSize *= v;
1152        TileSize = Math.max(Math.round(TileSize), 2);
1153        camera_x = -x * TileSize + mousePosCanvas.x;
1154        camera_y = -y * TileSize + mousePosCanvas.y;
1155        inp_tilesize.valueAsNumber = TileSize;
1156        normalizeCamera();
1157    }
1158    else if (e.shiftKey) {
1159        if (e.deltaY > 0)
1160            camera_x -= moveSpeed;
1161        else
1162            camera_x += moveSpeed;
1163        normalizeCamera();
1164    }
1165    else {
1166        if (e.deltaY > 0)
1167            camera_y -= moveSpeed;
1168        else
1169            camera_y += moveSpeed;
1170        normalizeCamera();
1171    }
1172});
1173let camera_moving = null;
1174let view_moving = null;
1175let entity_moving = null;
1176let decor_moving = null;
1177let selection = null;
1178let drawing = null;
1179let ctrl = false;
1180let shiftKey = false;
1181canvas.addEventListener("mousedown", e => {
1182    e.preventDefault();
1183    if (e.button == 0) {
1184        if (camera_moving) {
1185            centerView(e.offsetX - camera_x, e.offsetY - camera_y);
1186            camera_moving = null;
1187            return;
1188        }
1189        if (inp_mode_view.checked) {
1190            world.mousedown(e.offsetX, e.offsetY);
1191            return;
1192        }
1193        if (e.ctrlKey) {
1194            if (selection) {
1195                selection.cx = e.offsetX;
1196                selection.cy = e.offsetY;
1197                selection.changed = false;
1198            }
1199            else {
1200                selection = { x: e.offsetX, y: e.offsetY, cx: e.offsetX, cy: e.offsetY, changed: false };
1201            }
1202            if (inp_mode_entity.checked) {
1203                const { entity } = world.getEntity(e.offsetX - camera_x, e.offsetY - camera_y);
1204                if (entity) {
1205                    const i = selectedEntities.indexOf(entity);
1206                    if (i >= 0)
1207                        selectedEntities.splice(i, 1);
1208                    else
1209                        selectedEntities.push(entity);
1210                    selection.changed = true;
1211                }
1212            }
1213            else if (inp_mode_decor.checked) {
1214                const { decor } = world.getDecor(e.offsetX - camera_x, e.offsetY - camera_y);
1215                if (decor) {
1216                    const i = selectedDecors.indexOf(decor);
1217                    if (i >= 0)
1218                        selectedDecors.splice(i, 1);
1219                    else
1220                        selectedDecors.push(decor);
1221                    selection.changed = true;
1222                }
1223            }
1224        }
1225        else if (inp_mode_entity.checked) {
1226            const { entity } = world.getEntity(e.offsetX - camera_x, e.offsetY - camera_y);
1227            if (entity) {
1228                if (shiftKey)
1229                    entity.snapToPixels();
1230                else
1231                    entity.center();
1232                entity_moving = { x: e.offsetX, y: e.offsetY, dx: 0, dy: 0, entity };
1233                selectedEntity = entity;
1234            }
1235            else {
1236                drawing = "entity";
1237                selectedEntity = null;
1238            }
1239        }
1240        else if (inp_mode_decor.checked) {
1241            const { decor } = world.getDecor(e.offsetX - camera_x, e.offsetY - camera_y);
1242            if (decor) {
1243                if (shiftKey)
1244                    decor.snapToPixels();
1245                else
1246                    decor.center();
1247                decor_moving = { x: e.offsetX, y: e.offsetY, dx: 0, dy: 0, decor };
1248                selectedDecor = decor;
1249            }
1250            else {
1251                drawing = "decor";
1252                selectedDecor = null;
1253            }
1254        }
1255        else {
1256            if (inp_mode_pen.checked) {
1257                drawing = "pen";
1258            }
1259            else {
1260                drawing = "fill";
1261            }
1262        }
1263    }
1264    if (e.button == 2) {
1265        if (drawing) {
1266            centerView(e.offsetX - camera_x, e.offsetY - camera_y);
1267            drawing = null;
1268            return;
1269        }
1270        camera_moving = { x: e.offsetX, y: e.offsetY, cx: camera_x, cy: camera_y };
1271        setCursor("move");
1272    }
1273    if (e.button == 1) {
1274        let action = false;
1275        if (inp_mode_entity.checked) {
1276            const { entity } = world.getEntity(e.offsetX - camera_x, e.offsetY - camera_y);
1277            if (entity) {
1278                action = true;
1279                penEntity = entity.constructor;
1280                fastPalette.addEntity(penEntity);
1281            }
1282        }
1283        else if (inp_mode_decor.checked) {
1284            const { decor } = world.getDecor(e.offsetX - camera_x, e.offsetY - camera_y);
1285            if (decor) {
1286                action = true;
1287                penDecor = decor.constructor;
1288                penDecorData = decor.objData;
1289                fastPalette.addDecor(penDecor);
1290            }
1291        }
1292        else {
1293            world.pick(e.offsetX - camera_x, e.offsetY - camera_y);
1294        }
1295        if (!action) {
1296            if (selection) {
1297                selection.cx = e.offsetX;
1298                selection.cy = e.offsetY;
1299                selection.changed = false;
1300            }
1301            else {
1302                selection = { x: e.offsetX, y: e.offsetY, cx: e.offsetX, cy: e.offsetY, changed: false };
1303            }
1304        }
1305    }
1306});
1307canvas.addEventListener("mousemove", e => {
1308    mousePosCanvas.x = e.offsetX;
1309    mousePosCanvas.y = e.offsetY;
1310    setCursor("none");
1311    if (selection) {
1312        if (Math.abs(e.offsetX - selection.cx) > 5 || Math.abs(e.offsetY - selection.cy) > 5) {
1313            world.calcSelected();
1314            selection.changed = true;
1315        }
1316    }
1317    if (inp_mode_view.checked) {
1318        world.setCursor(e.offsetX - camera_x, e.offsetY - camera_y);
1319    }
1320    if (drawing) {
1321        if (drawing == "pen")
1322            world.pen(e.offsetX - camera_x, e.offsetY - camera_y);
1323        else if (drawing == "decor") {
1324            if (penDecor && penDecor.width >= 1 && penDecor.height >= 1) {
1325                world.decor(e.offsetX - camera_x, e.offsetY - camera_y);
1326            }
1327        }
1328    }
1329    else if (entity_moving) {
1330        // entity_moving.dx = e.offsetX - entity_moving.x;
1331        // entity_moving.dy = e.offsetY - entity_moving.y;
1332        if (shiftKey) {
1333            entity_moving.dx = Math.floor((e.offsetX - entity_moving.x + TileSize / 16 / 2) / (TileSize / 16)) * TileSize / 16;
1334            entity_moving.dy = Math.floor((e.offsetY - entity_moving.y + TileSize / 16 / 2) / (TileSize / 16)) * TileSize / 16;
1335        }
1336        else {
1337            entity_moving.dx = Math.floor((e.offsetX - entity_moving.x + TileSize / 2) / TileSize) * TileSize;
1338            entity_moving.dy = Math.floor((e.offsetY - entity_moving.y + TileSize / 2) / TileSize) * TileSize;
1339        }
1340        setCursor("move");
1341    }
1342    else if (decor_moving) {
1343        // decor_moving.dx = e.offsetX - decor_moving.x;
1344        // decor_moving.dy = e.offsetY - decor_moving.y;
1345        if (shiftKey) {
1346            decor_moving.dx = Math.floor((e.offsetX - decor_moving.x + TileSize / 16 / 2) / (TileSize / 16)) * TileSize / 16;
1347            decor_moving.dy = Math.floor((e.offsetY - decor_moving.y + TileSize / 16 / 2) / (TileSize / 16)) * TileSize / 16;
1348        }
1349        else {
1350            decor_moving.dx = Math.floor((e.offsetX - decor_moving.x + TileSize / 2) / TileSize) * TileSize;
1351            decor_moving.dy = Math.floor((e.offsetY - decor_moving.y + TileSize / 2) / TileSize) * TileSize;
1352        }
1353        setCursor("move");
1354    }
1355    else if (camera_moving) {
1356        camera_x = camera_moving.cx + e.offsetX - camera_moving.x;
1357        camera_y = camera_moving.cy + e.offsetY - camera_moving.y;
1358        setCursor("move");
1359        normalizeCamera();
1360    }
1361    else if (view_moving) {
1362        view_moving.dx = e.offsetX - view_moving.x;
1363        view_moving.dy = e.offsetY - view_moving.y;
1364        setCursor("move");
1365        normalizeCamera();
1366    }
1367});
1368canvas.addEventListener("mouseup", e => {
1369    setCursor("none");
1370    if (inp_mode_view.checked) {
1371        if (e.button == 0)
1372            world.mouseup(e.offsetX, e.offsetY);
1373        world.setCursor(e.offsetX - camera_x, e.offsetY - camera_y);
1374    }
1375    if (drawing == "pen")
1376        world.pen(e.offsetX - camera_x, e.offsetY - camera_y);
1377    if (drawing == "fill")
1378        world.fill(e.offsetX - camera_x, e.offsetY - camera_y);
1379    if (drawing == "entity")
1380        world.entity(e.offsetX - camera_x, e.offsetY - camera_y);
1381    if (drawing == "decor")
1382        world.decor(e.offsetX - camera_x, e.offsetY - camera_y);
1383    if (entity_moving)
1384        endEntityMove();
1385    if (decor_moving)
1386        endDecorMove();
1387    if (selection && !selection.changed) {
1388        selectedDecors = [];
1389        selectedEntities = [];
1390    }
1391    drawing = null;
1392    camera_moving = null;
1393    view_moving = null;
1394    entity_moving = null;
1395    decor_moving = null;
1396    selection = null;
1397});
1398canvas.addEventListener("mouseleave", () => {
1399    drawing = null;
1400    camera_moving = null;
1401    view_moving = null;
1402    entity_moving = null;
1403    setCursor("none");
1404});
1405canvas.addEventListener("contextmenu", e => e.preventDefault());
1406canvas.addEventListener("dblclick", e => {
1407    if (inp_mode_entity.checked && e.button == 0) {
1408        const { entity, vx, vy } = world.getEntity(e.offsetX - camera_x, e.offsetY - camera_y);
1409        if (entity) {
1410            selectedEntity = entity;
1411            entity.openMenu(vx, vy);
1412        }
1413    }
1414    if (inp_mode_decor.checked && e.button == 0) {
1415        const { decor, vx, vy } = world.getDecor(e.offsetX - camera_x, e.offsetY - camera_y);
1416        if (decor) {
1417            selectedDecor = decor;
1418            decor.openMenu(vx, vy);
1419        }
1420    }
1421});
1422window.addEventListener("keypress", e => {
1423    if (Popup.Opened)
1424        return;
1425    switch (e.code) {
1426        case "KeyF":
1427            inp_mode_fill.checked = true;
1428            break;
1429        case "KeyR":
1430            inp_mode_pen.checked = true;
1431            break;
1432        case "KeyS":
1433            inp_mode_view.checked = !inp_mode_view.checked;
1434            setPalete();
1435            break;
1436        case "KeyH":
1437            inp_highlight_tiles.checked = !inp_highlight_tiles.checked;
1438            break;
1439        case "KeyJ":
1440            inp_highlight_decor.checked = !inp_highlight_decor.checked;
1441            break;
1442        case "KeyE": {
1443            inp_mode_entity.checked = !inp_mode_entity.checked;
1444            if (inp_mode_entity.checked)
1445                inp_mode_decor.checked = false;
1446            else
1447                selectedEntity = null;
1448            selectedDecor = null;
1449            setPalete();
1450            break;
1451        }
1452        case "KeyD": {
1453            inp_mode_decor.checked = !inp_mode_decor.checked;
1454            if (inp_mode_decor.checked)
1455                inp_mode_entity.checked = false;
1456            else
1457                selectedDecor = null;
1458            selectedEntity = null;
1459            setPalete();
1460            break;
1461        }
1462        case "KeyW":
1463            pen.openGroup();
1464            break;
1465        // case "KeyC": endEntityMove()?.center(); break;
1466    }
1467});
1468window.addEventListener("keydown", e => {
1469    if (Popup.Opened)
1470        return;
1471    switch (e.code) {
1472        case "ArrowUp":
1473            world.up();
1474            break;
1475        case "ArrowRight":
1476            world.right();
1477            break;
1478        case "ArrowDown":
1479            world.down();
1480            break;
1481        case "ArrowLeft":
1482            world.left();
1483            break;
1484        case "KeyQ":
1485            fastPalette.open();
1486            break;
1487        case "ControlLeft":
1488        case "ControlRight":
1489            ctrl = true;
1490            break;
1491        case "ShiftLeft":
1492        case "ShiftRight":
1493            shiftKey = true;
1494            break;
1495    }
1496});
1497window.addEventListener("keyup", async (e) => {
1498    if (Popup.Opened)
1499        return;
1500    switch (e.code) {
1501        case "KeyQ":
1502            fastPalette.close();
1503            break;
1504        case "Delete":
1505            deleteSelected();
1506            break;
1507        case "ControlLeft":
1508        case "ControlRight":
1509            ctrl = false;
1510            break;
1511        case "ShiftLeft":
1512        case "ShiftRight":
1513            shiftKey = false;
1514            break;
1515        case "KeyC":
1516            if (e.ctrlKey)
1517                copySelected();
1518            break;
1519        case "KeyX":
1520            if (e.ctrlKey)
1521                cutSelected();
1522            break;
1523        case "KeyV":
1524            if (e.ctrlKey)
1525                pasteSelected();
1526            break;
1527    }
1528});
1529window.addEventListener("mousemove", e => {
1530    mousePos.x = e.pageX;
1531    mousePos.y = e.pageY;
1532});
1533function loadImages() {
1534    for (const k in tileIds) {
1535        const key = k;
1536        const imgName = tileIds[key];
1537        tileImages[key] = undefined;
1538        loadImage(imgName, img => tileImages[key] = img, "tiles");
1539    }
1540    loadImage("/icon-move.png", img => icon_move = img);
1541    loadImage("/icon-trash.png", img => icon_trash = img);
1542    loadImage("/icon-plus.png", img => icon_plus = img);
1543    loadImage("/icon-save.png", img => icon_save = img);
1544    entity.forEach(e => {
1545        loadImage(e.imgUrl, img => e.img = img, "entities");
1546    });
1547    decor.forEach(d => {
1548        loadImage(d.imgUrl, img => d.img = img, "decor");
1549    });
1550}
1551function centerView(x, y) {
1552    let width = TileSize * ViewWidth;
1553    let height = TileSize * ViewHeight;
1554    const X = Math.floor(x / width);
1555    const Y = Math.floor(y / height);
1556    if (X >= world.width || Y >= world.height)
1557        return;
1558    TileSize = Math.min(canvas.width / ViewWidth, canvas.height / ViewHeight);
1559    TileSize = Math.floor(TileSize);
1560    inp_tilesize.valueAsNumber = TileSize;
1561    width = TileSize * ViewWidth;
1562    height = TileSize * ViewHeight;
1563    camera_x = -X * width + Math.floor((canvas.width - width) / 2);
1564    camera_y = -Y * height + Math.floor((canvas.height - height) / 2);
1565    normalizeCamera();
1566}
1567function normalizeCamera() {
1568    const width = world.width * ViewWidth * TileSize;
1569    const height = world.height * ViewHeight * TileSize;
1570    const gapW = canvas.width / 2;
1571    const gapH = canvas.height / 2;
1572    if (width > canvas.width) {
1573        camera_x = Math.max(Math.min(camera_x, gapW), -width + canvas.width - gapW);
1574    }
1575    else
1576        camera_x = Math.min(camera_x, gapW);
1577    if (height > canvas.height) {
1578        camera_y = Math.max(Math.min(camera_y, gapH), -height + canvas.height - gapH);
1579    }
1580    else
1581        camera_y = Math.min(camera_y, gapH);
1582}
1583function setCursor(cursor) {
1584    switch (cursor) {
1585        case "none":
1586            canvas.classList.remove("cursor-move");
1587            canvas.classList.remove("cursor-pointer");
1588            break;
1589        case "pointer":
1590            canvas.classList.remove("cursor-move");
1591            canvas.classList.add("cursor-pointer");
1592            break;
1593        case "move":
1594            canvas.classList.add("cursor-move");
1595            canvas.classList.remove("cursor-pointer");
1596            break;
1597    }
1598}
1599function setPalete() {
1600    div_palette.innerHTML = "";
1601    if (inp_mode_view.checked) {
1602        const _TileSize = TileSize;
1603        TileSize = 15;
1604        startViews.forEach((e, i) => {
1605            const img = document.createElement("canvas");
1606            img.width = TileSize * ViewWidth;
1607            img.height = TileSize * ViewHeight;
1608            const ctx = getCanvasContext(img);
1609            const data = JSON.parse(startViews[i]);
1610            const view = View.fromData(data);
1611            view.drawInd(ctx);
1612            img.addEventListener("click", () => {
1613                div_palette.children[startView].classList.remove("palette-view-selected");
1614                div_palette.children[i].classList.add("palette-view-selected");
1615                startView = i;
1616            });
1617            const delbtn = document.createElement("button");
1618            delbtn.innerText = "×";
1619            delbtn.addEventListener("click", async () => {
1620                if (startViews.length <= 1) {
1621                    let popup = new Popup();
1622                    popup.cancelBtn = false;
1623                    popup.content.appendChild(Div([], [], `Нельзя удалить последний экран`));
1624                    popup.open();
1625                    return;
1626                }
1627                let popup = new Popup();
1628                popup.focusOn = "cancel";
1629                popup.content.appendChild(Div([], [], `Вы уверены, что хотите удалить экран из палитры?`));
1630                let r = await popup.openAsync();
1631                if (!r)
1632                    return;
1633                popup = new Popup();
1634                popup.focusOn = "cancel";
1635                popup.content.appendChild(Div([], [], `Вы точно уверены, что хотите удалить экран из палитры?`));
1636                popup.reverse = true;
1637                r = await popup.openAsync();
1638                if (!r)
1639                    return;
1640                startViews.splice(i, 1);
1641                startView = Math.min(startView, startViews.length - 1);
1642                localStorage.setItem("WorldEditor-startViews", JSON.stringify(startViews));
1643                setPalete();
1644            });
1645            div_palette.appendChild(Div(["palette-view"], [
1646                img,
1647                delbtn
1648            ]));
1649        });
1650        div_palette.children[startView].classList.add("palette-view-selected");
1651        TileSize = _TileSize;
1652    }
1653    else if (inp_mode_entity.checked) {
1654        const img = document.createElement("img");
1655        img.title = "Отключить добавление";
1656        img.src = "./imgs/none.png";
1657        img.addEventListener("click", () => penEntity = null);
1658        div_palette.appendChild(img);
1659        entity.forEach(e => {
1660            const img = document.createElement("canvas");
1661            img.title = e.className;
1662            div_palette.appendChild(img);
1663            function addImg() {
1664                if (!inp_mode_entity.checked)
1665                    return;
1666                if (e.img) {
1667                    e.draw(img, 48);
1668                    img.addEventListener("click", () => {
1669                        penEntity = e;
1670                        fastPalette.addEntity(penEntity);
1671                    });
1672                }
1673                else
1674                    setTimeout(addImg, 100);
1675            }
1676            addImg();
1677        });
1678    }
1679    else if (inp_mode_decor.checked) {
1680        selectedDecor = null;
1681        penDecorData = null;
1682        decor.forEach(d => {
1683            const img = document.createElement("canvas");
1684            img.title = d.className;
1685            div_palette.appendChild(img);
1686            function addImg() {
1687                if (!inp_mode_decor.checked)
1688                    return;
1689                if (d.img) {
1690                    d.draw(img, 48);
1691                    img.addEventListener("click", () => {
1692                        penDecor = d;
1693                        penDecorData = null;
1694                        fastPalette.addDecor(penDecor);
1695                    });
1696                }
1697                else
1698                    setTimeout(addImg, 100);
1699            }
1700            addImg();
1701        });
1702    }
1703    else {
1704        selectedEntity = null;
1705        for (const k of tileList) {
1706            const key = k.key;
1707            function addImg() {
1708                if (inp_mode_entity.checked)
1709                    return;
1710                const img = tileImages[key];
1711                if (img) {
1712                    img.title = key;
1713                    div_palette.appendChild(img);
1714                    img.addEventListener("click", () => {
1715                        if (k.group) {
1716                            pen.openGroup(k);
1717                        }
1718                        else {
1719                            pen.setPen(key);
1720                        }
1721                    });
1722                }
1723                else
1724                    setTimeout(addImg, 100);
1725            }
1726            addImg();
1727        }
1728    }
1729}
1730function endEntityMove() {
1731    function move(entity) {
1732        if (!entity_moving)
1733            return;
1734        entity.x += entity_moving.dx / TileSize;
1735        entity.y += entity_moving.dy / TileSize;
1736        entity.x = Math.min(Math.max(entity.x, 0), ViewWidth - entity.getWidth());
1737        entity.y = Math.min(Math.max(entity.y, 0), ViewHeight - entity.getHeight());
1738        if (shiftKey)
1739            entity.snapToPixels();
1740        else
1741            entity.center();
1742    }
1743    if (entity_moving) {
1744        move(entity_moving.entity);
1745        for (const entity of selectedEntities) {
1746            if (entity == entity_moving.entity)
1747                continue;
1748            move(entity);
1749        }
1750        entity_moving = null;
1751    }
1752}
1753function endDecorMove() {
1754    function move(decor) {
1755        if (!decor_moving)
1756            return;
1757        decor.x += decor_moving.dx / TileSize;
1758        decor.y += decor_moving.dy / TileSize;
1759        decor.x = Math.min(Math.max(decor.x, 0), ViewWidth - decor.getWidth());
1760        decor.y = Math.min(Math.max(decor.y, 0), ViewHeight - decor.getHeight());
1761        if (shiftKey)
1762            decor.snapToPixels();
1763        else
1764            decor.center();
1765    }
1766    if (decor_moving) {
1767        move(decor_moving.decor);
1768        for (const decor of selectedDecors) {
1769            if (decor == decor_moving.decor)
1770                continue;
1771            move(decor);
1772        }
1773        entity_moving = null;
1774    }
1775}
1776async function askForSure(action) {
1777    let popup = new Popup();
1778    popup.focusOn = "cancel";
1779    popup.content.appendChild(Div([], [], `Вы уверены, что хотите ${action}?`));
1780    let r = await popup.openAsync();
1781    if (!r)
1782        return false;
1783    popup = new Popup();
1784    popup.focusOn = "cancel";
1785    popup.content.appendChild(Div([], [], `Вы точно уверены, что хотите ${action}?`));
1786    popup.reverse = true;
1787    r = await popup.openAsync();
1788    if (!r)
1789        return false;
1790    popup = new Popup();
1791    popup.focusOn = "cancel";
1792    popup.content.appendChild(Div([], [], `Вы уверены, что хотите ${action}? Все несохранённые данные будут потеряны!`));
1793    r = await popup.openAsync();
1794    if (!r)
1795        return false;
1796    return true;
1797}
1798async function deleteSelected(askForSure = true) {
1799    if (selectedDecor || selectedDecors.length > 0) {
1800        function del(decor) {
1801            const { view, i } = world.findView(decor);
1802            if (!view || i < 0)
1803                return;
1804            view.decor.splice(i, 1);
1805        }
1806        if (selectedDecor && selectedDecors.length == 0)
1807            del(selectedDecor);
1808        selectedDecor = null;
1809        for (const decor of selectedDecors) {
1810            del(decor);
1811        }
1812        selectedDecors = [];
1813    }
1814    else if (selectedEntity || selectedEntities.length > 0) {
1815        function del(entity) {
1816            const { view, i } = world.findView(entity);
1817            if (!view || i < 0)
1818                return;
1819            view.entity.splice(i, 1);
1820        }
1821        if (askForSure) {
1822            if (selectedEntities.length > 0) {
1823                let popup = new Popup();
1824                popup.focusOn = "cancel";
1825                const count = selectedEntities.length;
1826                popup.content.appendChild(Div([], [], `Вы уверены, что хотите удалить этих сущностей (${count}шт)?`));
1827                let r = await popup.openAsync();
1828                if (!r)
1829                    return;
1830            }
1831            else {
1832                let popup = new Popup();
1833                popup.focusOn = "cancel";
1834                popup.content.appendChild(Div([], [], "Вы уверены, что хотите удалить сущность?"));
1835                let r = await popup.openAsync();
1836                if (!r)
1837                    return;
1838            }
1839        }
1840        if (selectedEntity && selectedEntities.length == 0)
1841            del(selectedEntity);
1842        selectedEntity = null;
1843        for (const entity of selectedEntities) {
1844            del(entity);
1845        }
1846        selectedEntities = [];
1847    }
1848}
1849function copySelected() {
1850    if (inp_mode_decor.checked) {
1851        copied.decors = [];
1852        if (selectedDecor)
1853            copied.decors.push(selectedDecor);
1854        for (const decor of selectedDecors) {
1855            copied.decors.push(decor);
1856        }
1857    }
1858    if (inp_mode_entity.checked) {
1859        copied.entities = [];
1860        if (selectedEntity)
1861            copied.entities.push(selectedEntity);
1862        for (const entity of selectedEntities) {
1863            copied.entities.push(entity);
1864        }
1865    }
1866}
1867function cutSelected() {
1868    copySelected();
1869    deleteSelected();
1870}
1871function pasteSelected() {
1872    const { view } = world.getView(mousePosCanvas.x - camera_x, mousePosCanvas.y - camera_y);
1873    if (!view)
1874        return;
1875    if (inp_mode_decor.checked) {
1876        selectedDecor = null;
1877        selectedDecors = [];
1878        for (const decor of copied.decors) {
1879            const copy = Decor.fromData(decor.getData());
1880            if (copy)
1881                selectedDecors.push(copy);
1882        }
1883        view.decor = selectedDecors.concat(view.decor);
1884    }
1885    if (inp_mode_entity.checked) {
1886        selectedEntity = null;
1887        selectedEntities = [];
1888        for (const entity of copied.entities) {
1889            const copy = Entity.fromData(entity.getData());
1890            if (copy) {
1891                // view.entity.push(copy);
1892                selectedEntities.push(copy);
1893            }
1894        }
1895        view.entity = selectedEntities.concat(view.entity);
1896    }
1897}
1898loadImages();
1899setPalete();
1900World.loadFromLocalStorage();
1901loop();
1902setInterval(() => world.saveToLocalStarage(), 1000);
1903// btn_new.click() // Temp
1904// world.map[0][0] = new View(); // Temp
1905// penEntity = Entity_Crab; // Temp
1906// world.map[0][0]?.setEntity(100, 100); // Temp
1907// inp_mode_entity.checked = !inp_mode_entity.checked; // Temp
1908// setPalete(); // Temp
1909function loop() {
1910    const rect = div_viewport.getBoundingClientRect();
1911    canvas.width = rect.width;
1912    canvas.height = rect.height;
1913    // ctx.clearRect(0, 0, canvas.width, canvas.height);
1914    ctx.fillStyle = "hsl(195deg 53% 70%)";
1915    ctx.fillRect(0, 0, canvas.width, canvas.height);
1916    ctx.imageSmoothingEnabled = false;
1917    ctx.save();
1918    ctx.translate(camera_x, camera_y);
1919    world.draw();
1920    ctx.restore();
1921    if (selection) {
1922        ctx.save();
1923        ctx.strokeStyle = "orange";
1924        ctx.lineWidth = 2;
1925        ctx.strokeRect(selection.x, selection.y, mousePosCanvas.x - selection.x, mousePosCanvas.y - selection.y);
1926        ctx.restore();
1927    }
1928    ctx.save();
1929    ctx.strokeStyle = "blue";
1930    ctx.lineWidth = 2;
1931    const partX = -camera_x / (world.width * ViewWidth * TileSize - canvas.width);
1932    const partY = -camera_y / (world.height * ViewHeight * TileSize - canvas.height);
1933    ctx.beginPath();
1934    ctx.moveTo(0, canvas.height - 2);
1935    ctx.lineTo(canvas.width * partX, canvas.height - 2);
1936    ctx.moveTo(canvas.width - 2, 0);
1937    ctx.lineTo(canvas.width - 2, canvas.height * partY);
1938    ctx.stroke();
1939    ctx.restore();
1940    minimap.draw();
1941    requestAnimationFrame(loop);
1942}
1943console.log("saveScreenImg(x = 0, y = 0, w?: number)");
1944console.log('saveWorldImg(w?: number, back: string | null = "lightblue")');
1945function saveScreenImg(x = 0, y = 0, w) {
1946    let tileSize = TileSize;
1947    if (w == undefined)
1948        w = 1920;
1949    tileSize = Math.floor(w / ViewWidth);
1950    w = tileSize * ViewWidth;
1951    let h = tileSize * ViewHeight;
1952    const canvas = document.createElement("canvas");
1953    document.body.appendChild(canvas);
1954    canvas.width = w;
1955    canvas.height = h;
1956    const ctx = getCanvasContext(canvas);
1957    ctx.imageSmoothingEnabled = false;
1958    const view = world.map[y][x];
1959    if (view == undefined) {
1960        console.log("view == undefined");
1961        return;
1962    }
1963    for (let y = 0; y < ViewHeight; y++) {
1964        for (let x = 0; x < ViewWidth; x++) {
1965            let tile = view.tiles[y][x];
1966            const img = tileImages[tile.id];
1967            if (!img)
1968                continue;
1969            ctx.drawImage(img, x * tileSize, y * tileSize, tileSize, tileSize);
1970        }
1971    }
1972    const hide = inp_highlight_decor.checked;
1973    const decor = inp_mode_decor.checked;
1974    const _TileSize = TileSize;
1975    TileSize = tileSize;
1976    inp_highlight_decor.checked = false;
1977    for (const decor of view.decor)
1978        decor.draw(ctx);
1979    TileSize = _TileSize;
1980    inp_highlight_decor.checked = hide;
1981    inp_mode_decor.checked = decor;
1982    for (const entity of view.entity) {
1983        const obj = entity.constructor;
1984        if (obj.img == undefined)
1985            continue;
1986        ctx.drawImage(obj.img, 0, 0, obj.width, obj.height, (entity.x + obj.xImg) * tileSize, (entity.y + obj.yImg) * tileSize, obj.widthImg * tileSize, obj.heightImg * tileSize);
1987    }
1988    canvas.addEventListener("click", () => {
1989        document.body.removeChild(canvas);
1990    });
1991    // const img = canvas.toDataURL("image/png");
1992    // var image = new Image();
1993    // image.src = img;
1994    // const W = window.open("");
1995    // W?.document.write(image.outerHTML);
1996}
1997function saveWorldImg(w, back = "lightblue") {
1998    let tileSize = TileSize;
1999    let h = 0;
2000    if (w != undefined) {
2001        tileSize = Math.floor(w / ViewWidth);
2002    }
2003    w = tileSize * ViewWidth * world.width;
2004    h = tileSize * ViewHeight * world.height;
2005    const canvas = document.createElement("canvas");
2006    document.body.appendChild(canvas);
2007    canvas.width = w;
2008    canvas.height = h;
2009    const ctx = getCanvasContext(canvas);
2010    ctx.imageSmoothingEnabled = false;
2011    if (back != null) {
2012        ctx.fillStyle = back;
2013        ctx.fillRect(0, 0, w, h);
2014    }
2015    for (let Y = 0; Y < world.height; Y++) {
2016        for (let X = 0; X < world.width; X++) {
2017            const view = world.map[Y][X];
2018            if (view == undefined)
2019                continue;
2020            ctx.translate(X * ViewWidth * tileSize, Y * ViewHeight * tileSize);
2021            for (let y = 0; y < ViewHeight; y++) {
2022                for (let x = 0; x < ViewWidth; x++) {
2023                    let tile = view.tiles[y][x];
2024                    const img = tileImages[tile.id];
2025                    if (!img)
2026                        continue;
2027                    ctx.drawImage(img, x * tileSize, y * tileSize, tileSize, tileSize);
2028                }
2029            }
2030            for (const entity of view.entity) {
2031                const obj = entity.constructor;
2032                if (obj.img == undefined)
2033                    continue;
2034                ctx.drawImage(obj.img, 0, 0, obj.width, obj.height, (entity.x + obj.xImg) * tileSize, 
2034(entity.y + obj.yImg) * tileSize, obj.widthImg * tileSize, obj.heightImg * tileSize);
2035            }
2036            const hide = inp_highlight_decor.checked;
2037            const decor = inp_mode_decor.checked;
2038            const _TileSize = TileSize;
2039            TileSize = tileSize;
2040            inp_highlight_decor.checked = false;
2041            for (const decor of view.decor)
2042                decor.draw(ctx);
2043            TileSize = _TileSize;
2044            inp_highlight_decor.checked = hide;
2045            inp_mode_decor.checked = decor;
2046            ctx.translate(-X * ViewWidth * tileSize, -Y * ViewHeight * tileSize);
2047        }
2048    }
2049    canvas.addEventListener("click", () => {
2050        document.body.removeChild(canvas);
2051    });
2052    // const img = canvas.toDataURL("image/png");
2053    // var image = new Image();
2054    // image.src = img;
2055    // const W = window.open("");
2056    // W?.document.write(image.outerHTML);
2057}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.