/*
 * cmdv.c  SYSTEM35 V command
 *
 * Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
 *               1998-                           <masaki-c@is.aist-nara.ac.jp>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
*/
/* $Id: cmdv.c,v 1.21 2001/05/09 04:11:24 chikama Exp $ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include "portab.h"
#include "scheduler.h"
#include "xsystem35.h"
#include "scenario.h"
#include "ags.h"
#include "input.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

typedef struct {
	int x0Unit;
	int y0Unit;
	int nxUnit;
	int nyUnit;
	int bSpCol;
	bool fEnable;
	bool useTTP;
	int TTPunit;
} UnitMapSrcImg;

typedef struct {
	int unitWidth;     /* Unitの大きさ */
	int unitHeight;
	int patternNum;    /* パターン数 */
	int intervaltime;  /* 書換え間隔(ms) */
	int srcX;          /* 取得位置 */
	int srcY;
	int startX;        /* 表示位置 */
	int startY;
	int endX;          /* 移動先 */
	int endY;
	int saveX;         /* 背景退避位置 */
	int saveY;
	int spType;        /* スプライト方法 */
	int spCol;         /* スプライト色 */
	int state;         /* 現在の状態  0:停止 1:動 */
	int elaspCut;      /* 経過コマ数 */
	int startTime;     /* 開始時刻(ms) */
	int totalCut;      /* 全コマ数 */
	int preX;          /* 前回の位置 */
	int preY;
	int curX;          /* 現在位置 */
	int curY;
	bool draw;      /* UNITを描く？ */
	bool nomove;    /* 移動あり・なし */
	bool rewrite;   /* 画面更新の必要あり */
} VaParam;

typedef struct {
	int x;
	int y;
} VhMark;

#define UNITMAP_DISPLAY_PAGE_MAX       16  /* VSの最大ページ数 */
#define UNITMAP_VARIABLE_IMMOVALE     255  /* 歩数ペイントの移動不可能マーク */
#define UNITMAP_VARIABLE_OUTOFRANGE 65535  /* 範囲外のマーク */
#define UNITMAP_ATTRIB_DEPTH      (4)
#define UNITMAP_ATTRIB_UNITNUMBER (0)
#define UNITMAP_ATTRIB_VARIABLE   (1)
#define UNITMAP_ATTRIB_WALKPAINT  (2)
#define UNITMAP_ATTRIB_WALKRESULT (3)

/* UnitMAP 全体へのポインタ */
static int *UnitMap = NULL;
/* VC command */
static int nPageNum;
static int x0Map;
static int y0Map;
static int cxMap;
static int cyMap;
static int cxUnit;
static int cyUnit;
/* VH command */
static VhMark *vh_src, *vh_dst, *_vh_src, *_vh_dst;
static int     vh_cnt_src, vh_cnt_dst;
/* VP command */
static UnitMapSrcImg *srcimg;
/* VA command state */
#define VA_STOPPED     0
#define VA_RUNNING     1
#define VACMD_MAX 20                      /* Panyoで18まで */
static  VaParam VAcmd[VACMD_MAX];         
static  bool inAnimation      = false; /* 画面更新中 */

/* UnitMap 各種マクロ */
#define MAPSIZE_PER_ATTRIB (cxMap * cyMap)
#define MAPSIZE_PER_PAGE   ((MAPSIZE_PER_ATTRIB) * UNITMAP_ATTRIB_DEPTH)

#define UNITMAP_UNITNUMBER(page,x,y) (UnitMap + (page) * (MAPSIZE_PER_PAGE) + UNITMAP_ATTRIB_UNITNUMBER * (MAPSIZE_PER_ATTRIB) + (y) * cxMap + (x))
#define UNITMAP_VARIABLE(page,x,y)   (UnitMap + (page) * (MAPSIZE_PER_PAGE) + UNITMAP_ATTRIB_VARIABLE *   (MAPSIZE_PER_ATTRIB) + (y) * cxMap + (x))
#define UNITMAP_WALKPAINT(page,x,y)  (UnitMap + (page) * (MAPSIZE_PER_PAGE) + UNITMAP_ATTRIB_WALKPAINT *  (MAPSIZE_PER_ATTRIB) + (y) * cxMap + (x))
#define UNITMAP_WALKRESULT(page,x,y) (UnitMap + (page) * (MAPSIZE_PER_PAGE) + UNITMAP_ATTRIB_WALKRESULT * (MAPSIZE_PER_ATTRIB) + (y) * cxMap + (x))

#define UNITMAP_UNITNUMBER_PAGETOP(page) UNITMAP_UNITNUMBER((page),0,0)
#define UNITMAP_VARIABLE_PAGETOP(page)   UNITMAP_VARIABLE((page),0,0)
#define UNITMAP_WALKPAINT_PAGETOP(page)  UNITMAP_WALKPAINT((page),0,0)
#define UNITMAP_WALKRESULT_PAGETOP(page) UNITMAP_WALKRESULT((page),0,0)

