mtsedit/src/mts.c

760 lines
27 KiB
C

/*
* mtsedit/mts.c
*
* Copyright (C) 2019 bzt (bztsrc@gitlab)
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @brief MTS file format importer / exporter
*
*/
#include "main.h"
char mtsfile[MAXPATHLEN];
/* block type mapping palettes */
int numpalettes = 0, lenpalettes = 0;
char **palettes = NULL;
/* blocks types */
int numblocks = 0;
mtsblock_t *blocks = NULL;
/* the actual MTS in memory */
int mts_x = 0, mts_y = 0, mts_z = 0;
int currlayer = 127, gndlayer = 0;
char layerprob[256];
node_t nodes[256][256][256];
/* generated properties */
int mix = 255, max = 0, miy = 255, may = 0, miz = 255, maz = 0, bound_valid = 0;
/**
* Get schematic bounding cube and node id translation tables
*/
int mts_getbounds(int sanitize, unsigned short *tr, unsigned short *tr2)
{
int x, y, z, j;
if(!sanitize && !tr && bound_valid) return 0;
/* get boundind box and block ID translation tables, sanitize probability values */
j = 1; mix = 255; max = 0; miy = 255; may = 0; miz = 255; maz = 0;
for(y = 0; y < 256; y++) {
for(z = 0; z < 256; z++)
for(x = 0; x < 256; x++) {
if(nodes[y][z][x].param0) {
if(tr && tr2 && !tr2[nodes[y][z][x].param0]) {
tr2[nodes[y][z][x].param0] = j;
tr[j++] = nodes[y][z][x].param0;
}
if(x < mix) mix = x;
if(x > max) max = x;
if(y < miy) miy = y;
if(y > may) may = y;
if(z < miz) miz = z;
if(z > maz) maz = z;
if(sanitize && !(nodes[y][z][x].param1 & 0x7F)) nodes[y][z][x].param1 |= 127;
} else
if(sanitize) nodes[y][z][x].param1 = 0;
}
}
if(sanitize)
for(y = miy; y <= may; y++)
if(!layerprob[y]) layerprob[y] = 127;
bound_valid = 1;
return j;
}
/**
* Load an MTS file
*/
void mts_load(unsigned char *data, unsigned int size)
{
unsigned char *buff = NULL, *un = NULL, *b;
unsigned int idx;
int i, j, k, l, n, x, y, z, min_x = 0, min_y = 0, min_z = 0, *palref;
unsigned short *tr = NULL;
if(!data[4] && (data[5] == 3 || data[5] == 4) && !data[6] && data[7] && !data[8] && data[9] && !data[10] && data[11]) {
mts_x = data[7]; mts_y = data[9]; mts_z = data[11];
blocks[0].numref -= mts_y * mts_z * mts_x;
min_x = 127 - mts_x / 2;
min_y = 127 - mts_y / 2;
min_z = 127 - mts_z / 2;
buff = data + 12;
/* guess if we have layer probability map or not for version 3 */
if(buff[0] > 3 || data[5] == 4) {
memcpy(layerprob + min_y, buff, mts_y);
buff += mts_y;
}
n = (buff[0] << 8) | buff[1];
if(n > 0 && n < 1024) {
buff += 2;
tr = (unsigned short*)malloc(n * sizeof(unsigned short));
if(!tr) error(lang[ERR_MEM]);
palref = (int*)malloc(numpalettes * sizeof(int));
if(!palref) error(lang[ERR_MEM]);
memset(palref, 0, numpalettes * sizeof(int));
for(i = 0; i < n; i++) {
tr[i] = numblocks;
j = buff[1];
buff += 2;
for(k = 0; k < numblocks; k++)
for(l = 0; l < numpalettes + 3; l++)
if((!blocks[k].blocknames && (int)strlen(blocks[k].name) == j &&
!memcmp(blocks[k].name, buff, j)) || (blocks[k].blocknames && blocks[k].blocknames[l] &&
(int)strlen(blocks[k].blocknames[l]) == j && !memcmp(blocks[k].blocknames[l], buff, j))) {
if(k)
switch(l) {
case 0: break;
case 1: blocks[k].dobiome = 1; savebiome = 1; break;
case 2: if(!memcmp(buff, "mapgen", 6)) { savemapgen = 1; } break;
default: palref[l - 3]++; break;
}
tr[i] = k;
break;
}
if(tr[i] == numblocks) {
x = buff[j]; buff[j] = 0;
fprintf(stderr, "mtsedit: %s: %s %d '%s'\r\n", mtsfile, lang[ERR_NODE], i, buff);
buff[j] = x;
idx = numblocks++;
blocks = (mtsblock_t*)realloc(blocks, numblocks * sizeof(mtsblock_t));
if(!blocks) error(lang[ERR_MEM]);
memset(&blocks[idx], 0, sizeof(mtsblock_t));
blocks[idx].name = (char*)malloc(j + 1);
if(!blocks[idx].name) error(lang[ERR_MEM]);
memcpy(blocks[idx].name, buff, j);
blocks[idx].name[j] = 0;
}
buff += j;
}
/* pick the node palette which has the most node reference */
for(i = savepal = 0; i < numpalettes; i++)
if(palref[i] > palref[savepal]) savepal = i;
free(palref);
size -= (int)(buff - data);
un = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buff, size, 4096, &i, 1);
if(un) { buff = un; size = (unsigned int)i; }
if(4 * mts_x * mts_y * mts_z <= (int)size) {
for(z = 0, b = buff; z < mts_z; z++)
for(y = 0; y < mts_y; y++)
for(x = 0; x < mts_x; x++, b += 2) {
nodes[y+min_y][mts_z+min_z-z][x+min_x].param0 = tr[(b[0] << 8) | b[1]];
blocks[nodes[y+min_y][mts_z+min_z-z][x+min_x].param0].numref++;
}
for(z = 0; z < mts_z; z++)
for(y = 0; y < mts_y; y++)
for(x = 0; x < mts_x; x++, b++)
nodes[y+min_y][mts_z+min_z-z][x+min_x].param1 = data[5] == 3 ? b[0] >> 1 : b[0];
for(z = 0; z < mts_z; z++)
for(y = 0; y < mts_y; y++)
for(x = 0; x < mts_x; x++, b++)
nodes[y+min_y][mts_z+min_z-z][x+min_x].param2 = b[0];
gndlayer = currlayer = min_y;
for(y = 0; y < mts_y; y++)
for(z = 0; z < mts_z; z++)
for(x = 0; x < mts_x; x++)
if(nodes[y+min_y][z+min_z][x+min_x].param2 == 0x20) {
gndlayer = y+min_y;
nodes[y+min_y][z+min_z][x+min_x].param2 = 0;
}
status = lang[LOADED];
} else mts_x = mts_y = mts_z = 0;
free(tr);
if(un) free(un);
} else mts_x = mts_y = mts_z = 0;
}
}
/**
* Save an MTS file
*/
int mts_save()
{
char outfile[MAXPATHLEN], *c;
unsigned short *tr, *tr2;
unsigned char *b, *b2, hdr[12];
int i, j, k, x, y, z;
FILE *f;
status = lang[ERR_SAVE];
tr = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
if(!tr) error(lang[ERR_MEM]);
memset(tr, 0, numblocks * sizeof(unsigned short));
tr2 = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
if(!tr2) error(lang[ERR_MEM]);
memset(tr2, 0, numblocks * sizeof(unsigned short));
j = mts_getbounds(1, tr, tr2);
if(mix > max) { free(tr); free(tr2); return 1; }
for(y = miy; y <= may; y++) {
/* close windows and doors */
for(z = miz+1; z < maz; z++)
for(x = mix+1; x < max; x++)
if(!nodes[y][z][x].param0 && !nodes[y][z][x].param1 && (
(nodes[y][z-1][x].param1 && (nodes[y][z+1][x].param1 || (z<254 && nodes[y][z+2][x].param1))) ||
(nodes[y][z][x-1].param1 && (nodes[y][z][x+1].param1 || (x<254 && nodes[y][z][x+2].param1)))))
nodes[y][z][x].param1 = 127;
/* mark air in rooms temporarly */
for(z = miz+1; z < maz; z++)
for(x = mix+1; x < max; x++)
if(!nodes[y][z][x].param0 && !nodes[y][z][x].param1 &&
(nodes[y][z-1][x].param1 || nodes[y][z][x-1].param1))
nodes[y][z][x].param1 = 1;
/* clear rooms which are connected with the outside */
do {
k = 0;
for(z = miz+1; z < maz; z++)
for(x = mix+1; x < max; x++)
if(nodes[y][z][x].param1 == 1 &&
(!nodes[y][z-1][x].param1 || !nodes[y][z+1][x].param1 || !nodes[y][z][x-1].param1 ||
!nodes[y][z][x+1].param1)) {
nodes[y][z][x].param1 = 0;
k = 1;
}
} while(k);
/* make air in closed rooms permanent, also set the "force placement" flag for them */
for(z = miz+1; z < maz; z++)
for(x = mix+1; x < max; x++)
if(nodes[y][z][x].param1 == 1) nodes[y][z][x].param1 = 255;
/* mark ground level */
if(y == gndlayer)
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++)
if(!nodes[y][z][x].param0) nodes[y][z][x].param2 = 0x20;
}
/* get filename, buffer and open file */
strcpy(outfile, mtsfile);
c = strrchr(outfile, '.');
if(!c) c = &outfile[strlen(outfile)];
strcpy(c, ".mts");
f = fopen(outfile,"wb");
if(!f) return 1;
b = (unsigned char*)malloc((max-mix+1) * (may-miy+1) * (maz-miz+1) * 4);
if(!b) error(lang[ERR_MEM]);
memset(b, 0, (max-mix+1) * (may-miy+1) * (maz-miz+1) * 4);
memset(hdr, 0, sizeof(hdr));
/* write header */
memcpy(hdr, "MTSM", 4);
hdr[5] = 4;
hdr[7] = max-mix+1;
hdr[9] = may-miy+1;
hdr[11] = maz-miz+1;
fwrite(hdr, 12, 1, f);
fwrite(layerprob + miy, may-miy+1, 1, f);
/* write Name-ID table using the selected node palette */
hdr[0] = 0; hdr[1] = j;
fwrite(hdr, 2, 1, f);
if(savepal < 0 || savepal >= numpalettes) savepal = 0;
for(i = 0; i < j; i++) {
c = !blocks[tr[i]].blocknames ? blocks[tr[i]].name : (savebiome && blocks[tr[i]].blocknames[1] &&
blocks[tr[i]].dobiome ? blocks[tr[i]].blocknames[1] : (savemapgen && blocks[tr[i]].blocknames[2] &&
blocks[tr[i]].blocknames[2][0] ? blocks[tr[i]].blocknames[2] : (blocks[tr[i]].blocknames[savepal+3] &&
blocks[tr[i]].blocknames[savepal+3][0] ? blocks[tr[i]].blocknames[savepal+3] : blocks[tr[i]].name)));
hdr[1] = strlen(c);
fwrite(hdr, 2, 1, f);
fwrite(c, hdr[1], 1, f);
}
/* construct param buffer */
for(z = miz, b2 = b; z <= maz; z++)
for(y = miy; y <= may; y++)
for(x = mix; x <= max; x++, b2 += 2) {
b2[0] = tr2[nodes[y][maz+miz-z][x].param0] >> 8;
b2[1] = tr2[nodes[y][maz+miz-z][x].param0] & 0xFF;
}
for(z = miz; z <= maz; z++)
for(y = miy; y <= may; y++)
for(x = mix; x <= max; x++, b2++)
*b2 = nodes[y][maz+miz-z][x].param1;
for(z = miz; z <= maz; z++)
for(y = miy; y <= may; y++)
for(x = mix; x <= max; x++, b2++)
*b2 = nodes[y][maz+miz-z][x].param2;
/* compress and save */
x = 1;
b2 = stbi_zlib_compress(b, (max-mix+1) * (may-miy+1) * (maz-miz+1) * 4, &i, 9);
if(b2) {
fwrite(b2, i, 1, f);
free(b2);
status = lang[SAVED];
x = 0;
}
fclose(f);
free(b);
free(tr);
free(tr2);
return x;
}
/**
* Save a PNG preview file
*/
int mts_view(int type)
{
int i, j, w, h, x, y, z, lh = 0;
int r = 0, k, n, l;
char pngfile[MAXPATHLEN], *c;
unsigned char *buff = NULL;
SDL_Surface *preview, *blk;
SDL_Rect src, dst;
FILE *f;
status = lang[ERR_PREVIEW];
mts_getbounds(0, NULL, NULL);
if(mix > max) return 1;
bound_valid = 0;
/* calculate dimensions and create images */
n = (max - mix) + (maz - miz) + 1;
w = n * dx + 16;
lh = n * dz + 32 - dz;
l = 32 - 2*(_y0 + dz);
h = lh + (may - miy) * l;
blk = SDL_CreateRGBSurfaceFrom(NULL, 32, 32, 32, 32*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
preview = SDL_CreateRGBSurface(0, w, h, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
src.x = src.y = 0; src.w = src.h = dst.w = dst.h = 32;
n = ((max-mix)+(maz-miz))/2;
/* generate preview into surface */
for(y = miy; y <= may; y++)
for(z = miz; z <= maz; z++) {
if(type) r = ((z-miz+1)*(max-mix+1)*(y-miy)/(maz-miz+1))/(may-miy);
for(x = mix; x <= max - r; x++) {
if(nodes[y][z][x].param0) {
dst.x = dx * ((x-mix) - (z-miz)) + (maz-miz) * dx;
dst.y = h - lh + dz * ((z-miz) + (x-mix)) - (y-miy) * l;
i = nodes[y][z][x].param0;
if(i && blocks[i].img) {
j = nodes[y][z][x].param2;
j %= (int)blocks[i].numpar2;
memcpy(tmpblk, blocks[i].img + j * 32 * 32 * 4, 32 * 32 * 4);
for(j = 0; j < 32 * 32 * 4; j += 4) {
k = (int)(tmpblk[j+0]) * ((x-mix)+(z-miz)) / n;
tmpblk[j+0] = k > 255 ? 255 : (k > tmpblk[j+0] / 2 ? k : tmpblk[j+0] / 2);
k = (int)(tmpblk[j+1]) * ((x-mix)+(z-miz)) / n;
tmpblk[j+1] = k > 255 ? 255 : (k > tmpblk[j+1] / 2 ? k : tmpblk[j+1] / 2);
k = (int)(tmpblk[j+2]) * ((x-mix)+(z-miz)) / n;
tmpblk[j+2] = k > 255 ? 255 : (k > tmpblk[j+2] / 2 ? k : tmpblk[j+2] / 2);
}
blk->pixels = tmpblk;
SDL_BlitSurface(blk, &src, preview, &dst);
}
}
}
}
SDL_FreeSurface(blk);
/* crop the result */
buff = preview->pixels;
for(i = miy = 0; i < preview->pitch * h; i += 4)
if(buff[i + 3]) { miy = i / preview->pitch; break; }
for(i = may = preview->pitch * preview->h - 4; i ; i -= 4)
if(buff[i + 3]) { may = i / preview->pitch; break; }
for(i = 0, mix = -1; i < w/2 && mix == -1; i++)
for(j = 0; j < h; j++)
if(buff[j * preview->pitch + i * 4 + 3]) { mix = i; break; }
for(i = w-1, max = -1; i > w/2 && max == -1; i--)
for(j = 0; j < h; j++)
if(buff[j * preview->pitch + i * 4 + 3]) { max = i; break; }
if(mix >= 0 && max > 0 && may > miy) {
blk = SDL_CreateRGBSurface(0, max-mix+1, may-miy+1, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
src.x = mix; src.y = miy;
dst.x = dst.y = 0;
src.w = dst.w = blk->w;
src.h = dst.h = blk->h;
SDL_BlitSurface(preview, &src, blk, &dst);
SDL_FreeSurface(preview);
/* write result to PNG file */
buff = (unsigned char *)stbi_write_png_to_mem(blk->pixels, blk->pitch, blk->w, blk->h, 4, &i);
SDL_FreeSurface(blk);
if(buff) {
strcpy(pngfile, mtsfile);
c = strrchr(pngfile, '.');
if(!c) c = &pngfile[strlen(pngfile)];
strcpy(c, ".png");
f = fopen(pngfile,"wb");
if(f) {
fwrite(buff, i, 1, f);
fclose(f);
status = lang[PREVIEWSAVED];
fprintf(stderr, "mtsedit: %s: %s\r\n", pngfile, status);
}
free(buff);
}
return 0;
}
SDL_FreeSurface(preview);
return 1;
}
/**
* Export a PNG blueprint file
*/
int mts_blueprint(int type)
{
int oldsmax = strmaxw, i, j, k, l, bw, w, h, x, y, z;
char pngfile[MAXPATHLEN], *s, *e, *c;
unsigned char *buff = NULL;
uint32_t *dst;
SDL_Surface *oldscr = screen;
SDL_Rect rect;
FILE *f;
status = lang[ERR_PREVIEW];
mts_getbounds(0, NULL, NULL);
if(mix > max) return 1;
bound_valid = 0;
strcpy(pngfile, mtsfile);
s = strrchr(pngfile, DIRSEP);
if(!s) s = pngfile; else s++;
e = strrchr(pngfile, '.');
if(!e) e = &pngfile[strlen(pngfile)];
/* calculate dimensions and create image */
bw = maz - miz;
h = (may - miy + 1) * (max - mix + 2) + 3;
if(!type) {
for(i = 1, j = l = 0; i < numblocks; i++)
if(blocks[i].numref) {
l += font->height;
k = mbstrlen(blocks[i].name);
if(k > j) j = k;
}
w = bw + 8 + (4 + j) * (font->width + 1);
if(l > h) h = l;
h += font->height + 5;
} else
w = bw + 3;
screen = SDL_CreateRGBSurface(0, w, h, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
memset(screen->pixels, 0, w * h * 4);
dst = (uint32_t*)screen->pixels;
if(!type) {
for(i = 0; i < w * h; i++) dst[i] = theme[THEME_BP_BG];
/* print title */
for(c = s; c < e; c++)
if(*c == '_') *c = ' ';
*e = 0; strmaxw = screen->w - 1;
i = mbstrlen(s) * (font->width+1);
sdlprint((screen->w - (i < screen->w ? i : screen->w)) / 2, 0, THEME_BP_FG, THEME_BP_BG, s);
/* print legend */
rect.x = bw + font->height; rect.y = font->height + 4; rect.w = rect.h = font->height - 2;
for(i = 1; i < numblocks; i++)
if(blocks[i].numref) {
SDL_FillRect(screen, &rect, blocks[i].color);
sdlprint(rect.x + 4 + font->height, rect.y, THEME_BP_FG, THEME_BP_BG, blocks[i].name);
rect.y += font->height;
}
i = w * (font->height + 5);
} else
i = w;
/* generate blueprint into surface */
for(y = may; y >= miy; y--, i += w) {
dst[i] = (y == gndlayer ? 0xFF0000FF : 0xFF000000);
for(x = mix; x <= max; x++) {
i++;
for(z = maz; z >= miz; z--, i++)
dst[i] = (nodes[y][z][x].param0 ? blocks[nodes[y][z][x].param0].color : 0);
if(y == gndlayer)
dst[i] = 0xFF0000FF;
i += w - bw - 2;
}
}
/* write result to PNG file */
buff = (unsigned char *)stbi_write_png_to_mem(screen->pixels, screen->pitch, screen->w, screen->h, 4, &i);
SDL_FreeSurface(screen);
screen = oldscr;
strmaxw = oldsmax;
if(buff) {
strcpy(pngfile, mtsfile);
strcpy(e, "_bp.png");
f = fopen(pngfile,"wb");
if(f) {
fwrite(buff, i, 1, f);
fclose(f);
status = lang[BPRINTSAVED];
fprintf(stderr, "mtsedit: %s: %s\r\n", pngfile, status);
}
free(buff);
return 0;
}
return 1;
}
/**
* Dump structure
*/
int mts_dump()
{
unsigned short *tr, *tr2;
int i, j, x, y, z;
tr = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
if(!tr) error(lang[ERR_MEM]);
memset(tr, 0, numblocks * sizeof(unsigned short));
tr2 = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
if(!tr2) error(lang[ERR_MEM]);
memset(tr2, 0, numblocks * sizeof(unsigned short));
j = mts_getbounds(0, tr, tr2);
if(mix > max) { free(tr); free(tr2); return 1; }
printf("-------------------------------- %s --------------------------------\r\n", mtsfile);
printf("Map (x: %d y: %d z: %d):\r\n", max-mix+1, may-miy+1, maz-miz+1);
if(savepal < 0 || savepal >= numpalettes) savepal = 0;
for(i = 0; i < j; i++)
printf("%4x: %s\r\n", i, blocks[tr[i]].blocknames && blocks[tr[i]].blocknames[savepal+3] ?
blocks[tr[i]].blocknames[savepal+3] : blocks[tr[i]].name);
for(y = miy; y <= may; y++) {
printf("\r\nLayer %d (probability %d%s):\r\n", y - miy, layerprob[y], y == gndlayer ? ", Ground level" : "");
for(z = miz; z <= maz; z++) {
if(j < 15) printf(" ");
for(x = mix; x <= max; x++) {
if(nodes[y][z][x].param0) {
if(j < 16) printf("%x", tr2[nodes[y][z][x].param0]);
else printf(" %02x", tr2[nodes[y][z][x].param0]);
} else printf(j < 16 ? "." : " ..");
}
printf(" ");
for(x = mix; x <= max; x++)
printf(" %02x", nodes[y][z][x].param1);
printf(" ");
for(x = mix; x <= max; x++)
printf(" %02x", y == gndlayer && !nodes[y][z][x].param0 ? 32 : nodes[y][z][x].param2);
printf("\r\n");
}
}
free(tr);
free(tr2);
return 0;
}
/**
* Change layer probability
*/
void mts_layerprob(int diff)
{
if(diff < 0 && (int)layerprob[currlayer] > 0) layerprob[currlayer]--;
if(diff > 0 && (int)layerprob[currlayer] < 127) layerprob[currlayer]++;
}
/**
* Rotate the entire MTS structure
*/
void mts_rotate(int ccw)
{
int nx, nz, x, y, z, o;
node_t layer[256][256];
mts_getbounds(0, NULL, NULL);
if(mix > max) return;
for(y = miy; y <= may; y++) {
memcpy(layer, nodes[y], sizeof(node_t)*256*256);
memset(nodes[y], 0, sizeof(node_t)*256*256);
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++) {
if(ccw) { nz = 255-x; nx = z; } else { nz = x; nx = 255-z; }
memcpy(&nodes[y][nz][nx], &layer[z][x], sizeof(node_t));
if(nodes[y][nz][nx].param0 && blocks[nodes[y][nz][nx].param0].rotate) {
o = nodes[y][nz][nx].param2;
if(ccw)
nodes[y][nz][nx].param2 = (nodes[y][nz][nx].param2 & ~3) | (((nodes[y][nz][nx].param2 & 3)-1) & 3);
else
nodes[y][nz][nx].param2 = (nodes[y][nz][nx].param2 & ~3) | (((nodes[y][nz][nx].param2 & 3)+1) & 3);
if(o != nodes[y][nz][nx].param2) {
hist_prepare(HIST_NODE, 0);
hist_add(y, nz, nx, nodes[y][nz][nx].param0, o, nodes[y][nz][nx].param0, nodes[y][nz][nx].param2);
hist_commit();
}
}
}
}
bound_valid = 0;
mts_getbounds(0, NULL, NULL);
}
/**
* Flip the entire MTS structure along the Z axis
*/
void mts_flip()
{
int x, y, z, o;
node_t layer[256][256];
mts_getbounds(0, NULL, NULL);
if(mix > max) return;
for(y = miy; y <= may; y++) {
memcpy(layer, nodes[y], sizeof(node_t)*256*256);
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++) {
memcpy(&nodes[y][z][x], &layer[maz-z+miz][x], sizeof(node_t));
if(nodes[y][z][x].param0 && blocks[nodes[y][z][x].param0].rotate) {
o = nodes[y][z][x].param2;
nodes[y][z][x].param2 = (nodes[y][z][x].param2 & ~3) | (((nodes[y][z][x].param2 & 3)+2) & 3);
if(o != nodes[y][z][x].param2) {
hist_prepare(HIST_NODE, 0);
hist_add(y, z, x, nodes[y][z][x].param0, o, nodes[y][z][x].param0, nodes[y][z][x].param2);
hist_commit();
}
}
}
}
bound_valid = 0;
mts_getbounds(0, NULL, NULL);
}
/**
* Insert an Y layer
*/
void mts_addy(int y, int copy)
{
int x, Y, z;
mts_getbounds(0, NULL, NULL);
if(miy > may || y < miy || y > may) return;
for(Y = miy-1 < 0 ? 0 : miy-1; Y < y; Y++)
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++)
memcpy(&nodes[Y][z][x], &nodes[Y+1][z][x], sizeof(node_t));
if(!copy)
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++)
memset(&nodes[y][z][x], 0, sizeof(node_t));
if(miy) miy--;
}
/**
* Remove an Y layer
*/
void mts_dely(int y)
{
int x, Y, z;
mts_getbounds(0, NULL, NULL);
if(miy > may || y < miy || y > may) return;
for(Y = y; Y >= miy; Y--)
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++)
memcpy(&nodes[Y][z][x], &nodes[Y-1][z][x], sizeof(node_t));
for(z = miz; z <= maz; z++)
for(x = mix; x <= max; x++)
memset(&nodes[miy][z][x], 0, sizeof(node_t));
if(miy < y) miy++;
}
/**
* Insert an X layer
*/
void mts_addx(int x, int copy)
{
int X, y, z;
mts_getbounds(0, NULL, NULL);
if(mix > max || x < mix || x > max) return;
for(y = miy; y <= may; y++)
for(z = miz; z <= maz; z++)
for(X = max+1 > 255 ? 255 : max+1; X > x ; X--)
memcpy(&nodes[y][z][X], &nodes[y][z][X-1], sizeof(node_t));
if(!copy)
for(y = miy; y <= may; y++)
for(z = miz; z <= maz; z++)
memset(&nodes[y][z][x], 0, sizeof(node_t));
if(max < 255) max++;
}
/**
* Remove an X layer
*/
void mts_delx(int x)
{
int X, y, z;
mts_getbounds(0, NULL, NULL);
if(mix > max || x < mix || x > max) return;
for(y = miy; y <= may; y++)
for(z = miz; z <= maz; z++)
for(X = x; X <= max; X++)
memcpy(&nodes[y][z][X], &nodes[y][z][X+1], sizeof(node_t));
for(y = miy; y <= may; y++)
for(z = miz; z <= maz; z++)
memset(&nodes[y][z][max], 0, sizeof(node_t));
if(max > x) max--;
}
/**
* Insert a Z layer
*/
void mts_addz(int z, int copy)
{
int x, y, Z;
mts_getbounds(0, NULL, NULL);
if(miz > maz || z < miz || z > maz) return;
for(y = miy; y <= may; y++)
for(Z = miz-1 < 0 ? 0 : miz-1; Z < z; Z++)
for(x = mix; x <= max; x++)
memcpy(&nodes[y][Z][x], &nodes[y][Z+1][x], sizeof(node_t));
if(!copy)
for(y = miy; y <= may; y++)
for(x = mix; x <= max; x++)
memset(&nodes[y][z][x], 0, sizeof(node_t));
if(miz) miz--;
}
/**
* Remove a Z layer
*/
void mts_delz(int z)
{
int x, y, Z;
mts_getbounds(0, NULL, NULL);
if(miz > maz || z < miz || z > maz) return;
for(y = miy; y <= may; y++)
for(Z = z; Z >= miz; Z--)
for(x = mix; x <= max; x++)
memcpy(&nodes[y][Z][x], &nodes[y][Z-1][x], sizeof(node_t));
for(y = miy; y <= may; y++)
for(x = mix; x <= max; x++)
memset(&nodes[y][miz][x], 0, sizeof(node_t));
if(miz < z) miz++;
}