2 <html lang="en" dir="ltr">
7 <style>html, body, .map {
12 outline: 1px dotted black;
18 background-color: rgba(200, 200, 200, 0.5);
21 font-family: sans-serif;
35 outline: 1px solid #333;
48 <script type="text/javascript">function ViewFrame() {
50 const map = document.createElement('canvas'); // the map we see
51 map.className = 'map';
52 map.height = window.innerHeight;
53 map.width = window.innerWidth;
54 document.getElementsByTagName('body')[0].append(map);
55 // this is the map that we actually draw on
56 this.map = map.getContext('2d', {
59 this.map.imageSmoothingEnabled = false;
61 // the info box in the corner
62 this.infobox = document.getElementsByClassName('infoboxTable')[0];
63 this.infoboxSlots = new Array();
66 this.chunkScript = document.createElement('script');
67 this.chunkScript.type = 'text/javascript';
68 this.chunkScript.src = 'Metadata.js';
69 document.getElementsByTagName('body')[0].append(this.chunkScript);
70 this.chunkScript.addEventListener('load', () => {
71 ViewFrame.initInfobox(this.infobox, this.infoboxSlots);
72 this.x = ViewFrame.chunks.startCoords[0];
73 this.y = ViewFrame.chunks.startCoords[1];
74 this.availableChunks = ViewFrame.chunks.chunkMetadata;
80 // Tracks images that have been loaded and are on the map
81 this.loadedChunksByName = new Map();
82 // this is needed because [1, 2] != [1, 2] and thats how we store coords.
83 this.loadedChunksByCoords = new Map();
84 this.availableChunks = null; // the chunks in ./Metadata.js
85 // so that we dont render twice at the same time
86 this.rendering = false;
89 this.y = -1; // can be fractional
90 this.zoom = 32; // pixels wide the shards are to be
93 // prototypes, some less... notable? methods are
94 // in ViewFrameUtils.js
95 ViewFrame.prototype.reloadChunkList = function () {
96 if (this.chunkScript) {
97 this.chunkScript.remove();
98 delete this.chunkScript;
101 this.chunkScript = document.createElement('script');
102 this.chunkScript.type = 'text/javascript';
103 this.chunkScript.src = 'Metadata.js';
104 document.getElementsByTagName('body')[0].append(this.chunkScript);
106 this.chunkScript.addEventListener('load', () => {
107 this.availableChunks = ViewFrame.chunks.chunkMetadata;
112 ViewFrame.prototype.render = function () {
113 if (!this.availableChunks) return;
114 if (this.rendering) clearInterval(ViewFrame.intervalRef);
115 this.rendering = true;
117 this.map.clearRect(0, 0, window.innerWidth, window.innerHeight);
119 this.loadedChunksByCoords
120 .forEach((chunk, coord) => { // check the bounds
121 if (coord[0] < this.eastChunk &&
122 coord[0] >= this.westChunk &&
123 coord[1] <= this.northChunk &&
124 coord[1] >= this.southChunk) {
126 this.place(chunk, coord[0], coord[1]);
129 // its out of range!!!
131 this.loadedChunksByCoords.delete(coord);
132 this.loadedChunksByName.delete(coord.join('_'));
136 // gathering what we need to load
137 const neededChunks = new Set();
138 for (var x = this.westChunk; x < this.eastChunk; x++) {
139 for (var y = this.southChunk; y < this.northChunk; y++) {
140 const chunKey = [x, y]; // chunk + key = chunKey :)
141 const name = chunKey.join('_');
142 // continue if its not available, or it is loaded
143 if (!this.availableChunks.has(name) ||
144 this.loadedChunksByName.has(name)) continue;
145 neededChunks.add(chunKey);
148 // iterating over everything we need to load
149 const it = neededChunks.values();
150 ViewFrame.intervalRef = setInterval(() => {
151 let round = it.next();
154 const img = new Image(32, 32);
155 const name = round.value.join('_');
157 img.src = 'Chunks/' + name + '.png';
159 decode(img, loadedImage => {
160 this.place(img, round.value[0], round.value[1]);
162 this.loadedChunksByName.set(name, img);
163 this.loadedChunksByCoords.set(round.value, img);
165 clearInterval(ViewFrame.intervalRef);
166 this.rendering = false;
171 ViewFrame.prototype.place = function (img, x, y) {
177 y += this.height / 2;
179 this.map.drawImage(img, Math.floor(x), Math.floor(y), this.zoom, this.zoom);
182 ViewFrame.prototype.updateInfobox = function (chunkName) {
183 const chunkMeta = this.availableChunks.get(chunkName);
184 this.infoboxSlots.forEach((l, k) => {
185 l.innerText = chunkMeta ? chunkMeta[k] : '0';
188 <script type="text/javascript">ViewFrame.initInfobox = function (ibox, iboxSlots) {
191 .chunkMetadataNames.forEach((item, i) => {
192 const slot = document.createElement('tr');
193 const head = document.createElement('th');
194 head.innerText = item;
195 const row = document.createElement('td');
198 slot.append(head, row);
203 ViewFrame.prototype.updateEdges = function () {
204 if (this.width != window.innerWidth || this.height != window.innerHeight) {
205 this.width = window.innerWidth;
206 this.map.canvas.width = this.width;
207 this.height = window.innerHeight;
208 this.map.canvas.height = this.height;
209 this.map.imageSmoothingEnabled = false;
211 const chunksWide = Math.ceil(this.width / this.zoom);
212 const chunksHigh = Math.ceil(this.height / this.zoom);
214 this.east = this.x + chunksWide / 2; // this is fractional and is used to keep track of the edges of the window
215 this.eastChunk = Math.ceil(this.east); // this is not and is used to track the chunks that need to load
216 this.west = this.x - chunksWide / 2;
217 this.westChunk = Math.floor(this.west);
218 this.north = this.y + chunksHigh / 2;
219 this.northChunk = Math.ceil(this.north);
220 this.south = this.y - chunksHigh / 2;
221 this.southChunk = Math.floor(this.south);
225 * dx and dy are the distances in chunks
227 ViewFrame.prototype.moveCenter = function (dx, dy) {
228 // to pan when we click on the map!
229 this.x += (dx - this.width / 2) / this.zoom;
230 this.y += (dy - this.height / 2) / this.zoom;
234 * x and y are positions in chunks
236 ViewFrame.prototype.setCenter = function (x, y) {
242 * x and y are positions in BLOCKS!!!!
244 ViewFrame.prototype.setBlockwiseCenter = function (x, y) {
245 this.x = x / this.zoom;
246 this.y = y / this.zoom;
249 ViewFrame.prototype.clear = function () {
250 this.loadedChunksByName.clear();
251 this.loadedChunksByCoords.clear();
252 if (this.chunkScript) this.chunkScript.remove();
253 delete this.chunkScript;
254 delete ViewFrame.chunks;
255 this.map.clearRect(0, 0, window.innerWidth, window.innerHeight);
258 function decode(img, cb) {
263 .catch(() => {}); // so images arent added on error
265 <div class="infobox">
267 <table class="infoboxTable">
270 <script type="text/javascript">const vf = new ViewFrame();
271 vf.reloadChunkList();
273 // the event handlers are in iifes so they dont make unneeded globals.
274 // resize, delay re-render to reduce lag.
277 window.addEventListener('resize', () => {
279 id = setTimeout(() => {
288 vf.map.canvas.addEventListener('mousedown', event => {
290 vf.moveCenter(event.pageX, event.pageY);
291 id = setTimeout(() => {
297 // #### CONTROLS ####
302 vf.map.canvas.addEventListener('mousemove', event => {
303 // only count if the mouse moved more than a chunk
304 let x = Math.floor(vf.x +
305 (event.clientX - vf.width / 2) / vf.zoom);
306 let y = Math.floor(vf.y +
307 (event.clientY - vf.height / 2) / vf.zoom);
308 if (x == lastX && y == lastY) return;
311 vf.updateInfobox(x + '_' + y);
318 vf.map.canvas.addEventListener('wheel', event => {
320 id = setTimeout(() => {
323 // this makes it so zooming out is faster
324 let dir = -Math.sign(event.deltaY);
325 if (vf.zoom < 16) // arbitrary value
330 vf.zoom += 1; // make sure it doesnt go to negative values
334 // reload the chunk list every six seconds
335 var devBlockReload = false; // disable via command line
338 if (devBlockReload) return;
339 vf.reloadChunkList();
341 }());</script></script>