static bool vh_checkImmovableArea(int page, int x, int y, int w, int h);
static void    vh_append_pos(int x, int y);
static void    vh_copy_to_src();
static void    vh_check_udlr(int n, int nPage, int x, int y);
static void    va_drawUnit(int no);
static void    va_restoreUnit(int no);
static void    va_updateUnit(int i);
static void    va_updatePreArea(int i);
static void    va_animationAlone(int i);
static void    va_update();

void commandVC() { /* from Rance4 */
	nPageNum = getCaliValue();
	x0Map    = getCaliValue();
	y0Map    = getCaliValue();
	cxMap    = getCaliValue();
	cyMap    = getCaliValue();
	cxUnit   = getCaliValue();
	cyUnit   = getCaliValue();
	
	TRACE("VC %d,%d,%d,%d,%d,%d,%d:",nPageNum, x0Map, y0Map, cxMap, cyMap, cxUnit, cyUnit);
	
	if (NULL != UnitMap ) {
		free(UnitMap);
		free(srcimg);
		free(_vh_src);
		free(_vh_dst);
	}
	
	if (nPageNum > UNITMAP_DISPLAY_PAGE_MAX) {
		WARNING("VC nPageNum too big %d", nPageNum);
		sysVar[0] = 0;
		return;
	}
	
	UnitMap = (int *)calloc(cxMap * cyMap * nPageNum * UNITMAP_ATTRIB_DEPTH, sizeof(int));
	srcimg  = (UnitMapSrcImg *)calloc(nPageNum, sizeof(UnitMapSrcImg));
	
	if (NULL == UnitMap || NULL == srcimg) {
		NOMEMERR();
	}
	
	_vh_src = (VhMark *)calloc(3 * (cxMap + cyMap), sizeof(VhMark));
	_vh_dst = (VhMark *)calloc(3 * (cxMap + cyMap), sizeof(VhMark));
	if (NULL == _vh_src || NULL == _vh_dst) {
		NOMEMERR();
	}
	sysVar[0] = 1;
}

void commandVP() { /* from T2 */
	int nPage  = getCaliValue();
	int x0Unit = getCaliValue();
	int y0Unit = getCaliValue();
	int nxUnit = getCaliValue();
	int nyUnit = getCaliValue();
	int bSpCol = getCaliValue();
	
	TRACE("VP %d,%d,%d,%d,%d,%d:",nPage, x0Unit, y0Unit, nxUnit, nyUnit, bSpCol);
	
	if (nPage >= nPageNum) {
		WARNING("VP nPage too large %d", nPage);
		return;
	}
	
	srcimg[nPage].x0Unit = x0Unit;
	srcimg[nPage].y0Unit = y0Unit;
	srcimg[nPage].nxUnit = nxUnit;
	srcimg[nPage].nyUnit = nyUnit;
	srcimg[nPage].bSpCol = bSpCol;
	srcimg[nPage].useTTP = false;
	srcimg[nPage].fEnable = true;
}

void commandVS() { /* from Rance4 */
	int nPage = getCaliValue();
	int nType = getCaliValue();
	int x     = getCaliValue();
	int y     = getCaliValue();
	int wData = getCaliValue();
	
	TRACE("VS %d,%d,%d,%d,%d:",nPage, nType, x, y, wData);
	
	if (nPage >= nPageNum) {
		WARNING("VS nPage too large %d", nPage);
		return;
	}
	
	if (x >= cxMap || x < 0) {
		WARNING("VS x out of range %d", x);
		return;
	}
	
	if (y >= cyMap || y < 0) {
		WARNING("VS y out of range %d", y);
		return;
	}
	if (wData < 0) {
		WARNING("VS wData illegal value %d", wData);
		return;
	}

	/* どうやら、 sysVar[0] に値を返してはいけないらしい thanx 村田さん*/
	switch(nType) {
	case 1:
		/* sysVar[0] = *UNITMAP_UNITNUMBER(nPage, x, y); */
		*UNITMAP_UNITNUMBER(nPage, x, y) = wData;
		break;
	case 2:
		/* sysVar[0] = *UNITMAP_VARIABLE(nPage,   x, y); */
		*UNITMAP_VARIABLE(nPage,   x, y) = wData;
		break;
	case 3:
		/* sysVar[0] = *UNITMAP_WALKPAINT(nPage,  x, y); */
		*UNITMAP_WALKPAINT(nPage,  x, y) = wData;
		break;
	case 4:
		/* sysVar[0] = *UNITMAP_WALKRESULT(nPage, x, y); */
		*UNITMAP_WALKRESULT(nPage, x, y) = wData;
		break;
	default:
		WARNING("VS unknown type %d", nType);
	}
}

