Files
kichikuou_system3-sdl2/src/sys/nact_sys1.cpp
T
kichikuou 1a53c50c75 Rework NACT command tracing
- Now tracing is not a build-time config but a runtime option (`-trace`)
- One command per line, so that traces are not mixed with other outputs
- Send traces to DAP client
- Fix garbled characters in non-SJIS environments
2025-01-02 16:53:01 +09:00

968 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
ALICE SOFT SYSTEM1 for Win32
D.P.S. - Dream Program System
Intruder -桜屋敷の探索-
Little Vampire
あぶないてんぐ伝説
クレセントムーンがぁる
婦警さん物語VX
[ NACT - command ]
*/
#include "nact.h"
#include <algorithm>
#include "ags.h"
#include "mako.h"
#include "msgskip.h"
#include "game_id.h"
#include "../fileio.h"
#include "encoding.h"
#include "texthook.h"
#define WAIT(tm) \
{ \
Uint32 dwTime = SDL_GetTicks() + (tm); \
for(;;) { \
if(terminate) { \
return; \
} \
if(dwTime <= SDL_GetTicks()) { \
break; \
} \
sys_sleep(16); \
} \
}
#define WAIT_KEYQUIT(tm) \
{ \
Uint32 dwTime = SDL_GetTicks() + (tm); \
for(;;) { \
if(terminate) { \
return; \
} \
if(get_key()) { \
for(;;) { \
if(terminate) { \
return; \
} \
if(!get_key()) { \
break; \
} \
if(dwTime <= SDL_GetTicks()) { \
break; \
} \
sys_sleep(16); \
} \
break; \
} \
if(dwTime <= SDL_GetTicks()) { \
break; \
} \
sys_sleep(16); \
} \
}
NACT_Sys1::NACT_Sys1(const Config& config, const GameId& game_id)
: NACT(config, game_id)
{
switch (game_id.crc32_a) {
case CRC32_DPS:
case CRC32_DPS_SG:
//case CRC32_DPS_SG2:
case CRC32_DPS_SG3:
text_refresh = false;
strcpy(tvar[0], strings.dps_custom.c_str());
strcpy(tvar[1], strings.dps_linus.c_str());
strcpy(tvar[2], strings.dps_katsumi.c_str());
strcpy(tvar[3], strings.dps_yumiko.c_str());
strcpy(tvar[4], strings.dps_itsumi.c_str());
strcpy(tvar[5], strings.dps_hitomi.c_str());
strcpy(tvar[6], strings.dps_mariko.c_str());
break;
case CRC32_INTRUDER:
paint_x = paint_y = map_page = 0;
break;
}
}
void NACT_Sys1::opening()
{
switch (game_id.crc32_a) {
case CRC32_CRESCENT:
mako->play_music(2);
ags->load_cg(1, -1);
WAIT(3000);
ags->load_cg(81, -1);
break;
case CRC32_INTRUDER:
ags->load_cg(77, -1);
WAIT(1667);
ags->load_cg(74, -1);
break;
case CRC32_VAMPIRE:
case CRC32_VAMPIRE_ENG:
mako->play_music(4);
ags->load_cg(3, -1);
WAIT(2000);
ags->load_cg(38, -1);
cmd_a();
ags->draw_box(0);
break;
case CRC32_GAKUEN:
case CRC32_GAKUEN_ENG:
mako->set_cd_track(1, 1);
ags->load_cg(302, -1);
WAIT(3000);
ags->load_cg(303, -1);
WAIT(3000);
mako->play_music(1);
ags->load_cg(304, -1);
WAIT(4000);
break;
}
}
void NACT_Sys1::cmd_branch()
{
int condition = cali();
int nest = 0;
bool set_menu = false;
if(!condition) {
// 次の'}'命令までスキップする(ネストも考慮する)
for(;;) {
uint8 cmd = sco.fetch_command();
if(cmd == '!') {
int index = sco.getd();
if(!(0x80 <= index && index <= 0xbf)) {
sco.getd();
}
cali();
} else if(cmd == '{') {
cali();
nest++;
} else if(cmd == '}') {
if(nest) {
nest--;
} else {
break;
}
} else if(cmd == '@') {
sco.getw();
} else if(cmd == '\\') {
sco.getw();
} else if(cmd == '&') {
cali();
} else if(cmd == '%') {
cali();
} else if(cmd == '$') {
if(!set_menu) {
sco.getw();
set_menu = true;
} else {
set_menu = false;
}
} else if(cmd == '[') {
sco.getd();
sco.getd();
sco.getw();
} else if(cmd == ':') {
cali();
sco.getd();
sco.getd();
sco.getw();
} else if(cmd == ']') {
} else if(cmd == 'A') {
} else if(cmd == 'F') {
} else if(cmd == 'G') {
sco.getd();
} else if(cmd == 'L') {
sco.getd();
} else if(cmd == 'P') {
sco.getd();
} else if(cmd == 'Q') {
sco.getd();
} else if(cmd == 'R') {
} else if(cmd == 'S') {
sco.getd();
} else if(cmd == 'U') {
sco.getd();
sco.getd();
} else if(cmd == 'X') {
sco.getd();
} else if(cmd == 'Y') {
cali();
cali();
} else if(cmd == 'Z') {
cali();
cali();
} else if (cmd == '\'' || cmd == '"') { // SysEng
sco.skip_syseng_string(encoding.get(), cmd);
} else if (is_message(cmd)) {
sco.skip(encoding->mblen(cmd) - 1);
} else {
sco.unknown_command(cmd);
}
}
}
TRACE("{%d:", condition);
}
void NACT_Sys1::cmd_open_verb()
{
// 動詞メニューの表示
TRACE("open verb-obj menu");
verb_obj = false;
// 表示する動詞のチェック
bool chk[MAX_VERB] = {};
int page = 0;
if (game_id.is_gakuen())
menu_window = 1;
int menu_max = ags->calculate_menu_max(menu_window);
for (const MenuItem& item : menu_items) {
chk[item.verb] = true;
}
int cnt = std::count(chk, chk + MAX_VERB, true);
top:
// メニュー項目の準備
int id[32], index = 0;
ags->clear_menu_window();
ags->menu_dest_y = 0;
ags->draw_menu = true;
if(cnt <= menu_max) {
// 1ページ内に全て表示できる
for(int i = 0; i < MAX_VERB; i++) {
if(chk[i]) {
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(caption_verb[i].c_str());
id[index++] = i;
ags->menu_dest_y += ags->menu_font_size + 2;
}
}
} else {
top2:
for(int i = page; i < MAX_VERB; i++) {
if(chk[i]) {
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(caption_verb[i].c_str());
id[index++] = i;
ags->menu_dest_y += ags->menu_font_size + 2;
}
page = i + 1;
if(index == menu_max - 1) {
break;
}
}
if(index == 0) {
// 最初のページに戻る
page = 0;
goto top2;
}
// 次のページを追加
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(strings.next_page.c_str());
id[index++] = -1;
ags->menu_dest_y += ags->menu_font_size + 2;
}
ags->draw_menu = false;
int selection = menu_select(index);
if (terminate)
return;
if(selection == -1) {
sco.jump_to(sco.default_addr());
} else if(id[selection] == -1) {
goto top;
} else {
cmd_open_obj(id[selection]);
}
menu_items.clear();
}
void NACT_Sys1::cmd_open_obj(int verb)
{
int menu_max = ags->calculate_menu_max(menu_window);
// 目的語メニューの表示
verb_obj = false;
// 表示する目的語のチェック
bool chk[MAX_OBJ] = {};
int addr[MAX_OBJ], page = 0;
for (const MenuItem& item : menu_items) {
if (item.verb == verb) {
chk[item.obj] = true;
addr[item.obj] = item.addr;
}
}
int cnt = std::count(chk, chk + MAX_OBJ, true);
// 目的語がない場合
if(chk[0] && cnt == 1) {
sco.jump_to(addr[0]);
return;
}
// 以後、obj=0は戻るとして扱う
chk[0] = false;
addr[0] = sco.default_addr();
top:
// メニュー項目の準備
int id[32], index = 0;
ags->clear_menu_window();
ags->menu_dest_y = 0;
ags->draw_menu = true;
if(cnt <= menu_max - 1) {
// 1ページ内に全て表示できる
for(int i = 0; i < MAX_OBJ; i++) {
if(chk[i]) {
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(caption_obj[i].c_str());
id[index++] = i;
ags->menu_dest_y += ags->menu_font_size + 2;
}
}
// 戻るを追加
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(strings.back.c_str());
id[index++] = 0;
ags->menu_dest_y += ags->menu_font_size + 2;
} else {
top2:
for(int i = page; i < MAX_OBJ; i++) {
if(chk[i]) {
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(caption_obj[i].c_str());
id[index++] = i;
ags->menu_dest_y += ags->menu_font_size + 2;
}
page = i + 1;
if(index == menu_max - 2) {
break;
}
}
if(index == 0) {
// 最初のページに戻る
page = 0;
goto top2;
}
// 戻るを追加
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(strings.back.c_str());
id[index++] = 0;
ags->menu_dest_y += ags->menu_font_size + 2;
// 次のページを追加
ags->menu_dest_x = 2;
ags->menu_dest_y += 2;
ags->draw_text(strings.next_page.c_str());
id[index++] = -1;
ags->menu_dest_y += ags->menu_font_size + 2;
}
ags->draw_menu = false;
int selection = menu_select(index);
if (terminate)
return;
if(selection == -1) {
sco.jump_to(sco.default_addr());
} else if(id[selection] == -1) {
goto top;
} else {
sco.jump_to(addr[id[selection]]);
}
}
void NACT_Sys1::cmd_b()
{
// Unused
sco.unknown_command('B');
}
void NACT_Sys1::cmd_d()
{
// Unused
sco.unknown_command('D');
}
void NACT_Sys1::cmd_e()
{
// Unused
sco.unknown_command('E');
}
void NACT_Sys1::cmd_g()
{
int page = sco.getd();
TRACE("G %d:", page);
if (game_id.crc32_a == CRC32_INTRUDER) {
page = (page == 97) ? 96 : (page == 98) ? 97 : page;
} else if (game_id.is_gakuen()) {
page = (page == 3) ? 94 : page;
}
if (enable_graphics)
ags->load_cg(page, -1);
if (game_id.crc32_a == CRC32_INTRUDER) {
if(page == 94) {
WAIT(400);
}
if(page == 81 || page == 82) {
map_page = page;
}
}
}
void NACT_Sys1::cmd_h()
{
// Unused
sco.unknown_command('H');
}
void NACT_Sys1::cmd_i()
{
// Unused
sco.unknown_command('I');
}
void NACT_Sys1::cmd_j()
{
// Unused
sco.unknown_command('J');
}
void NACT_Sys1::cmd_k()
{
// Unused
sco.unknown_command('K');
}
void NACT_Sys1::cmd_l()
{
int index = sco.getd();
TRACE("L %d:", index);
if (1 <= index && index <= 26) {
// ASLEEP_A.DAT - ASLEEP_Z.DAT
load(index);
}
}
void NACT_Sys1::cmd_m()
{
// Unused
sco.unknown_command('M');
}
void NACT_Sys1::cmd_n()
{
// Unused
sco.unknown_command('N');
}
void NACT_Sys1::cmd_o()
{
// Unused
sco.unknown_command('O');
}
void NACT_Sys1::cmd_p()
{
int param = sco.getd();
ags->text_font_color = (uint8)((param & 0x7) + 16);
TRACE("P %d:", param);
}
void NACT_Sys1::cmd_q()
{
static char header[112] = {
'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 's', 'a', 'v', 'e', ' ', 'd', 'a', 't',
'a', ' ', 'f', 'o', 'r', ' ', 'S', 'y', 's', 't', 'e', 'm', '1', ' ', ' ', 'U',
'n', 'i', 'o', 'n', ' ', '[', 'W', 'i', 'n', '9', 'x', '/', 'N', 'T', '4', '0',
'/', '2', '0', '0', '0', '/', 'X', 'P', '/', 'C', 'E', ']', ' ', 'F', 'o', 'r',
' ', 'N', 'A', 'C', 'T', '/', 'A', 'D', 'V', ' ', 's', 'y', 's', 't', 'e', 'm',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '(', 'C', ')', '2', '0', '0',
'4', ' ', 'A', 'L', 'I', 'C', 'E', '-', 'S', 'O', 'F', 'T', ' ', ' ', ' ', ' '
};
int index = sco.getd();
TRACE("Q %d:", index);
if (1 <= index && index <= 26) {
// ASLEEP_A.DAT - ASLEEP_Z.DAT
save(index, header);
}
}
void NACT_Sys1::cmd_t()
{
// Unused
sco.unknown_command('T');
}
void NACT_Sys1::cmd_u()
{
int page = sco.getd();
int transparent = sco.getd();
TRACE("U %d,%d:", page, transparent);
if (game_id.crc32_a == CRC32_INTRUDER) {
ags->load_cg(page, 11);
if(page == 5) {
WAIT(400);
}
} else {
ags->load_cg(page, transparent);
}
}
void NACT_Sys1::cmd_v()
{
// Unused
sco.unknown_command('V');
}
void NACT_Sys1::cmd_w()
{
// Unused
sco.unknown_command('W');
}
void NACT_Sys1::cmd_y()
{
int cmd = cali();
int param = cali();
TRACE("Y %d,%d:", cmd, param);
if (game_id.crc32_a == CRC32_INTRUDER) {
switch(cmd) {
case 0:
ags->clear_text_window(text_window, true);
break;
case 1:
if(show_push) {
ags->draw_push(text_window);
}
for(;;) {
if(terminate) {
return;
}
if(get_key()) {
break;
}
sys_sleep(16);
}
sys_sleep(100);
for(;;) {
if(terminate) {
return;
}
if(!(get_key() & 0x18)) {
break;
}
sys_sleep(16);
}
ags->clear_text_window(text_window, true);
break;
case 2:
if(param == 0) {
quit(NACT_HALT);
} else {
WAIT(100)
}
break;
case 3:
WAIT(param * 1000)
break;
case 255:
quit(NACT_HALT);
break;
default:
WARNING("Unimplemented command: Y %d, %d", cmd, param);
break;
}
} else if (game_id.is_gakuen()) {
switch (cmd) {
case 1:
RND = (param == 0 || param == 1) ? 0 : random(param);
break;
case 2:
// Clear variables up to param, though in practice param is always 21.
for (int i = 0; i <= param; i++) {
var[i] = 0;
}
break;
case 3:
{
char buf[16];
snprintf(buf, sizeof(buf), "%d", param);
ags->draw_text(buf);
}
break;
case 4:
if (param == 0) {
mako->play_music(1);
} else {
mako->stop_music();
}
break;
case 5:
enable_graphics = (param == 0);
break;
case 6:
// Dummied. 0 - "Display text slowly," 1 - disable.
break;
case 7:
// Dummied. 0 - "Switch to interlace mode," 1 - disable.
break;
default:
WARNING("Unimplemented command: Y %d, %d", cmd, param);
break;
}
} else {
switch(cmd) {
case 0:
ags->clear_text_window(text_window, true);
break;
case 1:
{
char buf[16];
buf[0] = '0' + (int)(param / 10000);
param %= 10000;
buf[1] = '0' + (int)(param / 1000);
param %= 1000;
buf[2] = '0' + (int)(param / 100);
param %= 100;
buf[3] = '0' + (int)(param / 10);
buf[4] = '0' + (param % 10);
buf[5] = '\0';
int p = 0;
for(;;) {
if(buf[p] != '0') {
break;
}
p++;
}
ags->draw_text(&buf[p < 4 ? p : 4]);
}
break;
case 2:
if (game_id.crc32_a == CRC32_CRESCENT) {
for(int i = 39; i <= 48; i++) {
var[i] = 0;
}
} else if (game_id.is_system1_dps()) {
for(int i = 0; i <= 20; i++) {
var[i] = 0;
}
} else {
for(int i = 0; i <= 16; i++) {
var[i] = 0;
}
}
break;
case 3:
WAIT(param * 1000 / 60)
break;
case 4:
RND = (param == 0 || param == 1) ? 0 : random(param);
break;
case 7:
if (game_id.is_system1_dps()) {
if(param == 1) {
ags->box_fill(0, 40, 8, 598, 270, 0);
}
}
break;
case 130:
if (game_id.crc32_a == CRC32_TENGU) {
WAIT(2000)
ags->load_cg(180, -1);
WAIT(2000)
ags->draw_box(0);
ags->dest_screen = 1;
ags->load_cg(173, -1);
ags->dest_screen = 0;
ags->gcopy(0x1ecd, 0x3a2a, 8, 60, 3);
WAIT(667);
ags->gcopy(0x1b65, 0x262e, 8, 60, 3);
ags->gcopy(0x2e25, 0x38ee, 7, 9, 3);
WAIT(667);
ags->gcopy(0x1914, 0x1914, 8, 63, 3);
WAIT(667);
ags->gcopy(0x182c, 0x182c, 7, 50, 3);
WAIT(667);
ags->gcopy(0x0000, 0x1ecd, 32, 60, 1);
WAIT(667);
for(int i = 12; i <= 57; i++) {
ags->gcopy(i + 0x2306, i + 0x2306, 1, 165, 3);
}
WAIT_KEYQUIT(6000)
ags->draw_box(param);
}
break;
case 240:
ags->draw_hankaku = (param == 1) ? true : false;
break;
case 253:
quit(0);
break;
case 254:
RND = 0;
break;
case 255:
if (game_id.is_system1_dps()) {
quit(0);
} else {
quit(NACT_HALT);
}
break;
default:
WARNING("Unimplemented command: Y %d, %d", cmd, param);
break;
}
}
}
void NACT_Sys1::cmd_z()
{
if (game_id.crc32_a == CRC32_CRESCENT) {
cmd_y();
return;
}
int cmd = cali();
int param = cali();
TRACE("Z %d,%d:", cmd, param);
switch (game_id.crc32_a) {
case CRC32_INTRUDER:
if(cmd == 1 && 1 <= param && param <= 4) {
const char* buf[4] = {
"\x81\x83", // "<" in SJIS
"\x81\xC8", // "∧" in SJIS
"\x81\x84", // ">" in SJIS
"\x81\xC9", // "∨" in SJIS
};
// 矢印を消去する
if(map_page) {
ags->load_cg(map_page, -1);
if(paint_x) {
ags->paint(paint_x, paint_y * 2, 2 + 16);
}
}
// 矢印を表示する
int x = ags->text_dest_x;
int y = ags->text_dest_y;
int color = ags->text_font_color;
ags->text_font_size = 24;
ags->text_font_color = 16;
ags->text_dest_x = 456;
ags->text_dest_y = 103;
ags->draw_text(buf[param - 1]);
ags->text_dest_x = x;
ags->text_dest_y = y;
ags->text_font_size = 16;
ags->text_font_color = color;
} else if(cmd == 2) {
if (param == 0) {
// Clear the screen
ags->box_fill(0, 0, 0, 640, 400, 0);
} else {
// Restore the screen
ags->load_cg(74, 8);
ags->load_cg(81, -1);
map_page = 81;
}
} else if(cmd == 3) {
RND = (param == 0 || param == 1) ? 0 : random(param);
} else if(460 <= cmd && cmd <= 625 && 20 <= param && param <= 55) {
if(paint_x) {
ags->paint(paint_x, paint_y * 2, 5 + 16);
}
ags->paint(cmd, param * 2, 2 + 16);
paint_x = cmd;
paint_y = param;
} else {
paint_x = 0;
}
break;
case CRC32_TENGU:
if(cmd == 10) {
// nop
} else if(cmd == 20) {
if(param == 20) {
ags->gcopy(8, 8, 46, 144, 2);
} else if(param == 21) {
ags->gcopy(8, 8, 46, 144, 3);
}
}
break;
case CRC32_GAKUEN:
case CRC32_GAKUEN_ENG:
if (param != 30 && (param < 34 || param > 46)) {
if (enable_graphics) {
ags->load_cg(param + 250, -1);
}
}
// Display the Gakuen Senki party status bar for party members. The Z commands make the slots appear in reverse, so 39 and 45 draw
// the box for character 1, 38 and 44 draw for character 2, and so on. Z 1, 30: draws the first character slot, which is always occupied.
//
// One the actual MSX, this bar was constructed in an irregular fashion. This code constructs a more consistent version, where each square
// is 35px wide with 2px borders on each side. Since the borders are overlapped, this means each sub-sprite is placed 37px apart.
else if (param == 46) {
// Originally this would have loaded Image 296, the party status bar, to screen 1 for future reference. However, due to trouble with
// the palette, I decided to load it before using each subcommand, instead.
/*ags->dest_screen = 1;
ags->load_cg(296, -1);
ags->dest_screen = 0;*/
}
else if (param == 30) {
// Load party status bar to screen 1.
ags->dest_screen = 1;
ags->load_cg(296, -1);
ags->dest_screen = 0;
// Draw Wataru's icon.
ags->src_screen = 1;
ags->copy(10, 182, 48, 224, 7, 210);
ags->src_screen = 0;
}
else if (param >= 34 && param <= 39) {
int x = (39 - param) * 37 + 45;
// Load party status bar to screen 1.
ags->dest_screen = 1;
ags->load_cg(296, -1);
ags->dest_screen = 0;
// Draw a blank party icon to init or clear the party box.
ags->src_screen = 1;
ags->copy(269, 182, 307, 224, x, 210);
ags->src_screen = 0;
}
else {
int x = (45 - param) * 37 + 45;
// Load party status bar to screen 1.
ags->dest_screen = 1;
ags->load_cg(296, -1);
ags->dest_screen = 0;
// Draw a party member's icon to the party bar.
ags->src_screen = 1;
ags->copy(x + 2, 182, x + 38, 224, x, 210);
ags->src_screen = 0;
}
}
}
// 下位関数
uint16 NACT_Sys1::cali()
{
uint32 cali[256];
int p = 1;
bool ok = false;
while(p > 0) {
uint8 dat = sco.getd();
// 除算はサポートしない?
if(0x80 <= dat && dat <= 0xbf) {
cali[p++] = var[dat & 0x3f];
} else if(0xc0 <= dat && dat <= 0xff) {
cali[p++] = var[((dat & 0x3f) << 8) | sco.getd()];
} else if(0x00 <= dat && dat <= 0x3f) {
cali[p++] = ((dat & 0x3f) << 8) | sco.getd();
} else if(0x40 <= dat && dat <= 0x77) {
cali[p++] = dat & 0x3f;
} else if(dat == 0x78) {
cali[p - 2] = cali[p - 2] * cali[p - 1];
if (cali[p - 2] > 65535) {
cali[p - 2] = 65535;
}
p--;
} else if(dat == 0x79) {
cali[p - 2] = cali[p - 2] + cali[p - 1];
if (cali[p - 2] > 65535) {
cali[p - 2] = 65535;
}
p--;
} else if(dat == 0x7a) {
if(cali[p - 2] > cali[p - 1]) {
cali[p - 2] = cali[p - 2] - cali[p - 1];
} else {
cali[p - 2] = 0;
}
p--;
} else if(dat == 0x7b) {
cali[p - 2] = (cali[p - 2] == cali[p - 1]) ? 1 : 0;
p--;
} else if(dat == 0x7c) {
cali[p - 2] = (cali[p - 2] < cali[p - 1]) ? 1 : 0;
p--;
} else if(dat == 0x7d) {
cali[p - 2] = (cali[p - 2] > cali[p - 1]) ? 1 : 0;
p--;
} else if(dat == 0x7e) {
cali[p - 2] = (cali[p - 2] != cali[p - 1]) ? 1 : 0;
p--;
} else if(dat == 0x7f) {
if(p == 2) {
ok = true;
}
p = 0;
}
}
if (!ok) {
sys_error("cali: invalid expression at %d:%04x", sco.page(), sco.cmd_addr());
}
return (uint16)(cali[1] & 0xffff);
}