void commandVG() { /* from Rance4 */
	int nPage = getCaliValue();
	int nType = getCaliValue();
	int x = getCaliValue();
	int y = getCaliValue();
	
	TRACE("VG %d,%d,%d,%d:",nPage, nType, x, y);

	if (nPage >= nPageNum) {
		WARNING("VG nPage too large %d", nPage);
		return;
	}
	
	if (x >= cxMap || x < 0) {
		NOTICE("VG x out of range %d", x);
		sysVar[0] = UNITMAP_VARIABLE_OUTOFRANGE;
		return;
	}
	
	if (y >= cyMap || y < 0) {
		NOTICE("VG y out of range %d", y);
		sysVar[0] = UNITMAP_VARIABLE_OUTOFRANGE;
		return;
	}
	
	switch(nType) {
	case 1:
		sysVar[0] = *UNITMAP_UNITNUMBER(nPage, x, y); break;
		break;
	case 2:
		sysVar[0] = *UNITMAP_VARIABLE(nPage,   x, y); break;
	case 3:
		sysVar[0] = *UNITMAP_WALKPAINT(nPage,  x, y); break;
	case 4:
		sysVar[0] = *UNITMAP_WALKRESULT(nPage, x, y); break;
	default:
		WARNING("VG unknown type %d", nType);
	}
}

void commandVH() { /* from Rance4 */
	int nPage  = getCaliValue();
	int x      = getCaliValue();
	int y      = getCaliValue();
	int width  = getCaliValue();
	int height = getCaliValue();
	int _max   = getCaliValue();
	int xx, yy, i, n;
	int maxfoot, lmtx, lmty, lmtw, lmth;
	
	TRACE("VH %d,%d,%d,%d,%d,%d:",nPage, x, y, width, height, _max);
	
	if (nPage >= nPageNum) {
		WARNING("VH nPage too large %d", nPage);
		return;
	}
	if (x >= cxMap || x < 0) {
		WARNING("VH x out of range %d", x);
		return;
	}
	
	if (y >= cyMap || y < 0) {
		WARNING("VH y out of range %d", y);
		return;
	}

	if (_max == 0) {
		for (yy = 0; yy < cyMap; yy++) {
			for (xx = 0; xx < cxMap; xx++) {
				*UNITMAP_WALKRESULT(nPage, xx , yy) = UNITMAP_VARIABLE_IMMOVALE;;
			}
		}
		/* 自分自身は 0 */
		*UNITMAP_WALKRESULT(nPage, x , y) = 0;
		return;
	}
	/*
	for (yy = 0; yy < cyMap; yy++) {
		for (xx = 0; xx < cxMap; xx++) {
			*UNITMAP_WALKPAINT(nPage, xx, yy) == 255 ? putchar('*') : putchar(' '+ *UNITMAP_WALKPAINT(nPage, xx, yy)); 
		}
		printf("\n");
	}
	*/
	maxfoot = min(_max, UNITMAP_VARIABLE_IMMOVALE);
        lmtx = max(0, x - maxfoot);
        lmty = max(0, y - maxfoot);
        lmtw = min(maxfoot + cxMap - x, min(cxMap, 2 * maxfoot + 1));
        lmth = min(maxfoot + cyMap - y, min(cyMap, 2 * maxfoot + 1));
  
	for (yy = 0; yy < cyMap; yy++) {
		for (xx = 0; xx < cxMap; xx++) {
			/* 最大歩数の外は不可 */
			if (maxfoot < (abs(yy-y)+abs(xx-x))) {
				*UNITMAP_WALKRESULT(nPage, xx , yy) = UNITMAP_VARIABLE_IMMOVALE;
			} else if (*UNITMAP_WALKPAINT(nPage, xx, yy) >= UNITMAP_VARIABLE_IMMOVALE) {
				/* 障害物なら */
				*UNITMAP_WALKRESULT(nPage, xx , yy) = UNITMAP_VARIABLE_IMMOVALE;
			} else {
				/* いずれでもないときは 0 でならす */
				*UNITMAP_WALKRESULT(nPage, xx , yy) = 0;
			}
		}
	}
	
	/* 移動不可領域の決定 */
	if (width > 1 || height > 1) {
		for (yy = 0; yy < lmth; yy++) {
			for (xx = 0; xx < lmtw; xx++) {
				if (vh_checkImmovableArea(nPage, xx + lmtx, yy + lmty, width, height)) {
					*UNITMAP_WALKRESULT(nPage, xx + lmtx , yy + lmty) = UNITMAP_VARIABLE_IMMOVALE;
				}
			}
		}
	}

	/* list の初期化 */
	vh_src = _vh_src;
	vh_dst = _vh_dst;
	/* 初期位置の設定 */
	vh_src -> x = x;
	vh_src -> y = y;
	vh_cnt_src = n = 1;
	vh_cnt_dst = 0;
	
	while(true) {
		for (i = 0; i < vh_cnt_src; i++) {
			vh_check_udlr(n, nPage, (vh_src + i)->x, (vh_src + i)->y);
		}
		if (vh_cnt_dst == 0) break;  /* 未踏地が無くなったら終了 */
		vh_copy_to_src();
		if (n >= maxfoot)    break;  /* maxfoot までマークしたら終了 */
		n++;
	}
	
	/* 閉領域を255に */
	for (yy = 0; yy < lmth; yy++) {
		for (xx = 0; xx < lmtw; xx++) {
			if (*UNITMAP_WALKRESULT(nPage, xx + lmtx, yy + lmty) == 0) {
				*UNITMAP_WALKRESULT(nPage, xx + lmtx , yy + lmty) = UNITMAP_VARIABLE_IMMOVALE;
			}
		}
	}
	
	/* 自分自身は 0 */
	*UNITMAP_WALKRESULT(nPage, x , y) = 0;
	
	/*
	for (yy = 0; yy < cyMap; yy++) {
		for (xx = 0; xx < cxMap; xx++) {
			*UNITMAP_WALKRESULT(nPage, xx, yy) == 255 ? putchar('*') :putchar('0' + *UNITMAP_WALKRESULT(nPage, xx, yy)); 
		}
		printf("\n");
	}
	*/
}

void commandVF() { /* from Panyo */
	int x, y, i;
	int unit, unit_x, unit_y;
	UnitMapSrcImg *img;
	
	for (i = 0; i < nPageNum; i++) {
		img = &srcimg[i];
		
		if (!img->fEnable) continue;
		
		for (y = 0; y < cyMap; y++) {
			for (x = 0; x < cxMap; x++) {
				unit = *UNITMAP_UNITNUMBER(i, x, y);
				if (img->useTTP && img->TTPunit == unit) continue;
				// printf("i = %d, x = %d, y = %d, unit no = %d\n", i, x, y,unit);
				unit_x = unit % img->nxUnit;
				unit_y = unit / img->nxUnit;
				if (img->bSpCol == 256) {
					ags_copyArea(img->x0Unit + cxUnit * unit_x,
						     img->y0Unit + cyUnit * unit_y,
						     cxUnit, cyUnit,
						     x0Map + x * cxUnit,
						     y0Map + y * cyUnit);
				} else {
					ags_copyAreaSP(img->x0Unit + cxUnit * unit_x,
						       img->y0Unit + cyUnit * unit_y,
						       cxUnit, cyUnit,
						       x0Map + x * cxUnit,
						       y0Map + y * cyUnit, img->bSpCol);
				}
			}
		}
	}
	
	ags_updateArea(x0Map, y0Map, cxMap * cxUnit, cyMap * cyUnit);
	TRACE("VF:");
}

void commandVV() { /* from T2 */
	int nPage   = getCaliValue();
	int fEnable = getCaliValue();
	
	TRACE("VV %d,%d:",nPage, fEnable);
	
	srcimg[nPage].fEnable = fEnable == 1;
}

void commandVR() { /* from Rance4 */
	int nPage = getCaliValue();
	int nType = getCaliValue();
	int *var  = getCaliVariable();
	int *dst;
	
	TRACE("VR %d,%d,%p:",nPage, nType, var);
	
	if (nPage >= nPageNum) {
		WARNING("VR nPage too large %d", nPage);
		return;
	}
	
	switch(nType) {
	case 1:
		dst = UNITMAP_UNITNUMBER_PAGETOP(nPage); break;
	case 2:
		dst = UNITMAP_VARIABLE_PAGETOP(nPage); break;
	case 3:
		dst = UNITMAP_WALKPAINT_PAGETOP(nPage); break;
	case 4:
		dst = UNITMAP_WALKRESULT_PAGETOP(nPage); break;
	default:
		WARNING("VR unknown type %d", nType);
		return;
	}
	
	memcpy(dst, var, sizeof(int) * MAPSIZE_PER_ATTRIB);
}

void commandVW() { /* from Rance4 */
	int nPage = getCaliValue();
	int nType = getCaliValue();
	int *var  = getCaliVariable();
	int *src;
	
	TRACE("VW %d,%d,%p:",nPage, nType, var);
	
	if (nPage >= nPageNum) {
		WARNING("VW nPage too large %d", nPage);
		return;
	}
	
	switch(nType) {
	case 1:
		src = UNITMAP_UNITNUMBER_PAGETOP(nPage); break;
	case 2:
		src = UNITMAP_VARIABLE_PAGETOP(nPage); break;
	case 3:
		src = UNITMAP_WALKPAINT_PAGETOP(nPage); break;
	case 4:
		src = UNITMAP_WALKRESULT_PAGETOP(nPage); break;
	default:
		WARNING("VW unknown type %d", nType);
		return;
	}
	
	memcpy(var, src, sizeof(int) * MAPSIZE_PER_ATTRIB);
}

void commandVE() { /* from T2 */
	int x       = getCaliValue();
	int y       = getCaliValue();
	int width   = getCaliValue();
	int height  = getCaliValue();
	int out_ptn = getCaliValue();
	int flag    = getCaliValue();
	int xx, yy, i;
	int unit, unit_x, unit_y;
	UnitMapSrcImg *img;
	
	TRACE("VE %d,%d,%d,%d,%d,%d:", x, y, width, height, out_ptn, flag);
	if (flag == 1) {
		WARNING("VE flag1 is not yet");
	}
	
	for (i = 0; i < nPageNum; i++) {
		img = &srcimg[i];
		/* VV による */
		if (!img->fEnable) continue;
		
		for (yy = y; yy < (y+height); yy++) {
			for (xx = x; xx < (x+width); xx++) {
				if (xx >= cxMap || yy >= cyMap) {
					unit = out_ptn;
				} else {
					unit = *UNITMAP_UNITNUMBER(i, xx, yy);
				}

				if (img->useTTP && img->TTPunit == unit) continue;
				//printf("i = %d, xx = %d, yy = %d, unit no = %d\n", i, xx, yy,unit);
				unit_x = unit % img->nxUnit;
				unit_y = unit / img->nxUnit;
				ags_copyAreaSP(img->x0Unit + cxUnit * unit_x,
					       img->y0Unit + cyUnit * unit_y,
					       cxUnit, cyUnit,
					       x0Map + (xx-x) * cxUnit,
					       y0Map + (yy-y) * cyUnit, img->bSpCol);
			}
		}
	}
	
	ags_updateArea(x0Map, y0Map, cxMap * cxUnit, cyMap * cyUnit);
}

void commandVZ() { /* from T2 */
	int p1 = sl_getc();
	int p2 = getCaliValue();
	int p3 = getCaliValue();
	
	TRACE("VZ %d,%d,%d:", p1, p2, p3);
	
	switch(p1) {
	case 0:
		srcimg[p2].useTTP = false; break;
	case 1:
		srcimg[p2].useTTP = true;
		srcimg[p2].TTPunit = p3;
		break;
	case 2:
		x0Map = p2; y0Map = p3;
		break;
	case 3:
		cxUnit = p2; cyUnit = p3;
		break;
	default:
		WARNING("unknown VZ %d:", p1);
	}
}

void commandVX() { /* from T2 */
	int p1 = getCaliValue();
	int p2 = getCaliValue();
	int p3 = getCaliValue();
	int p4 = getCaliValue();
	
	TRACE("VX %d,%d,%d,%d:", p1, p2, p3, p4);
	
	switch(p1) {
	case 0:
		srcimg[p2].x0Unit = p3;
		srcimg[p2].y0Unit = p4; break;
	case 1:
		srcimg[p2].nxUnit = p3;
		srcimg[p2].nyUnit = p4; break;
	case 2:
		srcimg[p2].bSpCol = p3; break;
	case 3:
		/* かえるにょ国にょアリスで問題が出たのでとりあえず */
		// commandVF();
		break;
	default:
		WARNING("VX unknown command %d:", p1);
	}
}

void commandVT() { /* from Panyo */
	int sp     = getCaliValue();
	int sa     = getCaliValue();
	int sx     = getCaliValue();
	int sy     = getCaliValue();
	int width  = getCaliValue();
	int height = getCaliValue();
	int dp     = getCaliValue();
	int da     = getCaliValue();
	int dx     = getCaliValue();
	int dy     = getCaliValue();
	int x, y, u;
	
	for (y = 0; y < height; y++) {
		for (x = 0; x < width; x++) {
			switch(sa) {
			case 1:
				u = *UNITMAP_UNITNUMBER(sp, sx + x, sy + y); break;
			case 2:
				u = *UNITMAP_VARIABLE(sp, sx + x, sy + y)  ; break;
			case 3:
				u = *UNITMAP_WALKPAINT(sp, sx + x, sy + y) ; break;
			case 4:
				u = *UNITMAP_WALKRESULT(sp, sx + x, sy + y); break;
			default:
				WARNING("VT unknown type %d", sa); u = 0;
			}
			switch(da) {
			case 1:
				*UNITMAP_UNITNUMBER(dp, dx + x, dy + y) = u; break;
			case 2:
				*UNITMAP_VARIABLE(dp, dx + x, dy + y)   = u; break;
			case 3:
				*UNITMAP_WALKPAINT(dp, dx + x, dy + y)  = u; break;
			case 4:
				*UNITMAP_WALKRESULT(dp, dx + x, dy + y) = u; break;
			}
		}
	}
	
	TRACE("VT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d:", sp, sa, sx, sy, width, height, dp, da, dx, dy);
}

void commandVB() {
	int page   = getCaliValue();
	int type   = getCaliValue();
	int x_pos  = getCaliValue();
	int y_pos  = getCaliValue();
	int x_size = getCaliValue();
	int y_size = getCaliValue();
	int data   = getCaliValue();
	
	TRACE_UNIMPLEMENTED("VB %d,%d,%d,%d,%d,%d,%d:", page, type, x_pos, y_pos, x_size, y_size, data);
}

void commandVIC() { /* from Panyo */
	int sx     = getCaliValue();
	int sy     = getCaliValue();
	int width  = getCaliValue();
	int height = getCaliValue();
	int unit, unit_x, unit_y;
	int x, y, i;
	UnitMapSrcImg *img;

	TRACE("VIC %d,%d,%d,%d:", sx, sy, width, height);
	
	for (i = 0; i < nPageNum; i++) {
		img = &srcimg[i];
		if (!img->fEnable) continue;
		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++) {
				unit = *UNITMAP_UNITNUMBER(i, sx + x, sy + y);
				if (img->useTTP && img->TTPunit == unit) continue;
				//printf("i = %d, xx = %d, yy = %d, unit no = %d\n", i, xx, yy, unit);
				unit_x = unit % img->nxUnit;
				unit_y = unit / img->nxUnit;
				ags_copyAreaSP(img->x0Unit + cxUnit * unit_x,
					       img->y0Unit + cyUnit * unit_y,
					       cxUnit, cyUnit,
					       x0Map + (sx + x) * cxUnit,
					       y0Map + (sy + y) * cyUnit, img->bSpCol);
			}
		}
	}
	ags_updateArea(x0Map + sx * cxUnit, y0Map + sy * cyUnit, width * cxUnit, height * cyUnit);	
}

void commandVIP() {
	int x      = getCaliValue();
	int y      = getCaliValue();
	int width  = getCaliValue();
	int height = getCaliValue();
	
	TRACE_UNIMPLEMENTED("VIP %d,%d,%d,%d:", x, y, width, height);
}

void commandVJ() {
	/* 重み付き歩数ペイント */
	int page  = getCaliValue();
	int x     = getCaliValue();
	int y     = getCaliValue();
	int max   = getCaliValue();
	
	TRACE_UNIMPLEMENTED("VJ %d,%d,%d,%d:", page, x, y, max);
}

void commandVA() { /* from Panyo */
	int no = sl_getc();
	int p1 = getCaliValue();
	int p2, p3;
	int *var1, *var2;
	
	if (no >= 10) {
		var1 = getCaliVariable();
		var2 = getCaliVariable();
		TRACE("VA %d,%d,%p,%p:", no, p1, var1, var2);
	} else {
		p2 = getCaliValue();
		p3 = getCaliValue();
		TRACE("VA %d,%d,%d,%d:", no, p1, p2, p3);
	}
	
	if (p1 > VACMD_MAX) {
		WARNING("VA p1 is too lagrge %d", p1);
		return;
	}
	
	p1--;
	switch(no) {
	case 0:
		if (p2 == 0) {
			/* 停止 */
			inAnimation = true;
			VAcmd[p1].state = VA_STOPPED;
			VAcmd[p1].draw  = true;
			if (p3 == 0) {
				/* ユニット消し */
				va_restoreUnit(p1);
				va_updatePreArea(p1);
			} else {
				/* ユニット書く */
				va_drawUnit(p1);
				va_updateUnit(p1);
			}
			VAcmd[p1].draw  = false;
		} else {
			/* 開始 (p3=コマ数,0:無限(開始位置==終了位置のとき))*/
			inAnimation = true;
			VAcmd[p1].elaspCut  = 0;
			VAcmd[p1].startTime = sys_get_ticks();
			VAcmd[p1].totalCut  = p3;
			
			if (p3 == 0) {
				VAcmd[p1].nomove = true;
			} else {
				if (VAcmd[p1].startX == VAcmd[p1].endX && VAcmd[p1].startY == VAcmd[p1].endY) {
					VAcmd[p1].nomove = true;
				} else {
					VAcmd[p1].nomove = false;
				}
			}
			/* animation start */
			nact->is_va_animation = true;
	
			VAcmd[p1].state   = VA_RUNNING;
			VAcmd[p1].draw    = true;
			va_drawUnit(p1);
			va_updateUnit(p1);
			if (p2 == 2) {
				VAcmd[p1].rewrite = true;
				/* キー抜け無し ,p3=0は指定不可 */
				while(VAcmd[p1].state == VA_RUNNING) {
					va_animationAlone(p1);
					sys_sleep(10);
				}
				va_drawUnit(p1);
				va_updateUnit(p1);
			} else if (p2 == 3) {
				/* キー抜けあり ,p3=0は指定不可*/
				int key = 0;
				VAcmd[p1].rewrite = true;
				while(VAcmd[p1].state == VA_RUNNING) {
					va_animationAlone(p1);
					sys_sleep(10);
					key = sys_getInputInfo();
					if (key != 0) {
						sysVar[0] = key;
						break;
					}
					
				}
				va_drawUnit(p1);
				va_updateUnit(p1);
			} else {
				/* すぐに制御を戻す */
			}
		}
		break;
	case 1:
		/* 表示位置 */
		VAcmd[p1].curX = VAcmd[p1].preX = VAcmd[p1].startX = p2;
		VAcmd[p1].curY = VAcmd[p1].preY = VAcmd[p1].startY = p3; break;
	case 2:
		/* 移動先(終ったら自動的に停止 */
		VAcmd[p1].endX = p2;
		VAcmd[p1].endY = p3; break;
	case 3:
		/* サイズ */
		VAcmd[p1].unitWidth  = p2;
		VAcmd[p1].unitHeight = p3; break;
	case 4:
		/* パターン数・描替間隔(1/100sec) */
		VAcmd[p1].patternNum   = p2;
		VAcmd[p1].intervaltime = p3 * 10; break;
	case 5:
		/* 取得位置 */
		VAcmd[p1].srcX = p2;
		VAcmd[p1].srcY = p3; break;
	case 6:
		/* 背景退避位置 */
		VAcmd[p1].saveX = p2;
		VAcmd[p1].saveY = p3; break;
	case 7:
		/* スプライト方法・色 */
		VAcmd[p1].spType = p2;             /* p2=0:通常コピー , p2=1:色指定スプライト, p2=2:影データスプライト */
		VAcmd[p1].spCol  = p3; break;
	case 10:
		/* 状態取得(var1=0:停止1:動,var2=番号) */
		*var1 = VAcmd[p1].state == 0 ? 0 : 1;
		*var2 = VAcmd[p1].elaspCut;
		scheduler_on_event(SCHEDULER_EVENT_VA_STATUS_CHECK);
		break;
	case 11:
		/* 位置取得 */
		*var1 = VAcmd[p1].curX;
		*var2 = VAcmd[p1].curY;
		scheduler_on_event(SCHEDULER_EVENT_VA_STATUS_CHECK);
		break;
	default:
		WARNING("Unknown VA command %d", no);
	}
}
	
static bool vh_checkImmovableArea(int page, int x, int y, int w, int h) {
	int _x, _y;
	
	if (x + w > cxMap) return true; 
	
	if (y + h > cyMap) return true; 

	for (_y = y; _y < (y+h); _y++) {
		for (_x = x; _x < (x+w); _x++) {
			if (*UNITMAP_WALKRESULT(page, _x, _y) == 255) return true;
		}
	}
	
	return false;
}

static void vh_append_pos(int x, int y) {
	/* x, y を list に追加 */
	(vh_dst + vh_cnt_dst)->x = x;
	(vh_dst + vh_cnt_dst)->y = y;
	vh_cnt_dst++;
}

static void vh_copy_to_src() {
	VhMark *tmp;

	/* src list と dst list の入れ換え */
	tmp    = vh_src;
	vh_src = vh_dst;
	vh_dst = tmp;

	vh_cnt_src = vh_cnt_dst;
	vh_cnt_dst = 0;
}

static void vh_check_udlr(int n, int nPage, int x, int y) {
	/* 上下左右が未踏地なら値をセットしてlistに追加 */
	if (x > 0) {
                if (*UNITMAP_WALKRESULT(nPage, x -1, y) == 0) {
                        *UNITMAP_WALKRESULT(nPage, x -1, y) = n;
                        vh_append_pos(x -1, y);
                }
        }
        if (x < (cxMap-1)) {
                if (*UNITMAP_WALKRESULT(nPage, x +1, y) == 0) {
                        *UNITMAP_WALKRESULT(nPage, x +1, y) = n;
                        vh_append_pos(x +1, y);
                }
        }
        if (y > 0) {
                if (*UNITMAP_WALKRESULT(nPage, x, y -1) == 0) {
                        *UNITMAP_WALKRESULT(nPage, x, y -1) = n;
                        vh_append_pos(x, y -1);
                }
        }
        if (y < (cyMap-1)) {
                if (*UNITMAP_WALKRESULT(nPage, x, y +1) == 0) {
                        *UNITMAP_WALKRESULT(nPage, x, y +1) = n;
                        vh_append_pos(x, y +1);
                }
	}
}

static void va_drawUnit(int no) {
	int unitno = VAcmd[no].elaspCut % VAcmd[no].patternNum;
	int unitX = unitno % 10; /* し〜らないっと :-p */
	int unitY = unitno / 10;

	/* save */
	ags_copyArea(VAcmd[no].curX, VAcmd[no].curY, VAcmd[no].unitWidth, VAcmd[no].unitHeight, 
		     VAcmd[no].saveX, VAcmd[no].saveY);
	/* drawUnit */
	if (VAcmd[no].spType == 0) {
		ags_copyArea(VAcmd[no].srcX + VAcmd[no].unitWidth * unitX,
			     VAcmd[no].srcY + VAcmd[no].unitHeight * unitY,
			     VAcmd[no].unitWidth, VAcmd[no].unitHeight, 
			     VAcmd[no].curX, VAcmd[no].curY);
	} else if (VAcmd[no].spType == 1) {
		ags_copyAreaSP(VAcmd[no].srcX + VAcmd[no].unitWidth * unitX,
			       VAcmd[no].srcY + VAcmd[no].unitHeight * unitY,
			       VAcmd[no].unitWidth, VAcmd[no].unitHeight, 
			       VAcmd[no].curX, VAcmd[no].curY,
			       VAcmd[no].spCol);
	}
}

static void va_restoreUnit(int no) {
	ags_copyArea(VAcmd[no].saveX, VAcmd[no].saveY, VAcmd[no].unitWidth, VAcmd[no].unitHeight, 
		     VAcmd[no].preX, VAcmd[no].preY);
}

static void va_updateUnit(int i) {
	ags_updateArea(VAcmd[i].curX, VAcmd[i].curY, VAcmd[i].unitWidth, VAcmd[i].unitHeight);
}

static void va_updatePreArea(int i) {
	ags_updateArea(VAcmd[i].preX, VAcmd[i].preY, VAcmd[i].unitWidth, VAcmd[i].unitHeight);
}

void va_animation(void) {
	int i;
	int x, y, w, h;

	va_update();

	inAnimation = true;
	
	for (i = 0; i < VACMD_MAX; i++) {
		if (VAcmd[i].state == VA_STOPPED) continue;
		if (!VAcmd[i].rewrite)            continue;
		if (VAcmd[i].draw) va_restoreUnit(i);
		if (VAcmd[i].draw) va_drawUnit(i);
		x = min(VAcmd[i].curX, VAcmd[i].preX);
		y = min(VAcmd[i].curY, VAcmd[i].preY);
		w = max(VAcmd[i].curX + VAcmd[i].unitWidth, VAcmd[i].preX + VAcmd[i].unitWidth) - x; 
		h = max(VAcmd[i].curY + VAcmd[i].unitHeight, VAcmd[i].preY + VAcmd[i].unitHeight) - y; 
		ags_updateArea(x, y, w, h);
		// printf("x = %d, y = %d, w = %d, h = %d\n", x, y, w, h);
		VAcmd[i].rewrite = false;
	}
	
	inAnimation = false;
}
	
static void va_animationAlone(int i) {
	int x, y, w, h; /* update region */

	va_update();

	if (!VAcmd[i].rewrite) return;

	inAnimation = true;
	
	va_restoreUnit(i);
	va_drawUnit(i);

	x = min(VAcmd[i].curX, VAcmd[i].preX);
	y = min(VAcmd[i].curY, VAcmd[i].preY);
	w = max(VAcmd[i].curX + VAcmd[i].unitWidth, VAcmd[i].preX + VAcmd[i].unitWidth) - x; 
	h = max(VAcmd[i].curY + VAcmd[i].unitHeight, VAcmd[i].preY + VAcmd[i].unitHeight) - y; 
	ags_updateArea(x, y, w, h);
	
	VAcmd[i].rewrite = false;
	inAnimation = false;
}

static void va_update() {
	bool proceeding = false;
	int i;
	int curTime = sys_get_ticks();
	
	for (i = 0; i < VACMD_MAX; i++) {
		if (VAcmd[i].state == VA_RUNNING) {
			proceeding = true;
			int cut = (curTime - VAcmd[i].startTime) / VAcmd[i].intervaltime;
			
			if (cut > VAcmd[i].elaspCut) {
				/* まだ更新していない場合はskip */
				if (VAcmd[i].rewrite) continue;
				VAcmd[i].rewrite = true;
				VAcmd[i].elaspCut++;
				/* 古い場所 */
				VAcmd[i].preX = VAcmd[i].curX;
				VAcmd[i].preY = VAcmd[i].curY;
				/* 全コマ終ったら終了 */
				if (VAcmd[i].elaspCut >= VAcmd[i].totalCut) {
					VAcmd[i].state = VA_STOPPED;
					continue;
				}
				/* 移動なしの場合は skip */
				if (VAcmd[i].nomove)  continue;
				/* 新しい場所 */
				VAcmd[i].curX = VAcmd[i].startX + VAcmd[i].elaspCut * (VAcmd[i].endX - VAcmd[i].startX) / VAcmd[i].totalCut;
				VAcmd[i].curY = VAcmd[i].startY + VAcmd[i].elaspCut * (VAcmd[i].endY - VAcmd[i].startY) / VAcmd[i].totalCut;
 			}
		}
	}
	/* 更新するものが無い場合は、タイマーを止めて、アニメーションストップ */
	if (!proceeding) {
		nact->is_va_animation = false;
	}
}

void va_reset(void) {
	free(UnitMap);
	UnitMap = NULL;

	nPageNum = 0;
	x0Map = 0;
	y0Map = 0;
	cxMap = 0;
	cyMap = 0;
	cxUnit = 0;
	cyUnit = 0;

	free(_vh_src);
	free(_vh_dst);
	_vh_src = vh_src = NULL;
	_vh_dst = vh_dst = NULL;
	vh_cnt_src = 0;
	vh_cnt_dst = 0;

	free(srcimg);
	srcimg = NULL;

	memset(VAcmd, 0, sizeof(VAcmd));
	inAnimation = false;
}
