Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
engines/parallaction/metaengine.cpp
engines/parallaction/saveload.cpp

View File

@@ -0,0 +1,830 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "audio/fmopl.h"
#include "audio/mpu401.h"
#include "audio/mididrv.h"
namespace Parallaction {
const uint kNumVoices = 9;
// adlib FM voices 0-5
const uint kNumMelodic = 6;
// adlib FM voice 6 and 7-8
const uint kNumPercussion = 5;
// mask for maximum volume level
#define LEVEL_MASK 0x7f
struct OPLOperator {
uint8 characteristic; // amplitude modulation, vibrato, envelope, keyboard scaling, modulator frequency
uint8 levels;
uint8 attackDecay;
uint8 sustainRelease;
uint8 waveform;
};
struct MelodicProgram {
OPLOperator op[2];
uint8 feedbackAlgo;
};
struct PercussionNote {
OPLOperator op[2];
uint8 feedbackAlgo;
uint8 percussion;
uint8 valid;
uint16 frequency;
uint8 octave;
};
static const MelodicProgram melodicPrograms[128] = {
{{{ 0x1, 0x51, 0xf2, 0xb2, 0x0 }, { 0x11, 0x0, 0xf2, 0xa2, 0x0 }}, 0x0 },
{{{ 0xc2, 0x4b, 0xf1, 0x53, 0x0 }, { 0xd2, 0x0, 0xf2, 0x74, 0x0 }}, 0x4 },
{{{ 0x81, 0x9d, 0xf2, 0x74, 0x0 }, { 0x13, 0x0, 0xf2, 0xf1, 0x0 }}, 0x6 },
{{{ 0x3, 0x4f, 0xf1, 0x53, 0x0 }, { 0x17, 0x3, 0xf2, 0x74, 0x0 }}, 0x6 },
{{{ 0xd1, 0x81, 0x81, 0x73, 0x2 }, { 0xd4, 0x0, 0xe1, 0x34, 0x0 }}, 0x3 },
{{{ 0x1, 0x0, 0x94, 0xa6, 0x0 }, { 0x2, 0x0, 0x83, 0x26, 0x0 }}, 0x1 },
{{{ 0xf3, 0x84, 0x81, 0x2, 0x1 }, { 0x55, 0x80, 0xdd, 0x3, 0x0 }}, 0x4 },
{{{ 0x5, 0x8a, 0xf2, 0x26, 0x0 }, { 0x1, 0x80, 0xf3, 0x48, 0x0 }}, 0x0 },
{{{ 0x32, 0x0, 0xb1, 0x14, 0x0 }, { 0x12, 0x0, 0xfd, 0x36, 0x0 }}, 0x3 },
{{{ 0x1, 0x0, 0x82, 0xa, 0x2 }, { 0x2, 0x0, 0x85, 0x15, 0x0 }}, 0x3 },
{{{ 0xd1, 0x1, 0x97, 0xaa, 0x0 }, { 0x4, 0xd, 0xf3, 0xa5, 0x1 }}, 0x9 },
{{{ 0x17, 0x0, 0xf2, 0x62, 0x0 }, { 0x12, 0x0, 0xf2, 0x72, 0x0 }}, 0x8 },
{{{ 0x6, 0x0, 0xff, 0xf4, 0x0 }, { 0xc4, 0x0, 0xf8, 0xb5, 0x0 }}, 0xe },
{{{ 0xc0, 0x81, 0xf2, 0x13, 0x2 }, { 0xc0, 0xc1, 0xf3, 0x14, 0x2 }}, 0xb },
{{{ 0x44, 0x53, 0xf5, 0x31, 0x0 }, { 0x60, 0x80, 0xfd, 0x22, 0x0 }}, 0x6 },
{{{ 0xe0, 0x80, 0xf4, 0xf2, 0x0 }, { 0x61, 0x0, 0xf2, 0x6, 0x0 }}, 0x8 },
{{{ 0xc1, 0x6, 0x83, 0x23, 0x0 }, { 0xc1, 0x4, 0xf0, 0x26, 0x0 }}, 0x1 },
{{{ 0x26, 0x0, 0xf4, 0xb6, 0x0 }, { 0x21, 0x0, 0x81, 0x4b, 0x0 }}, 0x1 },
{{{ 0x24, 0x80, 0xff, 0xf, 0x0 }, { 0x21, 0x80, 0xff, 0xf, 0x0 }}, 0x1 },
{{{ 0x24, 0x4f, 0xf2, 0xb, 0x0 }, { 0x31, 0x0, 0x52, 0xb, 0x0 }}, 0xb },
{{{ 0x31, 0x8, 0x81, 0xb, 0x0 }, { 0xa1, 0x80, 0x92, 0x3b, 0x0 }}, 0x0 },
{{{ 0x70, 0xc5, 0x52, 0x11, 0x1 }, { 0x71, 0x80, 0x31, 0xfe, 0x1 }}, 0x0 },
{{{ 0x51, 0x88, 0x10, 0xf0, 0x0 }, { 0x42, 0x83, 0x40, 0xfc, 0x0 }}, 0x8 },
{{{ 0xf0, 0xd9, 0x81, 0x3, 0x0 }, { 0xb1, 0x80, 0xf1, 0x5, 0x0 }}, 0xa },
{{{ 0x21, 0x4f, 0xf1, 0x31, 0x0 }, { 0x2, 0x80, 0xc3, 0x45, 0x0 }}, 0x0 },
{{{ 0x7, 0x8f, 0x9c, 0x33, 0x1 }, { 0x1, 0x80, 0x8a, 0x13, 0x0 }}, 0x0 },
{{{ 0x21, 0x40, 0xf1, 0x31, 0x0 }, { 0x6, 0x80, 0xf4, 0x44, 0x0 }}, 0x0 },
{{{ 0x21, 0x40, 0xf1, 0x31, 0x3 }, { 0x81, 0x0, 0xf4, 0x44, 0x2 }}, 0x2 },
{{{ 0x11, 0x8d, 0xfd, 0x11, 0x0 }, { 0x11, 0x80, 0xfd, 0x11, 0x0 }}, 0x8 },
{{{ 0xf0, 0x1, 0x97, 0x17, 0x0 }, { 0x21, 0xd, 0xf1, 0x18, 0x0 }}, 0x8 },
{{{ 0xf1, 0x1, 0x97, 0x17, 0x0 }, { 0x21, 0xd, 0xf1, 0x18, 0x0 }}, 0x8 },
{{{ 0xcd, 0x9e, 0x55, 0xd1, 0x0 }, { 0xd1, 0x0, 0xf2, 0x71, 0x0 }}, 0xe },
{{{ 0x1, 0x0, 0xf2, 0x88, 0x0 }, { 0x1, 0x0, 0xf5, 0x88, 0x0 }}, 0x1 },
{{{ 0x30, 0xd, 0xf2, 0xef, 0x0 }, { 0x21, 0x0, 0xf5, 0x78, 0x0 }}, 0x6 },
{{{ 0x0, 0x10, 0xf4, 0xd9, 0x0 }, { 0x0, 0x0, 0xf5, 0xd7, 0x0 }}, 0x4 },
{{{ 0x1, 0x4c, 0xf2, 0x50, 0x0 }, { 0x1, 0x40, 0xd2, 0x59, 0x0 }}, 0x8 },
{{{ 0x20, 0x11, 0xe2, 0x8a, 0x0 }, { 0x20, 0x0, 0xe4, 0xa8, 0x0 }}, 0xa },
{{{ 0x21, 0x40, 0x7b, 0x4, 0x1 }, { 0x21, 0x0, 0x75, 0x72, 0x0 }}, 0x2 },
{{{ 0x31, 0xd, 0xf2, 0xef, 0x0 }, { 0x21, 0x0, 0xf5, 0x78, 0x0 }}, 0xa },
{{{ 0x1, 0xc, 0xf5, 0x2f, 0x1 }, { 0x0, 0x80, 0xf5, 0x5c, 0x0 }}, 0x0 },
{{{ 0xb0, 0x1c, 0x81, 0x3, 0x2 }, { 0x20, 0x0, 0x54, 0x67, 0x2 }}, 0xe },
{{{ 0x1, 0x0, 0xf1, 0x65, 0x0 }, { 0x1, 0x80, 0xa3, 0xa8, 0x2 }}, 0x1 },
{{{ 0xe1, 0x4f, 0xc1, 0xd3, 0x2 }, { 0x21, 0x0, 0x32, 0x74, 0x1 }}, 0x0 },
{{{ 0x2, 0x0, 0xf6, 0x16, 0x0 }, { 0x12, 0x0, 0xf2, 0xf8, 0x0 }}, 0x1 },
{{{ 0xe0, 0x63, 0xf8, 0xf3, 0x0 }, { 0x70, 0x80, 0xf7, 0xf3, 0x0 }}, 0x4 },
{{{ 0x1, 0x6, 0xf3, 0xff, 0x0 }, { 0x8, 0x0, 0xf7, 0xff, 0x0 }}, 0x4 },
{{{ 0x21, 0x16, 0xb0, 0x81, 0x1 }, { 0x22, 0x0, 0xb3, 0x13, 0x1 }}, 0xc },
{{{ 0x1, 0x4f, 0xf0, 0xff, 0x0 }, { 0x30, 0x0, 0x90, 0xf, 0x0 }}, 0x6 },
{{{ 0x0, 0x10, 0xf1, 0xf2, 0x2 }, { 0x1, 0x0, 0xf1, 0xf2, 0x3 }}, 0x0 },
{{{ 0x1, 0x4f, 0xf1, 0x50, 0x0 }, { 0x21, 0x80, 0xa3, 0x5, 0x3 }}, 0x6 },
{{{ 0xb1, 0x3, 0x55, 0x3, 0x0 }, { 0xb1, 0x3, 0x8, 0xa, 0x0 }}, 0x9 },
{{{ 0x22, 0x0, 0xa9, 0x34, 0x1 }, { 0x1, 0x0, 0xa2, 0x42, 0x2 }}, 0x2 },
{{{ 0xa0, 0xdc, 0x81, 0x31, 0x3 }, { 0xb1, 0x80, 0xf1, 0x1, 0x3 }}, 0x0 },
{{{ 0x1, 0x4f, 0xf1, 0x50, 0x0 }, { 0x21, 0x80, 0xa3, 0x5, 0x3 }}, 0x6 },
{{{ 0xf1, 0x80, 0xa0, 0x72, 0x0 }, { 0x74, 0x0, 0x90, 0x22, 0x0 }}, 0x9 },
{{{ 0xe1, 0x13, 0x71, 0xae, 0x0 }, { 0xe1, 0x0, 0xf0, 0xfc, 0x1 }}, 0xa },
{{{ 0x31, 0x1c, 0x41, 0xb, 0x0 }, { 0xa1, 0x80, 0x92, 0x3b, 0x0 }}, 0xe },
{{{ 0x71, 0x1c, 0x41, 0x1f, 0x0 }, { 0xa1, 0x80, 0x92, 0x3b, 0x0 }}, 0xe },
{{{ 0x21, 0x1c, 0x53, 0x1d, 0x0 }, { 0xa1, 0x80, 0x52, 0x3b, 0x0 }}, 0xc },
{{{ 0x21, 0x1d, 0xa4, 0xae, 0x1 }, { 0x21, 0x0, 0xb1, 0x9e, 0x0 }}, 0xc },
{{{ 0xe1, 0x16, 0x71, 0xae, 0x0 }, { 0xe1, 0x0, 0x81, 0x9e, 0x0 }}, 0xa },
{{{ 0xe1, 0x15, 0x71, 0xae, 0x0 }, { 0xe2, 0x0, 0x81, 0x9e, 0x0 }}, 0xe },
{{{ 0x21, 0x16, 0x71, 0xae, 0x0 }, { 0x21, 0x0, 0x81, 0x9e, 0x0 }}, 0xe },
{{{ 0x71, 0x1c, 0x41, 0x1f, 0x0 }, { 0xa1, 0x80, 0x92, 0x3b, 0x0 }}, 0xe },
{{{ 0x21, 0x4f, 0x81, 0x53, 0x0 }, { 0x32, 0x0, 0x22, 0x2c, 0x0 }}, 0xa },
{{{ 0x22, 0x4f, 0x81, 0x53, 0x0 }, { 0x32, 0x0, 0x22, 0x2c, 0x0 }}, 0xa },
{{{ 0x23, 0x4f, 0x81, 0x53, 0x0 }, { 0x34, 0x0, 0x22, 0x2c, 0x0 }}, 0xa },
{{{ 0xe1, 0x16, 0x71, 0xae, 0x0 }, { 0xe1, 0x0, 0x81, 0x9e, 0x0 }}, 0xa },
{{{ 0x71, 0xc5, 0x6e, 0x17, 0x0 }, { 0x22, 0x5, 0x8b, 0xe, 0x0 }}, 0x2 },
{{{ 0xe6, 0x27, 0x70, 0xf, 0x1 }, { 0xe3, 0x0, 0x60, 0x9f, 0x0 }}, 0xa },
{{{ 0x30, 0xc8, 0xd5, 0x19, 0x0 }, { 0xb1, 0x80, 0x61, 0x1b, 0x0 }}, 0xc },
{{{ 0x32, 0x9a, 0x51, 0x1b, 0x0 }, { 0xa1, 0x82, 0xa2, 0x3b, 0x0 }}, 0xc },
{{{ 0xad, 0x3, 0x74, 0x29, 0x0 }, { 0xa2, 0x82, 0x73, 0x29, 0x0 }}, 0x7 },
{{{ 0x21, 0x83, 0x74, 0x17, 0x0 }, { 0x62, 0x8d, 0x65, 0x17, 0x0 }}, 0x7 },
{{{ 0x94, 0xb, 0x85, 0xff, 0x1 }, { 0x13, 0x0, 0x74, 0xff, 0x0 }}, 0xc },
{{{ 0x74, 0x87, 0xa4, 0x2, 0x0 }, { 0xd6, 0x80, 0x45, 0x42, 0x0 }}, 0x2 },
{{{ 0xb3, 0x85, 0x76, 0x21, 0x1 }, { 0x20, 0x0, 0x3d, 0xc1, 0x0 }}, 0x6 },
{{{ 0x17, 0x4f, 0xf2, 0x61, 0x0 }, { 0x12, 0x8, 0xf1, 0xb4, 0x0 }}, 0x8 },
{{{ 0x4f, 0x86, 0x65, 0x1, 0x0 }, { 0x1f, 0x0, 0x32, 0x74, 0x0 }}, 0x4 },
{{{ 0xe1, 0x23, 0x71, 0xae, 0x0 }, { 0xe4, 0x0, 0x82, 0x9e, 0x0 }}, 0xa },
{{{ 0x11, 0x86, 0xf2, 0xbd, 0x0 }, { 0x4, 0x80, 0xa0, 0x9b, 0x1 }}, 0x8 },
{{{ 0x20, 0x90, 0xf5, 0x9e, 0x2 }, { 0x11, 0x0, 0xf4, 0x5b, 0x3 }}, 0xc },
{{{ 0xf0, 0x80, 0x34, 0xe4, 0x0 }, { 0x7e, 0x0, 0xa2, 0x6, 0x0 }}, 0x8 },
{{{ 0x90, 0xf, 0xff, 0x1, 0x3 }, { 0x0, 0x0, 0x1f, 0x1, 0x0 }}, 0xe },
{{{ 0x1, 0x4f, 0xf0, 0xff, 0x0 }, { 0x33, 0x0, 0x90, 0xf, 0x0 }}, 0x6 },
{{{ 0x1e, 0x0, 0x1f, 0xf, 0x0 }, { 0x10, 0x0, 0x1f, 0x7f, 0x0 }}, 0x0 },
{{{ 0xbe, 0x0, 0xf1, 0x1, 0x3 }, { 0x31, 0x0, 0xf1, 0x1, 0x0 }}, 0x4 },
{{{ 0xbe, 0x0, 0xf1, 0x1, 0x3 }, { 0x31, 0x0, 0xf1, 0x1, 0x0 }}, 0x4 },
{{{ 0x93, 0x6, 0xc1, 0x4, 0x1 }, { 0x82, 0x0, 0x51, 0x9, 0x0 }}, 0x6 },
{{{ 0xa0, 0x0, 0x96, 0x33, 0x0 }, { 0x20, 0x0, 0x55, 0x2b, 0x0 }}, 0x6 },
{{{ 0x0, 0xc0, 0xff, 0x5, 0x0 }, { 0x0, 0x0, 0xff, 0x5, 0x3 }}, 0x0 },
{{{ 0x4, 0x8, 0xf8, 0x7, 0x0 }, { 0x1, 0x0, 0x82, 0x74, 0x0 }}, 0x8 },
{{{ 0x0, 0x0, 0x2f, 0x5, 0x0 }, { 0x20, 0x0, 0xff, 0x5, 0x3 }}, 0xa },
{{{ 0x93, 0x0, 0xf7, 0x7, 0x2 }, { 0x0, 0x0, 0xf7, 0x7, 0x0 }}, 0xa },
{{{ 0x0, 0x40, 0x80, 0x7a, 0x0 }, { 0xc4, 0x0, 0xc0, 0x7e, 0x0 }}, 0x8 },
{{{ 0x90, 0x80, 0x55, 0xf5, 0x0 }, { 0x0, 0x0, 0x55, 0xf5, 0x0 }}, 0x8 },
{{{ 0xe1, 0x80, 0x34, 0xe4, 0x0 }, { 0x69, 0x0, 0xf2, 0x6, 0x0 }}, 0x8 },
{{{ 0x3, 0x2, 0xf0, 0xff, 0x3 }, { 0x11, 0x80, 0xf0, 0xff, 0x2 }}, 0x2 },
{{{ 0x1e, 0x0, 0x1f, 0xf, 0x0 }, { 0x10, 0x0, 0x1f, 0x7f, 0x0 }}, 0x0 },
{{{ 0x0, 0x0, 0x2f, 0x1, 0x0 }, { 0x0, 0x0, 0xff, 0x1, 0x0 }}, 0x4 },
{{{ 0xbe, 0x0, 0xf1, 0x1, 0x3 }, { 0x31, 0x0, 0xf1, 0x1, 0x0 }}, 0x4 },
{{{ 0x93, 0x85, 0x3f, 0x6, 0x1 }, { 0x0, 0x0, 0x5f, 0x7, 0x0 }}, 0x6 },
{{{ 0x6, 0x0, 0xa0, 0xf0, 0x0 }, { 0x44, 0x0, 0xc5, 0x75, 0x0 }}, 0xe },
{{{ 0x60, 0x0, 0x10, 0x81, 0x0 }, { 0x20, 0x8c, 0x12, 0x91, 0x0 }}, 0xe },
{{{ 0x1, 0x40, 0xf1, 0x53, 0x0 }, { 0x8, 0x40, 0xf1, 0x53, 0x0 }}, 0x0 },
{{{ 0x31, 0x0, 0x56, 0x31, 0x0 }, { 0x16, 0x0, 0x7d, 0x41, 0x0 }}, 0x0 },
{{{ 0x0, 0x10, 0xf2, 0x72, 0x0 }, { 0x13, 0x0, 0xf2, 0x72, 0x0 }}, 0xc },
{{{ 0x10, 0x0, 0x75, 0x93, 0x1 }, { 0x1, 0x0, 0xf5, 0x82, 0x1 }}, 0x0 },
{{{ 0x0, 0x0, 0xf6, 0xff, 0x2 }, { 0x0, 0x0, 0xf6, 0xff, 0x0 }}, 0x8 },
{{{ 0x30, 0x0, 0xff, 0xa0, 0x3 }, { 0x63, 0x0, 0x65, 0xb, 0x2 }}, 0x0 },
{{{ 0x2a, 0x0, 0xf6, 0x87, 0x0 }, { 0x2b, 0x0, 0x76, 0x25, 0x0 }}, 0x0 },
{{{ 0x85, 0x0, 0xb8, 0x84, 0x0 }, { 0x43, 0x0, 0xe5, 0x8f, 0x0 }}, 0x6 },
{{{ 0x7, 0x4f, 0xf2, 0x60, 0x0 }, { 0x12, 0x0, 0xf2, 0x72, 0x0 }}, 0x8 },
{{{ 0x5, 0x40, 0xb3, 0xd3, 0x0 }, { 0x86, 0x80, 0xf2, 0x24, 0x0 }}, 0x2 },
{{{ 0xd0, 0x0, 0x11, 0xcf, 0x0 }, { 0xd1, 0x0, 0xf4, 0xe8, 0x3 }}, 0x0 },
{{{ 0x5, 0x4e, 0xda, 0x25, 0x2 }, { 0x1, 0x0, 0xf9, 0x15, 0x0 }}, 0xa },
{{{ 0x3, 0x0, 0x8f, 0x7, 0x2 }, { 0x2, 0x0, 0xff, 0x6, 0x0 }}, 0x0 },
{{{ 0x13, 0x0, 0x8f, 0x7, 0x2 }, { 0x2, 0x0, 0xf9, 0x5, 0x0 }}, 0x0 },
{{{ 0xf0, 0x1, 0x97, 0x17, 0x0 }, { 0x21, 0xd, 0xf1, 0x18, 0x0 }}, 0x8 },
{{{ 0xf1, 0x41, 0x11, 0x11, 0x0 }, { 0xf1, 0x41, 0x11, 0x11, 0x0 }}, 0x2 },
{{{ 0x13, 0x0, 0x8f, 0x7, 0x2 }, { 0x2, 0x0, 0xff, 0x6, 0x0 }}, 0x0 },
{{{ 0x1, 0x0, 0x2f, 0x1, 0x0 }, { 0x1, 0x0, 0xaf, 0x1, 0x3 }}, 0xf },
{{{ 0x1, 0x6, 0xf3, 0xff, 0x0 }, { 0x8, 0x0, 0xf7, 0xff, 0x0 }}, 0x4 },
{{{ 0xc0, 0x4f, 0xf1, 0x3, 0x0 }, { 0xbe, 0xc, 0x10, 0x1, 0x0 }}, 0x2 },
{{{ 0x0, 0x2, 0xf0, 0xff, 0x0 }, { 0x11, 0x80, 0xf0, 0xff, 0x0 }}, 0x6 },
{{{ 0x81, 0x47, 0xf1, 0x83, 0x0 }, { 0xa2, 0x4, 0x91, 0x86, 0x0 }}, 0x6 },
{{{ 0xf0, 0xc0, 0xff, 0xff, 0x3 }, { 0xe5, 0x0, 0xfb, 0xf0, 0x0 }}, 0xe },
{{{ 0x0, 0x2, 0xf0, 0xff, 0x0 }, { 0x11, 0x80, 0xf0, 0xff, 0x0 }}, 0x6 }
};
static const PercussionNote percussionNotes[47] = {
{{{ 0x0, 0xb, 0xa8, 0x38, 0x0 }, { 0x0, 0x0, 0xd6, 0x49, 0x0 }}, 0x0, 0x4, 0x1, 0x97, 0x4 },
{{{ 0xc0, 0xc0, 0xf8, 0x3f, 0x2 }, { 0xc0, 0x0, 0xf6, 0x8e, 0x0 }}, 0x0, 0x4, 0x1, 0xf7, 0x4 },
{{{ 0xc0, 0x80, 0xc9, 0xab, 0x0 }, { 0xeb, 0x40, 0xb5, 0xf6, 0x0 }}, 0x1, 0x3, 0x1, 0x6a, 0x6 },
{{{ 0xc, 0x0, 0xd8, 0xa6, 0x0 }, { 0x0, 0x0, 0xd6, 0x4f, 0x0 }}, 0x1, 0x3, 0x1, 0x6c, 0x5 },
{{{ 0x1, 0x0, 0xe2, 0xd2, 0x0 }, { 0x3, 0x41, 0x8f, 0x48, 0x49 }}, 0xc, 0x4, 0x1, 0x2f, 0x5 },
{{{ 0x0, 0x0, 0xc8, 0x58, 0x3 }, { 0x0, 0x0, 0xf6, 0x4f, 0x0 }}, 0x9, 0x3, 0x1, 0x108, 0x4 },
{{{ 0x1, 0x0, 0xff, 0x5, 0x0 }, { 0xf2, 0xff, 0xe0, 0x50, 0x52 }}, 0x5d, 0x2, 0x1, 0x9f, 0x5 },
{{{ 0xe, 0x9, 0xb9, 0x47, 0x0 }, { 0xeb, 0x40, 0xf5, 0xe6, 0x0 }}, 0x0, 0x0, 0x1, 0x82, 0x6 },
{{{ 0x0, 0x0, 0xd6, 0x83, 0x0 }, { 0xd6, 0xd7, 0xe0, 0x41, 0x5e }}, 0x4a, 0x2, 0x1, 0xc7, 0x5 },
{{{ 0x1, 0x9, 0x89, 0x67, 0x0 }, { 0xd6, 0xd7, 0xe0, 0x41, 0x5e }}, 0x4a, 0x0, 0x1, 0x80, 0x6 },
{{{ 0x1, 0x0, 0xd6, 0x96, 0x0 }, { 0xd6, 0xd7, 0xe0, 0x41, 0x5e }}, 0x4a, 0x2, 0x1, 0xed, 0x5 },
{{{ 0x0, 0x9, 0xa9, 0x55, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x1, 0x82, 0x6 },
{{{ 0x2, 0x0, 0xc6, 0x96, 0x0 }, { 0xe0, 0x0, 0xe0, 0x40, 0x0 }}, 0x1, 0x2, 0x1, 0x123, 0x5 },
{{{ 0x5, 0x0, 0xf6, 0x56, 0x0 }, { 0xf7, 0xff, 0xb3, 0x90, 0x4f }}, 0x1, 0x2, 0x1, 0x15b, 0x5 },
{{{ 0x1, 0x0, 0xf7, 0x14, 0x0 }, { 0xf7, 0xff, 0x36, 0x90, 0x79 }}, 0xe7, 0x1, 0x1, 0x1ac, 0x5 },
{{{ 0x0, 0x0, 0xf6, 0x56, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x1, 0x2, 0x1, 0x18b, 0x5 },
{{{ 0x0, 0x83, 0xfb, 0x5, 0x0 }, { 0xf7, 0x41, 0x39, 0x90, 0x79 }}, 0x1, 0x1, 0x1, 0xc8, 0x5 },
{{{ 0x0, 0x0, 0xff, 0x5, 0x0 }, { 0xf7, 0xff, 0x36, 0x90, 0x79 }}, 0xe7, 0x1, 0x1, 0xf9, 0x5 },
{{{ 0x1, 0x0, 0xa0, 0x5, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x2, 0x1, 0x27a, 0x6 },
{{{ 0x0, 0x5, 0xf3, 0x6, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x2, 0x1, 0x108, 0x7 },
{{{ 0x1, 0x0, 0xf9, 0x34, 0x0 }, { 0xf7, 0xff, 0x36, 0x90, 0x79 }}, 0xe7, 0x1, 0x1, 0x147, 0x4 },
{{{ 0x0, 0x0, 0xf7, 0x16, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x2, 0x1, 0x120, 0x6 },
{{{ 0x1, 0x0, 0xff, 0x5, 0x0 }, { 0xf7, 0xff, 0x36, 0x90, 0x79 }}, 0xe7, 0x1, 0x1, 0x42, 0x6 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x1, 0x0, 0xff, 0x5, 0x0 }, { 0xf7, 0xff, 0x36, 0x90, 0x79 }}, 0xe7, 0x1, 0x1, 0x6d, 0x5 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 },
{{{ 0x0, 0x0, 0x0, 0x0, 0x0 }, { 0x0, 0x0, 0x0, 0x0, 0x0 }}, 0x0, 0x0, 0x0, 0x3fc, 0x4 }
};
const uint16 melodicFrequencies[36] = {
0x55, 0x5a, 0x60, 0x66, 0x6c, 0x72, 0x79, 0x80, 0x88,
0x90, 0x99, 0xa1, 0xab, 0xb5, 0xc0, 0xcc, 0xd8, 0xe5,
0xf2, 0x101, 0x110, 0x120, 0x132, 0x143, 0x156, 0x16b, 0x181,
0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x286
};
class AdLibDriver;
class AdLibChannel : public MidiChannel_MPU401 {
public:
void reset();
uint8 _program;
uint8 _volume;
uint8 _pedal;
};
struct MelodicVoice {
bool _used;
uint8 _channel;
uint8 _program;
uint8 _key;
uint32 _timestamp;
uint16 _frequency;
int8 _octave;
};
class AdLibDriver : public MidiDriver {
public:
AdLibDriver(Audio::Mixer *mixer) {
for (uint i = 0; i < 16; ++i)
_channels[i].init(this, i);
_isOpen = false;
_opl = nullptr;
memset(_voices, 0, sizeof(_voices));
_lastVoice = 0;
_percussionMask = 0;
_adlibTimerProc = nullptr;
_adlibTimerParam = nullptr;
}
int open() override;
void close() override;
void send(uint32 b) override;
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override { return &_channels[9]; }
bool isOpen() const override { return _isOpen; }
uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override {
_adlibTimerProc = timerProc;
_adlibTimerParam = timerParam;
}
protected:
OPL::OPL *_opl;
AdLibChannel _channels[16];
MelodicVoice _voices[kNumMelodic];
uint8 _notesPerPercussion[kNumPercussion];
uint _lastVoice;
uint8 _percussionMask;
void noteOff(uint8 channel, uint8 note);
void noteOn(uint8 channel, uint8 note, uint8 velocity);
void allNotesOff();
void setModulationWheel(uint8 channel, uint8 value);
void setFootController(uint8 channel, uint8 value);
void setVolume(uint8 channel, uint8 value);
void setPitchBend(uint8 channel, int16 value);
void playNote(uint8 voice, uint8 octave, uint16 frequency);
void programOperatorSimple(uint8 offset, const OPLOperator &op);
void programOperator(uint8 offset, const OPLOperator &op);
void setOperatorLevel(uint8 offset, const OPLOperator &op, uint8 velocity, uint8 channel, bool forceVolume);
void setupPercussion(const PercussionNote &note);
void playPercussion(uint8 channel, const PercussionNote &note, uint8 velocity);
void programMelodicVoice(uint8 voice, uint8 program);
void playMelodicNote(uint8 voice, uint8 channel, uint8 note, uint8 velocity);
void muteMelodicVoice(uint8 voice);
void initVoices();
private:
void onTimer();
Common::TimerManager::TimerProc _adlibTimerProc;
void *_adlibTimerParam;
bool _isOpen;
};
MidiDriver *createAdLibDriver() {
return new AdLibDriver(g_system->getMixer());
}
void AdLibChannel::reset() {
_program = 0;
_volume = 127;
_pedal = 0;
}
/*
bit 7 - Clear: AM depth is 1 dB
bit 6 - Clear: Vibrato depth is 7 cent
bit 5 - Set: Rhythm enabled (6 melodic voices)
bit 4 - Bass drum off
bit 3 - Snare drum off
bit 2 - Tom tom off
bit 1 - Cymbal off
bit 0 - Hi Hat off
*/
const uint8 kDefaultPercussionMask = 0x20;
int AdLibDriver::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
_isOpen = true;
_opl = OPL::Config::create();
_opl->init();
_opl->writeReg(0x1, 0x20); // set bit 5 (enable all waveforms)
// Reset the OPL registers.
for (uint i = 0; i < kNumVoices; ++i) {
_opl->writeReg(0xA0 + i, 0); // frequency
_opl->writeReg(0xB0 + i, 0); // key on
_opl->writeReg(0xC0 + i, 0); // feedback
}
_opl->writeReg(0xBD, kDefaultPercussionMask);
initVoices();
_opl->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::onTimer));
return 0;
}
void AdLibDriver::close() {
if (!_isOpen)
return;
_isOpen = false;
delete _opl;
}
void AdLibDriver::send(uint32 b) {
uint channel = b & 0xf;
uint cmd = (b >> 4) & 0xf;
uint param1 = (b >> 8) & 0xff;
uint param2 = (b >> 16) & 0xff;
switch (cmd) {
case 8:
noteOff(channel, param1);
break;
case 9:
// TODO: map volume?
noteOn(channel, param1, param2);
break;
case 11:
// controller change
switch (param1) {
case 1:
setModulationWheel(channel, param2);
break;
case 4:
setFootController(channel, param2);
break;
case 7:
setVolume(channel, param2);
break;
case 123:
// all notes off
allNotesOff();
break;
default:
break;
}
break;
case 12:
// program change
_channels[channel]._program = param1;
break;
case 14:
setPitchBend(channel, (param1 | (param2 << 7)) - 0x2000);
break;
default:
break;
}
}
void AdLibDriver::noteOff(uint8 channel, uint8 note) {
if (channel == 9) {
if (note < 35 || note > 81)
return;
_percussionMask &= ~(1 << percussionNotes[note - 35].percussion);
_opl->writeReg(0xBD, _percussionMask);
return;
}
for (int i = kNumMelodic - 1; i >= 0; --i) {
if (_voices[i]._channel != channel)
continue;
if (_voices[i]._key != note)
continue;
muteMelodicVoice(i);
_voices[i]._used = false;
return;
}
//debug(1, "failed to find voice off for channel %d, note %d", channel, note);
}
void AdLibDriver::noteOn(uint8 channel, uint8 note, uint8 velocity) {
if (channel == 9) {
if (note < 35 || note > 81)
return;
const PercussionNote &info = percussionNotes[note - 35];
if (!info.valid)
return;
if (note != _notesPerPercussion[info.percussion]) {
setupPercussion(info);
_notesPerPercussion[info.percussion] = note;
}
playPercussion(channel, info, velocity);
return;
}
if (velocity == 0) {
noteOff(channel, note);
return;
}
// We want to play a note on a melodic (voice) channel.
// First, look for a voice playing the same note with the same program.
for (uint i = 0; i < kNumMelodic; ++i) {
if (_voices[i]._channel != channel || _voices[i]._key != note)
continue;
if (_voices[i]._program != _channels[channel]._program)
continue;
muteMelodicVoice(i);
playMelodicNote(i, channel, note, velocity);
return;
}
// The loops below try to start at _lastVoice and find a voice to use.
// They ignore _lastVoice itself, and update _lastVoice if they succeed.
// Then, try finding a melodic voice with the same program.
for (uint i = (_lastVoice + 1) % kNumMelodic; i != _lastVoice; i = (i + 1) % kNumMelodic) {
if (_voices[i]._used)
continue;
if (_voices[i]._program != _channels[channel]._program)
continue;
playMelodicNote(i, channel, note, velocity);
_lastVoice = i;
return;
}
// Then, try finding a free melodic voice of any kind.
for (uint i = (_lastVoice + 1) % kNumMelodic; i != _lastVoice; i = (i + 1) % kNumMelodic) {
if (_voices[i]._used)
continue;
programMelodicVoice(i, _channels[channel]._program);
playMelodicNote(i, channel, note, velocity);
_lastVoice = i;
return;
}
// Then just try finding a melodic voice with the same program,
// and steal it.
for (uint i = (_lastVoice + 1) % kNumMelodic; i != _lastVoice; i = (i + 1) % kNumMelodic) {
if (_voices[i]._program != _channels[channel]._program)
continue;
muteMelodicVoice(i);
playMelodicNote(i, channel, note, velocity);
_lastVoice = i;
return;
}
// Finally, just take control of the channel used least recently.
uint voiceId = 0;
uint32 bestTimestamp = 0xffffffff;
for (uint i = 0; i < kNumMelodic; ++i)
if (bestTimestamp > _voices[i]._timestamp) {
voiceId = i;
bestTimestamp = _voices[i]._timestamp;
}
//debug(1, "ran out of voices for channel %d, note %d, program %d: reused voice %d", channel, note, _channels[channel]._program, voiceId);
programMelodicVoice(voiceId, _channels[channel]._program);
playMelodicNote(voiceId, channel, note, velocity);
_lastVoice = voiceId;
}
// TODO: this doesn't match original
void AdLibDriver::allNotesOff() {
for (uint i = 0; i < kNumMelodic; ++i) {
muteMelodicVoice(i);
_voices[i]._used = false;
}
_percussionMask = kDefaultPercussionMask;
_opl->writeReg(0xBD, kDefaultPercussionMask);
}
void AdLibDriver::setModulationWheel(uint8 channel, uint8 value) {
if (value >= 64)
_percussionMask |= 0x80;
else
_percussionMask &= 0x7f;
_opl->writeReg(0xBD, _percussionMask);
}
void AdLibDriver::setFootController(uint8 channel, uint8 value) {
_channels[channel]._pedal = (value >= 64);
}
void AdLibDriver::setVolume(uint8 channel, uint8 value) {
_channels[channel]._volume = value;
}
void AdLibDriver::setPitchBend(uint8 channel, int16 value) {
for (uint i = 0; i < kNumMelodic; ++i) {
if (_voices[i]._channel != channel || !_voices[i]._used)
continue;
// index into frequency table
uint f = 12 + (_voices[i]._key % 12);
int16 bendAmount = value;
if (bendAmount > 0) {
// bend up two semitones
bendAmount *= (melodicFrequencies[f + 2] - melodicFrequencies[f]);
} else {
// bend down two semitones
bendAmount *= (melodicFrequencies[f] - melodicFrequencies[f - 2]);
}
bendAmount /= 0x2000;
bendAmount += melodicFrequencies[f]; // add the base frequency
playNote(i, _voices[i]._octave, bendAmount);
_voices[i]._timestamp = g_system->getMillis();
}
}
void AdLibDriver::playNote(uint8 voice, uint8 octave, uint16 frequency) {
/* Percussions are always fed keyOn = 0 even to set the note, as they are activated using the
BD register instead. I wonder if they can just be fed the same value as melodic voice and
be done with it. */
uint8 keyOn = (voice < kNumMelodic) ? 0x20 : 0;
// key on, octave, high 2 bits of frequency
_opl->writeReg(0xB0 + voice, keyOn | ((octave & 7) << 2) | ((frequency >> 8) & 3));
// low 8 bits of frequency
_opl->writeReg(0xA0 + voice, frequency & 0xff);
}
void AdLibDriver::programOperatorSimple(uint8 offset, const OPLOperator &op) {
_opl->writeReg(0x40 + offset, op.levels & LEVEL_MASK);
_opl->writeReg(0x60 + offset, op.attackDecay);
_opl->writeReg(0x80 + offset, op.sustainRelease);
}
void AdLibDriver::programOperator(uint8 offset, const OPLOperator &op) {
_opl->writeReg(0x20 + offset, op.characteristic);
_opl->writeReg(0x60 + offset, op.attackDecay);
_opl->writeReg(0x80 + offset, op.sustainRelease);
_opl->writeReg(0xE0 + offset, op.waveform);
_opl->writeReg(0x40 + offset, op.levels);
}
const uint16 adlibLogVolume[] = {
0, 37, 58, 73, 85, 95, 103, 110, 116, 121, 127, 131, 135, 139, 143, 146,
149, 153, 155, 158, 161, 163, 165, 168, 170, 172, 174, 176, 178, 179, 181, 183,
184, 186, 188, 189, 191, 192, 193, 195, 196, 197, 198, 200, 201, 202, 203, 204,
205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 219,
220, 221, 222, 223, 223, 224, 225, 226, 226, 227, 228, 228, 229, 230, 231, 231,
232, 233, 233, 234, 234, 235, 236, 236, 237, 237, 238, 239, 239, 240, 240, 241,
241, 242, 242, 243, 244, 244, 245, 245, 246, 246, 247, 247, 248, 248, 248, 249,
249, 250, 250, 251, 251, 252, 252, 253, 253, 253, 254, 254, 255, 255, 256, 256,
256
};
void AdLibDriver::setOperatorLevel(uint8 offset, const OPLOperator &op, uint8 velocity, uint8 channel, bool forceVolume) {
uint8 programLevel = LEVEL_MASK;
if (!forceVolume)
programLevel -= (op.levels & LEVEL_MASK);
uint32 noteLevel = adlibLogVolume[velocity];
uint32 channelLevel = adlibLogVolume[_channels[channel]._volume];
// programLevel comes from the static data and is probably already in the correct logarithmic scale
uint32 finalLevel = LEVEL_MASK - ((noteLevel * channelLevel * programLevel) >> 16);
// high 2 bits are scaling level, the rest is (inversed) volume
_opl->writeReg(0x40 + offset, (op.levels & 0xc0) | (finalLevel & 0x3f));
}
const uint8 operatorOffsetsForPercussion[] = {
0x11, // hi-hat
0x15, // cymbal
0x12, // tom tom
0x14 // snare drum
};
void AdLibDriver::setupPercussion(const PercussionNote &note) {
if (note.percussion < 4) {
// simple percussion (1 operator)
// turn off relevant percussion
_percussionMask &= ~(1 << note.percussion);
_opl->writeReg(0xBD, _percussionMask);
programOperatorSimple(operatorOffsetsForPercussion[note.percussion], note.op[0]);
return;
}
// bass drum (2 operators)
// turn off bass drum
_percussionMask &= ~(0x10);
_opl->writeReg(0xBD, _percussionMask);
programOperator(0x10, note.op[0]);
programOperator(0x13, note.op[1]);
_opl->writeReg(0xC0 + 6, note.feedbackAlgo);
}
void AdLibDriver::playPercussion(uint8 channel, const PercussionNote &note, uint8 velocity) {
if (note.percussion < 4) {
// simple percussion (1 operator)
// turn off relevant percussion
_percussionMask &= ~(1 << note.percussion);
_opl->writeReg(0xBD, _percussionMask);
setOperatorLevel(operatorOffsetsForPercussion[note.percussion], note.op[0], velocity, channel, true);
if (note.percussion == 2) {
// tom tom
playNote(8, note.octave, note.frequency);
} else if (note.percussion == 3) {
// snare drum
playNote(7, note.octave, note.frequency);
}
// turn on relevant percussion
_percussionMask |= (1 << note.percussion);
_opl->writeReg(0xBD, _percussionMask);
return;
}
// turn off bass drum
_percussionMask &= ~(0x10);
_opl->writeReg(0xBD, _percussionMask);
if (note.feedbackAlgo & 1) {
// operators 1 and 2 in additive synthesis
setOperatorLevel(0x10, note.op[0], velocity, channel, true);
setOperatorLevel(0x13, note.op[1], velocity, channel, true);
} else {
// operator 2 is modulating operator 1
setOperatorLevel(0x13, note.op[1], velocity, channel, true);
}
playNote(6, note.octave, note.frequency);
// turn on bass drum
_percussionMask |= 0x10;
_opl->writeReg(0xBD, _percussionMask);
}
const uint8 offset1ForMelodic[kNumVoices] = { 0x0, 0x1, 0x2, 0x8, 0x9, 0xa, 0x10, 0x11, 0x12 };
const uint8 offset2ForMelodic[kNumVoices] = { 0x3, 0x4, 0x5, 0xb, 0xc, 0xd, 0x13, 0x14, 0x15 };
void AdLibDriver::programMelodicVoice(uint8 voice, uint8 program) {
assert(program < 128);
assert(voice < kNumMelodic);
const MelodicProgram &info = melodicPrograms[program];
uint8 offset1 = offset1ForMelodic[voice];
uint8 offset2 = offset2ForMelodic[voice];
// Start at lowest volume.
_opl->writeReg(0x40 + offset1, LEVEL_MASK);
_opl->writeReg(0x40 + offset2, LEVEL_MASK);
muteMelodicVoice(voice);
programOperator(offset1, info.op[0]);
programOperator(offset2, info.op[1]);
_opl->writeReg(0xC0 + voice, info.feedbackAlgo);
}
void AdLibDriver::playMelodicNote(uint8 voice, uint8 channel, uint8 note, uint8 velocity) {
assert(voice < kNumMelodic);
uint8 octave = note / 12;
uint8 f = 12 + (note % 12);
if (octave > 7)
octave = 7;
const MelodicProgram &info = melodicPrograms[_channels[channel]._program];
uint8 offset1 = offset1ForMelodic[voice];
uint8 offset2 = offset2ForMelodic[voice];
if (info.feedbackAlgo & 1) {
setOperatorLevel(offset1, info.op[0], velocity, channel, false);
setOperatorLevel(offset2, info.op[1], velocity, channel, false);
} else {
setOperatorLevel(offset2, info.op[1], velocity, channel, true);
}
playNote(voice, octave, melodicFrequencies[f]);
_voices[voice]._program = _channels[channel]._program;
_voices[voice]._key = note;
_voices[voice]._channel = channel;
_voices[voice]._timestamp = g_system->getMillis();
_voices[voice]._frequency = melodicFrequencies[f];
_voices[voice]._octave = octave;
_voices[voice]._used = true;
}
void AdLibDriver::muteMelodicVoice(uint8 voice) {
_opl->writeReg(0xB0 + voice, 0 | ((_voices[voice]._octave & 7) << 2) | ((_voices[voice]._frequency >> 8) & 3));
}
MidiChannel *AdLibDriver::allocateChannel() {
for (uint i = 0; i < 16; ++i) {
if (i == 9)
continue;
if (_channels[i].allocate())
return &_channels[i];
}
return nullptr;
}
void AdLibDriver::onTimer() {
if (_adlibTimerProc)
(*_adlibTimerProc)(_adlibTimerParam);
}
void AdLibDriver::initVoices() {
_percussionMask = kDefaultPercussionMask;
_opl->writeReg(0xBD, _percussionMask);
for (uint i = 0; i < 16; ++i)
_channels[i].reset();
for (uint i = 0; i < kNumMelodic; ++i) {
_voices[i]._key = 0xff;
_voices[i]._program = 0xff;
_voices[i]._channel = 0xff;
_voices[i]._timestamp = 0;
_voices[i]._frequency = 0;
_voices[i]._octave = 0;
_voices[i]._used = false;
}
for (uint i = 0; i < kNumPercussion; ++i)
_notesPerPercussion[i] = 0xff;
_lastVoice = 0;
}
} // namespace Parallaction

View File

@@ -0,0 +1,775 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/graphics.h"
#include "parallaction/parallaction.h"
#include "common/tokenizer.h"
namespace Parallaction {
class WrappedLineFormatter {
protected:
Common::String _line;
Font *_font;
uint16 _lines, _lineWidth;
virtual void setup() = 0;
virtual void action() = 0;
virtual void end() = 0;
virtual Common::String expand(const Common::String &token) { return token; }
void textAccum(const Common::String &token, uint16 width) {
if (token.empty()) {
return;
}
_lineWidth += width;
_line += token;
}
void textNewLine() {
_lines++;
_lineWidth = 0;
_line.clear();
}
public:
WrappedLineFormatter(Font *font) : _font(font), _lines(0), _lineWidth(0) { }
virtual ~WrappedLineFormatter() { }
virtual void calc(const Common::String &text, uint16 maxwidth) {
setup();
_lineWidth = 0;
_line.clear();
_lines = 0;
Common::StringTokenizer tokenizer(text, " ");
Common::String token;
Common::String blank(" ");
uint16 blankWidth = _font->getStringWidth(" ");
uint16 tokenWidth = 0;
while (!tokenizer.empty()) {
token = tokenizer.nextToken();
token = expand(token);
if (token == "/") {
tokenWidth = 0;
action();
textNewLine();
} else {
// todo: expand '%'
tokenWidth = _font->getStringWidth(token.c_str());
if (_lineWidth == 0) {
textAccum(token, tokenWidth);
} else {
if (_lineWidth + blankWidth + tokenWidth <= maxwidth) {
textAccum(blank, blankWidth);
textAccum(token, tokenWidth);
} else {
action();
textNewLine();
textAccum(token, tokenWidth);
}
}
}
}
end();
}
};
class StringExtent_NS : public WrappedLineFormatter {
uint _width, _height;
protected:
Common::String expand(const Common::String &token) override {
if (token.compareToIgnoreCase("%p") == 0) {
return Common::String("/");
}
return token;
}
void setup() override {
_width = _height = 0;
_line.clear();
_lines = 0;
_width = 0;
}
void action() override {
if (_lineWidth > _width) {
_width = _lineWidth;
}
_height = _lines * _font->height();
}
void end() override {
action();
}
public:
StringExtent_NS(Font *font) : WrappedLineFormatter(font), _width(0), _height(0) { }
uint width() const { return _width; }
uint height() const { return _height; }
};
class StringWriter_NS : public WrappedLineFormatter {
Parallaction_ns *_vm;
uint _width, _height;
byte _color;
Graphics::Surface *_surf;
protected:
Common::String expand(const Common::String& token) override {
if (token.compareToIgnoreCase("%p") == 0) {
Common::String t(_vm->_password);
for (int i = t.size(); i < 7; i++) {
t += '.';
}
return Common::String("> ") + t;
} else
if (token.compareToIgnoreCase("%s") == 0) {
char buf[20];
Common::sprintf_s(buf, "%i", _vm->_score);
return Common::String(buf);
}
return token;
}
void setup() override {
}
void action() override {
if (_line.empty()) {
return;
}
uint16 rx = 10;
uint16 ry = 4 + _lines * _font->height(); // y
_font->setColor(_color);
_font->drawString(_surf, rx, ry, _line.c_str());
}
void end() override {
action();
}
public:
StringWriter_NS(Parallaction_ns *vm, Font *font) : WrappedLineFormatter(font), _vm(vm),
_width(0), _height(0), _color(0), _surf(nullptr) { }
void write(const Common::String &text, uint maxWidth, byte color, Graphics::Surface *surf) {
StringExtent_NS se(_font);
se.calc(text, maxWidth);
_width = se.width() + 10;
_height = se.height() + 20;
_color = color;
_surf = surf;
calc(text, maxWidth);
}
};
#define BALLOON_TRANSPARENT_COLOR_NS 2
#define BALLOON_TRANSPARENT_COLOR_BR 0
#define BALLOON_TAIL_WIDTH 12
#define BALLOON_TAIL_HEIGHT 10
byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = {
{
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
},
{
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02,
0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02
}
};
class BalloonManager_ns : public BalloonManager {
Parallaction_ns *_vm;
static int16 _dialogueBalloonX[5];
byte _textColors[3];
struct Balloon {
Common::Rect outerBox;
Common::Rect innerBox;
Graphics::Surface *surface;
GfxObj *obj;
} _intBalloons[5];
uint _numBalloons;
int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
Balloon *getBalloon(uint id);
StringWriter_NS _sw;
StringExtent_NS _se;
public:
BalloonManager_ns(Parallaction_ns *vm, Font *font);
~BalloonManager_ns() override;
void reset() override;
int setLocationBalloon(const Common::String &text, bool endGame) override;
int setDialogueBalloon(const Common::String &text, uint16 winding, TextColor textColor) override;
int setSingleBalloon(const Common::String &text, uint16 x, uint16 y, uint16 winding, TextColor textColor) override;
void setBalloonText(uint id, const Common::String &text, TextColor textColor) override;
int hitTestDialogueBalloon(int x, int y) override;
};
int16 BalloonManager_ns::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 };
BalloonManager_ns::BalloonManager_ns(Parallaction_ns *vm, Font *font) : _vm(vm), _numBalloons(0), _sw(vm, font), _se(font) {
_textColors[kSelectedColor] = 0;
_textColors[kUnselectedColor] = 3;
_textColors[kNormalColor] = 0;
}
BalloonManager_ns::~BalloonManager_ns() {
}
BalloonManager_ns::Balloon* BalloonManager_ns::getBalloon(uint id) {
assert(id < _numBalloons);
return &_intBalloons[id];
}
int BalloonManager_ns::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) {
assert(_numBalloons < 5);
int id = _numBalloons;
Balloon *balloon = &_intBalloons[id];
int16 real_h = (winding == -1) ? h : h + 9;
balloon->surface = new Graphics::Surface;
balloon->surface->create(w, real_h, Graphics::PixelFormat::createFormatCLUT8());
balloon->surface->fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR_NS);
Common::Rect r(w, h);
balloon->surface->fillRect(r, 0);
balloon->outerBox = r;
r.grow(-borderThickness);
balloon->surface->fillRect(r, 1);
balloon->innerBox = r;
if (winding != -1) {
// draws tail
// TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill.
winding = (winding == 0 ? 1 : 0);
Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT);
s.moveTo(r.width()/2 - 5, r.bottom - 1);
_vm->_gfx->blt(s, _resBalloonTail[winding], balloon->surface, LAYER_FOREGROUND, 100, BALLOON_TRANSPARENT_COLOR_NS);
}
_numBalloons++;
return id;
}
int BalloonManager_ns::setSingleBalloon(const Common::String &text, uint16 x, uint16 y, uint16 winding, TextColor textColor) {
int16 w, h;
_se.calc(text, MAX_BALLOON_WIDTH);
w = _se.width() + 14;
h = _se.height() + 20;
_vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
int id = createBalloon(w+5, h, winding, 1);
Balloon *balloon = &_intBalloons[id];
_sw.write(text, MAX_BALLOON_WIDTH, _textColors[textColor], balloon->surface);
// TODO: extract some text to make a name for obj
balloon->obj = _vm->_gfx->registerBalloon(new SurfaceToFrames(balloon->surface), nullptr);
balloon->obj->x = x;
balloon->obj->y = y;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
return id;
}
int BalloonManager_ns::setDialogueBalloon(const Common::String &text, uint16 winding, TextColor textColor) {
int16 w, h;
_se.calc(text, MAX_BALLOON_WIDTH);
w = _se.width() + 14;
h = _se.height() + 20;
int id = createBalloon(w+5, h, winding, 1);
Balloon *balloon = &_intBalloons[id];
_sw.write(text, MAX_BALLOON_WIDTH, _textColors[textColor], balloon->surface);
// TODO: extract some text to make a name for obj
balloon->obj = _vm->_gfx->registerBalloon(new SurfaceToFrames(balloon->surface), nullptr);
balloon->obj->x = _dialogueBalloonX[id];
balloon->obj->y = 10;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
if (id > 0) {
balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].outerBox.height();
}
return id;
}
void BalloonManager_ns::setBalloonText(uint id, const Common::String &text, TextColor textColor) {
Balloon *balloon = getBalloon(id);
balloon->surface->fillRect(balloon->innerBox, 1);
if (textColor != kUnselectedColor && !text.contains("%P")) {
_vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
}
_sw.write(text, MAX_BALLOON_WIDTH, _textColors[textColor], balloon->surface);
}
int BalloonManager_ns::setLocationBalloon(const Common::String &text, bool endGame) {
int16 w, h;
_se.calc(text, MAX_BALLOON_WIDTH);
w = _se.width() + 14;
h = _se.height() + 20;
int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR_NS);
Balloon *balloon = &_intBalloons[id];
_sw.write(text, MAX_BALLOON_WIDTH, _textColors[kNormalColor], balloon->surface);
// TODO: extract some text to make a name for obj
balloon->obj = _vm->_gfx->registerBalloon(new SurfaceToFrames(balloon->surface), nullptr);
balloon->obj->x = 5;
balloon->obj->y = 5;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
return id;
}
int BalloonManager_ns::hitTestDialogueBalloon(int x, int y) {
Common::Point p;
for (uint i = 0; i < _numBalloons; i++) {
p.x = x - _intBalloons[i].obj->x;
p.y = y - _intBalloons[i].obj->y;
if (_intBalloons[i].innerBox.contains(p))
return i;
}
return -1;
}
void BalloonManager_ns::reset() {
for (uint i = 0; i < _numBalloons; i++) {
_intBalloons[i].obj = nullptr;
_intBalloons[i].surface = nullptr; // no need to delete surface, since it is done by Gfx
}
_numBalloons = 0;
if (_vm->_password.size() == 0) {
_vm->stopTextToSpeech();
}
}
class StringExtent_BR : public WrappedLineFormatter {
uint _width, _height;
protected:
void setup() override {
_width = _height = 0;
_line.clear();
_lines = 0;
_width = 0;
}
void action() override {
if (_lineWidth > _width) {
_width = _lineWidth;
}
_height = _lines * _font->height();
}
void end() override {
action();
}
public:
StringExtent_BR(Font *font) : WrappedLineFormatter(font), _width(0), _height(0) { }
uint width() const { return _width; }
uint height() const { return _height; }
};
class StringWriter_BR : public WrappedLineFormatter {
uint _width, _height;
byte _color;
uint _x, _y;
Graphics::Surface *_surf;
protected:
StringWriter_BR(Font *font, byte color) : WrappedLineFormatter(font), _width(0), _height(0),
_color(color), _x(0), _y(0), _surf(nullptr) {
}
void setup() override {
}
void action() override {
if (_line.empty()) {
return;
}
uint16 rx = _x + (_surf->w - _lineWidth) / 2;
uint16 ry = _y + _lines * _font->height(); // y
_font->setColor(_color);
_font->drawString(_surf, rx, ry, _line.c_str());
}
void end() override {
action();
}
public:
StringWriter_BR(Font *font) : WrappedLineFormatter(font), _width(0), _height(0),
_color(0), _x(0), _y(0), _surf(nullptr) { }
void write(const Common::String &text, uint maxWidth, byte color, Graphics::Surface *surf) {
StringExtent_BR se(_font);
se.calc(text, maxWidth);
_width = se.width() + 10;
_height = se.height() + 12;
_color = color;
_surf = surf;
_x = 0;
_y = (_surf->h - _height) / 2;
calc(text, maxWidth);
}
};
class BalloonManager_br : public BalloonManager {
Parallaction_br *_vm;
byte _textColors[3];
struct Balloon {
Common::Rect box;
Graphics::Surface *surface;
GfxObj *obj;
} _intBalloons[3];
uint _numBalloons;
Frames *_leftBalloon;
Frames *_rightBalloon;
void cacheAnims();
int createBalloon(int16 w, int16 h, uint16 borderThickness);
Balloon *getBalloon(uint id);
Graphics::Surface *expandBalloon(Frames *data, int frameNum);
StringWriter_BR _sw;
StringExtent_BR _se;
public:
BalloonManager_br(Parallaction_br *vm, Font *font);
~BalloonManager_br() override;
void reset() override;
int setLocationBalloon(const Common::String &text, bool endGame) override;
int setDialogueBalloon(const Common::String &text, uint16 winding, TextColor textColor) override;
int setSingleBalloon(const Common::String &text, uint16 x, uint16 y, uint16 winding, TextColor textColor) override;
void setBalloonText(uint id, const Common::String &text, TextColor textColor) override;
int hitTestDialogueBalloon(int x, int y) override;
};
BalloonManager_br::Balloon* BalloonManager_br::getBalloon(uint id) {
assert(id < _numBalloons);
return &_intBalloons[id];
}
Graphics::Surface *BalloonManager_br::expandBalloon(Frames *data, int frameNum) {
Common::Rect rect;
data->getRect(frameNum, rect);
rect.translate(-rect.left, -rect.top);
Graphics::Surface *surf = new Graphics::Surface;
surf->create(rect.width(), rect.height(), Graphics::PixelFormat::createFormatCLUT8());
_vm->_gfx->unpackBlt(rect, data->getData(frameNum), data->getRawSize(frameNum), surf, LAYER_FOREGROUND, 100, BALLOON_TRANSPARENT_COLOR_BR);
return surf;
}
int BalloonManager_br::setSingleBalloon(const Common::String &text, uint16 x, uint16 y, uint16 winding, TextColor textColor) {
cacheAnims();
_vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
int id = _numBalloons;
Frames *src = nullptr;
int srcFrame = 0;
Balloon *balloon = &_intBalloons[id];
if (winding == 0) {
src = _rightBalloon;
srcFrame = 0;
} else
if (winding == 1) {
src = _leftBalloon;
srcFrame = 0;
}
assert(src);
balloon->surface = expandBalloon(src, srcFrame);
src->getRect(srcFrame, balloon->box);
_sw.write(text, 216, _textColors[textColor], balloon->surface);
// TODO: extract some text to make a name for obj
balloon->obj = _vm->_gfx->registerBalloon(new SurfaceToFrames(balloon->surface), nullptr);
balloon->obj->x = x + balloon->box.left;
balloon->obj->y = y + balloon->box.top;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
_numBalloons++;
return id;
}
int BalloonManager_br::setDialogueBalloon(const Common::String &text, uint16 winding, TextColor textColor) {
cacheAnims();
int id = _numBalloons;
Frames *src = nullptr;
int srcFrame = 0;
Balloon *balloon = &_intBalloons[id];
if (winding == 0) {
src = _rightBalloon;
srcFrame = 0;
} else
if (winding == 1) {
src = _leftBalloon;
srcFrame = id;
}
assert(src);
balloon->surface = expandBalloon(src, srcFrame);
src->getRect(srcFrame, balloon->box);
// TODO: fix text positioning in the Amiga version
_sw.write(text, 216, _textColors[textColor], balloon->surface);
// TODO: extract some text to make a name for obj
balloon->obj = _vm->_gfx->registerBalloon(new SurfaceToFrames(balloon->surface), nullptr);
balloon->obj->x = balloon->box.left;
balloon->obj->y = balloon->box.top;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
_numBalloons++;
return id;
}
void BalloonManager_br::setBalloonText(uint id, const Common::String &text, TextColor textColor) {
Balloon *balloon = getBalloon(id);
if (textColor != kUnselectedColor) {
_vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
}
_sw.write(text, 216, _textColors[textColor], balloon->surface);
}
int BalloonManager_br::createBalloon(int16 w, int16 h, uint16 borderThickness) {
assert(_numBalloons < 5);
int id = _numBalloons;
Balloon *balloon = &_intBalloons[id];
balloon->surface = new Graphics::Surface;
balloon->surface->create(w, h, Graphics::PixelFormat::createFormatCLUT8());
Common::Rect rect(w, h);
balloon->surface->fillRect(rect, 1);
rect.grow(-borderThickness);
balloon->surface->fillRect(rect, 15);
_numBalloons++;
return id;
}
int BalloonManager_br::setLocationBalloon(const Common::String &text, bool endGame) {
_se.calc(text, 240);
int id = createBalloon(_se.width() + 20, _se.height() + 30, 2);
Balloon *balloon = &_intBalloons[id];
_sw.write(text, 240, kNormalColor, balloon->surface);
balloon->obj = _vm->_gfx->registerBalloon(new SurfaceToFrames(balloon->surface), nullptr);
balloon->obj->x = 5;
balloon->obj->y = 5;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
return 0;
}
int BalloonManager_br::hitTestDialogueBalloon(int x, int y) {
for (uint i = 0; i < _numBalloons; i++) {
if (_intBalloons[i].box.contains(x, y)) {
return i;
}
}
return -1;
}
void BalloonManager_br::reset() {
for (uint i = 0; i < _numBalloons; i++) {
_intBalloons[i].obj = nullptr;
_intBalloons[i].surface = nullptr; // no need to delete surface, since it is done by Gfx
}
_numBalloons = 0;
_vm->stopTextToSpeech();
}
void BalloonManager_br::cacheAnims() {
if (!_leftBalloon) {
_leftBalloon = _vm->_disk->loadFrames("fumetto.ani");
_rightBalloon = _vm->_disk->loadFrames("fumdx.ani");
}
}
BalloonManager_br::BalloonManager_br(Parallaction_br *vm, Font *font) : _vm(vm), _numBalloons(0),
_leftBalloon(nullptr), _rightBalloon(nullptr), _sw(font), _se(font) {
if (_vm->getPlatform() == Common::kPlatformDOS) {
_textColors[kSelectedColor] = 12;
_textColors[kUnselectedColor] = 0;
_textColors[kNormalColor] = 0;
} else {
_textColors[kSelectedColor] = 11;
_textColors[kUnselectedColor] = 1;
_textColors[kNormalColor] = 1;
}
}
BalloonManager_br::~BalloonManager_br() {
delete _leftBalloon;
delete _rightBalloon;
}
void Parallaction_ns::setupBalloonManager() {
_balloonMan = new BalloonManager_ns(this, _dialogueFont);
}
void Parallaction_br::setupBalloonManager() {
_balloonMan = new BalloonManager_br(this, _dialogueFont);
}
} // namespace Parallaction

View File

@@ -0,0 +1,92 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/parallaction.h"
#include "common/textconsole.h"
namespace Parallaction {
void Parallaction_br::_c_null(void *) {
// do nothing :)
}
void Parallaction_br::_c_blufade(void *) {
warning("Parallaction_br::_c_blufade() not yet implemented");
}
void Parallaction_br::_c_resetpalette(void *) {
warning("Parallaction_br::_c_resetpalette() not yet implemented");
}
/*
Used in part 3 by location ULTIMAVAG to
simulate the movement of trees and railroad.
*/
void Parallaction_br::_c_ferrcycle(void *) {
Palette pal = _gfx->_palette;
if (_ferrcycleMode == 1) {
for (int i = 0; i < 16; ++i) {
pal.setEntry(4 * i + 192, 0, 0, 0);
pal.setEntry(4 * i + 193, 30, 12, 12);
pal.setEntry(4 * i + 194, 28, 47, 54);
pal.setEntry(4 * i + 195, 63, 63, 63);
}
} else if (_ferrcycleMode == 2) {
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
pal.setEntry(j + 16 * i + 192, 0, 0, 0);
pal.setEntry(j + 16 * i + 196, 30, 12, 12);
pal.setEntry(j + 16 * i + 200, 28, 47, 54);
pal.setEntry(j + 16 * i + 204, 63, 63, 63);
}
}
} else if (_ferrcycleMode == 0) {
for (int i = 0; i < 16; ++i) {
pal.setEntry(i + 192, 0, 0, 0);
pal.setEntry(i + 208, 30, 12, 12);
pal.setEntry(i + 224, 28, 47, 54);
pal.setEntry(i + 240, 63, 63, 63);
}
}
_gfx->setPalette(pal);
_ferrcycleMode = (_ferrcycleMode + 1) % 3;
}
void Parallaction_br::_c_lipsinc(void *) {
warning("Unexpected lipsinc routine call! Please notify the team");
}
void Parallaction_br::_c_albcycle(void *) {
warning("Parallaction_br::_c_albcycle() not yet implemented");
}
void Parallaction_br::_c_password(void *) {
warning("Parallaction_br::_c_password() not yet implemented");
}
} // namespace Parallaction

View File

@@ -0,0 +1,610 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "common/file.h"
#include "graphics/primitives.h" // for Graphics::drawLine
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/saveload.h"
#include "parallaction/sound.h"
namespace Parallaction {
/*
intro callables data members
*/
static uint16 _rightHandPositions[684] = {
0x0064, 0x0046, 0x006c, 0x0046, 0x0074, 0x0046, 0x007c, 0x0046,
0x0084, 0x0046, 0x008c, 0x0046, 0x0094, 0x0046, 0x009c, 0x0046,
0x00a4, 0x0046, 0x00ac, 0x0046, 0x00b4, 0x0046, 0x00bc, 0x0046,
0x00c4, 0x0046, 0x00cc, 0x0046, 0x00d4, 0x0046, 0x00dc, 0x0046,
0x00e4, 0x0046, 0x00ec, 0x0046, 0x00f4, 0x0046, 0x00fc, 0x0046,
0x0104, 0x0046, 0x00ff, 0x0042, 0x00ff, 0x004a, 0x00ff, 0x0052,
0x00ff, 0x005a, 0x00ff, 0x0062, 0x00ff, 0x006a, 0x00ff, 0x0072,
0x00ff, 0x007a, 0x00ff, 0x0082, 0x00ff, 0x008a, 0x00ff, 0x0092,
0x00ff, 0x009a, 0x00ff, 0x00a2, 0x0104, 0x0097, 0x00fc, 0x0097,
0x00f4, 0x0097, 0x00ec, 0x0097, 0x00e4, 0x0097, 0x00dc, 0x0097,
0x00d4, 0x0097, 0x00cc, 0x0097, 0x00c4, 0x0097, 0x00bc, 0x0097,
0x00b4, 0x0097, 0x00ac, 0x0097, 0x00a4, 0x0097, 0x009c, 0x0097,
0x0094, 0x0097, 0x008c, 0x0097, 0x0084, 0x0097, 0x007c, 0x0097,
0x0074, 0x0097, 0x006c, 0x0097, 0x0064, 0x0097, 0x0066, 0x0042,
0x0066, 0x004a, 0x0066, 0x0052, 0x0066, 0x005a, 0x0066, 0x0062,
0x0066, 0x006a, 0x0066, 0x0072, 0x0066, 0x007a, 0x0066, 0x0082,
0x0066, 0x008a, 0x0066, 0x0092, 0x0066, 0x009a, 0x0066, 0x00a2,
0x008c, 0x0091, 0x0099, 0x0042, 0x0099, 0x004a, 0x0099, 0x0052,
0x0099, 0x005a, 0x0099, 0x0062, 0x0099, 0x006a, 0x0099, 0x0072,
0x0099, 0x007a, 0x0099, 0x0082, 0x0099, 0x008a, 0x0099, 0x0092,
0x0099, 0x009a, 0x0099, 0x00a2, 0x00a0, 0x004d, 0x00cc, 0x0042,
0x00cc, 0x004a, 0x00cc, 0x0052, 0x00cc, 0x005a, 0x00cc, 0x0062,
0x00cc, 0x006a, 0x00cc, 0x0072, 0x00cc, 0x007a, 0x00cc, 0x0082,
0x00cc, 0x008a, 0x00cc, 0x0092, 0x00cc, 0x009a, 0x00cc, 0x00a2,
0x00ca, 0x0050, 0x00b1, 0x0050, 0x0081, 0x0052, 0x007e, 0x0052,
0x007c, 0x0055, 0x007c, 0x005c, 0x007e, 0x005e, 0x0080, 0x005e,
0x0082, 0x005c, 0x0082, 0x0054, 0x0080, 0x0052, 0x0078, 0x0052,
0x007c, 0x005e, 0x0077, 0x0061, 0x0074, 0x006e, 0x0074, 0x0078,
0x0076, 0x007a, 0x0079, 0x0078, 0x0079, 0x0070, 0x0078, 0x0070,
0x0078, 0x006b, 0x007b, 0x0066, 0x007a, 0x006f, 0x0084, 0x006f,
0x0085, 0x0066, 0x0086, 0x0070, 0x0085, 0x0070, 0x0085, 0x0079,
0x0088, 0x0079, 0x008a, 0x0078, 0x008a, 0x006c, 0x0087, 0x0061,
0x0085, 0x005f, 0x0082, 0x005f, 0x0080, 0x0061, 0x007e, 0x0061,
0x007b, 0x005f, 0x007c, 0x006f, 0x007c, 0x0071, 0x0079, 0x0074,
0x0079, 0x0089, 0x0076, 0x008c, 0x0076, 0x008e, 0x007a, 0x008e,
0x007f, 0x0089, 0x007f, 0x0083, 0x007e, 0x0083, 0x007e, 0x0077,
0x0080, 0x0077, 0x0080, 0x0083, 0x0080, 0x008b, 0x0084, 0x0090,
0x0088, 0x0090, 0x0088, 0x008e, 0x0085, 0x008b, 0x0085, 0x0074,
0x0082, 0x0071, 0x00b2, 0x0052, 0x00b0, 0x0054, 0x00b0, 0x0056,
0x00ae, 0x0058, 0x00af, 0x0059, 0x00af, 0x005e, 0x00b2, 0x0061,
0x00b5, 0x0061, 0x00b8, 0x005e, 0x00b8, 0x005a, 0x00b9, 0x0059,
0x00b9, 0x0058, 0x00b7, 0x0056, 0x00b7, 0x0054, 0x00b5, 0x0052,
0x00b2, 0x0052, 0x00ae, 0x005a, 0x00ab, 0x005b, 0x00ab, 0x006d,
0x00ae, 0x0072, 0x00b8, 0x0072, 0x00bc, 0x006d, 0x00bc, 0x005b,
0x00b9, 0x005a, 0x00bc, 0x005c, 0x00be, 0x005c, 0x00c1, 0x005f,
0x00c4, 0x0067, 0x00c4, 0x006d, 0x00c1, 0x0076, 0x00c0, 0x0077,
0x00bd, 0x0077, 0x00bb, 0x0075, 0x00bd, 0x0073, 0x00bb, 0x0072,
0x00be, 0x0070, 0x00be, 0x006a, 0x00a9, 0x006a, 0x00a9, 0x0070,
0x00ac, 0x0072, 0x00aa, 0x0073, 0x00ac, 0x0075, 0x00aa, 0x0077,
0x00a7, 0x0077, 0x00a3, 0x006d, 0x00a3, 0x0067, 0x00a6, 0x005f,
0x00a9, 0x005c, 0x00ab, 0x005c, 0x00ac, 0x0077, 0x00ac, 0x007c,
0x00ab, 0x007c, 0x00ab, 0x0084, 0x00ac, 0x0084, 0x00ac, 0x008b,
0x00a9, 0x008e, 0x00a9, 0x0090, 0x00ae, 0x0090, 0x00ae, 0x008d,
0x00b2, 0x008c, 0x00b2, 0x0087, 0x00b1, 0x0086, 0x00b1, 0x007b,
0x00b2, 0x0079, 0x00b4, 0x0079, 0x00b4, 0x007d, 0x00b5, 0x007d,
0x00b5, 0x0087, 0x00b4, 0x0087, 0x00b4, 0x008c, 0x00b6, 0x008c,
0x00b9, 0x0091, 0x00b4, 0x0091, 0x00bd, 0x008f, 0x00ba, 0x008c,
0x00ba, 0x0083, 0x00bb, 0x0082, 0x00bb, 0x0075, 0x00cc, 0x006e,
0x00d4, 0x006c, 0x00db, 0x0069, 0x00d9, 0x0068, 0x00d9, 0x0064,
0x00dc, 0x0064, 0x00dc, 0x0060, 0x00df, 0x0056, 0x00e5, 0x0052,
0x00e7, 0x0052, 0x00ec, 0x0056, 0x00ef, 0x005d, 0x00f1, 0x0065,
0x00f3, 0x0064, 0x00f3, 0x0069, 0x00f0, 0x0069, 0x00ec, 0x0065,
0x00ec, 0x005e, 0x00e9, 0x005f, 0x00e9, 0x005a, 0x00e7, 0x0058,
0x00e4, 0x0058, 0x00e3, 0x0054, 0x00e3, 0x0058, 0x00e1, 0x005c,
0x00e4, 0x0061, 0x00e7, 0x0061, 0x00e9, 0x005f, 0x00eb, 0x005d,
0x00e4, 0x0062, 0x00e0, 0x0064, 0x00e0, 0x0069, 0x00e2, 0x006b,
0x00e0, 0x0072, 0x00e0, 0x0077, 0x00ec, 0x0077, 0x00ec, 0x0071,
0x00ea, 0x006b, 0x00ec, 0x006a, 0x00ec, 0x0063, 0x00e7, 0x0063,
0x00e7, 0x0065, 0x00e1, 0x0069, 0x00e3, 0x0068, 0x00e6, 0x0069,
0x00ec, 0x005e, 0x00ea, 0x006b, 0x00e7, 0x006b, 0x00e7, 0x006a,
0x00e5, 0x006a, 0x00e5, 0x006b, 0x00e2, 0x006b, 0x00df, 0x006c,
0x00dc, 0x006f, 0x00dc, 0x0071, 0x00da, 0x0073, 0x00d8, 0x0073,
0x00d8, 0x006f, 0x00dc, 0x006b, 0x00dc, 0x0069, 0x00dd, 0x0068,
0x00ef, 0x0068, 0x00f0, 0x0069, 0x00f0, 0x006b, 0x00f4, 0x006f,
0x00f4, 0x0072, 0x00f3, 0x0073, 0x00f2, 0x0073, 0x00f0, 0x0071,
0x00f0, 0x006f, 0x00ec, 0x006b, 0x00ec, 0x007a, 0x00eb, 0x007b,
0x00eb, 0x007f, 0x00ec, 0x0080, 0x00ec, 0x0084, 0x00eb, 0x0085,
0x00eb, 0x008b, 0x00ec, 0x008c, 0x00ec, 0x008f, 0x00ed, 0x0091,
0x00e9, 0x0091, 0x00e9, 0x008f, 0x00e7, 0x008d, 0x00e7, 0x0090,
0x00e7, 0x0089, 0x00e8, 0x0088, 0x00e8, 0x0086, 0x00e7, 0x0085,
0x00e7, 0x007d, 0x00e6, 0x007c, 0x00e6, 0x0078, 0x00e5, 0x007d,
0x00e5, 0x0085, 0x00e4, 0x0086, 0x00e4, 0x0088, 0x00e5, 0x0089,
0x00e5, 0x0090, 0x00e5, 0x008b, 0x00e3, 0x0091, 0x00df, 0x0091,
0x00e0, 0x0090, 0x00e0, 0x008c, 0x00e2, 0x008b, 0x00e1, 0x0085,
0x00e0, 0x0084, 0x00e0, 0x0080, 0x00e1, 0x007f, 0x00e1, 0x007c,
0x00e0, 0x007b, 0x00e0, 0x0077
};
/*
game callables
*/
void Parallaction_ns::_c_null(void *parm) {
return;
}
void Parallaction_ns::_c_play_boogie(void *parm) {
static uint16 flag = 1;
if (flag == 0)
return;
flag = 0;
_soundManI->setMusicFile("boogie2");
_soundManI->playMusic();
return;
}
void Parallaction_ns::_c_score(void *parm) {
_score += 5;
return;
}
void Parallaction_ns::_c_fade(void *parm) {
Palette pal;
_gfx->setPalette(pal);
for (uint16 _di = 0; _di < 64; _di++) {
pal.fadeTo(_gfx->_palette, 1);
_gfx->setPalette(pal);
_gfx->updateScreen();
_system->delayMillis(20);
}
return;
}
void Parallaction_ns::startMovingSarcophagus(ZonePtr sarc) {
if (!_moveSarcGetZones[0]) {
// bind sarcophagi zones
_moveSarcGetZones[0] = _location.findZone("sarc1");
_moveSarcGetZones[1] = _location.findZone("sarc2");
_moveSarcGetZones[2] = _location.findZone("sarc3");
_moveSarcGetZones[3] = _location.findZone("sarc4");
_moveSarcGetZones[4] = _location.findZone("sarc5");
_moveSarcExaZones[0] = _location.findZone("sarc1exa");
_moveSarcExaZones[1] = _location.findZone("sarc2exa");
_moveSarcExaZones[2] = _location.findZone("sarc3exa");
_moveSarcExaZones[3] = _location.findZone("sarc4exa");
_moveSarcExaZones[4] = _location.findZone("sarc5exa");
}
/*
Each sarcophagus is made of 2 visible zones: one responds to
'get' actions, the other to 'examine'. We need to find out
both so they can be moved.
*/
for (uint16 i = 0; i < 5; i++) {
if (_moveSarcGetZones[i] == sarc) {
_moveSarcExaZone = _moveSarcExaZones[i];
_moveSarcGetZone = _moveSarcGetZones[i];
}
}
// calculate destination for the sarcophagus
int16 destX = _freeSarcophagusSlotX;
_sarcophagusDeltaX = destX - _moveSarcGetZone->getX(); // x movement delta
int16 destY = _moveSarcGetZone->getY() - (_sarcophagusDeltaX / 20); // gently degrade y when moving sideways
// set the new empty position (maybe this should be done on stopMovingSarcophagus?)
_freeSarcophagusSlotX = _moveSarcGetZone->getX();
// calculate which way and how many steps the character should move
int16 numSteps = _sarcophagusDeltaX / 2; // default to right
int16 delta = 2; // default to right
if (_sarcophagusDeltaX < 0) {
// move left
numSteps = -numSteps; // keep numSteps positive
delta = -delta; // make delta negative if moving to left
}
// GROSS HACK: since there is no obvious way to provide additional parameters to a script,
// the game packs the data needed to calculate the position of the 'sposta' animation in
// the coordinate fields of the animation itself, which are accessible from the scripts.
// In detail: the sarcophagus destination coords are stored into Z and F, while the number
// of calculated steps and step length in X and Y. See any of the sarc#.script files in
// disk2 for details about unpacking.
AnimationPtr a = _location.findAnimation("sposta");
a->forceXYZF(numSteps, delta, destX, destY);
// start moving
_movingSarcophagus = true;
}
void Parallaction_ns::stopMovingSarcophagus() {
// moves both sarcophagus zones at the destination, so that the user
// can interact with them
_moveSarcGetZone->translate(_sarcophagusDeltaX, -_sarcophagusDeltaX / 20);
_moveSarcExaZone->translate(_sarcophagusDeltaX, -_sarcophagusDeltaX / 20);
// check if the puzzle has been completed, by verifying the position of
// the sarcophagi
if (_moveSarcGetZones[0]->getX() == 35 &&
_moveSarcGetZones[1]->getX() == 68 &&
_moveSarcGetZones[2]->getX() == 101 &&
_moveSarcGetZones[3]->getX() == 134 &&
_moveSarcGetZones[4]->getX() == 167) {
AnimationPtr a = _location.findAnimation("finito");
a->_flags |= (kFlagsActive | kFlagsActing);
setLocationFlags(0x20); // GROSS HACK: activates 'finito' flag in dinoit_museo.loc
}
// stop moving
_movingSarcophagus = false;
}
void Parallaction_ns::_c_moveSarc(void *parm) {
if (!_movingSarcophagus) {
startMovingSarcophagus(*(ZonePtr *)parm);
} else {
stopMovingSarcophagus();
}
}
void Parallaction_ns::_c_contaFoglie(void *parm) {
num_foglie++;
if (num_foglie != 6)
return;
g_globalFlags |= 0x1000;
return;
}
void Parallaction_ns::_c_zeroFoglie(void *parm) {
num_foglie = 0;
return;
}
void Parallaction_ns::_c_trasformata(void *parm) {
g_engineFlags ^= kEngineTransformedDonna;
// No need to invoke changeCharacter here, as
// transformation happens on a location switch
// and character change is automatically triggered.
return;
}
void Parallaction_ns::_c_offMouse(void *parm) {
_input->setMouseState(MOUSE_DISABLED);
g_engineFlags |= kEngineBlockInput;
}
void Parallaction_ns::_c_onMouse(void *parm) {
g_engineFlags &= ~kEngineBlockInput;
_input->setMouseState(MOUSE_ENABLED_SHOW);
}
void Parallaction_ns::_c_setMask(void *parm) {
if (!_gfx->_backgroundInfo->hasMask())
return;
memset(_gfx->_backgroundInfo->_mask->data + 3600, 0, 3600);
_gfx->_backgroundInfo->layers[1] = 500;
return;
}
void Parallaction_ns::_c_endComment(void *param) {
/*
NOTE: this routine is only run when the full game
is over. The following command in the scripts is
QUIT, which causes the engine to exit and return
to system.
Since this routine is still *blocking*, QUIT is
not executed until the user presses a mouse
button. If the input is reconciled with the main
loop then the command sequence must be suspended
to avoid executing QUIT before this actual
routine gets a chance to be run. See bug #4184
for a similar situation.
*/
showLocationComment(_location._endComment, true);
Palette pal(_gfx->_palette);
pal.makeGrayscale();
for (uint di = 0; di < 64; di++) {
_gfx->_palette.fadeTo(pal, 1);
_gfx->setPalette(_gfx->_palette);
_gfx->updateScreen();
_system->delayMillis(20);
}
_input->waitForButtonEvent(kMouseLeftUp);
_gfx->freeDialogueObjects();
}
void Parallaction_ns::_c_frankenstein(void *parm) {
Palette pal0(_gfx->_palette);
Palette pal1;
for (uint16 i = 0; i < 32; i++) {
pal0.setEntry(i, -1, 0, 0); // leaves reds unchanged while zeroing other components
}
for (uint16 _di = 0; _di < 30; _di++) {
_system->delayMillis(20);
_gfx->setPalette(pal0);
_gfx->updateScreen();
_system->delayMillis(20);
_gfx->setPalette(pal1);
_gfx->updateScreen();
}
_gfx->setPalette(_gfx->_palette);
_gfx->updateScreen();
return;
}
void Parallaction_ns::_c_finito(void *parm) {
_saveLoad->setPartComplete(_char.getBaseName());
cleanInventory(true);
cleanupGame();
_gfx->setPalette(_gfx->_palette);
startEndPartSequence();
return;
}
void Parallaction_ns::_c_ridux(void *parm) {
changeCharacter(g_minidinoName);
return;
}
void Parallaction_ns::_c_testResult(void *parm) {
if (_inTestResult) { // NOTE: _inTestResult has been added because the scripts call _c_testResult multiple times to cope with
// the multiple buffering that was used in the original engine. _inTestResult now prevents the engine
// from crashing when the scripts are executed.
return;
}
_inTestResult = true;
_gfx->freeLabels();
_gfx->updateScreen();
parseLocation("common");
destroyTestResultLabels();
_testResultLabels[0] = _gfx->createLabel(_menuFont, _location._slideText[0].c_str(), 1);
_testResultLabels[1] = _gfx->createLabel(_menuFont, _location._slideText[1].c_str(), 1);
_gfx->showLabel(_testResultLabels[0], CENTER_LABEL_HORIZONTAL, 38);
_gfx->showLabel(_testResultLabels[1], CENTER_LABEL_HORIZONTAL, 58);
const char *charName = nullptr;
switch (_characterVoiceID) {
case kDoug:
charName = "Doug Nuts";
break;
case kDonna:
charName = "Donna Fatale";
break;
case kDino:
charName = "Dino Fagioli";
break;
}
if (charName != nullptr) {
sayText(charName, Common::TextToSpeechManager::QUEUE);
}
return;
}
void Parallaction_ns::_c_offSound(void *) {
_soundManI->stopSfx(0);
_soundManI->stopSfx(1);
_soundManI->stopSfx(2);
_soundManI->stopSfx(3);
}
void Parallaction_ns::_c_startMusic(void *) {
_soundManI->playMusic();
}
void Parallaction_ns::_c_closeMusic(void *) {
_soundManI->stopMusic();
}
/*
intro callables
*/
void Parallaction_ns::_c_startIntro(void *parm) {
_rightHandAnim = _location.findAnimation("righthand");
if (getPlatform() == Common::kPlatformDOS) {
_soundManI->setMusicFile("intro");
_soundManI->playMusic();
}
g_engineFlags |= kEngineBlockInput;
_input->setMouseState(MOUSE_DISABLED);
_intro = true;
}
void Parallaction_ns::_c_endIntro(void *parm) {
if (getFeatures() & GF_DEMO) {
// NOTE: suspend command execution queue, to
// avoid running the QUIT command before
// credits are displayed. This solves bug
// #4184.
// Execution of the command list will resume
// as soon as runGameFrame is run.
_cmdExec->suspend();
}
startCreditSequence();
_intro = false;
}
void Parallaction_ns::_c_moveSheet(void *parm) {
static uint16 x = 319;
if (x > 66)
x -= 16;
Common::Rect r;
r.left = x;
r.top = 47;
r.right = (x + 32 > 319) ? 319 : (x + 32);
r.bottom = 199;
_gfx->fillBackground(r, 1);
if (x >= 104) return;
r.left = x+215;
r.top = 47;
r.right = (x + 247 > 319) ? 319 : (x + 247);
r.bottom = 199;
_gfx->fillBackground(r, 12);
return;
}
class ZeroMaskPrimitives : public Graphics::Primitives {
void drawPoint(int x, int y, uint32 color, void *data) override {
BackgroundInfo *info = (BackgroundInfo *)data;
uint16 _ax = x + y * info->_mask->w;
info->_mask->data[_ax >> 2] &= ~(3 << ((_ax & 3) << 1));
}
};
void Parallaction_ns::_c_sketch(void *parm) {
static uint16 index = 1;
uint16 newx;
uint16 newy;
uint16 oldy = _rightHandPositions[2*(index-1)+1];
uint16 oldx = _rightHandPositions[2*(index-1)];
// WORKAROUND: original code overflowed _rightHandPositions by trying
// to access elements at positions 684 and 685. That used to happen
// when index == 342. Code now checks for this possibility and assigns
// the last valid value to the new coordinates for drawing without
// accessing the array.
if (index == 342) {
newy = oldy;
newx = oldx;
} else {
newy = _rightHandPositions[2*index+1];
newx = _rightHandPositions[2*index];
}
if (_gfx->_backgroundInfo->hasMask()) {
ZeroMaskPrimitives().drawLine(oldx, oldy, newx, newy, 0, _gfx->_backgroundInfo);
}
_rightHandAnim->setX(newx);
_rightHandAnim->setY(newy - 20);
index++;
return;
}
void Parallaction_ns::_c_shade(void *parm) {
Common::Rect r(
_rightHandAnim->getX() - 36,
_rightHandAnim->getY() - 36,
_rightHandAnim->getX(),
_rightHandAnim->getY()
);
uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo->_mask->internalWidth;
for (uint16 _si = r.top; _si < r.bottom; _si++) {
memset(_gfx->_backgroundInfo->_mask->data + _di, 0, r.width()/4+1);
_di += _gfx->_backgroundInfo->_mask->internalWidth;
}
return;
}
int16 projectorProgram[] = {
0, 50, 1, 50, 2, 49, 3, 49, 4, 48, 5, 48, 6, 47, 7, 47, 8, 46, 9, 46, 10, 45, 11, 45,
12, 44, 13, 44, 14, 43, 15, 43, 16, 42, 17, 42, 18, 41, 19, 41, 20, 40, 21, 40, 22, 39,
23, 39, 24, 38, 25, 38, 26, 37, 27, 37, 28, 36, 29, 36, 30, 35, 31, 35, 32, 34, 33, 34,
34, 33, 35, 33, 36, 32, 37, 32, 38, 31, 39, 31, 40, 30, 41, 30, 42, 29, 43, 29, 44, 28,
45, 28, 46, 27, 47, 27, 48, 26, 49, 26, 50, 25, 51, 25, 52, 24, 53, 24, 54, 23, 55, 23,
56, 22, 57, 22, 58, 21, 59, 21, 60, 20, 61, 20, 62, 19, 63, 19, 64, 18, 65, 18, 66, 17,
67, 17, 68, 16, 69, 16, 70, 15, 71, 15, 72, 14, 73, 14, 74, 13, 75, 13, 76, 12, 77, 12,
78, 11, 79, 11, 80, 10, 81, 10, 82, 9, 83, 9, 84, 8, 85, 8, 86, 7, 87, 7, 88, 6, 89, 6,
90, 5, 91, 5, 92, 4, 93, 4, 94, 3, 95, 3, 96, 2, 97, 2, 98, 1, 99, 1, 100, 0, 101, 0,
102, 1, 103, 1, 104, 2, 105, 2, 106, 3, 107, 3, 108, 4, 109, 4, 110, 5, 111, 5, 112, 6,
113, 6, 114, 7, 115, 7, 116, 8, 117, 8, 118, 9, 119, 9, 120, 10, 121, 10, 122, 11, 123,
11, 124, 12, 125, 12, 126, 13, 127, 13, 128, 14, 129, 14, 130, 15, 131, 15, 132, 16, 133,
16, 134, 17, 135, 17, 136, 18, 137, 18, 138, 19, 139, 19, 140, 20, 141, 20, 142, 21, 143,
21, 144, 22, 145, 22, 146, 23, 147, 23, 148, 24, 149, 24, 150, 25, 149, 25, 148, 25, 147,
25, 146, 25, 145, 25, 144, 25, 143, 25, 142, 25, 141, 25, 140, 25, 139, 25, 138, 25, 137,
25, 136, 25, 135, 25, 134, 25, 133, 25, 132, 25, 131, 25, 130, 25, 129, 25, 128, 25, 127,
25, 126, 25, 125, 25, 124, 25, 123, 25, 122, 25, 121, 25, 120, 25, 119, 25, 118, 25, 117,
25, 116, 25, 115, 25, 114, 25, 113, 25, 112, 25, 111, 25, 110, 25, -1, -1
};
void Parallaction_ns::_c_projector(void *) {
_gfx->setHalfbriteMode(true);
_gfx->setProjectorProgram(projectorProgram);
}
void Parallaction_ns::_c_HBOff(void *) {
_gfx->setHalfbriteMode(false);
}
void Parallaction_ns::_c_HBOn(void *) {
_gfx->setHalfbriteMode(true);
}
} // namespace Parallaction

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine parallaction "Parallaction" yes "" "" "" "midi"

View File

@@ -0,0 +1,3 @@
begin_section("Parallaction");
add_person("", "peres", "");
end_section();

View File

@@ -0,0 +1,330 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "parallaction/parallaction.h"
#include "parallaction/debug.h"
namespace Parallaction {
Debugger::Debugger(Parallaction *vm)
: GUI::Debugger() {
_vm = vm;
_mouseState = MOUSE_ENABLED_SHOW;
registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
registerCmd("location", WRAP_METHOD(Debugger, Cmd_Location));
registerCmd("give", WRAP_METHOD(Debugger, Cmd_Give));
registerCmd("zones", WRAP_METHOD(Debugger, Cmd_Zones));
registerCmd("animations", WRAP_METHOD(Debugger, Cmd_Animations));
registerCmd("globalflags",WRAP_METHOD(Debugger, Cmd_GlobalFlags));
registerCmd("toggleglobalflag",WRAP_METHOD(Debugger, Cmd_ToggleGlobalFlag));
registerCmd("localflags", WRAP_METHOD(Debugger, Cmd_LocalFlags));
registerCmd("locations", WRAP_METHOD(Debugger, Cmd_Locations));
registerCmd("gfxobjects", WRAP_METHOD(Debugger, Cmd_GfxObjects));
registerCmd("programs", WRAP_METHOD(Debugger, Cmd_Programs));
registerCmd("showmouse", WRAP_METHOD(Debugger, Cmd_ShowMouse));
}
void Debugger::preEnter() {
_mouseState = _vm->_input->getMouseState();
GUI::Debugger::preEnter();
}
void Debugger::postEnter() {
GUI::Debugger::postEnter();
_vm->_input->setMouseState(_mouseState);
_vm->_input->setArrowCursor(); // unselects the active item, if any
}
bool Debugger::Cmd_Location(int argc, const char **argv) {
const char *character; // = _vm->_char.getName();
const char *location; // = _vm->_location._name;
char tmp[PATH_LEN];
switch (argc) {
case 3:
character = const_cast<char *>(argv[2]);
location = const_cast<char *>(argv[1]);
Common::sprintf_s(tmp, "%s.%s", location, character);
_vm->scheduleLocationSwitch(tmp);
break;
case 2:
location = const_cast<char *>(argv[1]);
_vm->scheduleLocationSwitch(location);
break;
case 1:
debugPrintf("location <location name> [character name]\n");
break;
default:
break;
}
return true;
}
bool Debugger::Cmd_Locations(int argc, const char **argv) {
debugPrintf("+------------------------------+---------+\n"
"| location name | flags |\n"
"+------------------------------+---------+\n");
for (uint i = 0; i < _vm->_numLocations; i++) {
debugPrintf("|%-30s| %08x|\n", _vm->_locationNames[i], _vm->_localFlags[i]);
}
debugPrintf("+------------------------------+---------+\n");
return true;
}
bool Debugger::Cmd_GlobalFlags(int argc, const char **argv) {
uint32 flags = g_globalFlags;
debugPrintf("+------------------------------+---------+\n"
"| flag name | value |\n"
"+------------------------------+---------+\n");
for (uint i = 0; i < _vm->_globalFlagsNames->count(); i++) {
const char *value = ((flags & (1 << i)) == 0) ? "OFF" : "ON";
debugPrintf("|%-30s| %-6s|\n", _vm->_globalFlagsNames->item(i), value);
}
debugPrintf("+------------------------------+---------+\n");
return true;
}
bool Debugger::Cmd_ToggleGlobalFlag(int argc, const char **argv) {
int i;
switch (argc) {
case 2:
i = _vm->_globalFlagsNames->lookup(argv[1]);
if (i == Table::notFound) {
debugPrintf("invalid flag '%s'\n", argv[1]);
} else {
i--;
if ((g_globalFlags & (1 << i)) == 0)
g_globalFlags |= (1 << i);
else
g_globalFlags &= ~(1 << i);
}
break;
default:
debugPrintf("toggleglobalflag <flag name>\n");
}
return true;
}
bool Debugger::Cmd_LocalFlags(int argc, const char **argv) {
uint32 flags = _vm->getLocationFlags();
debugPrintf("+------------------------------+---------+\n"
"| flag name | value |\n"
"+------------------------------+---------+\n");
for (uint i = 0; i < _vm->_localFlagNames->count(); i++) {
const char *value = ((flags & (1 << i)) == 0) ? "OFF" : "ON";
debugPrintf("|%-30s| %-6s|\n", _vm->_localFlagNames->item(i), value);
}
debugPrintf("+------------------------------+---------+\n");
return true;
}
bool Debugger::Cmd_Give(int argc, const char **argv) {
if (argc == 1) {
debugPrintf("give <item name>\n");
} else {
int index = _vm->_objectsNames->lookup(argv[1]);
if (index != Table::notFound)
_vm->addInventoryItem(index + 4);
else
debugPrintf("invalid item name '%s'\n", argv[1]);
}
return true;
}
bool Debugger::Cmd_Zones(int argc, const char **argv) {
ZoneList::iterator b = _vm->_location._zones.begin();
ZoneList::iterator e = _vm->_location._zones.end();
Common::Rect r;
debugPrintf("+--------------------+---+---+---+---+--------+--------+\n"
"| name | l | t | r | b | type | flag |\n"
"+--------------------+---+---+---+---+--------+--------+\n");
for ( ; b != e; ++b) {
ZonePtr z = *b;
z->getRect(r);
debugPrintf("|%-20s|%3i|%3i|%3i|%3i|%8x|%8x|\n", z->_name, r.left, r.top, r.right, r.bottom, z->_type, z->_flags );
}
debugPrintf("+--------------------+---+---+---+---+--------+--------+\n");
return true;
}
Common::String Debugger::decodeZoneFlags(uint32 flags) {
const char *descs[33] = {
"none", // 0
"closed", // 1
"active", // 2
"remove", // 4
"acting", // 8
"locked", // 0x10
"fixed", // 0x20
"noname", // 0x40
"nomasked", // 0x80
"looping", // 0x100
"added", // 0x200
"character",// 0x400
"nowalk", // 0x800
"yourself", // 0x1000
"scaled", // 0x2000
"selfuse", // 0x4000
"0x8000", // 0x8000
"0x10000",
"0x20000",
"0x40000",
"0x80000",
"0x100000",
"0x200000",
"0x400000",
"0x800000",
"isanimation", // 0x1000000
"animlinked", // 0x2000000
"0x4000000",
"0x8000000",
"0x10000000",
"0x20000000",
"0x40000000",
"0x80000000"
};
uint32 mask = 1;
const char *matches[32];
uint numMatches = 0;
for (uint32 i = 1; i < 32; i++, mask<<=1) {
if (flags & mask) {
matches[numMatches] = descs[i];
numMatches++;
}
}
if (numMatches == 0) {
matches[0] = descs[0];
numMatches = 1;
}
Common::String s(matches[0]);
for (uint32 j = 1; j < numMatches; j++) {
s += '+';
s += matches[j];
}
return s;
}
bool Debugger::Cmd_Animations(int argc, const char **argv) {
AnimationList::iterator b = _vm->_location._animations.begin();
AnimationList::iterator e = _vm->_location._animations.end();
Common::String flags;
debugPrintf("+--------------------+----+----+----+---+--------+----------------------------------------+\n"
"| name | x | y | z | f | type | flags | \n"
"+--------------------+----+----+----+---+--------+----------------------------------------+\n");
for ( ; b != e; ++b) {
AnimationPtr a = *b;
flags = decodeZoneFlags(a->_flags);
debugPrintf("|%-20s|%4i|%4i|%4i|%3i|%8x|%-40s|\n", a->_name, a->getX(), a->getY(), a->getZ(), a->getF(), a->_type, flags.c_str() );
}
debugPrintf("+--------------------+---+---+---+---+--------+----------------------------------------+\n");
return true;
}
bool Debugger::Cmd_GfxObjects(int argc, const char **argv) {
const char *objType[] = { "DOOR", "GET", "ANIM" };
debugPrintf("+--------------------+-----+-----+-----+-----+-----+-------+-----+--------+\n"
"| name | x | y | w | h | z | layer | f | type |\n"
"+--------------------+-----+-----+-----+-----+-----+-------+-----+--------+\n");
GfxObjArray::iterator b = _vm->_gfx->_sceneObjects.begin();
GfxObjArray::iterator e = _vm->_gfx->_sceneObjects.end();
Common::Rect r;
for ( ; b != e; ++b) {
GfxObj *obj = *b;
obj->getRect(obj->frame, r);
debugPrintf("|%-20s|%5i|%5i|%5i|%5i|%5i|%7i|%5i|%8s|\n", obj->getName(), r.left, r.top, r.width(), r.height(),
obj->z, obj->layer, obj->frame, objType[obj->type]);
}
debugPrintf("+--------------------+-----+-----+-----+-----+-----+-------+-----+--------+\n");
return true;
}
bool Debugger::Cmd_Programs(int argc, const char** argv) {
ProgramList::iterator b = _vm->_location._programs.begin();
ProgramList::iterator e = _vm->_location._programs.end();
const char *status[] = { "idle", "running", "completed" };
int i = 1;
debugPrintf("+---+--------------------+--------+----------+\n"
"| # | bound animation | size | status |\n"
"+---+--------------------+--------+----------+\n");
for ( ; b != e; b++, i++) {
ProgramPtr p = *b;
debugPrintf("|%3i|%-20s|%8i|%-10s|\n", i, p->_anim->_name, p->_instructions.size(), status[p->_status] );
}
debugPrintf("+---+--------------------+--------+----------+\n");
return true;
}
bool Debugger::Cmd_ShowMouse(int argc, const char** argv) {
_mouseState = MOUSE_ENABLED_SHOW;
return true;
}
} // namespace Parallaction

View File

@@ -0,0 +1,41 @@
#ifndef PARALLACTION_DEBUGGER_H
#define PARALLACTION_DEBUGGER_H
#include "gui/debugger.h"
#include "parallaction/input.h"
namespace Parallaction {
class Parallaction;
class Debugger : public GUI::Debugger {
public:
Debugger(Parallaction *vm);
private:
void preEnter() override;
void postEnter() override;
private:
Parallaction *_vm;
MouseTriState _mouseState;
bool Cmd_Location(int argc, const char **argv);
bool Cmd_Give(int argc, const char **argv);
bool Cmd_Zones(int argc, const char **argv);
bool Cmd_Animations(int argc, const char **argv);
bool Cmd_LocalFlags(int argc, const char **argv);
bool Cmd_GlobalFlags(int argc, const char **argv);
bool Cmd_ToggleGlobalFlag(int argc, const char **argv);
bool Cmd_Locations(int argc, const char **argv);
bool Cmd_GfxObjects(int argc, const char **argv);
bool Cmd_Programs(int argc, const char** argv);
bool Cmd_ShowMouse(int argc, const char** argv);
Common::String decodeZoneFlags(uint32 flags);
};
} // End of namespace Parallaction
#endif

View File

@@ -0,0 +1,252 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/plugins.h"
#include "common/config-manager.h"
#include "engines/advancedDetector.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "parallaction/detection.h"
#include "parallaction/parallaction.h"
static const PlainGameDescriptor parallactionGames[] = {
{"nippon", "Nippon Safes, Inc."},
{"bra", "The Big Red Adventure"},
{nullptr, nullptr}
};
static const DebugChannelDef debugFlagList[] = {
{Parallaction::kDebugDialogue, "dialogue", "Dialogues debug level"},
{Parallaction::kDebugParser, "parser", "Parser debug level"},
{Parallaction::kDebugDisk, "disk", "Disk debug level"},
{Parallaction::kDebugWalk, "walk", "Walk debug level"},
{Parallaction::kDebugGraphics, "gfx", "Gfx debug level"},
{Parallaction::kDebugExec, "exec", "Execution debug level"},
{Parallaction::kDebugInput, "input", "Input debug level"},
{Parallaction::kDebugAudio, "audio", "Audio debug level"},
{Parallaction::kDebugMenu, "menu", "Menu debug level"},
{Parallaction::kDebugInventory, "inventory", "Inventory debug level"},
DEBUG_CHANNEL_END
};
namespace Parallaction {
static const PARALLACTIONGameDescription gameDescriptions[] = {
{
{
"nippon",
"Multi-lingual",
{
{ "disk1", 0, "610363308258e926dbabd5a9e7bb769f", 1060142},
{ "disk2", 0, "bfdd7bcfbc226f4acf3f67fa9efa2826", 907205},
{ "disk3", 0, "eec08180240888d76e3cfe3e183d5d5d", 1030721},
{ "disk4", 0, "5bffddc7db226bdaa7dd3e10e5a15e68", 1151403},
{ "en", 0, "65cbfa81eafe308621184796ed116700", 399360},
{ "fr", 0, "ac20c743ea10f2cb4491f76c5644582c", 410624},
{ "ge", 0, "50916bfa34aee1380e0e959b37eceb5a", 410624},
{ "it", 0, "89964aef04d2c53a615ee8983caf2775", 410624},
AD_LISTEND
},
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
},
// Alternate version with patched 'FR' file.
// Bugreport #13630
{
{
"nippon",
"Multi-lingual alt",
{
{ "disk1", 0, "610363308258e926dbabd5a9e7bb769f", 1060142},
{ "disk2", 0, "bfdd7bcfbc226f4acf3f67fa9efa2826", 907205},
{ "disk3", 0, "eec08180240888d76e3cfe3e183d5d5d", 1030721},
{ "disk4", 0, "5bffddc7db226bdaa7dd3e10e5a15e68", 1151403},
{ "en", 0, "65cbfa81eafe308621184796ed116700", 399360},
{ "fr", 0, "fd368bab0a8854021870b2199255b7ec", 410624},
{ "ge", 0, "50916bfa34aee1380e0e959b37eceb5a", 410624},
{ "it", 0, "89964aef04d2c53a615ee8983caf2775", 410624},
AD_LISTEND
},
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
},
{
{
"nippon",
"Multi-lingual",
{
{ "disk0", 0, "16cca8724fdf4ec8234385497a0c728a", 208437},
{ "disk1", 0, "6b29987cfe2298d3745b6d99a0080c44", 901120},
{ "disk2", 0, "2db40bf8198a57d18e4471a6deaab970", 901120},
{ "disk3", 0, "0486972962b2bfc230e789b9f88f9ec8", 901120},
{ "disk4", 0, "6f625e7f05da4a2f57d6b62d57013614", 901120},
{ "en", 0, "c9ec4f2267d736eef4877c5133e1c6e1", 174074},
{ "ge", 0, "42d6f10a4ebdadb25a6161d53ea4f450", 182298},
{ "fr", 0, "cf17defc24f143d1a9acb52eaa5c2406", 179958},
AD_LISTEND
},
Common::UNK_LANG,
Common::kPlatformAmiga,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_MULT,
},
{
{
"nippon",
"Demo",
AD_ENTRY2s("disk0", "6fed2e18a6bfe5e8bb49144fcc95fd11", 624640,
"fr", "72f04be4320dfac719431419ec2b9a0d", 12778),
Common::EN_ANY,
Common::kPlatformAmiga,
ADGF_DEMO,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_DEMO,
},
{
{
"nippon",
"",
{
{ "disk0", 0, "bfee75d8015f1fb97e75dbe08df4bef7", 354304},
{ "disk1", 0, "f339dd108c1a1f5cd4853d9966e5d01f", 901120},
{ "disk2", 0, "2db40bf8198a57d18e4471a6deaab970", 901120},
{ "disk3", 0, "0486972962b2bfc230e789b9f88f9ec8", 901120},
{ "disk4", 0, "6f625e7f05da4a2f57d6b62d57013614", 901120},
{ "it", 0, "746088eb8de2b2713685d243a4e4678f", 185344},
AD_LISTEND
},
Common::IT_ITA,
Common::kPlatformAmiga,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_IT,
},
{
{
"bra",
"Multi-lingual",
AD_ENTRY2s("tbra.bmp", "3174c095a0e1a4eaf05c403445711e9b", 80972,
"russia.fnt", "57f85ff62aeca6334fdcaf718e313b49", 18344),
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_UNSTABLE,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
},
{
{
"bra",
"Demo",
AD_ENTRY1s("russia.fnt", "0dd55251d2886d6783718df2b184bf97", 10649),
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_DEMO | ADGF_UNSTABLE,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_DEMO,
},
{
{
"bra",
"En/Fr/De",
AD_ENTRY1s("request.win", "7a844b9518310e4cc72eabb9c0340314", 6497),
Common::UNK_LANG,
Common::kPlatformAmiga,
ADGF_UNSTABLE,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_MULT,
},
{
{
"bra",
"Demo",
AD_ENTRY1s("request.win", "3b6a99ffd626e324b663839bbad59cb3", 5326),
Common::UNK_LANG,
Common::kPlatformAmiga,
ADGF_DEMO | ADGF_UNSTABLE,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_DEMO,
},
{ AD_TABLE_END_MARKER, 0, 0 }
};
}
class ParallactionMetaEngineDetection : public AdvancedMetaEngineDetection<Parallaction::PARALLACTIONGameDescription> {
public:
ParallactionMetaEngineDetection() : AdvancedMetaEngineDetection(Parallaction::gameDescriptions, parallactionGames) {
_guiOptions = GUIO1(GUIO_NOLAUNCHLOAD);
}
const char *getName() const override {
return "parallaction";
}
const char *getEngineName() const override {
return "Parallaction";
}
const char *getOriginalCopyright() const override {
return "Nippon Safes, Inc. (C) Dynabyte";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
REGISTER_PLUGIN_STATIC(PARALLACTION_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, ParallactionMetaEngineDetection);

View File

@@ -0,0 +1,56 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_DETECTION_H
#define PARALLACTION_DETECTION_H
#include "engines/advancedDetector.h"
namespace Parallaction {
enum {
GF_DEMO = 1 << 0,
GF_LANG_EN = 1 << 1,
GF_LANG_FR = 1 << 2,
GF_LANG_DE = 1 << 3,
GF_LANG_IT = 1 << 4,
GF_LANG_MULT = 1 << 5
};
enum ParallactionGameType {
GType_Nippon = 1,
GType_BRA
};
struct PARALLACTIONGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
int gameType;
uint32 features;
};
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS1
} // End of namespace Parallaction
#endif // PARALLACTION_DETECTION_H

View File

@@ -0,0 +1,622 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/events.h"
#include "common/debug-channels.h"
#include "common/textconsole.h"
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
#define MAX_PASSWORD_LENGTH 7
/*
#define QUESTION_BALLOON_X 140
#define QUESTION_BALLOON_Y 10
#define QUESTION_CHARACTER_X 190
#define QUESTION_CHARACTER_Y 80
#define ANSWER_CHARACTER_X 10
#define ANSWER_CHARACTER_Y 80
*/
struct BalloonPositions {
Common::Point _questionBalloon;
Common::Point _questionChar;
Common::Point _answerChar;
};
#ifdef USE_TTS
static const int kNumberOfVoiceDatas = 65;
#endif
class DialogueManager {
Parallaction *_vm;
Dialogue *_dialogue;
bool isNpc;
GfxObj *_questioner;
GfxObj *_answerer;
int _faceId;
int _questionerVoiceID;
int _answererVoiceID;
Question *_q;
int _answerId;
int _selection, _oldSelection;
uint32 _mouseButtons;
Common::Point _mousePos;
protected:
BalloonPositions _ballonPos;
struct VisibleAnswer {
Answer *_a;
int _index; // index into Question::_answers[]
int _balloon;
} _visAnswers[5];
int _numVisAnswers;
bool _isKeyDown;
uint16 _downKey;
protected:
Gfx *_gfx;
BalloonManager *_balloonMan;
public:
DialogueManager(Parallaction *vm, ZonePtr z);
virtual ~DialogueManager();
void start();
bool isOver() {
return _state == DIALOGUE_OVER;
}
void run();
ZonePtr _z;
CommandList *_cmdList;
protected:
enum DialogueState {
DIALOGUE_START,
RUN_QUESTION,
RUN_ANSWER,
NEXT_QUESTION,
NEXT_ANSWER,
DIALOGUE_OVER
} _state;
static const int NO_ANSWER_SELECTED = -1;
void transitionToState(DialogueState newState);
bool displayQuestion();
void displayAnswers();
bool testAnswerFlags(Answer *a);
virtual void addVisibleAnswers(Question *q) = 0;
virtual int16 selectAnswer() = 0;
int16 selectAnswer1();
int16 selectAnswerN();
int16 getHoverAnswer(int16 x, int16 y);
void runQuestion();
void runAnswer();
void nextQuestion();
void nextAnswer();
};
DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) {
_gfx = _vm->_gfx;
_balloonMan = _vm->_balloonMan;
_dialogue = _z->u._speakDialogue;
isNpc = !_z->u._filename.empty() && _z->u._filename.compareToIgnoreCase("yourself");
_questioner = isNpc ? _vm->_disk->loadTalk(_z->u._filename.c_str()) : _vm->_char._talk;
_answerer = _vm->_char._talk;
#ifdef USE_TTS
_answererVoiceID = _vm->_characterVoiceID;
if (!isNpc) {
_questionerVoiceID = _answererVoiceID;
} else {
_questionerVoiceID = kNarratorVoiceID;
for (int i = 1; i < kNumberOfVoiceDatas; ++i) {
if (characterVoiceDatas[i].characterName && !scumm_stricmp(characterVoiceDatas[i].characterName, _z->u._filename.c_str())) {
_questionerVoiceID = i;
break;
}
}
}
#endif
_cmdList = nullptr;
_answerId = 0;
_faceId = 0;
_q = nullptr;
memset(_visAnswers, 0, sizeof(_visAnswers));
_numVisAnswers = 0;
_selection = _oldSelection = 0;
_isKeyDown = false;
_downKey = 0;
_mouseButtons = 0;
_state = DIALOGUE_START;
}
void DialogueManager::start() {
_vm->_gfx->hideFloatingLabel();
assert(_dialogue);
_q = _dialogue->_questions[0];
_state = DIALOGUE_START;
transitionToState(displayQuestion() ? RUN_QUESTION : NEXT_ANSWER);
}
DialogueManager::~DialogueManager() {
if (isNpc) {
delete _questioner;
}
_z.reset();
}
void DialogueManager::transitionToState(DialogueState newState) {
static const char *dialogueStates[] = {
"start",
"runquestion",
"runanswer",
"nextquestion",
"nextanswer",
"over"
};
if (_state != newState) {
debugC(3, kDebugDialogue, "DialogueManager moved to state '%s'", dialogueStates[newState]);
if (DebugMan.isDebugChannelEnabled(kDebugDialogue) && gDebugLevel == 9) {
switch (newState) {
case RUN_QUESTION:
debug(" Q : %s", _q->_text.c_str());
break;
case RUN_ANSWER:
for (int i = 0; i < _numVisAnswers; ++i) {
debug(" A%02i: %s", i, _visAnswers[i]._a->_text.c_str());
}
break;
default:
break;
}
}
}
_state = newState;
}
bool DialogueManager::testAnswerFlags(Answer *a) {
uint32 flags = _vm->getLocationFlags();
if (a->_yesFlags & kFlagsGlobal)
flags = g_globalFlags | kFlagsGlobal;
return ((a->_yesFlags & flags) == a->_yesFlags) && ((a->_noFlags & ~flags) == a->_noFlags);
}
void DialogueManager::displayAnswers() {
_vm->setTTSVoice(_answererVoiceID);
// create balloons
int id;
for (int i = 0; i < _numVisAnswers; ++i) {
id = _balloonMan->setDialogueBalloon(_visAnswers[i]._a->_text, 1, BalloonManager::kUnselectedColor);
assert(id >= 0);
_visAnswers[i]._balloon = id;
}
int mood = 0;
if (_numVisAnswers == 1) {
mood = _visAnswers[0]._a->speakerMood();
_balloonMan->setBalloonText(_visAnswers[0]._balloon, _visAnswers[0]._a->_text, BalloonManager::kNormalColor);
} else
if (_numVisAnswers > 1) {
mood = _visAnswers[0]._a->speakerMood();
_oldSelection = NO_ANSWER_SELECTED;
_selection = 0;
}
_faceId = _gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
_gfx->setItemFrame(_faceId, mood);
}
int16 DialogueManager::selectAnswer1() {
if (_visAnswers[0]._a->textIsNull()) {
return _visAnswers[0]._index;
}
if (_mouseButtons == kMouseLeftUp) {
return _visAnswers[0]._index;
}
return NO_ANSWER_SELECTED;
}
int16 DialogueManager::selectAnswerN() {
_selection = _balloonMan->hitTestDialogueBalloon(_mousePos.x, _mousePos.y);
if (_selection != _oldSelection) {
if (_oldSelection != NO_ANSWER_SELECTED) {
VisibleAnswer *oldAnswer = &_visAnswers[_oldSelection];
_balloonMan->setBalloonText(oldAnswer->_balloon, oldAnswer->_a->_text, BalloonManager::kUnselectedColor);
}
if (_selection != NO_ANSWER_SELECTED) {
VisibleAnswer *answer = &_visAnswers[_selection];
_balloonMan->setBalloonText(answer->_balloon, answer->_a->_text, BalloonManager::kSelectedColor);
_gfx->setItemFrame(_faceId, answer->_a->speakerMood());
}
}
_oldSelection = _selection;
if ((_mouseButtons == kMouseLeftUp) && (_selection != NO_ANSWER_SELECTED)) {
return _visAnswers[_selection]._index;
}
return NO_ANSWER_SELECTED;
}
bool DialogueManager::displayQuestion() {
if (_q->textIsNull()) return false;
#ifdef USE_TTS
// Some dialogue exchanges involve more than 1 character, differentiated by the mood
if (_questionerVoiceID < kNumberOfVoiceDatas - 1 && characterVoiceDatas[_questionerVoiceID + 1].characterName == nullptr) {
_vm->setTTSVoice(_questionerVoiceID + _q->speakerMood());
} else {
_vm->setTTSVoice(_questionerVoiceID);
}
#endif
_balloonMan->setSingleBalloon(_q->_text, _ballonPos._questionBalloon.x, _ballonPos._questionBalloon.y, _q->balloonWinding(), BalloonManager::kNormalColor);
_faceId = _gfx->setItem(_questioner, _ballonPos._questionChar.x, _ballonPos._questionChar.y);
_gfx->setItemFrame(_faceId, _q->speakerMood());
return true;
}
void DialogueManager::runQuestion() {
if (_mouseButtons == kMouseLeftUp) {
_gfx->freeDialogueObjects();
transitionToState(NEXT_ANSWER);
}
}
void DialogueManager::nextAnswer() {
if (_q->_answers[0] == nullptr) {
transitionToState(DIALOGUE_OVER);
return;
}
// try and check if there are any suitable answers,
// given the current game state.
addVisibleAnswers(_q);
if (!_numVisAnswers) {
// if there are no answers, then chicken out
transitionToState(DIALOGUE_OVER);
return;
}
if (_visAnswers[0]._a->textIsNull()) {
// if the first answer is null (it's implied that it's the
// only one because we already called addVisibleAnswers),
// then jump to the next question
_answerId = _visAnswers[0]._index;
transitionToState(NEXT_QUESTION);
} else {
// at this point we are sure there are non-null answers to show
displayAnswers();
transitionToState(RUN_ANSWER);
}
}
void DialogueManager::runAnswer() {
_answerId = selectAnswer();
if (_answerId != NO_ANSWER_SELECTED) {
_cmdList = &_q->_answers[_answerId]->_commands;
_gfx->freeDialogueObjects();
transitionToState(NEXT_QUESTION);
}
}
void DialogueManager::nextQuestion() {
_q = _dialogue->findQuestion(_q->_answers[_answerId]->_followingName);
if (_q == nullptr) {
transitionToState(DIALOGUE_OVER);
} else {
transitionToState(displayQuestion() ? RUN_QUESTION : NEXT_ANSWER);
}
}
void DialogueManager::run() {
// cache event data
_mouseButtons = _vm->_input->getLastButtonEvent();
_vm->_input->getCursorPos(_mousePos);
_isKeyDown = _vm->_input->getLastKeyDown(_downKey);
switch (_state) {
case RUN_QUESTION:
runQuestion();
break;
case NEXT_ANSWER:
nextAnswer();
break;
case NEXT_QUESTION:
nextQuestion();
break;
case RUN_ANSWER:
runAnswer();
break;
case DIALOGUE_OVER:
break;
default:
error("unknown state in DialogueManager");
}
}
class DialogueManager_ns : public DialogueManager {
protected:
Parallaction_ns *_vm;
bool _passwordChanged;
bool _askPassword;
bool checkPassword() {
return ((!scumm_stricmp(_vm->_char.getBaseName(), g_doughName) && _vm->_password.hasPrefix("1732461")) ||
(!scumm_stricmp(_vm->_char.getBaseName(), g_donnaName) && _vm->_password.hasPrefix("1622")) ||
(!scumm_stricmp(_vm->_char.getBaseName(), g_dinoName) && _vm->_password.hasPrefix("179")));
}
void resetPassword() {
_vm->_password.clear();
_passwordChanged = true;
}
void accumPassword(uint16 ascii) {
if (!Common::isDigit(ascii)) {
return;
}
_vm->_password += ascii;
_passwordChanged = true;
}
int16 askPassword() {
if (_isKeyDown) {
accumPassword(_downKey);
}
if (_passwordChanged) {
_balloonMan->setBalloonText(_visAnswers[0]._balloon, _visAnswers[0]._a->_text, BalloonManager::kNormalColor);
_passwordChanged = false;
}
if ((_vm->_password.size() == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) {
_vm->sayText(_vm->_password, Common::TextToSpeechManager::INTERRUPT);
if (checkPassword()) {
return 0;
} else {
resetPassword();
}
}
return NO_ANSWER_SELECTED;
}
public:
DialogueManager_ns(Parallaction_ns *vm, ZonePtr z) : DialogueManager(vm, z), _vm(vm),
_passwordChanged(false), _askPassword(false) {
_ballonPos._questionBalloon = Common::Point(140, 10);
_ballonPos._questionChar = Common::Point(190, 80);
_ballonPos._answerChar = Common::Point(10, 80);
}
bool canDisplayAnswer(Answer *a) {
return testAnswerFlags(a);
}
void addVisibleAnswers(Question *q) override {
_askPassword = false;
_numVisAnswers = 0;
for (int i = 0; i < NUM_ANSWERS && q->_answers[i]; i++) {
Answer *a = q->_answers[i];
if (!canDisplayAnswer(a)) {
continue;
}
if (a->_text.contains("%P")) {
_askPassword = true;
_vm->sayText(a->_text.substr(0, a->_text.find('@')), Common::TextToSpeechManager::INTERRUPT);
}
_visAnswers[_numVisAnswers]._a = a;
_visAnswers[_numVisAnswers]._index = i;
_numVisAnswers++;
}
resetPassword();
}
int16 selectAnswer() override {
int ans = NO_ANSWER_SELECTED;
if (_askPassword) {
ans = askPassword();
} else
if (_numVisAnswers == 1) {
ans = selectAnswer1();
} else {
ans = selectAnswerN();
}
return ans;
}
};
class DialogueManager_br : public DialogueManager {
Parallaction_br *_vm;
public:
DialogueManager_br(Parallaction_br *vm, ZonePtr z) : DialogueManager(vm, z), _vm(vm) {
_ballonPos._questionBalloon = Common::Point(0, 0);
_ballonPos._questionChar = Common::Point(380, 80);
_ballonPos._answerChar = Common::Point(10, 80);
}
bool canDisplayAnswer(Answer *a) {
if (!a)
return false;
if (a->_hasCounterCondition) {
_vm->testCounterCondition(a->_counterName, a->_counterOp, a->_counterValue);
return (_vm->getLocationFlags() & kFlagsTestTrue) != 0;
}
return testAnswerFlags(a);
}
void addVisibleAnswers(Question *q) override {
_numVisAnswers = 0;
for (int i = 0; i < NUM_ANSWERS && q->_answers[i]; i++) {
Answer *a = q->_answers[i];
if (!canDisplayAnswer(a)) {
continue;
}
_visAnswers[_numVisAnswers]._a = a;
_visAnswers[_numVisAnswers]._index = i;
_numVisAnswers++;
}
}
int16 selectAnswer() override {
int16 ans = NO_ANSWER_SELECTED;
if (_numVisAnswers == 1) {
ans = selectAnswer1();
} else {
ans = selectAnswerN();
}
return ans;
}
};
void Parallaction::enterDialogueMode(ZonePtr z) {
if (!z->u._speakDialogue) {
return;
}
debugC(1, kDebugDialogue, "Parallaction::enterDialogueMode(%s)", z->u._filename.c_str());
_dialogueMan = createDialogueManager(z);
assert(_dialogueMan);
_dialogueMan->start();
_input->_inputMode = Input::kInputModeDialogue;
}
void Parallaction::exitDialogueMode() {
debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()");
_input->_inputMode = Input::kInputModeGame;
setTTSVoice(_characterVoiceID);
/* Since the current instance of _dialogueMan must be destroyed before the
zone commands are executed, as they may create a new instance of _dialogueMan that
would overwrite the current, we need to save the references to the command lists.
*/
CommandList *_cmdList = _dialogueMan->_cmdList;
ZonePtr z = _dialogueMan->_z;
// destroy the _dialogueMan here
destroyDialogueManager();
// run the lists saved
if (_cmdList) {
_cmdExec->run(*_cmdList);
}
_cmdExec->run(z->_commands, z);
}
void Parallaction::destroyDialogueManager() {
// destroy the _dialogueMan here
delete _dialogueMan;
_dialogueMan = nullptr;
}
void Parallaction::runDialogueFrame() {
if (_input->_inputMode != Input::kInputModeDialogue) {
return;
}
_dialogueMan->run();
if (_dialogueMan->isOver()) {
exitDialogueMode();
}
return;
}
DialogueManager *Parallaction_ns::createDialogueManager(ZonePtr z) {
return new DialogueManager_ns(this, z);
}
DialogueManager *Parallaction_br::createDialogueManager(ZonePtr z) {
return new DialogueManager_br(this, z);
}
} // namespace Parallaction

282
engines/parallaction/disk.h Normal file
View File

@@ -0,0 +1,282 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_DISK_H
#define PARALLACTION_DISK_H
#define PATH_LEN 200
#include "common/archive.h"
#include "common/str.h"
namespace Common {
class FSDirectory;
class ReadStream;
class SeekableReadStream;
}
namespace Graphics {
struct Surface;
}
namespace Parallaction {
class Table;
class Parallaction;
class Gfx;
class Script;
class Font;
struct Frames;
struct Cnv;
struct Sprites;
struct BackgroundInfo;
class GfxObj;
struct MaskBuffer;
struct PathBuffer;
class Disk {
public:
Disk() { }
virtual ~Disk() { }
virtual void init() { }
virtual Common::String selectArchive(const Common::String &name) = 0;
virtual void setLanguage(uint16 language) = 0;
virtual Script* loadLocation(const char *name) = 0;
virtual Script* loadScript(const char* name) = 0;
virtual GfxObj* loadTalk(const char *name) = 0;
virtual GfxObj* loadObjects(const char *name, uint8 part = 0) = 0;
virtual Frames* loadPointer(const char *name) = 0;
virtual GfxObj* loadHead(const char* name) = 0;
virtual Font* loadFont(const char* name) = 0;
virtual GfxObj* loadStatic(const char* name) = 0;
virtual Frames* loadFrames(const char* name) = 0;
virtual void loadSlide(BackgroundInfo& info, const char *filename) = 0;
virtual void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) = 0;
virtual Table* loadTable(const char* name) = 0;
virtual Common::SeekableReadStream* loadMusic(const char* name) = 0;
virtual Common::SeekableReadStream* loadSound(const char* name) = 0;
virtual MaskBuffer *loadMask(const char *name, uint32 w, uint32 h) { return 0; }
virtual PathBuffer *loadPath(const char *name, uint32 w, uint32 h) { return 0; }
};
class Disk_ns : public Disk {
protected:
Parallaction *_vm;
Common::SearchSet _sset;
Common::String _resArchiveName;
Common::String _language;
Common::SeekableReadStream *openFile(const char *filename);
virtual Common::SeekableReadStream *tryOpenFile(const char *filename) { return 0; }
void errorFileNotFound(const char *filename);
void addArchive(const Common::String& name, int priority);
virtual void decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) = 0;
Cnv *makeCnv(Common::SeekableReadStream *stream);
public:
Disk_ns(Parallaction *vm);
~Disk_ns() override;
Common::String selectArchive(const Common::String &name) override;
void setLanguage(uint16 language) override;
Script* loadLocation(const char *name) override;
Script* loadScript(const char* name) override;
};
class DosDisk_ns : public Disk_ns {
private:
void unpackBackground(Common::ReadStream *stream, byte *screen, byte *mask, byte *path);
Cnv* loadCnv(const char *filename);
void loadBackground(BackgroundInfo& info, const char *filename);
void createMaskAndPathBuffers(BackgroundInfo &info);
void parseDepths(BackgroundInfo &info, Common::SeekableReadStream &stream);
Font *createFont(const char *name, Cnv* cnv);
protected:
Gfx *_gfx;
Common::SeekableReadStream *tryOpenFile(const char* name) override;
void decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) override;
public:
DosDisk_ns(Parallaction *vm);
~DosDisk_ns() override;
void init() override;
GfxObj* loadTalk(const char *name) override;
GfxObj* loadObjects(const char *name, uint8 part = 0) override;
Frames* loadPointer(const char *name) override;
GfxObj* loadHead(const char* name) override;
Font* loadFont(const char* name) override;
GfxObj* loadStatic(const char* name) override;
Frames* loadFrames(const char* name) override;
void loadSlide(BackgroundInfo& info, const char *filename) override;
void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) override;
Table* loadTable(const char* name) override;
Common::SeekableReadStream* loadMusic(const char* name) override;
Common::SeekableReadStream* loadSound(const char* name) override;
};
class AmigaDisk_ns : public Disk_ns {
protected:
void patchFrame(byte *dst, byte *dlta, uint16 bytesPerPlane, uint16 height);
void unpackFrame(byte *dst, byte *src, uint16 planeSize);
void unpackBitmap(byte *dst, byte *src, uint16 numFrames, uint16 bytesPerPlane, uint16 height);
Common::SeekableReadStream *tryOpenFile(const char* name) override;
Font *createFont(const char *name, Common::SeekableReadStream &stream);
void loadMask_internal(BackgroundInfo& info, const char *name);
void loadPath_internal(BackgroundInfo& info, const char *name);
void loadBackground(BackgroundInfo& info, const char *name);
void buildMask(byte* buf);
void decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) override;
public:
AmigaDisk_ns(Parallaction *vm);
~AmigaDisk_ns() override;
void init() override;
GfxObj* loadTalk(const char *name) override;
GfxObj* loadObjects(const char *name, uint8 part = 0) override;
Frames* loadPointer(const char *name) override;
GfxObj* loadHead(const char* name) override;
Font* loadFont(const char* name) override;
GfxObj* loadStatic(const char* name) override;
Frames* loadFrames(const char* name) override;
void loadSlide(BackgroundInfo& info, const char *filename) override;
void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) override;
Table* loadTable(const char* name) override;
Common::SeekableReadStream* loadMusic(const char* name) override;
Common::SeekableReadStream* loadSound(const char* name) override;
};
class Disk_br : public Disk {
Common::SeekableReadStream *openFile_internal(bool errorOnNotFound, const Common::String &name, const Common::String &ext);
protected:
Parallaction *_vm;
Common::SearchSet _sset;
Common::FSDirectory *_baseDir;
uint16 _language;
Common::String _currentPart;
Common::SeekableReadStream *tryOpenFile(const Common::String &name, const Common::String &ext = Common::String());
Common::SeekableReadStream *openFile(const Common::String &name, const Common::String &ext = Common::String());
void errorFileNotFound(const Common::String &filename);
public:
Disk_br(Parallaction *vm);
~Disk_br() override;
};
// for the moment DosDisk_br subclasses Disk. When Amiga support will
// be taken into consideration, it might be useful to add another level
// like we did for Nippon Safes.
class DosDisk_br : public Disk_br {
protected:
Font *createFont(const char *name, Common::ReadStream &stream);
Sprites* createSprites(Common::ReadStream *stream);
void loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette);
GfxObj* createInventoryObjects(Common::SeekableReadStream &stream);
public:
DosDisk_br(Parallaction *vm);
void init() override;
Common::String selectArchive(const Common::String &name) override;
void setLanguage(uint16 language) override;
Script* loadLocation(const char *name) override;
Script* loadScript(const char* name) override;
GfxObj* loadTalk(const char *name) override;
GfxObj* loadObjects(const char *name, uint8 part = 0) override;
Frames* loadPointer(const char *name) override;
GfxObj* loadHead(const char* name) override;
Font* loadFont(const char* name) override;
GfxObj* loadStatic(const char* name) override;
Frames* loadFrames(const char* name) override;
void loadSlide(BackgroundInfo& info, const char *filename) override;
void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) override;
Table* loadTable(const char* name) override;
Common::SeekableReadStream* loadMusic(const char* name) override;
Common::SeekableReadStream* loadSound(const char* name) override;
MaskBuffer *loadMask(const char *name, uint32 w, uint32 h) override;
PathBuffer *loadPath(const char *name, uint32 w, uint32 h) override;
};
class DosDemoDisk_br : public DosDisk_br {
public:
DosDemoDisk_br(Parallaction *vm);
void init() override;
Common::String selectArchive(const Common::String& name) override;
};
class AmigaDisk_br : public DosDisk_br {
protected:
Sprites* createSprites(Common::ReadStream *stream);
Font *createFont(const char *name, Common::SeekableReadStream &stream);
void loadBackground(BackgroundInfo& info, const char *filename);
void adjustForPalette(Graphics::Surface &surf, int transparentColor = -1);
public:
AmigaDisk_br(Parallaction *vm);
void init() override;
Common::String selectArchive(const Common::String& name) override;
GfxObj* loadTalk(const char *name) override;
Font* loadFont(const char* name) override;
GfxObj* loadStatic(const char* name) override;
Frames* loadFrames(const char* name) override;
void loadSlide(BackgroundInfo& info, const char *filename) override;
void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) override;
GfxObj* loadObjects(const char *name, uint8 part = 0) override;
Common::SeekableReadStream* loadMusic(const char* name) override;
Common::SeekableReadStream* loadSound(const char* name) override;
MaskBuffer *loadMask(const char *name, uint32 w, uint32 h) override;
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,767 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/fs.h"
#include "common/textconsole.h"
#include "image/iff.h"
#include "parallaction/parallaction.h"
#include "parallaction/parser.h"
namespace Parallaction {
extern byte braAmigaFramesDefaultPalette[];
struct Sprite {
uint16 size;
uint16 x;
uint16 y;
uint16 w;
uint16 h;
byte *packedData;
Sprite() : size(0), x(0), y(0), w(0), h(0), packedData(nullptr) {
}
~Sprite() {
free(packedData);
}
};
struct Sprites : public Frames {
uint16 _num;
Sprite* _sprites;
Sprites(uint num) : _num(0), _sprites(nullptr) {
_num = num;
_sprites = new Sprite[_num];
}
~Sprites() override {
delete[] _sprites;
}
uint16 getNum() override {
return _num;
}
byte* getData(uint16 index) override {
assert(index < _num);
return _sprites[index].packedData;
}
void getRect(uint16 index, Common::Rect &r) override {
assert(index < _num);
r.setWidth(_sprites[index].w);
r.setHeight(_sprites[index].h);
r.moveTo(_sprites[index].x, _sprites[index].y);
}
uint getRawSize(uint16 index) override {
assert(index < _num);
return _sprites[index].size;
}
uint getSize(uint16 index) override {
assert(index < _num);
return _sprites[index].w * _sprites[index].h;
}
};
Common::SeekableReadStream *Disk_br::openFile_internal(bool errorOnNotFound, const Common::String &name, const Common::String &ext) {
assert(!name.empty());
debugC(5, kDebugDisk, "Disk_br::openFile(%s, %s)", name.c_str(), ext.c_str());
Common::Path lookup(name);
if (!ext.empty() && !name.hasSuffix(ext.c_str())) {
// make sure we are using the specified extension
debugC(9, kDebugDisk, "Disk_br::openFile: appending explicit extension (%s) to (%s)", ext.c_str(), name.c_str());
lookup.appendInPlace(ext);
}
Common::SeekableReadStream *stream = _sset.createReadStreamForMember(lookup);
if (stream) {
return stream;
}
// as a very last resort, try trimming the file name to 8 chars
Common::String filename = lookup.baseName();
if (!ext.empty() && filename.hasSuffix(ext.c_str())) {
int len = filename.size();
if (len > 8) {
debugC(9, kDebugDisk, "Disk_br::openFile: trimming filename (%s) to 8 characters", name.c_str());
while (len-- > 8) {
filename.deleteLastChar();
}
filename += ext;
stream = _sset.createReadStreamForMember(lookup.getParent().appendComponent(filename));
}
}
if (!stream && errorOnNotFound) {
errorFileNotFound(name);
}
return stream;
}
Common::SeekableReadStream *Disk_br::openFile(const Common::String &name, const Common::String &ext) {
return openFile_internal(true, name, ext);
}
Common::SeekableReadStream *Disk_br::tryOpenFile(const Common::String &name, const Common::String &ext) {
return openFile_internal(false, name, ext);
}
void Disk_br::errorFileNotFound(const Common::String &filename) {
error("File '%s' not found", filename.c_str());
}
Common::String DosDisk_br::selectArchive(const Common::String& name) {
debugC(5, kDebugDisk, "DosDisk_br::selectArchive");
Common::String oldPath = _currentPart;
_currentPart = name;
debugC(5, kDebugDisk, "DosDisk_br::selectArchive: adding part directory to search set");
_sset.remove("part");
_sset.add("part", _baseDir->getSubDirectory(Common::Path(name), 3), 10);
return oldPath;
}
void DosDisk_br::setLanguage(uint16 language) {
debugC(5, kDebugDisk, "DosDisk_br::setLanguage");
assert(language < 4);
_language = language;
}
DosDisk_br::DosDisk_br(Parallaction* vm) : Disk_br(vm) {
}
void DosDisk_br::init() {
// TODO: clarify whether the engine or OSystem should add the base game directory to the search manager.
// Right now, I am keeping an internal search set to do the job.
_baseDir = new Common::FSDirectory(ConfMan.getPath("path"));
_sset.add("base", _baseDir, 5, true);
}
GfxObj* DosDisk_br::loadTalk(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadTalk(%s)", name);
Common::SeekableReadStream *stream = openFile("tal/" + Common::String(name), ".tal");
// talk position is set to (0,0), because talks are always displayed at
// absolute coordinates, set in the dialogue manager. The original used
// to null out coordinates every time they were needed. We do it better!
Sprites *spr = createSprites(stream);
for (int i = 0; i < spr->getNum(); i++) {
spr->_sprites[i].x = 0;
spr->_sprites[i].y = 0;
}
return new GfxObj(0, spr, name);
}
Script* DosDisk_br::loadLocation(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadLocation");
static const char * const langs[4] = { "it/", "fr/", "en/", "ge/" };
Common::String fullName(name);
if (!fullName.hasSuffix(".slf")) {
fullName += ".loc";
}
Common::SeekableReadStream *stream = openFile(langs[_language] + fullName);
return new Script(stream, true);
}
Script* DosDisk_br::loadScript(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadScript");
Common::SeekableReadStream *stream = openFile("scripts/" + Common::String(name), ".scr");
return new Script(stream, true);
}
// there are no Head resources in Big Red Adventure
GfxObj* DosDisk_br::loadHead(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadHead");
return nullptr;
}
void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette) {
stream.skip(4);
uint width = stream.readUint32BE();
if (width & 1) width++;
uint height = stream.readUint32BE();
stream.skip(20);
if (palette) {
stream.read(palette, 768);
} else {
stream.skip(768);
}
surf.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
stream.read(surf.getPixels(), width * height);
}
Frames* DosDisk_br::loadPointer(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadPointer");
Common::SeekableReadStream *stream = openFile(Common::String(name), ".ras");
Graphics::Surface *surf = new Graphics::Surface;
loadBitmap(*stream, *surf, nullptr);
delete stream;
return new SurfaceToFrames(surf);
}
Font* DosDisk_br::loadFont(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadFont");
Common::SeekableReadStream *stream = openFile(name, ".fnt");
Font *font = createFont(name, *stream);
delete stream;
return font;
}
GfxObj* DosDisk_br::loadObjects(const char *name, uint8 part) {
debugC(5, kDebugDisk, "DosDisk_br::loadObjects");
Common::SeekableReadStream *stream = openFile(name);
GfxObj *obj = createInventoryObjects(*stream);
delete stream;
return obj;
}
GfxObj* DosDisk_br::loadStatic(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadStatic");
Common::SeekableReadStream *stream = openFile("ras/" + Common::String(name), ".ras");
Graphics::Surface *surf = new Graphics::Surface;
loadBitmap(*stream, *surf, nullptr);
delete stream;
return new GfxObj(0, new SurfaceToFrames(surf), name);
}
Sprites* DosDisk_br::createSprites(Common::ReadStream *stream) {
uint16 num = stream->readUint16LE();
Sprites *sprites = new Sprites(num);
for (uint i = 0; i < num; i++) {
Sprite *spr = &sprites->_sprites[i];
spr->size = stream->readUint16LE();
spr->x = stream->readUint16LE();
spr->y = stream->readUint16LE();
spr->w = stream->readUint16LE();
spr->h = stream->readUint16LE();
spr->packedData = (byte *)malloc(spr->size);
stream->read(spr->packedData, spr->size);
}
delete stream;
return sprites;
}
Frames* DosDisk_br::loadFrames(const char* name) {
Common::SeekableReadStream *stream = nullptr;
debugC(5, kDebugDisk, "DosDisk_br::loadFrames");
Common::String path(name);
if (path.hasSuffix(".win")) {
stream = openFile(path);
} else {
stream = openFile("ani/" + Common::String(name), ".ani");
}
return createSprites(stream);
}
// Slides in Nippon Safes are basically screen-sized pictures with valid
// palette data used for menu and for location switches. Big Red Adventure
// doesn't need slides in that sense, but it still has some special
// graphics resources with palette data, so those will be named slides.
//
void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadSlide");
Common::SeekableReadStream *stream = openFile(name, ".bmp");
byte rgb[768];
loadBitmap(*stream, info.bg, rgb);
info.width = info.bg.w;
info.height = info.bg.h;
delete stream;
for (uint i = 0; i < 256; i++) {
info.palette.setEntry(i, rgb[i] >> 2, rgb[i+256] >> 2, rgb[i+512] >> 2);
}
}
MaskBuffer *DosDisk_br::loadMask(const char *name, uint32 w, uint32 h) {
if (!name) {
return nullptr;
}
Common::SeekableReadStream *stream = openFile("msk/" + Common::String(name), ".msk");
MaskBuffer *buffer = new MaskBuffer;
assert(buffer);
buffer->create(w, h);
buffer->bigEndian = false;
stream->read(buffer->data, buffer->size);
delete stream;
return buffer;
}
PathBuffer *DosDisk_br::loadPath(const char *name, uint32 w, uint32 h) {
if (!name) {
return nullptr;
}
Common::SeekableReadStream *stream = openFile("pth/" + Common::String(name), ".pth");
PathBuffer *buffer = new PathBuffer;
assert(buffer);
buffer->create(w, h);
buffer->bigEndian = false;
stream->read(buffer->data, buffer->size);
delete stream;
return buffer;
}
void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {
debugC(5, kDebugDisk, "DosDisk_br::loadScenery");
Common::SeekableReadStream *stream;
if (name) {
stream = openFile("bkg/" + Common::String(name), ".bkg");
byte rgb[768];
loadBitmap(*stream, info.bg, rgb);
info.width = info.bg.w;
info.height = info.bg.h;
for (uint i = 0; i < 256; i++) {
info.palette.setEntry(i, rgb[i] >> 2, rgb[i+256] >> 2, rgb[i+512] >> 2);
}
delete stream;
}
if (mask) {
info._mask = loadMask(mask, info.width, info.height);
}
if (path) {
info._path = loadPath(path, info.width, info.height);
}
}
Table* DosDisk_br::loadTable(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadTable");
return createTableFromStream(100, openFile(name, ".tab"));
}
Common::SeekableReadStream* DosDisk_br::loadMusic(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadMusic");
return openFile("msc/" + Common::String(name), ".msc");
}
Common::SeekableReadStream* DosDisk_br::loadSound(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadSound");
return openFile("sfx/" + Common::String(name), ".sfx");
}
DosDemoDisk_br::DosDemoDisk_br(Parallaction *vm) : DosDisk_br(vm) {
}
void DosDemoDisk_br::init() {
// TODO: clarify whether the engine or OSystem should add the base game directory to the search manager.
// Right now, I am keeping an internal search set to do the job.
_baseDir = new Common::FSDirectory(ConfMan.getPath("path"), 2);
_sset.add("base", _baseDir, 5, false);
}
Common::String DosDemoDisk_br::selectArchive(const Common::String& name) {
debugC(5, kDebugDisk, "DosDemoDisk_br::selectArchive");
Common::String oldPath = _currentPart;
_currentPart = name;
return oldPath;
}
AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) {
}
void AmigaDisk_br::init() {
_baseDir = new Common::FSDirectory(ConfMan.getPath("path"));
_sset.add("base", _baseDir, 5, false);
const char *subDirNames[3] = { "fonts", "backs", "common" };
const char *subDirPrefixes[3] = { "fonts", "backs", "" };
// The common sub directory, doesn't exist in the Amiga demo
uint numDir = (_vm->getFeatures() & GF_DEMO) ? 2 : 3;
for (uint i = 0; i < numDir; i++)
_sset.add(subDirNames[i], _baseDir->getSubDirectory(subDirPrefixes[i], subDirNames[i], 2), 6);
}
void AmigaDisk_br::adjustForPalette(Graphics::Surface &surf, int transparentColor) {
uint size = surf.w * surf.h;
byte *data = (byte *)surf.getPixels();
for (uint i = 0; i < size; i++, data++) {
if (transparentColor == -1 || transparentColor != *data)
*data += 16;
}
}
void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *filename) {
byte r,g,b;
const byte *p;
Common::SeekableReadStream *stream;
Image::IFFDecoder decoder;
uint i;
stream = tryOpenFile("backs/" + Common::String(filename), ".ap");
if (stream) {
// NOTE: Additional palette (15-colors) is always loaded onto colors 1-15
uint32 size = stream->size() / 3;
for (i = 1; i < size; i++) {
r = stream->readByte() >> 2;
g = stream->readByte() >> 2;
b = stream->readByte() >> 2;
info.palette.setEntry(i, r, g, b);
}
delete stream;
} else {
p = braAmigaFramesDefaultPalette;
for (i = 0; i < 16; i++) {
r = *p >> 2;
p++;
g = *p >> 2;
p++;
b = *p >> 2;
p++;
info.palette.setEntry(i, r, g, b);
}
}
stream = openFile("backs/" + Common::String(filename), ".bkg");
decoder.loadStream(*stream);
info.bg.copyFrom(*decoder.getSurface());
info.width = info.bg.w;
info.height = info.bg.h;
// Overwrite the first color (transparent key) in the palette
p = decoder.getPalette().data();
info.palette.setEntry(0, p[0] >> 2, p[1] >> 2, p[2] >> 0);
for (i = 16; i < 32; i++) {
r = *p >> 2;
p++;
g = *p >> 2;
p++;
b = *p >> 2;
p++;
info.palette.setEntry(i, r, g, b);
}
// background data is drawn used the upper portion of the palette
adjustForPalette(info.bg);
}
void finalpass(byte *buffer, uint32 size) {
byte b = 0xC0;
byte r1 = 0x40;
byte r2 = 0x80;
for (uint32 i = 0; i < size*4; i++) {
byte s = buffer[i/4];
s &= b;
if (s == r1) {
buffer[i/4] |= b;
} else
if (s == b) {
buffer[i/4] ^= r2;
}
b >>= 2; if (b == 0) { b = 0xC0; }
r1 >>= 2; if (r1 == 0) { r1 = 0x40; }
r2 >>= 2; if (r2 == 0) { r2 = 0x80; }
}
}
MaskBuffer *AmigaDisk_br::loadMask(const char *name, uint32 w, uint32 h) {
if (!name) {
return nullptr;
}
debugC(1, kDebugDisk, "AmigaDisk_br::loadMask '%s'", name);
Common::SeekableReadStream *stream = tryOpenFile("msk/" + Common::String(name), ".msk");
if (!stream) {
return nullptr;
}
Image::IFFDecoder decoder;
decoder.setNumRelevantPlanes(2); // use only 2 first bits from each pixels
decoder.setPixelPacking(true); // pack 4 2bit pixels into 1 byte
decoder.loadStream(*stream);
MaskBuffer *buffer = new MaskBuffer;
// surface width was shrunk to 1/4th of the bitmap width due to the pixel packing
buffer->create(decoder.getSurface()->w * 4, decoder.getSurface()->h);
memcpy(buffer->data, decoder.getSurface()->getPixels(), buffer->size);
buffer->bigEndian = true;
finalpass(buffer->data, buffer->size);
return buffer;
}
void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadScenery '%s', '%s' '%s'", name, mask, path);
if (name) {
loadBackground(info, name);
}
if (mask) {
info._mask = loadMask(mask, info.width, info.height);
}
if (path) {
info._path = loadPath(path, info.width, info.height);
}
}
void AmigaDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadSlide '%s'", name);
loadBackground(info, name);
}
GfxObj* AmigaDisk_br::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadStatic '%s'", name);
Common::String sName = name;
Common::SeekableReadStream *stream = openFile("ras/" + sName, ".ras");
Image::IFFDecoder decoder;
decoder.loadStream(*stream);
Graphics::Surface *surf = new Graphics::Surface;
assert(surf);
surf->copyFrom(*decoder.getSurface());
// Static pictures are drawn used the upper half of the palette: this must be
// done before shadow mask is applied. This way, only really transparent pixels
// will have zero as a color.
adjustForPalette(*surf);
// NOTE: this assumes that the extension is always present in the file name
sName.deleteLastChar();
sName.deleteLastChar();
sName.deleteLastChar();
sName.deleteLastChar();
stream = tryOpenFile("ras/" + sName + ".ras_shdw");
if (!stream) {
debugC(9, kDebugDisk, "Cannot find shadow file for '%s'\n", name);
} else {
uint32 shadowWidth = ((surf->w + 15)/8) & ~1;
uint32 shadowSize = shadowWidth * surf->h;
byte *shadow = new byte[shadowSize];
assert(shadow);
stream->read(shadow, shadowSize);
for (int32 i = 0; i < surf->h; ++i) {
byte *src = shadow + shadowWidth * i;
byte *dst = (byte *)surf->getPixels() + surf->pitch * i;
for (int32 j = 0; j < surf->w; ++j, ++dst) {
byte bit = src[j/8] & (1 << (7 - (j & 7)));
if (bit == 0) *dst = 0;
}
}
delete[] shadow;
delete stream;
}
return new GfxObj(0, new SurfaceToFrames(surf), name);
}
Sprites* AmigaDisk_br::createSprites(Common::ReadStream *stream) {
uint16 num = stream->readUint16BE();
Sprites *sprites = new Sprites(num);
for (uint i = 0; i < num; i++) {
Sprite *spr = &sprites->_sprites[i];
spr->size = stream->readUint16BE();
spr->x = stream->readUint16BE();
spr->y = stream->readUint16BE();
spr->w = stream->readUint16BE();
spr->h = stream->readUint16BE() - 1;
spr->packedData = (byte *)malloc(spr->size);
stream->read(spr->packedData, spr->size);
}
delete stream;
return sprites;
}
Frames* AmigaDisk_br::loadFrames(const char* name) {
Common::SeekableReadStream *stream = nullptr;
debugC(5, kDebugDisk, "AmigaDisk_br::loadFrames");
Common::String path(name);
if (path.hasSuffix(".win")) {
stream = openFile(path);
} else {
stream = openFile("anims/" + Common::String(name), ".ani");
}
return createSprites(stream);
}
GfxObj* AmigaDisk_br::loadTalk(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadTalk '%s'", name);
Common::SeekableReadStream *stream = openFile("talks/" + Common::String(name), ".tal");
// talk position is set to (0,0), because talks are always displayed at
// absolute coordinates, set in the dialogue manager. The original used
// to null out coordinates every time they were needed. We do it better!
Sprites *spr = createSprites(stream);
for (int i = 0; i < spr->getNum(); i++) {
spr->_sprites[i].x = 0;
spr->_sprites[i].y = 0;
}
return new GfxObj(0, spr, name);
}
Font* AmigaDisk_br::loadFont(const char* name) {
debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name);
Common::SeekableReadStream *stream = openFile("fonts/" + Common::String(name), ".font");
Common::String fontDir;
Common::String fontFile;
byte ch;
stream->seek(4, SEEK_SET);
while ((ch = stream->readByte()) != 0x2F) fontDir += ch;
while ((ch = stream->readByte()) != 0) fontFile += ch;
delete stream;
stream = openFile("fonts/" + fontDir + "/" + fontFile);
Font *font = createFont(name, *stream);
delete stream;
return font;
}
Common::SeekableReadStream* AmigaDisk_br::loadMusic(const char* name) {
debugC(5, kDebugDisk, "AmigaDisk_br::loadMusic");
return tryOpenFile("msc/" + Common::String(name), ".msc");
}
Common::SeekableReadStream* AmigaDisk_br::loadSound(const char* name) {
debugC(5, kDebugDisk, "AmigaDisk_br::loadSound");
return openFile("sfx/" + Common::String(name), ".sfx");
}
static const uint16 objectsMax[5] = {
5, 73, 71, 19, 48
};
GfxObj* AmigaDisk_br::loadObjects(const char *name, uint8 part) {
debugC(5, kDebugDisk, "AmigaDisk_br::loadObjects");
Common::SeekableReadStream *stream = openFile(name);
Image::IFFDecoder decoder;
decoder.loadStream(*stream);
uint16 max = objectsMax[part];
if (_vm->getFeatures() & GF_DEMO)
max = 72;
byte *data = new byte[max * 2601];
const byte *srcPtr = (const byte *)decoder.getSurface()->getBasePtr(0,0);
int w = decoder.getSurface()->w;
// Convert to the expected display format
for (int i = 0; i < max; i++) {
uint16 x = (i % 8) * 51;
uint16 y = (i / 8) * 51;
const byte *src = srcPtr + y * w + x;
byte *dst = data + i * 2601;
for (int h = 0; h < 51; h++) {
memcpy(dst, src, 51);
src += w;
dst += 51;
}
}
return new GfxObj(0, new Cnv(max, 51, 51, data, true));
}
Common::String AmigaDisk_br::selectArchive(const Common::String& name) {
debugC(5, kDebugDisk, "AmigaDisk_br::selectArchive");
Common::String oldPath = _currentPart;
_currentPart = name;
debugC(5, kDebugDisk, "AmigaDisk_br::selectArchive: adding part directory to search set");
_sset.remove("part");
_sset.add("part", _baseDir->getSubDirectory(Common::Path(name), 3), 10);
return oldPath;
}
Disk_br::Disk_br(Parallaction *vm) : _vm(vm), _baseDir(nullptr), _language(0) {
}
Disk_br::~Disk_br() {
_sset.clear();
}
} // namespace Parallaction

View File

@@ -0,0 +1,956 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/fs.h"
#include "common/formats/iff_container.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/textconsole.h"
#include "common/compression/powerpacker.h"
#include "image/iff.h"
#include "parallaction/parser.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
// HACK: Several archives ('de', 'en', 'fr' and 'disk0') in the multi-lingual
// Amiga version of Nippon Safes, and one archive ('fr') in the Amiga Demo of
// Nippon Safes used different internal offsets than all the other archives.
//
// When an archive is opened in the Amiga demo, its size is checked against
// SIZEOF_SMALL_ARCHIVE to detect when the smaller archive is used.
//
// When an archive is opened in Amiga multi-lingual version, the header is
// checked again NDOS to detect when a smaller archive is used.
//
#define SIZEOF_SMALL_ARCHIVE 12778
#define ARCHIVE_FILENAMES_OFS 0x16
#define NORMAL_ARCHIVE_FILES_NUM 384
#define SMALL_ARCHIVE_FILES_NUM 180
#define NORMAL_ARCHIVE_SIZES_OFS 0x3016
#define SMALL_ARCHIVE_SIZES_OFS 0x1696
#define NORMAL_ARCHIVE_DATA_OFS 0x4000
#define SMALL_ARCHIVE_DATA_OFS 0x1966
#define MAX_ARCHIVE_ENTRIES 384
class NSArchive : public Common::Archive {
Common::SeekableReadStream *_stream;
char _archiveDir[MAX_ARCHIVE_ENTRIES][32];
uint32 _archiveLengths[MAX_ARCHIVE_ENTRIES];
uint32 _archiveOffsets[MAX_ARCHIVE_ENTRIES];
uint32 _numFiles;
uint32 lookup(const char *name) const;
public:
NSArchive(Common::SeekableReadStream *stream, Common::Platform platform, uint32 features);
~NSArchive() override;
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
bool hasFile(const Common::Path &path) const override;
int listMembers(Common::ArchiveMemberList &list) const override;
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
};
NSArchive::NSArchive(Common::SeekableReadStream *stream, Common::Platform platform, uint32 features) : _stream(stream) {
if (!_stream) {
error("NSArchive: invalid stream passed to constructor");
}
bool isSmallArchive = false;
if (platform == Common::kPlatformAmiga) {
if (features & GF_DEMO) {
isSmallArchive = stream->size() == SIZEOF_SMALL_ARCHIVE;
} else if (features & GF_LANG_MULT) {
isSmallArchive = (stream->readUint32BE() != MKTAG('N','D','O','S'));
}
}
_numFiles = (isSmallArchive) ? SMALL_ARCHIVE_FILES_NUM : NORMAL_ARCHIVE_FILES_NUM;
_stream->seek(ARCHIVE_FILENAMES_OFS);
_stream->read(_archiveDir, _numFiles*32);
_stream->seek((isSmallArchive) ? SMALL_ARCHIVE_SIZES_OFS : NORMAL_ARCHIVE_SIZES_OFS);
uint32 dataOffset = (isSmallArchive) ? SMALL_ARCHIVE_DATA_OFS : NORMAL_ARCHIVE_DATA_OFS;
for (uint16 i = 0; i < _numFiles; i++) {
_archiveOffsets[i] = dataOffset;
_archiveLengths[i] = _stream->readUint32BE();
dataOffset += _archiveLengths[i];
}
}
NSArchive::~NSArchive() {
delete _stream;
}
uint32 NSArchive::lookup(const char *name) const {
uint32 i = 0;
for ( ; i < _numFiles; i++) {
if (!scumm_stricmp(_archiveDir[i], name)) break;
}
return i;
}
Common::SeekableReadStream *NSArchive::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
debugC(3, kDebugDisk, "NSArchive::createReadStreamForMember(%s)", name.c_str());
if (name.empty())
return nullptr;
uint32 index = lookup(name.c_str());
if (index == _numFiles) return nullptr;
debugC(9, kDebugDisk, "NSArchive::createReadStreamForMember: '%s' found in slot %i", name.c_str(), index);
int offset = _archiveOffsets[index];
int endOffset = _archiveOffsets[index] + _archiveLengths[index];
return new Common::SeekableSubReadStream(_stream, offset, endOffset, DisposeAfterUse::NO);
}
bool NSArchive::hasFile(const Common::Path &path) const {
Common::String name = path.toString();
if (name.empty())
return false;
return lookup(name.c_str()) != _numFiles;
}
int NSArchive::listMembers(Common::ArchiveMemberList &list) const {
for (uint32 i = 0; i < _numFiles; i++) {
list.push_back(Common::SharedPtr<Common::GenericArchiveMember>(new Common::GenericArchiveMember(Common::String(_archiveDir[i]), *this)));
}
return _numFiles;
}
const Common::ArchiveMemberPtr NSArchive::getMember(const Common::Path &path) const {
Common::String name = path.toString();
uint32 index = lookup(name.c_str());
if (index >= _numFiles) {
return Common::ArchiveMemberPtr();
}
const char *item = _archiveDir[index];
return Common::SharedPtr<Common::GenericArchiveMember>(new Common::GenericArchiveMember(Common::String(item), *this));
}
#define HIGHEST_PRIORITY 9
#define NORMAL_ARCHIVE_PRIORITY 5
#define LOW_ARCHIVE_PRIORITY 2
#define LOWEST_ARCHIVE_PRIORITY 1
Disk_ns::Disk_ns(Parallaction *vm) : _vm(vm) {
Common::FSDirectory *baseDir = new Common::FSDirectory(ConfMan.getPath("path"));
_sset.add("basedir", baseDir, HIGHEST_PRIORITY);
}
Disk_ns::~Disk_ns() {
_sset.clear();
}
void Disk_ns::errorFileNotFound(const char *s) {
error("File '%s' not found", s);
}
Common::SeekableReadStream *Disk_ns::openFile(const char *filename) {
Common::SeekableReadStream *stream = tryOpenFile(filename);
if (!stream)
errorFileNotFound(filename);
return stream;
}
void Disk_ns::addArchive(const Common::String& name, int priority) {
Common::SeekableReadStream *stream = _sset.createReadStreamForMember(Common::Path(name));
if (!stream)
error("Disk_ns::addArchive() couldn't find archive '%s'", name.c_str());
debugC(1, kDebugDisk, "Disk_ns::addArchive(name = %s, priority = %i)", name.c_str(), priority);
NSArchive *arc = new NSArchive(stream, _vm->getPlatform(), _vm->getFeatures());
_sset.add(name, arc, priority);
}
Common::String Disk_ns::selectArchive(const Common::String& name) {
Common::String oldName = _resArchiveName;
if (_sset.hasArchive(name)) {
return oldName;
}
if (!_resArchiveName.empty()) {
_sset.remove(_resArchiveName);
}
_resArchiveName = name;
addArchive(name, NORMAL_ARCHIVE_PRIORITY);
return oldName;
}
void Disk_ns::setLanguage(uint16 language) {
debugC(1, kDebugDisk, "setLanguage(%i)", language);
assert(language < 4);
if (!_language.empty()) {
_sset.remove(_language);
}
static const char *languages[] = { "it", "fr", "en", "ge" };
_language = languages[language];
if (_sset.hasArchive(_language)) {
return;
}
addArchive(_language, LOWEST_ARCHIVE_PRIORITY);
}
#pragma mark -
DosDisk_ns::DosDisk_ns(Parallaction* vm) : Disk_ns(vm), _gfx(nullptr) {
}
DosDisk_ns::~DosDisk_ns() {
}
void DosDisk_ns::init() {
// setup permament archives
addArchive("disk1", LOW_ARCHIVE_PRIORITY);
}
Common::SeekableReadStream *DosDisk_ns::tryOpenFile(const char* name) {
debugC(3, kDebugDisk, "DosDisk_ns::tryOpenFile(%s)", name);
Common::SeekableReadStream *stream = _sset.createReadStreamForMember(name);
if (stream)
return stream;
char path[PATH_LEN];
Common::sprintf_s(path, "%s.pp", name);
return _sset.createReadStreamForMember(path);
}
Script* Disk_ns::loadLocation(const char *name) {
char path[PATH_LEN];
const char *charName = _vm->_char.getBaseName();
// WORKAROUND: Special case for the Multilingual DOS version: during the ending
// sequence, it tries to load a non-existing file using "Dinor" as a character
// name. In this case, the character name should be just "dino".
if (!strcmp(charName, "Dinor"))
charName = "dino";
Common::sprintf_s(path, "%s%s/%s.loc", charName, _language.c_str(), name);
debugC(3, kDebugDisk, "Disk_ns::loadLocation(%s): trying '%s'", name, path);
Common::SeekableReadStream *stream = tryOpenFile(path);
if (!stream) {
Common::sprintf_s(path, "%s/%s.loc", _language.c_str(), name);
debugC(3, kDebugDisk, "DosDisk_ns::loadLocation(%s): trying '%s'", name, path);
stream = openFile(path);
}
return new Script(stream, true);
}
Script* Disk_ns::loadScript(const char* name) {
debugC(1, kDebugDisk, "Disk_ns::loadScript '%s'", name);
char path[PATH_LEN];
Common::sprintf_s(path, "%s.script", name);
Common::SeekableReadStream *stream = openFile(path);
return new Script(stream, true);
}
Cnv *Disk_ns::makeCnv(Common::SeekableReadStream *stream) {
assert(stream);
uint16 numFrames = stream->readByte();
uint16 width = stream->readByte();
assert((width & 7) == 0);
uint16 height = stream->readByte();
uint32 decsize = numFrames * width * height;
byte *data = new byte[decsize]();
assert(data);
decodeCnv(data, numFrames, width, height, stream);
delete stream;
return new Cnv(numFrames, width, height, data, true);
}
void DosDisk_ns::decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) {
int32 decsize = numFrames * width * height;
bool packed = (stream->size() - stream->pos()) != decsize;
if (packed) {
Common::PackBitsReadStream decoder(*stream);
decoder.read(data, decsize);
} else {
stream->read(data, decsize);
}
}
Cnv* DosDisk_ns::loadCnv(const char *filename) {
Common::SeekableReadStream *stream = openFile(filename);
assert(stream);
return makeCnv(stream);
}
GfxObj* DosDisk_ns::loadTalk(const char *name) {
const char *ext = strstr(name, ".talk");
if (ext) {
// npc talk
return new GfxObj(0, loadCnv(name), name);
}
char v20[30];
if (g_engineFlags & kEngineTransformedDonna) {
Common::sprintf_s(v20, "%stta.cnv", name);
} else {
Common::sprintf_s(v20, "%stal.cnv", name);
}
return new GfxObj(0, loadCnv(v20), name);
}
GfxObj* DosDisk_ns::loadHead(const char* name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%shead", name);
path[8] = '\0';
Common::strcat_s(path, ".cnv");
return new GfxObj(0, loadCnv(path));
}
Frames* DosDisk_ns::loadPointer(const char *name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%s.cnv", name);
return loadCnv(path);
}
Font* DosDisk_ns::loadFont(const char* name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%scnv.cnv", name);
return createFont(name, loadCnv(path));
}
GfxObj* DosDisk_ns::loadObjects(const char *name, uint8 part) {
char path[PATH_LEN];
Common::sprintf_s(path, "%sobj.cnv", name);
return new GfxObj(0, loadCnv(path), name);
}
GfxObj* DosDisk_ns::loadStatic(const char* name) {
return new GfxObj(0, loadCnv(name), name);
}
Frames* DosDisk_ns::loadFrames(const char* name) {
return loadCnv(name);
}
/*
Background images are compressed using a RLE algorithm that resembles PackBits.
The uncompressed data is then unpacked as following:
- color data [bits 0-5]
- mask data [bits 6-7] (z buffer)
- path data [bit 8] (walkable areas)
*/
void DosDisk_ns::unpackBackground(Common::ReadStream *stream, byte *screen, byte *mask, byte *path) {
byte storage[128];
uint32 storageLen = 0, len = 0;
uint32 j = 0;
while (1) {
// first extracts packbits variant data
do {
len = stream->readByte();
if (stream->eos())
return;
if (len == 128) {
storageLen = 0;
} else if (len <= 127) {
len++;
for (uint32 i = 0; i < len; i++) {
storage[i] = stream->readByte();
}
storageLen = len;
} else {
len = (256 - len) + 1;
byte v = stream->readByte();
memset(storage, v, len);
storageLen = len;
}
} while (storageLen == 0);
// then unpacks the bits to the destination buffers
for (uint32 i = 0; i < storageLen; i++, j++) {
byte b = storage[i];
path[j/8] |= ((b & 0x80) >> 7) << (j & 7);
mask[j/4] |= ((b & 0x60) >> 5) << ((j & 3) << 1);
screen[j] = b & 0x1F;
}
}
}
void DosDisk_ns::parseDepths(BackgroundInfo &info, Common::SeekableReadStream &stream) {
info.layers[0] = stream.readByte();
info.layers[1] = stream.readByte();
info.layers[2] = stream.readByte();
info.layers[3] = stream.readByte();
}
void DosDisk_ns::createMaskAndPathBuffers(BackgroundInfo &info) {
info._mask = new MaskBuffer;
assert(info._mask);
info._mask->create(info.width, info.height);
info._mask->bigEndian = true;
info._path = new PathBuffer;
assert(info._path);
info._path->create(info.width, info.height);
info._path->bigEndian = true;
}
void DosDisk_ns::loadBackground(BackgroundInfo& info, const char *filename) {
Common::SeekableReadStream *stream = openFile(filename);
info.width = _vm->_screenWidth; // 320
info.height = _vm->_screenHeight; // 200
// read palette
byte tmp[3];
for (uint i = 0; i < 32; i++) {
tmp[0] = stream->readByte();
tmp[1] = stream->readByte();
tmp[2] = stream->readByte();
info.palette.setEntry(i, tmp[0], tmp[1], tmp[2]);
}
// read z coordinates
parseDepths(info, *stream);
// read palette rotation parameters
PaletteFxRange range;
for (uint32 _si = 0; _si < 6; _si++) {
range._timer = stream->readUint16BE();
range._step = stream->readUint16BE();
range._flags = stream->readUint16BE();
range._first = stream->readByte();
range._last = stream->readByte();
info.setPaletteRange(_si, range);
}
// read bitmap, mask and path data and extract them into the 3 buffers
info.bg.create(info.width, info.height, Graphics::PixelFormat::createFormatCLUT8());
createMaskAndPathBuffers(info);
unpackBackground(stream, (byte *)info.bg.getPixels(), info._mask->data, info._path->data);
delete stream;
}
void DosDisk_ns::loadSlide(BackgroundInfo& info, const char *filename) {
char path[PATH_LEN];
Common::sprintf_s(path, "%s.slide", filename);
loadBackground(info, path);
}
void DosDisk_ns::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {
char filename[PATH_LEN];
Common::sprintf_s(filename, "%s.dyn", name);
// load bitmap
loadBackground(info, filename);
if (mask == nullptr) {
return;
}
// load external mask and path if present (overwriting the ones loaded by loadBackground)
char maskPath[PATH_LEN];
Common::sprintf_s(maskPath, "%s.msk", mask);
Common::SeekableReadStream *stream = openFile(maskPath);
assert(stream);
parseDepths(info, *stream);
createMaskAndPathBuffers(info);
stream->read(info._path->data, info._path->size);
stream->read(info._mask->data, info._mask->size);
delete stream;
}
Table* DosDisk_ns::loadTable(const char* name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%s.tab", name);
return createTableFromStream(100, openFile(path));
}
Common::SeekableReadStream* DosDisk_ns::loadMusic(const char* name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%s.mid", name);
return openFile(path);
}
Common::SeekableReadStream* DosDisk_ns::loadSound(const char* name) {
return nullptr;
}
#pragma mark -
AmigaDisk_ns::AmigaDisk_ns(Parallaction *vm) : Disk_ns(vm) {
}
AmigaDisk_ns::~AmigaDisk_ns() {
}
void AmigaDisk_ns::init() {
// setup permament archives
if (_vm->getFeatures() & GF_DEMO) {
addArchive("disk0", LOW_ARCHIVE_PRIORITY);
} else {
addArchive("disk0", LOW_ARCHIVE_PRIORITY);
addArchive("disk1", LOW_ARCHIVE_PRIORITY);
}
}
#define NUM_PLANES 5
/*
unpackFrame transforms images from 5-bitplanes format to
8-bit color-index mode
*/
void AmigaDisk_ns::unpackFrame(byte *dst, byte *src, uint16 planeSize) {
byte s0, s1, s2, s3, s4, mask, t0, t1, t2, t3, t4;
for (uint32 j = 0; j < planeSize; j++) {
s0 = src[j];
s1 = src[j+planeSize];
s2 = src[j+planeSize*2];
s3 = src[j+planeSize*3];
s4 = src[j+planeSize*4];
for (uint32 k = 0; k < 8; k++) {
mask = 1 << (7 - k);
t0 = (s0 & mask ? 1 << 0 : 0);
t1 = (s1 & mask ? 1 << 1 : 0);
t2 = (s2 & mask ? 1 << 2 : 0);
t3 = (s3 & mask ? 1 << 3 : 0);
t4 = (s4 & mask ? 1 << 4 : 0);
*dst++ = t0 | t1 | t2 | t3 | t4;
}
}
}
/*
patchFrame applies DLTA data (dlta) to specified buffer (dst)
*/
void AmigaDisk_ns::patchFrame(byte *dst, byte *dlta, uint16 bytesPerPlane, uint16 height) {
uint32 *dataIndex = (uint32 *)dlta;
uint32 *ofslenIndex = (uint32 *)dlta + 8;
uint16 *base = (uint16 *)dlta;
uint16 wordsPerLine = bytesPerPlane >> 1;
for (uint j = 0; j < NUM_PLANES; j++) {
uint16 *dst16 = (uint16 *)(dst + j * bytesPerPlane * height);
uint16 *data = base + READ_BE_UINT32(dataIndex);
dataIndex++;
uint16 *ofslen = base + READ_BE_UINT32(ofslenIndex);
ofslenIndex++;
while (*ofslen != 0xFFFF) {
uint16 ofs = READ_BE_UINT16(ofslen);
ofslen++;
uint16 size = READ_BE_UINT16(ofslen);
ofslen++;
while (size > 0) {
dst16[ofs] ^= *data++;
ofs += wordsPerLine;
size--;
}
}
}
}
// FIXME: no mask is loaded
void AmigaDisk_ns::unpackBitmap(byte *dst, byte *src, uint16 numFrames, uint16 bytesPerPlane, uint16 height) {
byte *baseFrame = src;
byte *tempBuffer = nullptr;
uint16 planeSize = bytesPerPlane * height;
for (uint32 i = 0; i < numFrames; i++) {
if (READ_BE_UINT32(src) == MKTAG('D','L','T','A')) {
uint size = READ_BE_UINT32(src + 4);
if (tempBuffer == nullptr)
tempBuffer = (byte *)malloc(planeSize * NUM_PLANES);
memcpy(tempBuffer, baseFrame, planeSize * NUM_PLANES);
patchFrame(tempBuffer, src + 8, bytesPerPlane, height);
unpackFrame(dst, tempBuffer, planeSize);
src += (size + 8);
dst += planeSize * 8;
} else {
unpackFrame(dst, src, planeSize);
src += planeSize * NUM_PLANES;
dst += planeSize * 8;
}
}
free(tempBuffer);
}
void AmigaDisk_ns::decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) {
byte bytesPerPlane = width / 8;
uint32 rawsize = numFrames * bytesPerPlane * NUM_PLANES * height;
byte *buf = new byte[rawsize];
assert(buf);
stream->read(buf, rawsize);
unpackBitmap(data, buf, numFrames, bytesPerPlane, height);
delete[] buf;
}
#undef NUM_PLANES
Frames* AmigaDisk_ns::loadPointer(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadPointer");
Common::SeekableReadStream *stream = openFile(name);
return makeCnv(stream);
}
GfxObj* AmigaDisk_ns::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadStatic '%s'", name);
Common::SeekableReadStream *s = openFile(name);
return new GfxObj(0, makeCnv(s), name);
}
Common::SeekableReadStream *AmigaDisk_ns::tryOpenFile(const char* name) {
debugC(3, kDebugDisk, "AmigaDisk_ns::tryOpenFile(%s)", name);
Common::PowerPackerStream *ret;
Common::SeekableReadStream *stream = _sset.createReadStreamForMember(name);
if (stream)
return stream;
char path[PATH_LEN];
Common::sprintf_s(path, "%s.pp", name);
stream = _sset.createReadStreamForMember(path);
if (stream) {
ret = new Common::PowerPackerStream(*stream);
delete stream;
return ret;
}
Common::sprintf_s(path, "%s.dd", name);
stream = _sset.createReadStreamForMember(path);
if (stream) {
ret = new Common::PowerPackerStream(*stream);
delete stream;
return ret;
}
return nullptr;
}
/*
FIXME: mask values are not computed correctly for level 1 and 2
NOTE: this routine is only able to build masks for Nippon Safes, since mask widths are hardcoded
into the main loop.
*/
void AmigaDisk_ns::buildMask(byte* buf) {
byte mask1[16] = { 0, 0x80, 0x20, 0xA0, 8, 0x88, 0x28, 0xA8, 2, 0x82, 0x22, 0xA2, 0xA, 0x8A, 0x2A, 0xAA };
byte mask0[16] = { 0, 0x40, 0x10, 0x50, 4, 0x44, 0x14, 0x54, 1, 0x41, 0x11, 0x51, 0x5, 0x45, 0x15, 0x55 };
byte plane0[40];
byte plane1[40];
for (int32 i = 0; i < _vm->_screenHeight; i++) {
memcpy(plane0, buf, 40);
memcpy(plane1, buf+40, 40);
for (uint32 j = 0; j < 40; j++) {
*buf++ = mask0[(plane0[j] & 0xF0) >> 4] | mask1[(plane1[j] & 0xF0) >> 4];
*buf++ = mask0[plane0[j] & 0xF] | mask1[plane1[j] & 0xF];
}
}
}
void AmigaDisk_ns::loadBackground(BackgroundInfo& info, const char *name) {
Common::SeekableReadStream *s = openFile(name);
Image::IFFDecoder decoder;
decoder.loadStream(*s);
info.bg.copyFrom(*decoder.getSurface());
info.width = info.bg.w;
info.height = info.bg.h;
const Graphics::Palette &p = decoder.getPalette();
for (uint i = 0; i < 32; i++) {
byte r, g, b;
p.get(i, r, g, b);
info.palette.setEntry(i, r >> 2, g >> 2, b >> 2);
}
const Common::Array<Image::IFFDecoder::PaletteRange> &paletteRanges = decoder.getPaletteRanges();
for (uint j = 0; j < 6 && j < paletteRanges.size(); j++) {
PaletteFxRange range;
range._timer = paletteRanges[j].timer;
range._step = paletteRanges[j].step;
range._flags = paletteRanges[j].flags;
range._first = paletteRanges[j].first;
range._last = paletteRanges[j].last;
info.setPaletteRange(j, range);
}
}
void AmigaDisk_ns::loadMask_internal(BackgroundInfo& info, const char *name) {
debugC(5, kDebugDisk, "AmigaDisk_ns::loadMask_internal(%s)", name);
char path[PATH_LEN];
Common::sprintf_s(path, "%s.mask", name);
Common::SeekableReadStream *s = tryOpenFile(path);
if (!s) {
debugC(5, kDebugDisk, "Mask file not found");
return; // no errors if missing mask files: not every location has one
}
Image::IFFDecoder decoder;
decoder.setNumRelevantPlanes(2); // use only 2 first bits from each pixel
decoder.setPixelPacking(true); // pack 4 2bit pixels into 1 byte
decoder.loadStream(*s);
const Graphics::Palette &p = decoder.getPalette();
for (uint i = 0; i < 4; i++) {
byte r, g, b;
p.get(i, r, g, b);
info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF;
}
info._mask = new MaskBuffer;
// surface width was shrunk to 1/4th of the bitmap width due to the pixel packing
info._mask->create(decoder.getSurface()->w * 4, decoder.getSurface()->h);
memcpy(info._mask->data, decoder.getSurface()->getPixels(), info._mask->size);
info._mask->bigEndian = true;
}
void AmigaDisk_ns::loadPath_internal(BackgroundInfo& info, const char *name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%s.path", name);
Common::SeekableReadStream *s = tryOpenFile(path);
if (!s) {
return; // no errors if missing path files: not every location has one
}
Image::IFFDecoder decoder;
decoder.setNumRelevantPlanes(1); // use only first bit from each pixel
decoder.setPixelPacking(true); // pack 8 1bit pixels into 1 byte
decoder.loadStream(*s);
info._path = new PathBuffer;
// surface width was shrunk to 1/8th of the bitmap width due to the pixel packing
info._path->create(decoder.getSurface()->w * 8, decoder.getSurface()->h);
memcpy(info._path->data, decoder.getSurface()->getPixels(), info._path->size);
info._path->bigEndian = true;
}
void AmigaDisk_ns::loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadScenery '%s', '%s'", background, mask);
char filename[PATH_LEN];
Common::sprintf_s(filename, "%s.bkgnd", background);
loadBackground(info, filename);
if (mask == nullptr) {
loadMask_internal(info, background);
loadPath_internal(info, background);
} else {
loadMask_internal(info, mask);
loadPath_internal(info, mask);
}
return;
}
void AmigaDisk_ns::loadSlide(BackgroundInfo& info, const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadSlide '%s'", name);
loadBackground(info, name);
return;
}
Frames* AmigaDisk_ns::loadFrames(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadFrames '%s'", name);
char path[PATH_LEN];
Common::sprintf_s(path, "anims/%s", name);
Common::SeekableReadStream *s = tryOpenFile(path);
if (!s)
s = openFile(name);
return makeCnv(s);
}
GfxObj* AmigaDisk_ns::loadHead(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadHead '%s'", name);
char path[PATH_LEN];
Common::sprintf_s(path, "%s.head", name);
Common::SeekableReadStream *s = openFile(path);
return new GfxObj(0, makeCnv(s), name);
}
GfxObj* AmigaDisk_ns::loadObjects(const char *name, uint8 part) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadObjects");
char path[PATH_LEN];
if (_vm->getFeatures() & GF_DEMO)
Common::sprintf_s(path, "%s.objs", name);
else
Common::sprintf_s(path, "objs/%s.objs", name);
Common::SeekableReadStream *s = openFile(path);
return new GfxObj(0, makeCnv(s), name);
}
GfxObj* AmigaDisk_ns::loadTalk(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadTalk '%s'", name);
char path[PATH_LEN];
if (_vm->getFeatures() & GF_DEMO)
Common::sprintf_s(path, "%s.talk", name);
else
Common::sprintf_s(path, "talk/%s.talk", name);
Common::SeekableReadStream *s = tryOpenFile(path);
if (!s) {
s = openFile(name);
}
return new GfxObj(0, makeCnv(s), name);
}
Table* AmigaDisk_ns::loadTable(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadTable '%s'", name);
char path[PATH_LEN];
if (!scumm_stricmp(name, "global")) {
Common::sprintf_s(path, "%s.table", name);
} else {
if (!(_vm->getFeatures() & GF_DEMO))
Common::sprintf_s(path, "objs/%s.table", name);
else
Common::sprintf_s(path, "%s.table", name);
}
return createTableFromStream(100, openFile(path));
}
Font* AmigaDisk_ns::loadFont(const char* name) {
debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name);
char path[PATH_LEN];
Common::sprintf_s(path, "%sfont", name);
Common::SeekableReadStream *stream = openFile(path);
Font *font = createFont(name, *stream);
delete stream;
return font;
}
Common::SeekableReadStream* AmigaDisk_ns::loadMusic(const char* name) {
return tryOpenFile(name);
}
Common::SeekableReadStream* AmigaDisk_ns::loadSound(const char* name) {
char path[PATH_LEN];
Common::sprintf_s(path, "%s.snd", name);
return tryOpenFile(path);
}
} // End of namespace Parallaction

View File

@@ -0,0 +1,204 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/exec.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
void ProgramExec::runScript(ProgramPtr script, AnimationPtr a) {
debugC(9, kDebugExec, "runScript(Animation = %s)", a->_name);
_ctxt._ip = script->_ip;
_ctxt._anim = a;
_ctxt._program = script;
_ctxt._suspend = false;
_ctxt._modCounter = _modCounter;
InstructionPtr inst;
for ( ; (a->_flags & kFlagsActing) ; ) {
inst = script->_instructions[_ctxt._ip];
_ctxt._inst = inst;
++_ctxt._ip;
debugC(9, kDebugExec, "inst [%02i] %s\n", inst->_index, _instructionNames[inst->_index - 1]);
script->_status = kProgramRunning;
(*_opcodes[inst->_index])(_ctxt);
if (_ctxt._suspend)
break;
}
script->_ip = _ctxt._ip;
}
void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) {
if (g_engineFlags & kEnginePauseJobs) {
return;
}
for (ProgramList::iterator it = first; it != last; ++it) {
AnimationPtr a = (*it)->_anim;
if (a->_flags & kFlagsCharacter)
a->resetZ();
if ((a->_flags & kFlagsActing) == 0)
continue;
runScript(*it, a);
if (a->_flags & kFlagsCharacter)
a->resetZ();
}
_modCounter++;
return;
}
ProgramExec::ProgramExec() : _modCounter(0), _instructionNames(nullptr) {
}
void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) {
uint32 useFlags = 0;
bool useLocalFlags;
_suspend = false;
_running = true;
for ( ; first != last; ++first) {
if (_vm->shouldQuit())
break;
CommandPtr cmd = *first;
if (cmd->_valid && !cmd->_zone && !cmd->_zoneName.empty()) {
// try binding the command to a zone
cmd->_zone = _vm->_location.findZone(cmd->_zoneName.c_str());
cmd->_valid = cmd->_zone != nullptr;
}
if (!cmd->_valid) {
continue;
}
if (cmd->_flagsOn & kFlagsGlobal) {
useFlags = g_globalFlags | kFlagsGlobal;
useLocalFlags = false;
} else {
useFlags = _vm->getLocationFlags();
useLocalFlags = true;
}
bool onMatch = (cmd->_flagsOn & useFlags) == cmd->_flagsOn;
bool offMatch = (cmd->_flagsOff & ~useFlags) == cmd->_flagsOff;
debugC(3, kDebugExec, "runCommands[%i] (on: %x, off: %x), (%s = %x)", cmd->_id, cmd->_flagsOn, cmd->_flagsOff,
useLocalFlags ? "LOCALFLAGS" : "GLOBALFLAGS", useFlags);
if (!onMatch || !offMatch) continue;
_ctxt._z = _execZone;
_ctxt._cmd = cmd;
(*_opcodes[cmd->_id])(_ctxt);
if (_suspend) {
createSuspendList(++first, last);
return;
}
}
_running = false;
}
void CommandExec::run(CommandList& list, ZonePtr z) {
if (list.size() == 0) {
debugC(3, kDebugExec, "runCommands: nothing to do");
return;
}
_execZone = z;
debugC(3, kDebugExec, "runCommands starting");
runList(list.begin(), list.end());
debugC(3, kDebugExec, "runCommands completed");
}
void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) {
if (first == last) {
return;
}
debugC(3, kDebugExec, "CommandExec::createSuspendList()");
_suspendedCtxt._valid = true;
_suspendedCtxt._first = first;
_suspendedCtxt._last = last;
_suspendedCtxt._zone = _execZone;
}
void CommandExec::cleanSuspendedList() {
debugC(3, kDebugExec, "CommandExec::cleanSuspended()");
_suspendedCtxt._valid = false;
_suspendedCtxt._first = _suspendedCtxt._last;
_suspendedCtxt._zone.reset();
}
void CommandExec::suspend() {
if (!_running)
return;
_suspend = true;
}
void CommandExec::runSuspended() {
if (g_engineFlags & kEngineWalking) {
return;
}
if (_suspendedCtxt._valid) {
debugC(3, kDebugExec, "CommandExec::runSuspended()");
_execZone = _suspendedCtxt._zone;
CommandList::iterator first = _suspendedCtxt._first;
CommandList::iterator last = _suspendedCtxt._last;
cleanSuspendedList();
runList(first, last);
}
}
CommandExec::CommandExec(Parallaction *vm) : _vm(vm), _suspend(false), _running(false) {
_suspendedCtxt._valid = false;
}
}

265
engines/parallaction/exec.h Normal file
View File

@@ -0,0 +1,265 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_EXEC_H
#define PARALLACTION_EXEC_H
#include "common/util.h"
#include "parallaction/objects.h"
namespace Parallaction {
class Parallaction_ns;
class Parallaction_br;
/* NOTE: CommandExec and ProgramExec perform similar tasks on different data.
CommandExec executes commands found in location scripts, while ProgramExec
runs animation programs.
The main difference is how suspension is handled. CommandExec is coded with
the assumption that there may be at most one suspended list of commands at any
moment, and thus stores the suspended context itself. It also offers a
runSuspended() routine that resumes execution on request.
ProgramExec instead stores the suspension information in the programs themselves.
Programs are in fact meant to be run (almost) regularly on each frame .
*/
struct CommandContext {
CommandPtr _cmd;
ZonePtr _z;
// TODO: add a way to invoke CommandExec::suspend() from the context. With that
// in place, opcodes dependency on CommandExec would be zero, and they could
// be moved into a Game object, together with the non-infrastructural code now
// in Parallaction_XX
};
typedef Common::Functor1<CommandContext&, void> CommandOpcode;
typedef Common::Array<const CommandOpcode *> CommandOpcodeSet;
#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op(CommandContext &)
struct ProgramContext {
AnimationPtr _anim;
ProgramPtr _program;
InstructionPtr _inst;
uint32 _ip;
uint16 _modCounter;
bool _suspend;
};
typedef Common::Functor1<ProgramContext&, void> ProgramOpcode;
typedef Common::Array<const ProgramOpcode *> ProgramOpcodeSet;
#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op(ProgramContext &)
template<class OpcodeSet>
class Exec {
protected:
OpcodeSet _opcodes;
typedef typename OpcodeSet::iterator OpIt;
public:
virtual ~Exec() {
for (OpIt i = _opcodes.begin(); i != _opcodes.end(); ++i)
delete *i;
_opcodes.clear();
}
};
class CommandExec : public Exec<CommandOpcodeSet> {
protected:
Parallaction *_vm;
CommandContext _ctxt;
ZonePtr _execZone;
bool _running;
bool _suspend;
struct SuspendedContext {
bool _valid;
CommandList::iterator _first;
CommandList::iterator _last;
ZonePtr _zone;
} _suspendedCtxt;
void runList(CommandList::iterator first, CommandList::iterator last);
void createSuspendList(CommandList::iterator first, CommandList::iterator last);
void cleanSuspendedList();
public:
CommandExec(Parallaction *vm);
void run(CommandList &list, ZonePtr z = ZonePtr());
void runSuspended();
void suspend();
};
class CommandExec_ns : public CommandExec {
protected:
Parallaction_ns *_vm;
DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(set);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(get);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
public:
CommandExec_ns(Parallaction_ns* vm);
};
class CommandExec_br : public CommandExec {
protected:
Parallaction_br *_vm;
DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(set);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(get);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(character);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(add);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(let);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(music);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(give);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(text);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(part);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave);
DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave);
public:
CommandExec_br(Parallaction_br* vm);
};
class ProgramExec : public Exec<ProgramOpcodeSet> {
protected:
ProgramContext _ctxt;
uint16 _modCounter;
const char **_instructionNames;
void runScript(ProgramPtr script, AnimationPtr a);
public:
void runScripts(ProgramList::iterator first, ProgramList::iterator last);
ProgramExec();
};
class ProgramExec_ns : public ProgramExec {
protected:
int _currentCredit;
Parallaction_ns *_vm;
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
public:
ProgramExec_ns(Parallaction_ns *vm);
};
class ProgramExec_br : public ProgramExec {
protected:
Parallaction_br *_vm;
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop);
public:
ProgramExec_br(Parallaction_br *vm);
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,694 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "common/textconsole.h"
namespace Parallaction {
#define INST_ON 1
#define INST_OFF 2
#define INST_X 3
#define INST_Y 4
#define INST_Z 5
#define INST_F 6
#define INST_LOOP 7
#define INST_ENDLOOP 8
#define INST_SHOW 9
#define INST_INC 10
#define INST_DEC 11
#define INST_SET 12
#define INST_PUT 13
#define INST_CALL 14
#define INST_WAIT 15
#define INST_START 16
#define INST_PROCESS 17
#define INST_MOVE 18
#define INST_COLOR 19
#define INST_SOUND 20
#define INST_MASK 21
#define INST_PRINT 22
#define INST_TEXT 23
#define INST_MUL 24
#define INST_DIV 25
#define INST_IFEQ 26
#define INST_IFLT 27
#define INST_IFGT 28
#define INST_ENDIF 29
#define INST_STOP 30
#define INST_ENDSCRIPT 31
#define SetOpcodeTable(x) table = &x;
typedef Common::Functor1Mem<CommandContext&, void, CommandExec_br> OpcodeV1;
#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_br::cmdOp_##op))
#define DECLARE_COMMAND_OPCODE(op) void CommandExec_br::cmdOp_##op(CommandContext &ctxt)
typedef Common::Functor1Mem<ProgramContext&, void, ProgramExec_br> OpcodeV2;
#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_br::instOp_##op))
#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_br::instOp_##op(ProgramContext &ctxt)
extern const char *_instructionNamesRes_br[];
void Parallaction_br::setupSubtitles(const char *s, const char *s2, int y) {
debugC(5, kDebugExec, "setupSubtitles(%s, %s, %i)", s, s2, y);
clearSubtitles();
if (!scumm_stricmp("clear", s)) {
return;
}
if (y != -1) {
_subtitleY = y;
}
// FIXME: render subtitles using the right color (10 instead of 0).
// The original game features a nasty hack, having the font rendering routine
// replacing color 12 of font RUSSIA with 10 when preparing subtitles.
uint8 color = (getPlatform() == Common::kPlatformAmiga) ? 11 : 0;
_subtitle[0] = _gfx->createLabel(_labelFont, s, color);
_gfx->showLabel(_subtitle[0], CENTER_LABEL_HORIZONTAL, _subtitleY);
if (s2) {
_subtitle[1] = _gfx->createLabel(_labelFont, s2, color);
_gfx->showLabel(_subtitle[1], CENTER_LABEL_HORIZONTAL, _subtitleY + 5 + _labelFont->height());
} else {
_subtitle[1] = nullptr;
}
#if 0 // disabled because no references to lip sync has been found in the scripts
_subtitleLipSync = 0;
#endif
}
void Parallaction_br::clearSubtitles() {
if (_subtitle[0]) {
_gfx->hideLabel(_subtitle[0]);
}
delete _subtitle[0];
_subtitle[0] = nullptr;
if (_subtitle[1]) {
_gfx->hideLabel(_subtitle[1]);
}
delete _subtitle[1];
_subtitle[1] = nullptr;
}
DECLARE_COMMAND_OPCODE(location) {
_vm->_location._startPosition = ctxt._cmd->_startPos;
_vm->_location._startFrame = 0;
_vm->_location._followerStartPosition = ctxt._cmd->_startPos2;
_vm->_location._followerStartFrame = 0;
_vm->scheduleLocationSwitch(ctxt._cmd->_string.c_str());
}
DECLARE_COMMAND_OPCODE(open) {
_vm->updateDoor(ctxt._cmd->_zone, false);
}
DECLARE_COMMAND_OPCODE(close) {
_vm->updateDoor(ctxt._cmd->_zone, true);
}
DECLARE_COMMAND_OPCODE(on) {
_vm->showZone(ctxt._cmd->_zone, true);
}
DECLARE_COMMAND_OPCODE(off) {
_vm->showZone(ctxt._cmd->_zone, false);
}
DECLARE_COMMAND_OPCODE(call) {
_vm->callFunction(ctxt._cmd->_callable, &ctxt._z);
}
DECLARE_COMMAND_OPCODE(drop) {
_vm->dropItem(ctxt._cmd->_object);
}
DECLARE_COMMAND_OPCODE(move) {
_vm->scheduleWalk(ctxt._cmd->_move.x, ctxt._cmd->_move.y, false);
suspend();
}
DECLARE_COMMAND_OPCODE(start) {
ctxt._cmd->_zone->_flags |= kFlagsActing;
}
DECLARE_COMMAND_OPCODE(stop) {
ctxt._cmd->_zone->_flags &= ~kFlagsActing;
}
DECLARE_COMMAND_OPCODE(character) {
debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", ctxt._cmd->_string.c_str());
_vm->changeCharacter(ctxt._cmd->_string.c_str());
}
DECLARE_COMMAND_OPCODE(followme) {
Common::String s(ctxt._cmd->_string);
if (!s.compareToIgnoreCase("NULL")) {
s.clear();
}
_vm->setFollower(s);
}
DECLARE_COMMAND_OPCODE(onmouse) {
_vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
DECLARE_COMMAND_OPCODE(offmouse) {
_vm->_input->setMouseState(MOUSE_DISABLED);
}
DECLARE_COMMAND_OPCODE(add) {
_vm->addInventoryItem(ctxt._cmd->_object);
}
DECLARE_COMMAND_OPCODE(leave) {
ZonePtr z = ctxt._cmd->_zone;
_vm->dropItem(z->u._getIcon);
_vm->showZone(z, true);
}
DECLARE_COMMAND_OPCODE(inc) {
int v = _vm->getCounterValue(ctxt._cmd->_counterName);
_vm->setCounterValue(ctxt._cmd->_counterName, v + ctxt._cmd->_counterValue);
}
DECLARE_COMMAND_OPCODE(dec) {
int v = _vm->getCounterValue(ctxt._cmd->_counterName);
_vm->setCounterValue(ctxt._cmd->_counterName, v - ctxt._cmd->_counterValue);
}
// these definitions must match those in parser_br.cpp
#define CMD_TEST 25
#define CMD_TEST_GT 26
#define CMD_TEST_LT 27
DECLARE_COMMAND_OPCODE(ifeq) {
_vm->testCounterCondition(ctxt._cmd->_counterName, CMD_TEST, ctxt._cmd->_counterValue);
}
DECLARE_COMMAND_OPCODE(iflt) {
_vm->testCounterCondition(ctxt._cmd->_counterName, CMD_TEST_LT, ctxt._cmd->_counterValue);
}
DECLARE_COMMAND_OPCODE(ifgt) {
_vm->testCounterCondition(ctxt._cmd->_counterName, CMD_TEST_GT, ctxt._cmd->_counterValue);
}
DECLARE_COMMAND_OPCODE(let) {
_vm->setCounterValue(ctxt._cmd->_counterName, ctxt._cmd->_counterValue);
}
DECLARE_COMMAND_OPCODE(music) {
warning("Parallaction_br::cmdOp_music not yet implemented");
}
DECLARE_COMMAND_OPCODE(fix) {
ctxt._cmd->_zone->_flags |= kFlagsFixed;
}
DECLARE_COMMAND_OPCODE(unfix) {
ctxt._cmd->_zone->_flags &= ~kFlagsFixed;
}
DECLARE_COMMAND_OPCODE(zeta) {
_vm->_location._zeta0 = ctxt._cmd->_zeta0;
_vm->_location._zeta1 = ctxt._cmd->_zeta1;
_vm->_location._zeta2 = ctxt._cmd->_zeta2;
}
DECLARE_COMMAND_OPCODE(scroll) {
Common::Point p;
_vm->_gfx->getScrollPos(p);
_vm->_gfx->initiateScroll(ctxt._cmd->_counterValue - p.x, 0);
}
DECLARE_COMMAND_OPCODE(swap) {
warning("Parallaction_br::cmdOp_swap does not handle a follower yet");
/*
TODO:
- fixup follower
- change mouse pointer
*/
const char *newCharacterName = ctxt._cmd->_string.c_str();
AnimationPtr newCharacterAnimation = _vm->_location.findAnimation(newCharacterName);
AnimationPtr oldCharaterAnimation = _vm->_char._ani;
Common::strlcpy(oldCharaterAnimation->_name, _vm->_char.getName(), ZONENAME_LENGTH);
_vm->_char.setName(newCharacterName);
_vm->_char._ani = newCharacterAnimation;
_vm->_char._talk = _vm->_disk->loadTalk(newCharacterName);
Common::strlcpy(_vm->_char._ani->_name, "yourself", ZONENAME_LENGTH);
_vm->linkUnlinkedZoneAnimations();
_vm->_inventory = _vm->findInventory(newCharacterName);
_vm->_inventoryRenderer->setInventory(_vm->_inventory);
_vm->_input->setCharacterPointer(newCharacterName);
}
DECLARE_COMMAND_OPCODE(give) {
int item = ctxt._cmd->_object;
Inventory *targetInventory = _vm->findInventory(ctxt._cmd->_characterName.c_str());
if (targetInventory) {
targetInventory->addItem(item);
}
_vm->_inventory->removeItem(item);
}
DECLARE_COMMAND_OPCODE(text) {
_vm->setupSubtitles(ctxt._cmd->_string.c_str(), ctxt._cmd->_string2.c_str(), ctxt._cmd->_zeta0);
}
DECLARE_COMMAND_OPCODE(part) {
_vm->_nextPart = ctxt._cmd->_counterValue;
}
DECLARE_COMMAND_OPCODE(testsfx) {
warning("Parallaction_br::cmdOp_testsfx not completely implemented");
_vm->clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled
}
DECLARE_COMMAND_OPCODE(ret) {
g_engineFlags |= kEngineReturn;
}
DECLARE_COMMAND_OPCODE(onsave) {
warning("Parallaction_br::cmdOp_onsave not yet implemented");
}
DECLARE_COMMAND_OPCODE(offsave) {
warning("Parallaction_br::cmdOp_offsave not yet implemented");
}
DECLARE_INSTRUCTION_OPCODE(invalid) {
error("Can't execute invalid opcode %i", ctxt._inst->_index);
}
DECLARE_COMMAND_OPCODE(clear) {
if (ctxt._cmd->_flags & kFlagsGlobal) {
ctxt._cmd->_flags &= ~kFlagsGlobal;
g_globalFlags &= ~ctxt._cmd->_flags;
} else {
_vm->clearLocationFlags(ctxt._cmd->_flags);
}
}
DECLARE_COMMAND_OPCODE(speak) {
// WORKAROUND: this avoids crashing when the zone is not parsed, like in the case
// of script bug in ticket #4251.
if (!ctxt._cmd->_zone) {
return;
}
if (ACTIONTYPE(ctxt._cmd->_zone) == kZoneSpeak && ctxt._cmd->_zone->u._speakDialogue) {
_vm->enterDialogueMode(ctxt._cmd->_zone);
} else {
_vm->_activeZone = ctxt._cmd->_zone;
}
}
DECLARE_COMMAND_OPCODE(get) {
ctxt._cmd->_zone->_flags &= ~kFlagsFixed;
_vm->runZone(ctxt._cmd->_zone);
}
DECLARE_COMMAND_OPCODE(toggle) {
if (ctxt._cmd->_flags & kFlagsGlobal) {
ctxt._cmd->_flags &= ~kFlagsGlobal;
g_globalFlags ^= ctxt._cmd->_flags;
} else {
_vm->toggleLocationFlags(ctxt._cmd->_flags);
}
}
DECLARE_COMMAND_OPCODE(quit) {
_vm->quitGame();
}
DECLARE_COMMAND_OPCODE(invalid) {
error("Can't execute invalid command '%i'", ctxt._cmd->_id);
}
DECLARE_COMMAND_OPCODE(set) {
if (ctxt._cmd->_flags & kFlagsGlobal) {
ctxt._cmd->_flags &= ~kFlagsGlobal;
g_globalFlags |= ctxt._cmd->_flags;
} else {
_vm->setLocationFlags(ctxt._cmd->_flags);
}
}
DECLARE_INSTRUCTION_OPCODE(on) {
_vm->showZone(ctxt._inst->_z, true);
}
DECLARE_INSTRUCTION_OPCODE(off) {
_vm->showZone(ctxt._inst->_z, false);
}
DECLARE_INSTRUCTION_OPCODE(set) {
ctxt._inst->_opA.setValue(ctxt._inst->_opB.getValue());
}
DECLARE_INSTRUCTION_OPCODE(inc) {
InstructionPtr inst = ctxt._inst;
int16 rvalue = inst->_opB.getValue();
if (inst->_flags & kInstMod) { // mod
int16 _bx = (rvalue > 0 ? rvalue : -rvalue);
if (ctxt._modCounter % _bx != 0) return;
rvalue = (rvalue > 0 ? 1 : -1);
}
int16 lvalue = inst->_opA.getValue();
switch (inst->_index) {
case INST_INC:
lvalue += rvalue;
break;
case INST_DEC:
lvalue -= rvalue;
break;
case INST_MUL:
lvalue *= rvalue;
break;
case INST_DIV:
lvalue /= rvalue;
break;
default:
error("This should never happen. Report immediately");
}
inst->_opA.setValue(lvalue);
}
DECLARE_INSTRUCTION_OPCODE(put) {
// NOTE: there is not a single occurrence of PUT in the scripts
warning("PUT instruction is not implemented");
}
DECLARE_INSTRUCTION_OPCODE(wait) {
// NOTE: there is not a single occurrence of WAIT in the scripts
warning("WAIT instruction is not implemented");
}
DECLARE_INSTRUCTION_OPCODE(start) {
ctxt._inst->_z->_flags |= kFlagsActing;
}
DECLARE_INSTRUCTION_OPCODE(process) {
_vm->_activeZone2 = ctxt._inst->_z;
}
DECLARE_INSTRUCTION_OPCODE(move) {
// NOTE: I couldn't find evidence of scripts containing this instruction being used
InstructionPtr inst = ctxt._inst;
_vm->scheduleWalk(inst->_opA.getValue(), inst->_opB.getValue(), false);
ctxt._suspend = true;
}
DECLARE_INSTRUCTION_OPCODE(color) {
InstructionPtr inst = ctxt._inst;
_vm->_gfx->_palette.setEntry(inst->_opB.getValue(), inst->_colors[0], inst->_colors[1], inst->_colors[2]);
_vm->_gfx->setPalette(_vm->_gfx->_palette);
}
DECLARE_INSTRUCTION_OPCODE(mask) {
#if 0
Instruction *inst = *ctxt._inst;
_gfx->_bgLayers[0] = inst->_opA.getRValue();
_gfx->_bgLayers[1] = inst->_opB.getRValue();
_gfx->_bgLayers[2] = inst->_opC.getRValue();
#endif
}
DECLARE_INSTRUCTION_OPCODE(print) {
// NOTE: there is not a single occurrence of PRINT in the scripts
// I suppose it was used for debugging
warning("PRINT instruction is not implemented");
}
DECLARE_INSTRUCTION_OPCODE(text) {
InstructionPtr inst = ctxt._inst;
_vm->setupSubtitles(inst->_text.c_str(), inst->_text2.c_str(), inst->_y);
}
DECLARE_INSTRUCTION_OPCODE(ifeq) {
InstructionPtr inst = ctxt._inst;
bool cond = inst->_opA.getValue() == inst->_opB.getValue();
if (!cond) {
ctxt._ip = inst->_endif;
}
}
DECLARE_INSTRUCTION_OPCODE(iflt) {
InstructionPtr inst = ctxt._inst;
bool cond = inst->_opA.getValue() < inst->_opB.getValue();
if (!cond) {
ctxt._ip = inst->_endif;
}
}
DECLARE_INSTRUCTION_OPCODE(ifgt) {
InstructionPtr inst = ctxt._inst;
bool cond = inst->_opA.getValue() > inst->_opB.getValue();
if (!cond) {
ctxt._ip = inst->_endif;
}
}
DECLARE_INSTRUCTION_OPCODE(endif) {
// nothing to do here
}
DECLARE_INSTRUCTION_OPCODE(stop) {
ZonePtr z = ctxt._inst->_z;
// Prevent execution if zone is missing. The known case is "PART2/insegui.scr", which has
// "STOP insegui", which doesn't exist (see ticket #9193 for the gory details)
if (!z) return;
if (ACTIONTYPE(z) == kZoneHear) {
warning("Parallaction_br::instOp_stop not yet implemented for HEAR zones");
// TODO: stop music or sound effects generated by a zone.
} else {
z->_flags &= ~kFlagsActing;
}
}
DECLARE_INSTRUCTION_OPCODE(loop) {
InstructionPtr inst = ctxt._inst;
ctxt._program->_loopCounter = inst->_opB.getValue();
ctxt._program->_loopStart = ctxt._ip;
}
DECLARE_INSTRUCTION_OPCODE(endloop) {
if (--ctxt._program->_loopCounter > 0) {
ctxt._ip = ctxt._program->_loopStart;
}
}
DECLARE_INSTRUCTION_OPCODE(show) {
ctxt._suspend = true;
}
DECLARE_INSTRUCTION_OPCODE(call) {
_vm->callFunction(ctxt._inst->_immediate, nullptr);
}
DECLARE_INSTRUCTION_OPCODE(endscript) {
if ((ctxt._anim->_flags & kFlagsLooping) == 0) {
ctxt._anim->_flags &= ~kFlagsActing;
_vm->_cmdExec->run(ctxt._anim->_commands, ctxt._anim);
ctxt._program->_status = kProgramDone;
}
ctxt._ip = 0;
ctxt._suspend = true;
}
CommandExec_br::CommandExec_br(Parallaction_br* vm) : CommandExec(vm), _vm(vm) {
CommandOpcodeSet *table = nullptr;
SetOpcodeTable(_opcodes);
COMMAND_OPCODE(invalid);
COMMAND_OPCODE(set);
COMMAND_OPCODE(clear);
COMMAND_OPCODE(start);
COMMAND_OPCODE(speak);
COMMAND_OPCODE(get);
COMMAND_OPCODE(location);
COMMAND_OPCODE(open);
COMMAND_OPCODE(close);
COMMAND_OPCODE(on);
COMMAND_OPCODE(off);
COMMAND_OPCODE(call);
COMMAND_OPCODE(toggle);
COMMAND_OPCODE(drop);
COMMAND_OPCODE(quit);
COMMAND_OPCODE(move);
COMMAND_OPCODE(stop);
COMMAND_OPCODE(character);
COMMAND_OPCODE(followme);
COMMAND_OPCODE(onmouse);
COMMAND_OPCODE(offmouse);
COMMAND_OPCODE(add);
COMMAND_OPCODE(leave);
COMMAND_OPCODE(inc);
COMMAND_OPCODE(dec);
COMMAND_OPCODE(ifeq);
COMMAND_OPCODE(iflt);
COMMAND_OPCODE(ifgt);
COMMAND_OPCODE(let);
COMMAND_OPCODE(music);
COMMAND_OPCODE(fix);
COMMAND_OPCODE(unfix);
COMMAND_OPCODE(zeta);
COMMAND_OPCODE(scroll);
COMMAND_OPCODE(swap);
COMMAND_OPCODE(give);
COMMAND_OPCODE(text);
COMMAND_OPCODE(part);
COMMAND_OPCODE(testsfx);
COMMAND_OPCODE(ret);
COMMAND_OPCODE(onsave);
COMMAND_OPCODE(offsave);
}
ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : _vm(vm) {
_instructionNames = _instructionNamesRes_br;
ProgramOpcodeSet *table = nullptr;
SetOpcodeTable(_opcodes);
INSTRUCTION_OPCODE(invalid);
INSTRUCTION_OPCODE(on);
INSTRUCTION_OPCODE(off);
INSTRUCTION_OPCODE(set); // x
INSTRUCTION_OPCODE(set); // y
INSTRUCTION_OPCODE(set); // z
INSTRUCTION_OPCODE(set); // f
INSTRUCTION_OPCODE(loop);
INSTRUCTION_OPCODE(endloop);
INSTRUCTION_OPCODE(show); // show
INSTRUCTION_OPCODE(inc);
INSTRUCTION_OPCODE(inc); // dec
INSTRUCTION_OPCODE(set);
INSTRUCTION_OPCODE(put);
INSTRUCTION_OPCODE(call);
INSTRUCTION_OPCODE(wait);
INSTRUCTION_OPCODE(start);
INSTRUCTION_OPCODE(process);
INSTRUCTION_OPCODE(move);
INSTRUCTION_OPCODE(color);
INSTRUCTION_OPCODE(process); // sound
INSTRUCTION_OPCODE(mask);
INSTRUCTION_OPCODE(print);
INSTRUCTION_OPCODE(text);
INSTRUCTION_OPCODE(inc); // mul
INSTRUCTION_OPCODE(inc); // div
INSTRUCTION_OPCODE(ifeq);
INSTRUCTION_OPCODE(iflt);
INSTRUCTION_OPCODE(ifgt);
INSTRUCTION_OPCODE(endif);
INSTRUCTION_OPCODE(stop);
INSTRUCTION_OPCODE(endscript);
}
} // namespace Parallaction

View File

@@ -0,0 +1,589 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
namespace Parallaction {
#ifdef USE_TTS
// Transcribed for TTS; only English is translated in-game
static const char *openingCreditsSecondLine[] = {
"Grafica Totale: Max M.", // Italian
"Graphiques Totaux: Max M.", // French
"Total Graphics: Max M.", // English
"Gesamtgrafik: Max M." // German
};
static const char *openingCreditsThirdLine[] = {
"Design del Gioco: Mr. Tzutzumi", // Italian
"Conception de Jeux: Mr. Tzutzumi", // French
"Game Design: Mr. Tzutzumi", // English
"Spieldesign: Mr. Tzutzumi" // German
};
// Transcribed for TTS; only Italian is translated in-game
static const char *endCreditsItalian[] = {
"La Cassiera",
"La Segretaria",
"Il Taxista",
"Il Giornalaio",
"Passante",
"Il Segretario",
"L'Uccello",
"L'Inserviente",
"Il Priore",
"Il Suicida",
"Il Direttore",
"Passante",
"Fratello Shinpui",
"Apollo il Gorilla",
"Il Portiere",
"Il Guru",
"Fratello Baka",
"Josko il Barman",
"Figaro L' Estetista",
"Il Guardiano del Museo",
"Passante",
"Il Losco Max",
"Mister Y",
"Il Punk",
"Passante",
"Il Signor Bemutsu",
"L' Annunciatore",
"La Punkina",
"L' Imperatore",
"Il Dottor Ki",
"Il Cuoco",
"Il Punk",
"Passante",
"Il Losco Figuro",
"Chan L'Onesto",
"",
"La Geisha",
"Kos 'O Professore",
"Il Giocatore di Pacinko"
};
static const char *endCreditsFrench[] = {
"La Cassi\350re",
"La Secr\351taire",
"Le Chaffeur de Taxi",
"Le Marchand de Journaux",
"La Pi\351tonne",
"Le Secr\351taire",
"La Corneille",
"Le Oshiya",
"Le Prieur",
"Le Candidat au Suicide",
"Le Directeur",
"La Pi\351tonne",
"Fr\350re Shinpui",
"Apollo le Garde du Corps",
"Le Concierge",
"Le Guru",
"Fr\350re Baka",
"Josko le Barman",
"Figaro L'esth\351ticien",
"Le Gardien du Mus\351e",
"La Pi\351tonne",
"Le Ombrag\351e Max",
"Monsieur Y",
"Le Punk",
"La Pi\351tonne",
"Monsieur Bemutsu",
"Le Annonceur",
"Le Punk",
"L'Empereur",
"Le Docteur Ki",
"Le Chef",
"Le Punk",
"Le Pi\351ton",
"Le Louche Personnage",
"Honest Chan",
"",
"La Geisha",
"Professeur Kos",
"Le Joueur de Pachinko"
};
static const char *endCreditsEnglish[] = {
"Cashier",
"Secretary",
"Taxi-driver",
"Newspaper Seller",
"Pedestrian",
"Secretary",
"Grackle",
"Oshiya",
"Prior",
"Suicidal Man",
"Governor",
"Pedestrian",
"Brother Shinpui",
"Apollo the Bodyguard",
"Door-keeper",
"Guru",
"Brother Baka",
"Josko the Barman",
"Figaro the Beautician",
"Museum Custodian",
"Pedestrian",
"Sullen Max",
"Mister Y",
"Punk",
"Pedestrian",
"Mister Bemutsu",
"Announcer",
"Punk",
"Emperor",
"Doctor Ki",
"Cook",
"Punk",
"Pedestrian",
"Shady Type",
"Honest Chan",
"",
"Geisha",
"Professor Kos",
"Pachinko Player"
};
static const char *endCreditsGerman[] = {
"Die Kassiererin",
"Die Sekret\344rin",
"Der Taxifahrer",
"Der Zeitungsverk\344ufer",
"Die Passantin",
"Der Sekret\344r",
"Des Grakula",
"Der Oshiya",
"Der Prior",
"Der Selbstm\366rder",
"Der Direktor",
"Die Passantin",
"Bruder Shinpui",
"Apollo der Bodyguard",
"Der Portier",
"Der Guru",
"Bruder Baka",
"Josko der Barman",
"Figaro, der Kosmetiker",
"Der Museumsw\344rter",
"Die Passantin",
"Schattig Max",
"Herr Y",
"Der Punker",
"Die Passantin",
"Herr Bemutsu",
"Der Ansager",
"Der Punker",
"Der Kaiser",
"Doktor Ki",
"Der Koch",
"Der Punker",
"Der Passant",
"Der Dunkler Typ",
"Honest Chan",
"",
"Die Geisha",
"Professor Kos",
"Der Pachinko-Spieler"
};
static const int kNumberOfCredits = ARRAYSIZE(endCreditsItalian);
#endif
#define INST_ON 1
#define INST_OFF 2
#define INST_X 3
#define INST_Y 4
#define INST_Z 5
#define INST_F 6
#define INST_LOOP 7
#define INST_ENDLOOP 8
#define INST_SHOW 9
#define INST_INC 10
#define INST_DEC 11
#define INST_SET 12
#define INST_PUT 13
#define INST_CALL 14
#define INST_WAIT 15
#define INST_START 16
#define INST_SOUND 17
#define INST_MOVE 18
#define INST_ENDSCRIPT 19
#define SetOpcodeTable(x) table = &x;
typedef Common::Functor1Mem<CommandContext&, void, CommandExec_ns> OpcodeV1;
#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_ns::cmdOp_##op))
#define DECLARE_COMMAND_OPCODE(op) void CommandExec_ns::cmdOp_##op(CommandContext& ctxt)
typedef Common::Functor1Mem<ProgramContext&, void, ProgramExec_ns> OpcodeV2;
#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_ns::instOp_##op))
#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_ns::instOp_##op(ProgramContext& ctxt)
extern const char *_instructionNamesRes_ns[];
DECLARE_INSTRUCTION_OPCODE(on) {
InstructionPtr inst = ctxt._inst;
inst->_a->_flags |= kFlagsActive;
inst->_a->_flags &= ~kFlagsRemove;
#ifdef USE_TTS
if (scumm_stricmp(inst->_a->_name, "telo3") == 0) {
_vm->setTTSVoice(kNarratorVoiceID);
_vm->sayText(openingCreditsSecondLine[_vm->getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
} else if (scumm_stricmp(inst->_a->_name, "game") == 0) {
_vm->setTTSVoice(kNarratorVoiceID);
_vm->sayText(openingCreditsThirdLine[_vm->getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
}
#endif
}
DECLARE_INSTRUCTION_OPCODE(off) {
ctxt._inst->_a->_flags |= kFlagsRemove;
}
DECLARE_INSTRUCTION_OPCODE(loop) {
InstructionPtr inst = ctxt._inst;
ctxt._program->_loopCounter = inst->_opB.getValue();
ctxt._program->_loopStart = ctxt._ip;
}
DECLARE_INSTRUCTION_OPCODE(endloop) {
if (ctxt._program->_loopStart == 11 && scumm_stricmp(_vm->_location._name, "test") == 0) {
// Delay moving on from test results until TTS is done or the player clicks,
// so the TTS system can speak them fully
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
ctxt._program->_loopCounter = 2;
int event = _vm->_input->getLastButtonEvent();
if (event == kMouseLeftUp) {
ttsMan->stop();
ctxt._program->_loopCounter = 0;
}
}
}
if (--ctxt._program->_loopCounter > 0) {
ctxt._ip = ctxt._program->_loopStart;
}
}
DECLARE_INSTRUCTION_OPCODE(inc) {
InstructionPtr inst = ctxt._inst;
int16 _si = inst->_opB.getValue();
if (inst->_flags & kInstMod) { // mod
int16 _bx = (_si > 0 ? _si : -_si);
if (ctxt._modCounter % _bx != 0) return;
_si = (_si > 0 ? 1 : -1);
}
int16 lvalue = inst->_opA.getValue();
if (inst->_index == INST_INC) {
#ifdef USE_TTS
if (_vm->_endCredits && ctxt._anim->_name[0] == 's' && _currentCredit < kNumberOfCredits) {
const char **credits;
switch (_vm->getInternLanguage()) {
case kItalian:
credits = endCreditsItalian;
break;
case kFrench:
credits = endCreditsFrench;
break;
case kEnglish:
credits = endCreditsEnglish;
break;
case kGerman:
credits = endCreditsGerman;
break;
default:
credits = endCreditsItalian;
break;
}
_vm->sayText(credits[_currentCredit], Common::TextToSpeechManager::QUEUE);
_currentCredit++;
}
#endif
lvalue += _si;
} else {
lvalue -= _si;
}
inst->_opA.setValue(lvalue);
}
DECLARE_INSTRUCTION_OPCODE(set) {
ctxt._inst->_opA.setValue(ctxt._inst->_opB.getValue());
}
DECLARE_INSTRUCTION_OPCODE(put) {
InstructionPtr inst = ctxt._inst;
Common::Rect r;
inst->_a->getFrameRect(r);
Graphics::Surface v18;
v18.init(r.width(), r.height(), r.width(), inst->_a->getFrameData(), Graphics::PixelFormat::createFormatCLUT8());
int16 x = inst->_opA.getValue();
int16 y = inst->_opB.getValue();
bool mask = (inst->_flags & kInstMaskedPut) == kInstMaskedPut;
_vm->_gfx->patchBackground(v18, x, y, mask);
}
DECLARE_INSTRUCTION_OPCODE(show) {
ctxt._suspend = true;
}
DECLARE_INSTRUCTION_OPCODE(invalid) {
error("Can't execute invalid opcode %i", ctxt._inst->_index);
}
DECLARE_INSTRUCTION_OPCODE(call) {
_vm->callFunction(ctxt._inst->_immediate, nullptr);
}
DECLARE_INSTRUCTION_OPCODE(wait) {
if (g_engineFlags & kEngineWalking) {
ctxt._ip--;
ctxt._suspend = true;
}
}
DECLARE_INSTRUCTION_OPCODE(start) {
ctxt._inst->_a->_flags |= (kFlagsActing | kFlagsActive);
}
DECLARE_INSTRUCTION_OPCODE(sound) {
_vm->_activeZone = ctxt._inst->_z;
}
DECLARE_INSTRUCTION_OPCODE(move) {
InstructionPtr inst = ctxt._inst;
int16 x = inst->_opA.getValue();
int16 y = inst->_opB.getValue();
_vm->scheduleWalk(x, y, false);
}
DECLARE_INSTRUCTION_OPCODE(endscript) {
if ((ctxt._anim->_flags & kFlagsLooping) == 0) {
ctxt._anim->_flags &= ~kFlagsActing;
_vm->_cmdExec->run(ctxt._anim->_commands, ctxt._anim);
ctxt._program->_status = kProgramDone;
}
ctxt._ip = 0;
ctxt._suspend = true;
}
DECLARE_COMMAND_OPCODE(invalid) {
error("Can't execute invalid command '%i'", ctxt._cmd->_id);
}
DECLARE_COMMAND_OPCODE(set) {
if (ctxt._cmd->_flags & kFlagsGlobal) {
ctxt._cmd->_flags &= ~kFlagsGlobal;
g_globalFlags |= ctxt._cmd->_flags;
} else {
_vm->setLocationFlags(ctxt._cmd->_flags);
}
}
DECLARE_COMMAND_OPCODE(clear) {
if (ctxt._cmd->_flags & kFlagsGlobal) {
ctxt._cmd->_flags &= ~kFlagsGlobal;
g_globalFlags &= ~ctxt._cmd->_flags;
} else {
_vm->clearLocationFlags(ctxt._cmd->_flags);
}
}
DECLARE_COMMAND_OPCODE(start) {
ctxt._cmd->_zone->_flags |= kFlagsActing;
}
DECLARE_COMMAND_OPCODE(speak) {
if (ACTIONTYPE(ctxt._cmd->_zone) == kZoneSpeak) {
_vm->enterDialogueMode(ctxt._cmd->_zone);
} else {
_vm->_activeZone = ctxt._cmd->_zone;
}
}
DECLARE_COMMAND_OPCODE(get) {
ctxt._cmd->_zone->_flags &= ~kFlagsFixed;
_vm->runZone(ctxt._cmd->_zone);
}
DECLARE_COMMAND_OPCODE(location) {
_vm->scheduleLocationSwitch(ctxt._cmd->_string.c_str());
}
DECLARE_COMMAND_OPCODE(open) {
_vm->updateDoor(ctxt._cmd->_zone, false);
}
DECLARE_COMMAND_OPCODE(close) {
_vm->updateDoor(ctxt._cmd->_zone, true);
}
DECLARE_COMMAND_OPCODE(on) {
_vm->showZone(ctxt._cmd->_zone, true);
}
DECLARE_COMMAND_OPCODE(off) {
_vm->showZone(ctxt._cmd->_zone, false);
}
DECLARE_COMMAND_OPCODE(call) {
_vm->callFunction(ctxt._cmd->_callable, &ctxt._z);
}
DECLARE_COMMAND_OPCODE(toggle) {
if (ctxt._cmd->_flags & kFlagsGlobal) {
ctxt._cmd->_flags &= ~kFlagsGlobal;
g_globalFlags ^= ctxt._cmd->_flags;
} else {
_vm->toggleLocationFlags(ctxt._cmd->_flags);
}
}
DECLARE_COMMAND_OPCODE(drop){
_vm->dropItem( ctxt._cmd->_object );
}
DECLARE_COMMAND_OPCODE(quit) {
_vm->quitGame();
}
DECLARE_COMMAND_OPCODE(move) {
_vm->scheduleWalk(ctxt._cmd->_move.x, ctxt._cmd->_move.y, false);
}
DECLARE_COMMAND_OPCODE(stop) {
ctxt._cmd->_zone->_flags &= ~kFlagsActing;
}
CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : CommandExec(vm), _vm(vm) {
CommandOpcodeSet *table = nullptr;
SetOpcodeTable(_opcodes);
COMMAND_OPCODE(invalid);
COMMAND_OPCODE(set);
COMMAND_OPCODE(clear);
COMMAND_OPCODE(start);
COMMAND_OPCODE(speak);
COMMAND_OPCODE(get);
COMMAND_OPCODE(location);
COMMAND_OPCODE(open);
COMMAND_OPCODE(close);
COMMAND_OPCODE(on);
COMMAND_OPCODE(off);
COMMAND_OPCODE(call);
COMMAND_OPCODE(toggle);
COMMAND_OPCODE(drop);
COMMAND_OPCODE(quit);
COMMAND_OPCODE(move);
COMMAND_OPCODE(stop);
}
ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) {
_currentCredit = 0;
_instructionNames = _instructionNamesRes_ns;
ProgramOpcodeSet *table = nullptr;
SetOpcodeTable(_opcodes);
INSTRUCTION_OPCODE(invalid);
INSTRUCTION_OPCODE(on);
INSTRUCTION_OPCODE(off);
INSTRUCTION_OPCODE(set); // x
INSTRUCTION_OPCODE(set); // y
INSTRUCTION_OPCODE(set); // z
INSTRUCTION_OPCODE(set); // f
INSTRUCTION_OPCODE(loop);
INSTRUCTION_OPCODE(endloop);
INSTRUCTION_OPCODE(show);
INSTRUCTION_OPCODE(inc);
INSTRUCTION_OPCODE(inc); // dec
INSTRUCTION_OPCODE(set);
INSTRUCTION_OPCODE(put);
INSTRUCTION_OPCODE(call);
INSTRUCTION_OPCODE(wait);
INSTRUCTION_OPCODE(start);
INSTRUCTION_OPCODE(sound);
INSTRUCTION_OPCODE(move);
INSTRUCTION_OPCODE(endscript);
}
} // namespace Parallaction

View File

@@ -0,0 +1,544 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "graphics/fonts/amigafont.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
class BraFont : public Font {
protected:
byte *_cp;
uint _bufPitch;
uint32 _height;
byte _numGlyphs;
byte *_widths;
uint *_offsets;
byte *_data;
const byte *_charMap;
byte mapChar(byte c) {
return (_charMap == nullptr) ? c : _charMap[c];
}
public:
BraFont(Common::ReadStream &stream, const byte *charMap = nullptr) {
_charMap = charMap;
_numGlyphs = stream.readByte();
_height = stream.readUint32BE();
_widths = (byte *)malloc(_numGlyphs);
stream.read(_widths, _numGlyphs);
_offsets = (uint *)malloc(_numGlyphs * sizeof(uint));
_offsets[0] = 0;
for (uint i = 1; i < _numGlyphs; i++)
_offsets[i] = _offsets[i-1] + _widths[i-1] * _height;
uint size = _offsets[_numGlyphs-1] + _widths[_numGlyphs-1] * _height;
_data = (byte *)malloc(size);
stream.read(_data, size);
_cp = nullptr;
_bufPitch = 0;
}
~BraFont() override {
free(_widths);
free(_offsets);
free(_data);
}
uint32 getStringWidth(const char *s) override {
uint32 len = 0;
while (*s) {
byte c = mapChar(*s);
len += (_widths[c] + 2);
s++;
}
return len;
}
uint16 height() override {
return (uint16)_height;
}
uint16 drawChar(unsigned char c) {
assert(c < _numGlyphs);
byte *src = _data + _offsets[c];
byte *dst = _cp;
uint16 w = _widths[c];
for (uint16 j = 0; j < height(); j++) {
for (uint16 k = 0; k < w; k++) {
if (*src) {
*dst = (_color) ? _color : *src;
}
dst++;
src++;
}
dst += (_bufPitch - w);
}
return w + 2;
}
void drawString(Graphics::Surface *src, int x, int y, const char *s) override {
if (src == nullptr)
return;
_bufPitch = src->pitch;
_cp = (byte *)src->getBasePtr(x, y);
while (*s) {
byte c = mapChar(*s);
_cp += drawChar(c);
s++;
}
}
};
const byte _braDosFullCharMap[256] = {
// 0
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 1
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 2
0x34, 0x49, 0x48, 0x34, 0x34, 0x34, 0x34, 0x47, 0x34, 0x34, 0x34, 0x34, 0x40, 0x34, 0x3F, 0x34,
// 3
0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x46, 0x45, 0x34, 0x34, 0x34, 0x42,
// 4
0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
// 5
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
// 6
0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
// 7
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
// 8
0x5E, 0x5D, 0x4E, 0x4B, 0x4D, 0x4C, 0x34, 0x5E, 0x4F, 0x51, 0x50, 0x34, 0x34, 0x34, 0x34, 0x34,
// 9
0x34, 0x34, 0x34, 0x57, 0x59, 0x58, 0x5B, 0x5C, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// A
0x4A, 0x52, 0x34, 0x5A, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// B
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// C
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// D
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// E
0x34, 0x5F, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// F
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
};
const byte _braDosDemoComicCharMap[] = {
// 0
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 1
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 2
0x34, 0x49, 0x48, 0x34, 0x34, 0x34, 0x34, 0x47, 0x34, 0x34, 0x34, 0x34, 0x40, 0x34, 0x3F, 0x34,
// 3
0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x46, 0x45, 0x34, 0x34, 0x34, 0x42,
// 4
0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
// 5
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
// 6
0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
// 7
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
// 8
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 9
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// A
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// B
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// C
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// D
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// E
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// F
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
};
const byte _braDosDemoRussiaCharMap[] = {
// 0
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 1
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 2
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 3
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 4
0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
// 5
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
// 6
0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
// 7
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
// 8
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 9
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// A
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// B
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// C
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// D
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// E
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// F
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
};
class BraInventoryObjects : public BraFont, public Frames {
public:
BraInventoryObjects(Common::ReadStream &stream) : BraFont(stream) {
}
// Frames implementation
uint16 getNum() override {
return _numGlyphs;
}
byte* getData(uint16 index) override {
assert(index < _numGlyphs);
return _data + (_height * _widths[index]) * index;
}
void getRect(uint16 index, Common::Rect &r) override {
assert(index < _numGlyphs);
r.left = 0;
r.top = 0;
r.setWidth(_widths[index]);
r.setHeight(_height);
}
uint getRawSize(uint16 index) override {
assert(index < _numGlyphs);
return _widths[index] * _height;
}
uint getSize(uint16 index) override {
assert(index < _numGlyphs);
return _widths[index] * _height;
}
};
class DosFont : public Font {
protected:
// drawing properties
byte *_cp;
Cnv *_data;
byte _pitch;
uint32 _bufPitch;
protected:
virtual uint16 drawChar(char c) = 0;
virtual uint16 width(byte c) = 0;
uint16 height() override = 0;
byte mapChar(byte c) {
if (c == 0xA5) return 0x5F;
if (c == 0xDF) return 0x60;
if (c > 0x7F) return c - 0x7F;
return c - 0x20;
}
public:
DosFont(Cnv *cnv) : _data(cnv), _pitch(cnv->_width), _cp(nullptr), _bufPitch(0) {
}
~DosFont() override {
delete _data;
}
void setData() {
}
uint32 getStringWidth(const char *s) override {
uint32 len = 0;
while (*s) {
byte c = mapChar(*s);
len += width(c);
s++;
}
return len;
}
void drawString(Graphics::Surface *src, int x, int y, const char *s) override {
if (src == nullptr)
return;
_bufPitch = src->pitch;
_cp = (byte *)src->getBasePtr(x, y);
while (*s) {
byte c = mapChar(*s);
_cp += drawChar(c);
s++;
}
}
};
class DosDialogueFont : public DosFont {
private:
static const byte _glyphWidths[126];
protected:
uint16 width(byte c) override {
return _glyphWidths[c];
}
uint16 height() override {
return _data->_height;
}
public:
DosDialogueFont(Cnv *cnv) : DosFont(cnv) {
}
protected:
uint16 drawChar(char c) override {
byte *src = _data->getFramePtr(c);
byte *dst = _cp;
uint16 w = width(c);
for (uint16 j = 0; j < height(); j++) {
for (uint16 k = 0; k < w; k++) {
if (!*src)
*dst = _color;
dst++;
src++;
}
src += (_pitch - w);
dst += (_bufPitch - w);
}
return w;
}
};
const byte DosDialogueFont::_glyphWidths[126] = {
0x04, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, 0x04, 0x06, 0x06, 0x03, 0x05, 0x03, 0x05,
0x06, 0x06, 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x03, 0x03, 0x05, 0x04, 0x05, 0x05,
0x03, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x07, 0x07, 0x07, 0x05, 0x06, 0x05, 0x08, 0x07,
0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x03, 0x04, 0x05, 0x05, 0x06, 0x06, 0x05,
0x05, 0x06, 0x05, 0x05, 0x05, 0x05, 0x06, 0x07, 0x05, 0x05, 0x05, 0x05, 0x02, 0x05, 0x05, 0x07,
0x08, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04,
0x05, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x06, 0x05, 0x05, 0x05, 0x05
};
class DosMonospacedFont : public DosFont {
protected:
uint16 _width;
protected:
uint16 width(byte c) override {
return _width;
}
uint16 height() override {
return _data->_height;
}
uint16 drawChar(char c) override {
byte *src = _data->getFramePtr(c);
byte *dst = _cp;
for (uint16 i = 0; i < height(); i++) {
for (uint16 j = 0; j < _width; j++) {
if (*src)
*dst = *src;
src++;
dst++;
}
dst += (_bufPitch - _width);
src += (_pitch - _width);
}
return _width;
}
public:
DosMonospacedFont(Cnv *cnv) : DosFont(cnv) {
_width = 8;
}
};
class AmigaFont : public Font {
Graphics::AmigaFont *_font;
public:
AmigaFont(Common::SeekableReadStream *stream = nullptr) {
_font = new Graphics::AmigaFont(stream);
}
~AmigaFont() override {
delete _font;
}
uint32 getStringWidth(const char *s) override {
return _font->getStringWidth(s);
}
void drawString(Graphics::Surface *src, int x, int y, const char *s) override {
_font->drawString(src, s, x, y, src->w, _color);
}
protected:
uint16 height() override {
return _font->getFontHeight();
}
};
Font *DosDisk_ns::createFont(const char *name, Cnv* cnv) {
Font *f = nullptr;
if (!scumm_stricmp(name, "comic"))
f = new DosDialogueFont(cnv);
else
if (!scumm_stricmp(name, "topaz"))
f = new DosMonospacedFont(cnv);
else
if (!scumm_stricmp(name, "slide"))
f = new DosMonospacedFont(cnv);
else
error("unknown dos font '%s'", name);
return f;
}
Font *AmigaDisk_ns::createFont(const char *name, Common::SeekableReadStream &stream) {
// TODO: implement AmigaLabelFont for labels
return new AmigaFont(&stream);
}
Font *DosDisk_br::createFont(const char *name, Common::ReadStream &stream) {
// debug("DosDisk_br::createFont(%s)", name);
Font *font;
if (_vm->getFeatures() & GF_DEMO) {
if (!scumm_stricmp(name, "russia")) {
font = new BraFont(stream, _braDosDemoRussiaCharMap);
} else {
font = new BraFont(stream, _braDosDemoComicCharMap);
}
} else {
font = new BraFont(stream, _braDosFullCharMap);
}
return font;
}
Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &stream) {
// TODO: implement AmigaLabelFont for labels
return new AmigaFont(&stream);
}
GfxObj* DosDisk_br::createInventoryObjects(Common::SeekableReadStream &stream) {
Frames *frames = new BraInventoryObjects(stream);
return new GfxObj(0, frames, "inventoryobjects");
}
void Parallaction_ns::initFonts() {
if (getPlatform() == Common::kPlatformDOS) {
_dialogueFont = _disk->loadFont("comic");
_labelFont = _disk->loadFont("topaz");
_menuFont = _disk->loadFont("slide");
_introFont = _disk->loadFont("slide");
} else {
_dialogueFont = _disk->loadFont("comic");
_labelFont = new AmigaFont();
_menuFont = _disk->loadFont("slide");
_introFont = _disk->loadFont("intro");
}
}
void Parallaction_br::initFonts() {
if (getPlatform() == Common::kPlatformDOS) {
_menuFont = _disk->loadFont("russia");
_dialogueFont = _disk->loadFont("comic");
_labelFont = _menuFont;
} else {
// TODO: Where is vanya used?
// fonts/vanya/16
_menuFont = _disk->loadFont("sonya");
_dialogueFont = _disk->loadFont("natasha");
_labelFont = _menuFont;
}
}
} // End of namespace Parallaction

View File

@@ -0,0 +1,444 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "graphics.h"
#include "disk.h"
#include "common/algorithm.h"
#include "common/textconsole.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
GfxObj::GfxObj(uint objType, Frames *frames, const char* name) :
_name(name), _frames(frames), x(0), y(0), z(0), _prog(0), _flags(0),
type(objType), frame(0), layer(3), scale(100), _hasMask(false), _hasPath(false),
transparentKey(0), _maskId(0), _pathId(0) {}
GfxObj::~GfxObj() {
delete _frames;
}
void GfxObj::release() {
// _keep = false;
delete this;
}
const char *GfxObj::getName() const {
return _name.c_str();
}
uint GfxObj::getNum() {
return _frames->getNum();
}
void GfxObj::getRect(uint f, Common::Rect &r) {
_frames->getRect(f, r);
}
byte *GfxObj::getData(uint f) {
return _frames->getData(f);
}
uint GfxObj::getRawSize(uint f) {
return _frames->getRawSize(f);
}
uint GfxObj::getSize(uint f) {
return _frames->getSize(f);
}
void GfxObj::setFlags(uint32 flags) {
_flags |= flags;
}
void GfxObj::clearFlags(uint32 flags) {
_flags &= ~flags;
}
void Gfx::addObjectToScene(GfxObj *obj) {
if (!obj) {
return;
}
if (!obj->isVisible()) {
return;
}
if (SCENE_DRAWLIST_SIZE == _sceneObjects.size()) {
warning("number of objects in the current scene is larger than the fixed drawlist size");
}
_sceneObjects.push_back(obj);
}
void Gfx::resetSceneDrawList() {
_sceneObjects.clear();
_sceneObjects.reserve(SCENE_DRAWLIST_SIZE);
}
GfxObj* Gfx::loadAnim(const char *name) {
debugC(1, kDebugGraphics, "Gfx::loadAnim(\"%s\")", name);
Frames* frames = _disk->loadFrames(name);
assert(frames);
GfxObj *obj = new GfxObj(kGfxObjTypeAnim, frames, name);
assert(obj);
// animation Z is not set here, but controlled by game scripts and user interaction.
// it is always >=0 and <screen height
obj->transparentKey = 0;
return obj;
}
GfxObj* Gfx::loadCharacterAnim(const char *name) {
return loadAnim(name);
}
GfxObj* Gfx::loadGet(const char *name) {
GfxObj *obj = _disk->loadStatic(name);
assert(obj);
obj->z = kGfxObjGetZ; // this preset Z value ensures that get zones are drawn after doors but before animations
obj->type = kGfxObjTypeGet;
obj->transparentKey = 0;
return obj;
}
GfxObj* Gfx::loadDoor(const char *name) {
Frames *frames = _disk->loadFrames(name);
assert(frames);
GfxObj *obj = new GfxObj(kGfxObjTypeDoor, frames, name);
assert(obj);
obj->z = kGfxObjDoorZ; // this preset Z value ensures that doors are drawn first
obj->transparentKey = 0;
return obj;
}
void Gfx::freeLocationObjects() {
freeDialogueObjects();
freeLabels();
}
void Gfx::freeCharacterObjects() {
freeDialogueObjects();
}
void BackgroundInfo::loadGfxObjMask(Parallaction *vm, const char *name, GfxObj *obj) {
debugC(1, kDebugGraphics, "BackgroundInfo::loadGfxObjMask(\"%s\")", name);
Common::Rect rect;
obj->getRect(0, rect);
MaskBuffer *buf = vm->_disk->loadMask(name, rect.width(), rect.height());
obj->_maskId = addMaskPatch(buf);
obj->_hasMask = true;
}
void BackgroundInfo::loadGfxObjPath(Parallaction *vm, const char *name, GfxObj *obj) {
Common::Rect rect;
obj->getRect(0, rect);
PathBuffer *buf = vm->_disk->loadPath(name, rect.width(), rect.height());
obj->_pathId = addPathPatch(buf);
obj->_hasPath = true;
}
void Gfx::showGfxObj(GfxObj* obj, bool visible) {
if (!obj) {
return;
}
debugC(1, kDebugGraphics, "Gfx::showGfxObj(\"%s\", visible:%d)", obj->getName(), visible ? 1 : 0);
if (visible) {
obj->setFlags(kGfxObjVisible);
} else {
obj->clearFlags(kGfxObjVisible);
}
if (obj->_hasMask) {
debugC(1, kDebugGraphics, "\tHas Mask");
_backgroundInfo->toggleMaskPatch(obj->_maskId, obj->x, obj->y, visible);
}
if (obj->_hasPath) {
debugC(1, kDebugGraphics, "\tHas Path");
_backgroundInfo->togglePathPatch(obj->_pathId, obj->x, obj->y, visible);
}
}
bool compareZ(const GfxObj* a1, const GfxObj* a2) {
return (a1->z == a2->z) ? (a1->_prog < a2->_prog) : (a1->z < a2->z);
}
void Gfx::sortScene() {
debugC(3, kDebugGraphics, "Gfx::sortScene()");
GfxObjArray::iterator first = _sceneObjects.begin();
GfxObjArray::iterator last = _sceneObjects.end();
Common::sort(first, last, compareZ);
}
void Gfx::drawGfxObject(GfxObj *obj, Graphics::Surface &surf) {
if (!obj->isVisible()) {
return;
}
Common::Rect rect;
byte *data;
obj->getRect(obj->frame, rect);
int x = obj->x;
int y = obj->y;
if (_overlayMode) {
x += _scrollPosX;
y += _scrollPosY;
}
rect.translate(x, y);
data = obj->getData(obj->frame);
// WORKAROUND: During the end credits, game scripts try to show a
// non-existing frame. We change it to an existing one here.
if (obj->frame == 14 && obj->getNum() == 9 && !strcmp(obj->getName(), "Dinor"))
obj->frame = 8;
if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) {
blt(rect, data, &surf, obj->layer, obj->scale, obj->transparentKey);
} else {
unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, obj->scale, obj->transparentKey);
}
}
void Gfx::drawText(Font *font, Graphics::Surface *surf, uint16 x, uint16 y, const char *text, byte color) {
font->setColor(color);
font->drawString(surf, x, y, text);
}
void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, uint scale, byte transparentColor) {
byte *d = _unpackedBitmap;
uint pixelsLeftInLine = r.width();
while (size > 0) {
uint8 p = *data++;
size--;
uint8 color = p & 0xF;
uint8 repeat = (p & 0xF0) >> 4;
if (repeat == 0) {
repeat = *data++;
size--;
}
if (repeat == 0) {
// end of line
repeat = pixelsLeftInLine;
pixelsLeftInLine = r.width();
} else {
pixelsLeftInLine -= repeat;
}
memset(d, color, repeat);
d += repeat;
}
blt(r, _unpackedBitmap, surf, z, scale, transparentColor);
}
void Gfx::bltMaskScale(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, uint scale, byte transparentColor) {
if (scale == 100) {
// use optimized path
bltMaskNoScale(r, data, surf, z, transparentColor);
return;
}
// unscaled rectangle size
uint width = r.width();
uint height = r.height();
// scaled rectangle size
uint scaledWidth = r.width() * scale / 100;
uint scaledHeight = r.height() * scale / 100;
// scaled rectangle origin
uint scaledLeft = r.left + (width - scaledWidth) / 2;
uint scaledTop = r.top + (height - scaledHeight);
// clipped scaled destination rectangle
Common::Rect dstRect(scaledWidth, scaledHeight);
dstRect.moveTo(scaledLeft, scaledTop);
Common::Rect clipper(surf->w, surf->h);
dstRect.clip(clipper);
if (!dstRect.isValidRect()) return;
// clipped source rectangle
Common::Rect srcRect;
srcRect.left = (dstRect.left - scaledLeft) * 100 / scale;
srcRect.top = (dstRect.top - scaledTop) * 100 / scale;
srcRect.setWidth(dstRect.width() * 100 / scale);
srcRect.setHeight(dstRect.height() * 100 / scale);
if (!srcRect.isValidRect()) return;
Common::Point dp;
dp.x = dstRect.left;
dp.y = dstRect.top;
byte *s = data + srcRect.left + srcRect.top * width;
byte *d = (byte *)surf->getBasePtr(dp.x, dp.y);
uint line = 0, col = 0;
uint xAccum = 0, yAccum = 0;
uint inc = width * (100 - scale);
uint thr = width * 100;
for (uint16 i = 0; i < srcRect.height(); i++) {
yAccum += inc;
if (yAccum >= thr) {
yAccum -= thr;
s += width;
continue;
}
xAccum = 0;
byte *d2 = d;
col = 0;
for (uint16 j = 0; j < srcRect.width(); j++) {
xAccum += inc;
if (xAccum >= thr) {
xAccum -= thr;
s++;
continue;
}
if (*s != transparentColor) {
if (_backgroundInfo->hasMask()) {
byte v = _backgroundInfo->_mask->getValue(dp.x + col, dp.y + line);
if (z >= v) *d2 = *s;
} else {
*d2 = *s;
}
}
s++;
d2++;
col++;
}
s += width - srcRect.width();
d += surf->w;
line++;
}
}
void Gfx::bltMaskNoScale(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) {
if (!_backgroundInfo->hasMask() || (z == LAYER_FOREGROUND)) {
// use optimized path
bltNoMaskNoScale(r, data, surf, transparentColor);
return;
}
Common::Point dp;
Common::Rect q(r);
Common::Rect clipper(surf->w, surf->h);
q.clip(clipper);
if (!q.isValidRect()) return;
dp.x = q.left;
dp.y = q.top;
q.translate(-r.left, -r.top);
byte *s = data + q.left + q.top * r.width();
byte *d = (byte *)surf->getBasePtr(dp.x, dp.y);
uint sPitch = r.width() - q.width();
uint dPitch = surf->w - q.width();
for (uint16 i = 0; i < q.height(); i++) {
for (uint16 j = 0; j < q.width(); j++) {
if (*s != transparentColor) {
if (_backgroundInfo->hasMask()) {
byte v = _backgroundInfo->_mask->getValue(dp.x + j, dp.y + i);
if (z >= v) *d = *s;
} else {
*d = *s;
}
}
s++;
d++;
}
s += sPitch;
d += dPitch;
}
}
void Gfx::bltNoMaskNoScale(const Common::Rect& r, byte *data, Graphics::Surface *surf, byte transparentColor) {
Common::Point dp;
Common::Rect q(r);
Common::Rect clipper(surf->w, surf->h);
q.clip(clipper);
if (!q.isValidRect()) return;
dp.x = q.left;
dp.y = q.top;
q.translate(-r.left, -r.top);
byte *s = data + q.left + q.top * r.width();
byte *d = (byte *)surf->getBasePtr(dp.x, dp.y);
uint sPitch = r.width() - q.width();
uint dPitch = surf->w - q.width();
for (uint16 i = q.top; i < q.bottom; i++) {
for (uint16 j = q.left; j < q.right; j++) {
if (*s != transparentColor)
*d = *s;
s++;
d++;
}
s += sPitch;
d += dPitch;
}
}
void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, uint scale, byte transparentColor) {
bltMaskScale(r, data, surf, z, scale, transparentColor);
}
} // namespace Parallaction

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,574 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_GRAPHICS_H
#define PARALLACTION_GRAPHICS_H
#include "common/list.h"
#include "common/rect.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/array.h"
#include "graphics/surface.h"
namespace Parallaction {
#include "common/pack-start.h" // START STRUCT PACKING
struct PaletteFxRange {
uint16 _timer;
uint16 _step;
uint16 _flags;
byte _first;
byte _last;
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
class Font {
protected:
byte _color;
public:
Font() : _color(0) {}
virtual ~Font() {}
virtual void setColor(byte color) {
_color = color;
}
virtual uint32 getStringWidth(const char *s) = 0;
virtual uint16 height() = 0;
virtual void drawString(Graphics::Surface *dst, int x, int y, const char *s) = 0;
};
struct Frames {
virtual uint16 getNum() = 0;
virtual byte* getData(uint16 index) = 0;
virtual void getRect(uint16 index, Common::Rect &r) = 0;
virtual uint getRawSize(uint16 index) = 0;
virtual uint getSize(uint16 index) = 0;
virtual ~Frames() { }
};
struct SurfaceToFrames : public Frames {
Graphics::Surface *_surf;
public:
SurfaceToFrames(Graphics::Surface *surf) : _surf(surf) {
}
~SurfaceToFrames() override {
_surf->free();
delete _surf;
}
uint16 getNum() override {
return 1;
}
byte* getData(uint16 index) override {
assert(index == 0);
return (byte *)_surf->getBasePtr(0,0);
}
void getRect(uint16 index, Common::Rect &r) override {
assert(index == 0);
r.left = 0;
r.top = 0;
r.setWidth(_surf->w);
r.setHeight(_surf->h);
}
uint getRawSize(uint16 index) override {
assert(index == 0);
return getSize(index);
}
uint getSize(uint16 index) override {
assert(index == 0);
return _surf->w * _surf->h;
}
};
struct Cnv : public Frames {
uint16 _count; // # of frames
uint16 _width; //
uint16 _height; //
byte** field_8; // unused
byte* _data;
bool _freeData;
Graphics::Surface *_surf;
public:
Cnv() {
_width = _height = _count = 0;
_data = NULL;
_surf = NULL;
_freeData = false;
field_8 = 0;
}
Cnv(uint16 numFrames, uint16 width, uint16 height, byte *data, bool freeData = false)
: _count(numFrames), _width(width), _height(height), _data(data), _freeData(freeData), field_8(0), _surf(NULL) {
}
Cnv(uint16 numFrames, uint16 width, uint16 height, Graphics::Surface *surf)
: _count(numFrames), _width(width), _height(height), _data(NULL), _freeData(true), field_8(0), _surf(surf) {
_data = (byte *)_surf->getBasePtr(0, 0);
}
~Cnv() override {
if (_freeData) {
if (_surf) {
_surf->free();
delete _surf;
} else {
delete[] _data;
}
}
}
byte* getFramePtr(uint16 index) {
if (index >= _count)
return NULL;
return &_data[index * _width * _height];
}
uint16 getNum() override {
return _count;
}
byte *getData(uint16 index) override {
return getFramePtr(index);
}
void getRect(uint16 index, Common::Rect &r) override {
r.left = 0;
r.top = 0;
r.setWidth(_width);
r.setHeight(_height);
}
uint getRawSize(uint16 index) override {
assert(index < _count);
return getSize(index);
}
uint getSize(uint16 index) override {
assert(index < _count);
return _width * _height;
}
};
struct MaskBuffer {
// handles a 2-bit depth buffer used for z-buffering
uint16 w;
uint16 internalWidth;
uint16 h;
uint size;
byte *data;
bool bigEndian;
byte* getPtr(uint16 x, uint16 y) const;
void bltOr(uint16 dx, uint16 dy, const MaskBuffer &src, uint16 sx, uint16 sy, uint width, uint height);
void bltCopy(uint16 dx, uint16 dy, const MaskBuffer &src, uint16 sx, uint16 sy, uint width, uint height);
public:
MaskBuffer();
~MaskBuffer();
void clone(const MaskBuffer &buf);
void create(uint16 width, uint16 height);
void free();
byte getValue(uint16 x, uint16 y) const;
};
struct PathBuffer {
// handles a 1-bit depth buffer used for masking non-walkable areas
uint16 w;
uint16 internalWidth;
uint16 h;
uint size;
byte *data;
bool bigEndian;
byte* getPtr(uint16 x, uint16 y) const;
void bltCopy(uint16 dx, uint16 dy, const PathBuffer &src, uint16 sx, uint16 sy, uint width, uint height);
public:
PathBuffer();
~PathBuffer();
void clone(const PathBuffer &buf);
void create(uint16 width, uint16 height);
void free();
byte getValue(uint16 x, uint16 y) const;
};
class Palette {
byte _data[768];
uint _colors;
uint _size;
bool _hb;
public:
Palette();
Palette(const Palette &pal);
void clone(const Palette &pal);
void makeBlack();
void setEntries(byte* data, uint first, uint num);
void getEntry(uint index, int &red, int &green, int &blue);
void setEntry(uint index, int red, int green, int blue);
void makeGrayscale();
void fadeTo(const Palette& target, uint step);
uint fillRGB(byte *rgb);
void rotate(uint first, uint last, bool forward);
};
#define CENTER_LABEL_HORIZONTAL -1
#define CENTER_LABEL_VERTICAL -1
#define MAX_BALLOON_WIDTH 130
class Parallaction;
struct DoorData;
struct GetData;
struct Label;
class Disk;
enum {
kGfxObjVisible = 1,
kGfxObjTypeDoor = 0,
kGfxObjTypeGet = 1,
kGfxObjTypeAnim = 2,
kGfxObjTypeLabel = 3,
kGfxObjTypeBalloon = 4,
kGfxObjTypeCharacter = 8,
kGfxObjTypeMenu = 16
};
enum {
kGfxObjDoorZ = -200,
kGfxObjGetZ = -100
};
class GfxObj {
Common::String _name;
Frames *_frames;
public:
int16 x, y;
int32 z;
uint32 _prog; // this value is used when sorting, in case that comparing z is not enough to tell which object goes on front
uint32 _flags;
uint type;
uint frame;
uint layer;
uint transparentKey;
uint scale;
int _maskId;
bool _hasMask;
int _pathId;
bool _hasPath;
Common::String _text;
GfxObj(uint type, Frames *frames, const char *name = NULL);
virtual ~GfxObj();
const char *getName() const;
uint getNum();
void getRect(uint frame, Common::Rect &r);
byte *getData(uint frame);
uint getRawSize(uint frame);
uint getSize(uint frame);
void setFlags(uint32 flags);
void clearFlags(uint32 flags);
bool isVisible() {
return (_flags & kGfxObjVisible) == kGfxObjVisible;
}
void release();
};
#define LAYER_FOREGROUND 3
/*
BackgroundInfo keeps information about the background bitmap that can be seen in the game.
These bitmaps can be of any size, smaller or larger than the visible screen, the latter
being the most common options.
*/
struct BackgroundInfo {
protected:
typedef Common::Array<MaskBuffer *> MaskPatches;
MaskPatches _maskPatches;
MaskBuffer _maskBackup;
void clearMaskData();
typedef Common::Array<PathBuffer *> PathPatches;
PathPatches _pathPatches;
PathBuffer _pathBackup;
void clearPathData();
public:
int _x, _y; // used to display bitmaps smaller than the screen
int width;
int height;
Graphics::Surface bg;
MaskBuffer *_mask;
PathBuffer *_path;
Palette palette;
int layers[4];
PaletteFxRange ranges[6];
BackgroundInfo();
~BackgroundInfo();
void setPaletteRange(int index, const PaletteFxRange& range);
// mask management
bool hasMask();
uint addMaskPatch(MaskBuffer *patch);
void toggleMaskPatch(uint id, int x, int y, bool apply);
uint16 getMaskLayer(uint16 z) const;
void finalizeMask();
void loadGfxObjMask(Parallaction *vm, const char *name, GfxObj *obj);
// path management
bool hasPath();
uint addPathPatch(PathBuffer *patch);
void togglePathPatch(uint id, int x, int y, bool apply);
void finalizePath();
void loadGfxObjPath(Parallaction *vm, const char *name, GfxObj *obj);
};
enum {
kBackgroundLocation = 1,
kBackgroundSlide = 2
};
class BalloonManager {
public:
enum TextColor {
kSelectedColor = 0,
kUnselectedColor = 1,
kNormalColor = 2
};
virtual ~BalloonManager() { }
virtual void reset() = 0;
virtual int setLocationBalloon(const Common::String &text, bool endGame) = 0;
virtual int setDialogueBalloon(const Common::String &text, uint16 winding, TextColor textColor) = 0;
virtual int setSingleBalloon(const Common::String &text, uint16 x, uint16 y, uint16 winding, TextColor textColor) = 0;
virtual void setBalloonText(uint id, const Common::String &text, TextColor textColor) = 0;
virtual int hitTestDialogueBalloon(int x, int y) = 0;
};
typedef Common::Array<GfxObj *> GfxObjArray;
#define SCENE_DRAWLIST_SIZE 100
class Gfx {
protected:
Parallaction* _vm;
void resetSceneDrawList();
public:
Disk *_disk;
void beginFrame();
void addObjectToScene(GfxObj *obj);
GfxObjArray _sceneObjects;
GfxObj* loadAnim(const char *name);
GfxObj* loadGet(const char *name);
GfxObj* loadDoor(const char *name);
GfxObj* loadCharacterAnim(const char *name);
void sortScene();
void freeCharacterObjects();
void freeLocationObjects();
void showGfxObj(GfxObj* obj, bool visible);
void blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, uint scale, byte transparentColor);
void unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, uint scale, byte transparentColor);
// labels
void showFloatingLabel(GfxObj *label);
void hideFloatingLabel();
GfxObj *renderFloatingLabel(Font *font, char *text);
GfxObj *createLabel(Font *font, const char *text, byte color);
void showLabel(GfxObj *label, int16 x, int16 y, bool queueTTS = true, bool voiceText = true);
void hideLabel(GfxObj *label);
void freeLabels();
void unregisterLabel(GfxObj *label);
// dialogue handling
GfxObj* registerBalloon(Frames *frames, const char *text);
int setItem(GfxObj* obj, uint16 x, uint16 y, byte transparentColor = 0);
void setItemFrame(uint item, uint16 f);
void freeDialogueObjects();
// background surface
BackgroundInfo *_backgroundInfo;
void setBackground(uint type, BackgroundInfo *info);
void patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask = false);
void grabBackground(const Common::Rect& r, Graphics::Surface &dst);
void fillBackground(const Common::Rect& r, byte color);
void invertBackground(const Common::Rect& r);
// palette
void setPalette(Palette &palette);
void setBlackPalette();
void animatePalette();
// amiga specific
void applyHalfbriteEffect_NS(Graphics::Surface &surf);
void setHalfbriteMode(bool enable);
void setProjectorPos(int x, int y);
void setProjectorProgram(int16 *data);
int16 *_nextProjectorPos;
// start programmatic relative scroll
void initiateScroll(int deltaX, int deltaY);
// immediate and absolute x,y scroll
void setScrollPosX(int scrollX);
void setScrollPosY(int scrollY);
// return current scroll position
void getScrollPos(Common::Point &p);
// init
Gfx(Parallaction* vm);
virtual ~Gfx();
void clearScreen();
void updateScreen();
public:
Palette _palette;
byte *_unpackedBitmap;
protected:
bool _halfbrite;
Common::Point _hbCirclePos;
int _hbCircleRadius;
// BRA specific
Palette _backupPal;
Graphics::Surface *lockScreen();
void unlockScreen();
void updateScreenIntern();
bool _doubleBuffering;
int _gameType;
Graphics::Surface _backBuffer;
void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h);
int _scrollPosX, _scrollPosY;
int _minScrollX, _maxScrollX, _minScrollY, _maxScrollY;
uint32 _requestedHScrollSteps;
uint32 _requestedVScrollSteps;
int32 _requestedHScrollDir;
int32 _requestedVScrollDir;
void scroll();
#define NO_FLOATING_LABEL 1000
struct Label {
Common::String _text;
int _x, _y;
int color;
bool _floating;
};
GfxObjArray _labels;
GfxObjArray _balloons;
GfxObjArray _items;
GfxObj *_floatingLabel;
// overlay mode enables drawing of graphics with automatic screen-to-game coordinate translation
bool _overlayMode;
void drawOverlay(Graphics::Surface &surf);
void drawInventory();
void drawList(Graphics::Surface &surface, GfxObjArray &list);
void updateFloatingLabel();
void copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst);
void drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color);
void drawGfxObject(GfxObj *obj, Graphics::Surface &surf);
void bltMaskScale(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, uint scale, byte transparentColor);
void bltMaskNoScale(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor);
void bltNoMaskNoScale(const Common::Rect& r, byte *data, Graphics::Surface *surf, byte transparentColor);
};
} // Parallaction
#endif

View File

@@ -0,0 +1,90 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/textconsole.h"
#include "parallaction/gui.h"
namespace Parallaction {
bool MenuInputHelper::run() {
if (_newState == nullptr) {
debugC(3, kDebugExec, "MenuInputHelper has set NULL state");
return false;
}
if (_newState != _state) {
debugC(3, kDebugExec, "MenuInputHelper changing state to '%s'", _newState->_name.c_str());
_newState->enter();
_state = _newState;
}
_newState = _state->run();
return true;
}
MenuInputHelper::~MenuInputHelper() {
StateMap::iterator b = _map.begin();
for ( ; b != _map.end(); ++b) {
delete b->_value;
}
_map.clear();
}
void Parallaction::runGuiFrame() {
if (_input->_inputMode != Input::kInputModeMenu) {
return;
}
if (!_menuHelper) {
error("No menu helper defined");
}
bool res = _menuHelper->run();
if (!res) {
cleanupGui();
_input->_inputMode = Input::kInputModeGame;
}
}
void Parallaction::cleanupGui() {
delete _menuHelper;
_menuHelper = nullptr;
}
void Parallaction::setInternLanguage(uint id) {
//TODO: assert id!
_language = id;
_disk->setLanguage(id);
}
uint Parallaction::getInternLanguage() {
return _language;
}
} // namespace Parallaction

View File

@@ -0,0 +1,89 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_GUI_H
#define PARALLACTION_GUI_H
#include "common/system.h"
#include "common/hashmap.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
namespace Parallaction {
class MenuInputState;
class MenuInputHelper {
typedef Common::HashMap<Common::String, MenuInputState *> StateMap;
StateMap _map;
MenuInputState *_state;
MenuInputState *_newState;
public:
MenuInputHelper() : _state(0), _newState(0) {
}
~MenuInputHelper();
void setState(const Common::String &name) {
// bootstrap routine
_newState = getState(name);
assert(_newState);
}
void addState(const Common::String &name, MenuInputState *state) {
_map.setVal(name, state);
}
MenuInputState *getState(const Common::String &name) {
return _map[name];
}
bool run();
};
class MenuInputState {
protected:
MenuInputHelper *_helper;
public:
MenuInputState(const Common::String &name, MenuInputHelper *helper) : _helper(helper), _name(name) {
debugC(3, kDebugExec, "MenuInputState(%s)", name.c_str());
_helper->addState(name, this);
}
Common::String _name;
virtual ~MenuInputState() { }
virtual MenuInputState* run() = 0;
virtual void enter() = 0;
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,559 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "common/textconsole.h"
#include "parallaction/gui.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/saveload.h"
namespace Parallaction {
class SplashInputState_BR : public MenuInputState {
protected:
Common::String _slideName;
uint32 _timeOut;
Common::String _nextState;
uint32 _startTime;
Palette blackPal;
Palette pal;
Parallaction *_vm;
int _fadeSteps;
public:
SplashInputState_BR(Parallaction *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm),
_timeOut(0), _startTime(0), _fadeSteps(0) {
}
MenuInputState* run() override {
if (_fadeSteps > 0) {
pal.fadeTo(blackPal, 1);
_vm->_gfx->setPalette(pal);
_fadeSteps--;
return this;
}
if (_fadeSteps == 0) {
return _helper->getState(_nextState);
}
uint32 curTime = _vm->_system->getMillis();
if (curTime - _startTime > _timeOut) {
_fadeSteps = 64;
pal.clone(_vm->_gfx->_backgroundInfo->palette);
}
return this;
}
void enter() override {
_vm->_gfx->clearScreen();
_vm->showSlide(_slideName.c_str(), CENTER_LABEL_HORIZONTAL, CENTER_LABEL_VERTICAL);
_vm->_input->setMouseState(MOUSE_DISABLED);
_startTime = _vm->_system->getMillis();
_fadeSteps = -1;
}
};
class SplashInputState0_BR : public SplashInputState_BR {
public:
SplashInputState0_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro0", helper) {
_slideName = "dyna";
_timeOut = 600;
_nextState = "intro1";
}
};
class SplashInputState1_BR : public SplashInputState_BR {
public:
SplashInputState1_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro1", helper) {
_slideName = "core";
_timeOut = 600;
_nextState = "mainmenu";
}
};
struct LocationPart {
int part;
const char *location;
};
class MainMenuInputState_BR : public MenuInputState {
Parallaction_br *_vm;
#define MENUITEMS_X 250
#define MENUITEMS_Y 200
#define MENUITEM_WIDTH 200
#define MENUITEM_HEIGHT 20
Frames* renderMenuItem(const char *text) {
// this builds a surface containing two copies of the text.
// one is in normal color, the other is inverted.
// the two 'frames' are used to display selected/unselected menu items
Graphics::Surface *surf = new Graphics::Surface();
surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8());
// build first frame to be displayed when item is not selected
if (_vm->getPlatform() == Common::kPlatformDOS) {
_vm->_menuFont->setColor(0);
} else {
_vm->_menuFont->setColor(23);
}
_vm->_menuFont->drawString(surf, 5, 2, text);
// build second frame to be displayed when item is selected
_vm->_menuFont->drawString(surf, 5, 2 + MENUITEM_HEIGHT, text);
byte *dst = (byte *)surf->getPixels() + MENUITEM_WIDTH * MENUITEM_HEIGHT;
for (int i = 0; i < MENUITEM_WIDTH * MENUITEM_HEIGHT; i++) {
*dst++ ^= 0xD;
}
// wrap the surface into the suitable Frames adapter
return new Cnv(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf);
}
enum MenuOptions {
kMenuPart0 = 0,
kMenuPart1 = 1,
kMenuPart2 = 2,
kMenuPart3 = 3,
kMenuPart4 = 4,
kMenuLoadGame = 5,
kMenuQuit = 6
};
#define NUM_MENULINES 7
GfxObj *_lines[NUM_MENULINES];
static const char *_menuStringsAmiga[NUM_MENULINES];
static const char *_menuStringsPC[NUM_MENULINES];
static const MenuOptions _optionsAmiga[NUM_MENULINES];
static const MenuOptions _optionsPC[NUM_MENULINES];
const char **_menuStrings;
const MenuOptions *_options;
static LocationPart _firstLocation[];
int _availItems;
int _selection;
void cleanup() {
_vm->_gfx->freeDialogueObjects();
for (int i = 0; i < _availItems; i++) {
delete _lines[i];
_lines[i] = nullptr;
}
}
void redrawMenu() {
Common::Point p;
_vm->_input->getCursorPos(p);
if ((p.x > MENUITEMS_X) && (p.x < (MENUITEMS_X+MENUITEM_WIDTH)) && (p.y > MENUITEMS_Y)) {
_selection = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT;
if (!(_selection < _availItems))
_selection = -1;
} else
_selection = -1;
for (int i = 0; i < _availItems; i++) {
_vm->_gfx->setItemFrame(i, _selection == i ? 1 : 0);
}
}
public:
MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) {
memset(_lines, 0, sizeof(_lines));
_menuStrings = nullptr;
_options = nullptr;
_availItems = 0;
_selection = 0;
}
~MainMenuInputState_BR() override {
cleanup();
}
MenuInputState* run() override {
int event = _vm->_input->getLastButtonEvent();
if (!((event == kMouseLeftUp) && _selection >= 0)) {
redrawMenu();
return this;
}
int selection = _options[_selection];
switch (selection) {
case kMenuQuit: {
_vm->quitGame();
break;
}
case kMenuLoadGame:
warning("loadgame not yet implemented");
if (!_vm->_saveLoad->loadGame()) {
return this;
}
break;
default:
_vm->_nextPart = _firstLocation[selection].part;
_vm->scheduleLocationSwitch(_firstLocation[selection].location);
}
_vm->_system->showMouse(false);
cleanup();
return nullptr;
}
void enter() override {
_vm->_gfx->clearScreen();
int x = 0, y = 0, i = 0;
if (_vm->getPlatform() == Common::kPlatformDOS) {
x = 20;
y = 50;
}
_vm->showSlide("tbra", x, y);
_availItems = 4;
bool complete[3];
_vm->_saveLoad->getGamePartProgress(complete, 3);
for (i = 0; i < 3 && complete[i]; i++, _availItems++)
;
if (_vm->getPlatform() == Common::kPlatformAmiga) {
_menuStrings = _menuStringsAmiga;
_options = _optionsAmiga;
} else {
_menuStrings = _menuStringsPC;
_options = _optionsPC;
}
for (i = 0; i < _availItems; i++) {
_lines[i] = new GfxObj(0, renderMenuItem(_menuStrings[i]), "MenuItem");
_vm->_gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF);
}
_selection = -1;
_vm->_input->setArrowCursor();
_vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
};
LocationPart MainMenuInputState_BR::_firstLocation[] = {
{ 0, "intro" },
{ 1, "museo" },
{ 2, "start" },
{ 3, "bolscoi" },
{ 4, "treno" }
};
const char *MainMenuInputState_BR::_menuStringsAmiga[NUM_MENULINES] = {
"See the introduction",
"Load a Saved Game",
"Exit to WorkBench",
"Start a new game",
"Start PART 2",
"Start PART 3",
"Start PART 4"
};
const MainMenuInputState_BR::MenuOptions MainMenuInputState_BR::_optionsAmiga[NUM_MENULINES] = {
kMenuPart0,
kMenuLoadGame,
kMenuQuit,
kMenuPart1,
kMenuPart2,
kMenuPart3,
kMenuPart4
};
const char *MainMenuInputState_BR::_menuStringsPC[NUM_MENULINES] = {
"SEE INTRO",
"NEW GAME",
"SAVED GAME",
"EXIT TO DOS",
"PART 2",
"PART 3",
"PART 4"
};
const MainMenuInputState_BR::MenuOptions MainMenuInputState_BR::_optionsPC[NUM_MENULINES] = {
kMenuPart0,
kMenuPart1,
kMenuLoadGame,
kMenuQuit,
kMenuPart2,
kMenuPart3,
kMenuPart4
};
void Parallaction_br::startGui(bool showSplash) {
_menuHelper = new MenuInputHelper;
new MainMenuInputState_BR(this, _menuHelper);
if (showSplash) {
new SplashInputState0_BR(this, _menuHelper);
new SplashInputState1_BR(this, _menuHelper);
_menuHelper->setState("intro0");
} else {
_menuHelper->setState("mainmenu");
}
_input->_inputMode = Input::kInputModeMenu;
}
class IngameMenuInputState_BR : public MenuInputState {
Parallaction_br *_vm;
GfxObj *_menuObj, *_mscMenuObj, *_sfxMenuObj;
int _menuObjId, _mscMenuObjId, _sfxMenuObjId;
Common::Rect _menuRect;
int _cellW, _cellH;
int _sfxStatus, _mscStatus;
int frameFromStatus(int status) const {
int frame;
if (status == 0) {
frame = 1;
} else
if (status == 1) {
frame = 0;
} else {
frame = 2;
}
return frame;
}
public:
IngameMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("ingamemenu", helper), _vm(vm) {
Frames *menuFrames = _vm->_disk->loadFrames("request.win");
assert(menuFrames);
_menuObj = new GfxObj(kGfxObjTypeMenu, menuFrames, "ingamemenu");
Frames *mscFrames = _vm->_disk->loadFrames("onoff.win");
assert(mscFrames);
_mscMenuObj = new GfxObj(kGfxObjTypeMenu, mscFrames, "msc");
Frames *sfxFrames = _vm->_disk->loadFrames("sfx.win");
assert(sfxFrames);
_sfxMenuObj = new GfxObj(kGfxObjTypeMenu, sfxFrames, "sfx");
_menuObj->getRect(0, _menuRect);
_cellW = _menuRect.width() / 3;
_cellH = _menuRect.height() / 2;
_menuObjId = _mscMenuObjId = _sfxMenuObjId = 0;
_sfxStatus = _mscStatus = 0;
}
~IngameMenuInputState_BR() override {
delete _menuObj;
delete _mscMenuObj;
delete _sfxMenuObj;
}
MenuInputState *run() override {
if (_vm->_input->getLastButtonEvent() != kMouseLeftUp) {
return this;
}
int cell = -1;
Common::Point p;
_vm->_input->getCursorPos(p);
if (_menuRect.contains(p)) {
cell = (p.x - _menuRect.left) / _cellW + 3 * ((p.y - _menuRect.top) / _cellH);
}
bool close = false;
switch (cell) {
case 4: // resume
case -1: // invalid cell
close = true;
break;
case 0: // toggle music
if (_mscStatus != -1) {
_vm->enableMusic(!_mscStatus);
_mscStatus = _vm->getMusicStatus();
_vm->_gfx->setItemFrame(_mscMenuObjId, frameFromStatus(_mscStatus));
}
break;
case 1: // toggle sfx
if (_sfxStatus != -1) {
_vm->enableSfx(!_sfxStatus);
_sfxStatus = _vm->getSfxStatus();
_vm->_gfx->setItemFrame(_sfxMenuObjId, frameFromStatus(_sfxStatus));
}
break;
case 2: // save
warning("Saving is not supported yet");
_vm->_saveLoad->saveGame();
break;
case 3: // load
warning("Loading is not supported yet");
close = _vm->_saveLoad->loadGame();
break;
case 5: // quit
return _helper->getState("quitdialog");
default:
break;
}
if (close) {
_vm->_gfx->freeDialogueObjects();
return nullptr;
}
_vm->_input->setArrowCursor();
return this;
}
void enter() override {
// TODO: find the right position of the menu object
_menuObjId = _vm->_gfx->setItem(_menuObj, 0, 0, 0);
_vm->_gfx->setItemFrame(_menuObjId, 0);
_mscMenuObjId = _vm->_gfx->setItem(_mscMenuObj, 0, 0, 0);
_mscStatus = _vm->getMusicStatus();
_vm->_gfx->setItemFrame(_mscMenuObjId, frameFromStatus(_mscStatus));
_sfxMenuObjId = _vm->_gfx->setItem(_sfxMenuObj, 0, 0, 0);
_sfxStatus = _vm->getSfxStatus();
_vm->_gfx->setItemFrame(_sfxMenuObjId, frameFromStatus(_sfxStatus));
}
};
class QuitDialogInputState_BR : public MenuInputState {
Parallaction_br *_vm;
Font *_font;
int _x, _y;
GfxObj *_obj;
public:
QuitDialogInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("quitdialog", helper), _vm(vm) {
_font = _vm->_dialogueFont;
const char *question = "Do you really want to quit ?";
const char *option = "Yes No";
int questionW = _font->getStringWidth(question);
int optionW = _font->getStringWidth(option);
int w = MAX(questionW, optionW) + 30;
_x = (640 - w) / 2;
_y = 90;
Graphics::Surface *surf = new Graphics::Surface;
surf->create(w, 110, Graphics::PixelFormat::createFormatCLUT8());
surf->fillRect(Common::Rect(0, 0, w, 110), 12);
surf->fillRect(Common::Rect(10, 10, w-10, 100), 15);
_font->setColor(0);
int x = (w - questionW)/2;
int y = 13;
_font->drawString(surf, x, y, question);
x = (w - optionW)/2;
y = 13 + _font->height()*2;
_font->drawString(surf, x, y, option);
_obj = new GfxObj(kGfxObjTypeMenu, new SurfaceToFrames(surf), "quitdialog");
assert(_obj);
}
~QuitDialogInputState_BR() override {
delete _obj;
}
MenuInputState *run() override {
uint16 key;
bool e = _vm->_input->getLastKeyDown(key);
if (!e) {
return this;
}
if (key == 'y' || key == 'Y') {
_vm->quitGame();
return nullptr;
} else
if (key == 'n' || key == 'N') {
// NOTE: when the quit dialog is hidden, the in-game menu is
// deleted for a frame, and then redrawn. This is because the
// current implementation of graphic 'items' doesn't allow
// deletion of a single 'item'.
_vm->_gfx->freeDialogueObjects();
return _helper->getState("ingamemenu");
}
return this;
}
void enter() override {
// setPaletteEntry(1, 0, 0, 0); // text color
// setPaletteEntry(15, 255, 255, 255); // background color
int id = _vm->_gfx->setItem(_obj, _x, _y, 0);
_vm->_gfx->setItemFrame(id, 0);
}
};
void Parallaction_br::startIngameMenu() {
_menuHelper = new MenuInputHelper;
new IngameMenuInputState_BR(this, _menuHelper);
new QuitDialogInputState_BR(this, _menuHelper);
_menuHelper->setState("ingamemenu");
_input->_inputMode = Input::kInputModeMenu;
}
} // namespace Parallaction

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,619 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/events.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/cursorman.h"
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/debug.h"
namespace Parallaction {
#define MOUSEARROW_WIDTH_NS 16
#define MOUSEARROW_HEIGHT_NS 16
#define MOUSECOMBO_WIDTH_NS 32 // sizes for cursor + selected inventory item
#define MOUSECOMBO_HEIGHT_NS 32
struct MouseComboProperties {
int _xOffset;
int _yOffset;
int _width;
int _height;
};
/*
// TODO: improve NS's handling of normal cursor before merging cursor code.
MouseComboProperties _mouseComboProps_NS = {
7, // combo x offset (the icon from the inventory will be rendered from here)
7, // combo y offset (ditto)
32, // combo (arrow + icon) width
32 // combo (arrow + icon) height
};
*/
MouseComboProperties _mouseComboProps_BR = {
8, // combo x offset (the icon from the inventory will be rendered from here)
8, // combo y offset (ditto)
68, // combo (arrow + icon) width
68 // combo (arrow + icon) height
};
Input::Input(Parallaction *vm) : _vm(vm) {
_gameType = _vm->getGameType();
_transCurrentHoverItem = 0;
_hasDelayedAction = false; // actived when the character needs to move before taking an action
_mouseState = MOUSE_DISABLED;
_activeItem._index = 0;
_activeItem._id = 0;
_mouseButtons = 0;
_delayedActionZone.reset();
_inputMode = 0;
_hasKeyPressEvent = false;
_dinoCursor = nullptr;
_dougCursor = nullptr;
_donnaCursor = nullptr;
_comboArrow = nullptr;
_mouseArrow = nullptr;
initCursors();
}
Input::~Input() {
if (_gameType == GType_Nippon) {
delete _mouseArrow;
}
delete _comboArrow;
delete _dinoCursor;
delete _dougCursor;
delete _donnaCursor;
}
// FIXME: the engine has 3 event loops. The following routine hosts the main one,
// and it's called from 8 different places in the code. There exist 2 more specialised
// loops which could possibly be merged into this one with some effort in changing
// caller code, i.e. adding condition checks.
//
void Input::readInput() {
bool updateMousePos = false;
Common::Event e;
_mouseButtons = kMouseNone;
_hasKeyPressEvent = false;
Common::EventManager *eventMan = _vm->_system->getEventManager();
while (eventMan->pollEvent(e)) {
updateMousePos = true;
switch (e.type) {
case Common::EVENT_KEYDOWN:
_hasKeyPressEvent = true;
_keyPressed = e.kbd;
updateMousePos = false;
break;
case Common::EVENT_LBUTTONDOWN:
_mouseButtons = kMouseLeftDown;
break;
case Common::EVENT_LBUTTONUP:
_mouseButtons = kMouseLeftUp;
break;
case Common::EVENT_RBUTTONDOWN:
_mouseButtons = kMouseRightDown;
break;
case Common::EVENT_RBUTTONUP:
_mouseButtons = kMouseRightUp;
break;
case Common::EVENT_RETURN_TO_LAUNCHER:
case Common::EVENT_QUIT:
return;
default:
break;
}
}
if (updateMousePos) {
setCursorPos(e.mouse);
}
}
bool Input::getLastKeyDown(uint16 &ascii) {
ascii = _keyPressed.ascii;
return (_hasKeyPressEvent);
}
// FIXME: see comment for readInput()
void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) {
if (buttonEventMask == kMouseNone) {
_mouseButtons = kMouseNone; // don't wait on nothing
return;
}
const int32 LOOP_RESOLUTION = 30;
if (timeout <= 0) {
do {
readInput();
_vm->_system->delayMillis(LOOP_RESOLUTION);
} while ((_mouseButtons & buttonEventMask) == 0);
} else {
do {
readInput();
_vm->_system->delayMillis(LOOP_RESOLUTION);
timeout -= LOOP_RESOLUTION;
} while ((timeout > 0) && (_mouseButtons & buttonEventMask) == 0);
}
}
int Input::updateGameInput() {
int event = kEvNone;
if (!isMouseEnabled() ||
(g_engineFlags & kEngineBlockInput) ||
(g_engineFlags & kEngineWalking) ||
(g_engineFlags & kEngineChangeLocation)) {
debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, block: %i, walking: %i, changeloc: %i)",
isMouseEnabled(),
(g_engineFlags & kEngineBlockInput) == 0,
(g_engineFlags & kEngineWalking) == 0,
(g_engineFlags & kEngineChangeLocation) == 0
);
return event;
}
if (_gameType == GType_Nippon) {
if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) {
if (_keyPressed.keycode == Common::KEYCODE_l) event = kEvLoadGame;
if (_keyPressed.keycode == Common::KEYCODE_s) event = kEvSaveGame;
}
} else
if (_gameType == GType_BRA) {
if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) {
if (_keyPressed.keycode == Common::KEYCODE_F5) event = kEvIngameMenu;
}
} else {
error("unsupported gametype in updateGameInput");
}
if (event == kEvNone) {
translateGameInput();
}
return event;
}
int Input::updateInput() {
int oldMode = _inputMode;
int event = kEvNone;
readInput();
switch (_inputMode) {
case kInputModeGame:
event = updateGameInput();
break;
case kInputModeInventory:
updateInventoryInput();
break;
default:
break;
}
// when mode changes, then consider any input consumed
// for the current frame
if (oldMode != _inputMode) {
_mouseButtons = kEvNone;
_hasKeyPressEvent = false;
}
return event;
}
void Input::trackMouse(ZonePtr z) {
if ((z != _hoverZone) && (_hoverZone)) {
stopHovering();
return;
}
if (!z) {
return;
}
if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) {
_hoverZone = z;
_vm->_gfx->showFloatingLabel(_hoverZone->_label);
return;
}
}
void Input::stopHovering() {
_hoverZone.reset();
_vm->_gfx->hideFloatingLabel();
}
void Input::takeAction(ZonePtr z) {
stopHovering();
_vm->pauseJobs();
_vm->runZone(z);
_vm->resumeJobs();
}
void Input::walkTo(const Common::Point &dest) {
stopHovering();
setArrowCursor();
_vm->scheduleWalk(dest.x, dest.y, true);
}
bool Input::translateGameInput() {
if (g_engineFlags & kEnginePauseJobs) {
return false;
}
if (_hasDelayedAction) {
// if walking is over, then take programmed action
takeAction(_delayedActionZone);
_hasDelayedAction = false;
_delayedActionZone.reset();
return true;
}
if (_mouseButtons == kMouseRightDown) {
// right button down shows inventory
enterInventoryMode();
return true;
}
Common::Point mousePos;
getAbsoluteCursorPos(mousePos);
// test if mouse is hovering on an interactive zone for the currently selected inventory item
ZonePtr z = _vm->hitZone(_activeItem._id, mousePos.x, mousePos.y);
if (((_mouseButtons == kMouseLeftUp) && (_activeItem._id == 0) && ((g_engineFlags & kEngineWalking) == 0)) && ((!z) || (ACTIONTYPE(z) != kZoneCommand))) {
walkTo(mousePos);
return true;
}
trackMouse(z);
if (!z) {
return true;
}
if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || (ACTIONTYPE(z) == kZoneCommand))) {
bool noWalk = z->_flags & kFlagsNoWalk; // check the explicit no-walk flag
if (_gameType == GType_BRA) {
// action performed on object marked for self-use do not need walk in BRA
noWalk |= ((z->_flags & kFlagsYourself) != 0);
}
if (noWalk) {
takeAction(z);
} else {
// action delayed: if Zone defined a moveto position the character is programmed to move there,
// else it will move to the mouse position
_delayedActionZone = z;
_hasDelayedAction = true;
if (z->_moveTo.y != 0) {
mousePos = z->_moveTo;
}
walkTo(mousePos);
}
_vm->beep();
setArrowCursor();
return true;
}
return true;
}
void Input::enterInventoryMode() {
Common::Point mousePos;
getAbsoluteCursorPos(mousePos);
bool hitCharacter = _vm->hitZone(kZoneYou, mousePos.x, mousePos.y);
if (hitCharacter) {
if (_activeItem._id != 0) {
_activeItem._index = (_activeItem._id >> 16) & 0xFFFF;
g_engineFlags |= kEngineDragging;
} else {
setArrowCursor();
}
}
stopHovering();
_vm->pauseJobs();
_vm->openInventory();
_transCurrentHoverItem = -1;
_inputMode = kInputModeInventory;
}
void Input::exitInventoryMode() {
// right up hides inventory
Common::Point mousePos;
getCursorPos(mousePos);
int pos = _vm->getHoverInventoryItem(mousePos.x, mousePos.y);
_vm->highlightInventoryItem(-1); // disable
if ((g_engineFlags & kEngineDragging)) {
g_engineFlags &= ~kEngineDragging;
ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos));
if (z) {
_vm->dropItem(z->u._mergeObj1);
_vm->dropItem(z->u._mergeObj2);
_vm->addInventoryItem(z->u._mergeObj3);
_vm->_cmdExec->run(z->_commands); // commands might set a new _inputMode
}
}
_vm->closeInventory();
if (pos == -1) {
setArrowCursor();
} else {
const InventoryItem *item = _vm->getInventoryItem(pos);
if (item->_index != 0) {
_activeItem._id = item->_id;
setInventoryCursor(item->_index);
}
}
_vm->resumeJobs();
// in case the input mode was not changed by the code above (especially by the commands
// executed in case of a merge), then assume we are going back to game mode
if (_inputMode == kInputModeInventory) {
_inputMode = kInputModeGame;
}
}
bool Input::updateInventoryInput() {
if (_mouseButtons == kMouseRightUp) {
exitInventoryMode();
return true;
}
Common::Point mousePos;
getCursorPos(mousePos);
int16 _si = _vm->getHoverInventoryItem(mousePos.x, mousePos.y);
if (_si != _transCurrentHoverItem) {
_transCurrentHoverItem = _si;
_vm->highlightInventoryItem(_si); // enable
}
return true;
}
void Input::setMouseState(MouseTriState state) {
assert(state == MOUSE_ENABLED_SHOW || state == MOUSE_ENABLED_HIDE || state == MOUSE_DISABLED);
_mouseState = state;
switch (_mouseState) {
case MOUSE_ENABLED_HIDE:
case MOUSE_DISABLED:
CursorMan.showMouse(false);
break;
case MOUSE_ENABLED_SHOW:
CursorMan.showMouse(true);
break;
default:
break;
}
}
MouseTriState Input::getMouseState() {
return _mouseState;
}
bool Input::isMouseEnabled() {
return (_mouseState == MOUSE_ENABLED_SHOW) || (_mouseState == MOUSE_ENABLED_HIDE);
}
void Input::getAbsoluteCursorPos(Common::Point& p) const {
_vm->_gfx->getScrollPos(p);
p.x += _mousePos.x;
p.y += _mousePos.y;
}
void Input::initCursors() {
_dinoCursor = _donnaCursor = _dougCursor = nullptr;
switch (_gameType) {
case GType_Nippon:
_comboArrow = _vm->_disk->loadPointer("pointer");
_mouseArrow = new Cnv(1, MOUSEARROW_WIDTH_NS, MOUSEARROW_HEIGHT_NS, _resMouseArrow_NS, false);
break;
case GType_BRA:
if (_vm->getPlatform() == Common::kPlatformDOS) {
_dinoCursor = _vm->_disk->loadPointer("pointer1");
_dougCursor = _vm->_disk->loadPointer("pointer2");
_donnaCursor = _vm->_disk->loadPointer("pointer3");
Graphics::Surface *surf = new Graphics::Surface;
surf->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, Graphics::PixelFormat::createFormatCLUT8());
_comboArrow = new SurfaceToFrames(surf);
} else {
// TODO: Where are the Amiga cursors?
Graphics::Surface *surf1 = new Graphics::Surface;
surf1->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, Graphics::PixelFormat::createFormatCLUT8());
_comboArrow = new SurfaceToFrames(surf1);
// TODO: scale mouse cursor (see staticres.cpp)
Graphics::Surface *surf2 = new Graphics::Surface;
surf2->create(32, 16, Graphics::PixelFormat::createFormatCLUT8());
memcpy(surf2->getPixels(), _resMouseArrow_BR_Amiga, 32*16);
_mouseArrow = new SurfaceToFrames(surf2);
}
break;
default:
warning("Input::initCursors: unknown gametype");
}
}
void Input::setArrowCursor() {
switch (_gameType) {
case GType_Nippon:
debugC(1, kDebugInput, "setting mouse cursor to arrow");
// this stuff is needed to avoid artifacts with labels and selected items when switching cursors
stopHovering();
_activeItem._id = 0;
CursorMan.replaceCursor(_mouseArrow->getData(0), MOUSEARROW_WIDTH_NS, MOUSEARROW_HEIGHT_NS, 0, 0, 0);
break;
case GType_BRA: {
Common::Rect r;
_mouseArrow->getRect(0, r);
CursorMan.replaceCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0);
CursorMan.showMouse(true);
_activeItem._id = 0;
break;
}
default:
warning("Input::setArrowCursor: unknown gametype");
}
}
void Input::setInventoryCursor(ItemName name) {
assert(name > 0);
switch (_gameType) {
case GType_Nippon: {
byte *v8 = _comboArrow->getData(0);
// FIXME: destination offseting is not clear
_vm->_inventoryRenderer->drawItem(name, v8 + 7 * MOUSECOMBO_WIDTH_NS + 7, MOUSECOMBO_WIDTH_NS);
CursorMan.replaceCursor(v8, MOUSECOMBO_WIDTH_NS, MOUSECOMBO_HEIGHT_NS, 0, 0, 0);
break;
}
case GType_BRA: {
byte *src = _mouseArrow->getData(0);
byte *dst = _comboArrow->getData(0);
// FIXME: destination offseting is not clear
Common::Rect srcRect, dstRect;
_mouseArrow->getRect(0, srcRect);
_comboArrow->getRect(0, dstRect);
for (uint y = 0; y < (uint)srcRect.height(); y++)
memcpy(dst + y * dstRect.width(), src + y * srcRect.width(), srcRect.width());
_vm->_inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width);
CursorMan.replaceCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0);
break;
}
default:
warning("Input::setInventoryCursor: unknown gametype");
}
}
void Input::setMenuPointer() {
switch (_gameType) {
case GType_Nippon: {
error("Input::setMenuPointer not supported for Nippon Safes");
}
case GType_BRA: {
if (_vm->getPlatform() == Common::kPlatformDOS) {
// Donna's cursor doubles as the cursor used
// in the startup menu.
_mouseArrow = _donnaCursor;
setArrowCursor();
} else {
warning("Input::setMenuPointer not yet implemented for Amiga");
}
break;
}
default:
warning("Input::setMenuPointer: unknown gametype");
}
}
void Input::setCharacterPointer(const char *name) {
switch (_gameType) {
case GType_Nippon: {
error("Input::setCharacterPointer not supported for Nippon Safes");
}
case GType_BRA: {
if (_vm->getPlatform() == Common::kPlatformDOS) {
if (!scumm_stricmp(name, "dino")) {
_mouseArrow = _dinoCursor;
} else if (!scumm_stricmp(name, "donna")) {
_mouseArrow = _donnaCursor;
} else if (!scumm_stricmp(name, "doug")) {
_mouseArrow = _dougCursor;
}
setArrowCursor();
} else {
warning("Input::setCharacterPointer not yet implemented for Amiga");
}
break;
}
default:
warning("Input::setCharacterPointer: unknown gametype");
}
}
} // namespace Parallaction

View File

@@ -0,0 +1,135 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_INPUT_H
#define PARALLACTION_INPUT_H
#include "common/keyboard.h"
#include "parallaction/objects.h"
#include "parallaction/inventory.h"
namespace Parallaction {
enum {
kMouseNone = 0,
kMouseLeftUp = 1,
kMouseLeftDown = 2,
kMouseRightUp = 4,
kMouseRightDown = 8
};
enum MouseTriState {
MOUSE_ENABLED_SHOW,
MOUSE_ENABLED_HIDE,
MOUSE_DISABLED
};
class Input {
int updateGameInput();
bool _hasKeyPressEvent;
Common::KeyState _keyPressed;
bool _hasDelayedAction; // actived when the character needs to move before taking an action
ZonePtr _delayedActionZone;
int16 _transCurrentHoverItem;
void translateInput();
bool translateGameInput();
bool updateInventoryInput();
void takeAction(ZonePtr z);
void walkTo(const Common::Point &dest);
Parallaction *_vm;
Common::Point _mousePos;
uint16 _mouseButtons;
ZonePtr _hoverZone;
void enterInventoryMode();
void exitInventoryMode();
int _gameType;
static byte _resMouseArrow_NS[256];
static byte _resMouseArrow_BR_Amiga[512];
Frames *_mouseArrow;
Frames *_comboArrow;
Frames *_dinoCursor;
Frames *_dougCursor;
Frames *_donnaCursor;
void initCursors();
public:
enum {
kInputModeGame = 0,
kInputModeComment = 1,
kInputModeDialogue = 2,
kInputModeInventory = 3,
kInputModeMenu = 4
};
Input(Parallaction *vm);
virtual ~Input();
void getAbsoluteCursorPos(Common::Point& p) const;
void getCursorPos(Common::Point& p) const {
p = _mousePos;
}
void setCursorPos(const Common::Point& p) {
_mousePos = p;
}
int _inputMode;
InventoryItem _activeItem;
void readInput();
int updateInput();
void trackMouse(ZonePtr z);
void waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1);
uint32 getLastButtonEvent() { return _mouseButtons; }
bool getLastKeyDown(uint16 &ascii);
void stopHovering();
MouseTriState _mouseState;
void setMouseState(MouseTriState state);
MouseTriState getMouseState();
bool isMouseEnabled();
void setArrowCursor();
void setInventoryCursor(ItemName name);
void setMenuPointer();
void setCharacterPointer(const char *name);
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,395 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "common/textconsole.h"
namespace Parallaction {
/*
#define INVENTORYITEM_PITCH 32
#define INVENTORYITEM_WIDTH 24
#define INVENTORYITEM_HEIGHT 24
#define INVENTORY_MAX_ITEMS 30
#define INVENTORY_ITEMS_PER_LINE 5
#define INVENTORY_LINES 6
#define INVENTORY_WIDTH (INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
#define INVENTORY_HEIGHT (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
*/
InventoryItem _verbs_NS[] = {
{ 1, kZoneDoor },
{ 3, kZoneExamine },
{ 2, kZoneGet },
{ 4, kZoneSpeak },
{ 0, 0 }
};
InventoryItem _verbs_BR[] = {
{ 1, kZoneBox },
{ 2, kZoneGet },
{ 3, kZoneExamine },
{ 4, kZoneSpeak },
{ 0, 0 }
};
InventoryProperties _invProps_NS = {
32, // INVENTORYITEM_PITCH
24, // INVENTORYITEM_WIDTH
24, // INVENTORYITEM_HEIGHT
30, // INVENTORY_MAX_ITEMS
5, // INVENTORY_ITEMS_PER_LINE
6, // INVENTORY_LINES
5 * 24, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
6 * 24 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
};
InventoryProperties _invProps_BR = {
51, // INVENTORYITEM_PITCH
51, // INVENTORYITEM_WIDTH
51, // INVENTORYITEM_HEIGHT
48, // INVENTORY_MAX_ITEMS
6, // INVENTORY_ITEMS_PER_LINE
8, // INVENTORY_LINES
6 * 51, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
8 * 51 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
};
int16 Parallaction::getHoverInventoryItem(int16 x, int16 y) {
return _inventoryRenderer->hitTest(Common::Point(x,y));
}
void Parallaction::highlightInventoryItem(ItemPosition pos) {
static ItemPosition lastHighlightedPos = -1;
if (lastHighlightedPos != -1) {
_inventoryRenderer->highlightItem(lastHighlightedPos, 12);
}
if (pos != -1) {
_inventoryRenderer->highlightItem(pos, 19);
}
lastHighlightedPos = pos;
}
int Parallaction::addInventoryItem(ItemName item) {
return _inventory->addItem(item);
}
int Parallaction::addInventoryItem(ItemName item, uint32 value) {
return _inventory->addItem(item, value);
}
void Parallaction::dropItem(uint16 v) {
_inventory->removeItem(v);
}
bool Parallaction::isItemInInventory(int32 v) {
return (_inventory->findItem(v) != -1);
}
const InventoryItem* Parallaction::getInventoryItem(int16 pos) {
return _inventory->getItem(pos);
}
int16 Parallaction::getInventoryItemIndex(int16 pos) {
return _inventory->getItemName(pos);
}
void Parallaction_ns::cleanInventory(bool keepVerbs) {
_inventory->clear(keepVerbs);
}
void Parallaction_br::cleanInventory(bool keepVerbs) {
_dinoInventory->clear(keepVerbs);
_donnaInventory->clear(keepVerbs);
_dougInventory->clear(keepVerbs);
}
void Parallaction::openInventory() {
_inventoryRenderer->showInventory();
}
void Parallaction::closeInventory() {
_inventoryRenderer->hideInventory();
}
Inventory *Parallaction_br::findInventory(const char *name) {
if (!scumm_stricmp(name, "dino")) {
return _dinoInventory;
}
if (!scumm_stricmp(name, "donna")) {
return _donnaInventory;
}
if (!scumm_stricmp(name, "doug")) {
return _dougInventory;
}
return nullptr;
}
InventoryRenderer::InventoryRenderer(Parallaction *vm, InventoryProperties *props) : _vm(vm), _props(props) {
_surf.create(_props->_width, _props->_height, Graphics::PixelFormat::createFormatCLUT8());
}
InventoryRenderer::~InventoryRenderer() {
_surf.free();
}
void InventoryRenderer::setInventory(Inventory *inventory) {
_inv = inventory;
}
void InventoryRenderer::showInventory() {
if (!_inv)
error("InventoryRenderer not bound to inventory");
uint16 lines = getNumLines();
Common::Point p;
_vm->_input->getCursorPos(p);
_pos.x = CLIP((int)(p.x - (_props->_width / 2)), 0, (int)(_vm->_screenWidth - _props->_width));
_pos.y = CLIP((int)(p.y - 2 - (lines * _props->_itemHeight)), 0, (int)(_vm->_screenHeight - lines * _props->_itemHeight));
refresh();
}
void InventoryRenderer::hideInventory() {
if (!_inv)
error("InventoryRenderer not bound to inventory");
}
void InventoryRenderer::getRect(Common::Rect& r) const {
r.setWidth(_props->_width);
r.setHeight(_props->_itemHeight * getNumLines());
r.moveTo(_pos);
}
ItemPosition InventoryRenderer::hitTest(const Common::Point &p) const {
Common::Rect r;
getRect(r);
if (!r.contains(p))
return -1;
return ((p.x - _pos.x) / _props->_itemWidth) + (_props->_itemsPerLine * ((p.y - _pos.y) / _props->_itemHeight));
}
void InventoryRenderer::drawItem(ItemPosition pos, ItemName name) {
Common::Rect r;
getItemRect(pos, r);
byte* d = (byte *)_surf.getBasePtr(r.left, r.top);
drawItem(name, d, _surf.pitch);
}
void InventoryRenderer::drawItem(ItemName name, byte *buffer, uint pitch) {
byte* s = _vm->_objects->getData(name);
byte* d = buffer;
for (uint i = 0; i < _props->_itemHeight; i++) {
memcpy(d, s, _props->_itemWidth);
s += _props->_itemPitch;
d += pitch;
}
}
int16 InventoryRenderer::getNumLines() const {
int16 num = _inv->getNumItems();
return (num / _props->_itemsPerLine) + ((num % _props->_itemsPerLine) > 0 ? 1 : 0);
}
void InventoryRenderer::refresh() {
for (uint16 i = 0; i < _props->_maxItems; i++) {
ItemName name = _inv->getItemName(i);
drawItem(i, name);
}
}
void InventoryRenderer::highlightItem(ItemPosition pos, byte color) {
if (pos == -1)
return;
Common::Rect r;
getItemRect(pos, r);
if (color != 12)
color = 19;
_surf.frameRect(r, color);
}
void InventoryRenderer::getItemRect(ItemPosition pos, Common::Rect &r) {
r.setHeight(_props->_itemHeight);
r.setWidth(_props->_itemWidth);
uint16 line = pos / _props->_itemsPerLine;
uint16 col = pos % _props->_itemsPerLine;
r.moveTo(col * _props->_itemWidth, line * _props->_itemHeight);
}
Inventory::Inventory(int maxItems, InventoryItem *verbs) : _numItems(0), _maxItems(maxItems) {
_items = (InventoryItem *)calloc(_maxItems, sizeof(InventoryItem));
int i = 0;
for ( ; verbs[i]._id; i++) {
addItem(verbs[i]._id, verbs[i]._index);
}
_numVerbs = i;
}
Inventory::~Inventory() {
free(_items);
}
ItemPosition Inventory::addItem(ItemName name, uint32 value) {
debugC(1, kDebugInventory, "addItem(%i, %i)", name, value);
if (_numItems == _maxItems) {
debugC(3, kDebugInventory, "addItem: inventory is full");
return -1;
}
// NOTE: items whose name == 0 aren't really inventory items,
// but the engine expects the inventory to accept them as valid.
// This nasty trick has been discovered because of regression
// after r29060.
if (name == 0)
return 0;
_items[_numItems]._id = value;
_items[_numItems]._index = name;
_numItems++;
debugC(3, kDebugInventory, "addItem: done");
return _numItems;
}
ItemPosition Inventory::addItem(ItemName name) {
return addItem(name, MAKE_INVENTORY_ID(name));
}
ItemPosition Inventory::findItem(ItemName name) const {
for (ItemPosition slot = 0; slot < _numItems; slot++) {
if (name == _items[slot]._index)
return slot;
}
return -1;
}
void Inventory::removeItem(ItemName name) {
debugC(1, kDebugInventory, "removeItem(%i)", name);
ItemPosition pos = findItem(name);
if (pos == -1) {
debugC(3, kDebugInventory, "removeItem: can't find item, nothing to remove");
return;
}
_numItems--;
if (_numItems != pos) {
memmove(&_items[pos], &_items[pos+1], (_numItems - pos) * sizeof(InventoryItem));
}
_items[_numItems]._id = 0;
_items[_numItems]._index = 0;
debugC(3, kDebugInventory, "removeItem: item removed");
}
void Inventory::clear(bool keepVerbs) {
debugC(1, kDebugInventory, "clearInventory()");
uint first = (keepVerbs ? _numVerbs : 0);
for (uint16 slot = first; slot < _numItems; slot++) {
_items[slot]._id = 0;
_items[slot]._index = 0;
}
_numItems = first;
}
ItemName Inventory::getItemName(ItemPosition pos) const {
return (pos >= 0 && pos < _maxItems) ? _items[pos]._index : 0;
}
const InventoryItem* Inventory::getItem(ItemPosition pos) const {
return &_items[pos];
}
void Parallaction_ns::initInventory() {
_inventory = new Inventory(_invProps_NS._maxItems, _verbs_NS);
assert(_inventory);
_inventoryRenderer = new InventoryRenderer(this, &_invProps_NS);
assert(_inventoryRenderer);
_inventoryRenderer->setInventory(_inventory);
}
void Parallaction_br::initInventory() {
_inventoryRenderer = new InventoryRenderer(this, &_invProps_BR);
assert(_inventoryRenderer);
_dinoInventory = new Inventory(_invProps_BR._maxItems, _verbs_BR);
_donnaInventory = new Inventory(_invProps_BR._maxItems, _verbs_BR);
_dougInventory = new Inventory(_invProps_BR._maxItems, _verbs_BR);
}
void Parallaction_ns::destroyInventory() {
delete _inventoryRenderer;
delete _inventory;
_inventory = nullptr;
_inventoryRenderer = nullptr;
}
void Parallaction_br::destroyInventory() {
delete _inventoryRenderer;
_inventory = nullptr;
_inventoryRenderer = nullptr;
delete _dinoInventory;
delete _donnaInventory;
delete _dougInventory;
_dinoInventory = nullptr;
_donnaInventory = nullptr;
_dougInventory = nullptr;
}
} // namespace Parallaction

View File

@@ -0,0 +1,120 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_INVENTORY_H
#define PARALLACTION_INVENTORY_H
#include "graphics/surface.h"
namespace Parallaction {
class Parallaction;
struct InventoryItem {
uint32 _id; // object name (lowest 16 bits are always zero)
uint16 _index; // index to frame in objs file
};
struct InventoryProperties {
uint _itemPitch;
uint _itemWidth;
uint _itemHeight;
int _maxItems;
int _itemsPerLine;
int _maxLines;
int _width;
int _height;
};
#define MAKE_INVENTORY_ID(x) (((x) & 0xFFFF) << 16)
typedef int16 ItemPosition;
typedef uint16 ItemName;
class Inventory {
protected:
uint16 _numVerbs;
InventoryItem *_items;
uint16 _numItems;
int _maxItems;
public:
Inventory(int maxItems, InventoryItem *verbs);
virtual ~Inventory();
ItemPosition addItem(ItemName name, uint32 value);
ItemPosition addItem(ItemName item);
void removeItem(ItemName name);
void clear(bool keepVerbs = true);
const InventoryItem* getItem(ItemPosition pos) const;
ItemName getItemName(ItemPosition pos) const;
ItemPosition findItem(ItemName name) const;
int16 getNumItems() const { return _numItems; }
};
class InventoryRenderer {
Parallaction *_vm;
InventoryProperties *_props;
Inventory *_inv;
Common::Point _pos;
Graphics::Surface _surf;
protected:
void getItemRect(ItemPosition pos, Common::Rect &r);
void drawItem(ItemPosition pos, ItemName name);
void refresh();
public:
InventoryRenderer(Parallaction *vm, InventoryProperties *props);
virtual ~InventoryRenderer();
void setInventory(Inventory *inventory);
void showInventory();
void hideInventory();
ItemPosition hitTest(const Common::Point &p) const;
void highlightItem(ItemPosition pos, byte color);
void drawItem(ItemName name, byte *buffer, uint pitch);
byte *getData() { return (byte *)_surf.getPixels(); }
void getRect(Common::Rect &r) const;
int16 getNumLines() const;
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,220 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/plugins.h"
#include "common/config-manager.h"
#include "engines/advancedDetector.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/standard-actions.h"
#include "parallaction/parallaction.h"
#include "parallaction/detection.h"
namespace Parallaction {
int Parallaction::getGameType() const { return _gameDescription->gameType; }
uint32 Parallaction::getFeatures() const { return _gameDescription->features; }
Common::Language Parallaction::getLanguage() const { return _gameDescription->desc.language; }
Common::Platform Parallaction::getPlatform() const { return _gameDescription->desc.platform; }
} // End of namespace Parallaction
#ifdef USE_TTS
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_TTS,
{
_s("Enable Text to Speech"),
_s("Use TTS to read the descriptions (if TTS is available)"),
"tts_enabled",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
#endif
class ParallactionMetaEngine : public AdvancedMetaEngine<Parallaction::PARALLACTIONGameDescription> {
public:
const char *getName() const override {
return "parallaction";
}
#ifdef USE_TTS
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
#endif
bool hasFeature(MetaEngineFeature f) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const Parallaction::PARALLACTIONGameDescription *desc) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
bool removeSaveState(const char *target, int slot) const override;
Common::String getSavegameFile(int saveGameIdx, const char *target) const override {
if (!target)
target = getName();
const Common::String prefix = ConfMan.getDomain(target)->getVal("gameid");
if (saveGameIdx == kSavegameFilePattern)
return prefix + ".###";
else
return prefix + Common::String::format(".%03d", saveGameIdx);
}
};
bool ParallactionMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSimpleSavesNames) ||
(f == kSupportsDeleteSave);
}
bool Parallaction::Parallaction::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher);
}
Common::Error ParallactionMetaEngine::createInstance(OSystem *syst, Engine **engine, const Parallaction::PARALLACTIONGameDescription *gd) const {
switch (gd->gameType) {
case Parallaction::GType_Nippon:
*engine = new Parallaction::Parallaction_ns(syst, gd);
break;
case Parallaction::GType_BRA:
*engine = new Parallaction::Parallaction_br(syst, gd);
break;
default:
return Common::kUnsupportedGameidError;
}
return Common::kNoError;
}
Common::KeymapArray ParallactionMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
Common::String gameId = ConfMan.get("gameid", target);
Keymap *engineKeymap;
if (gameId == "nippon")
engineKeymap = new Keymap(Keymap::kKeymapTypeGame, "parallaction", "Nippon Safes, Inc.");
else if (gameId == "bra")
engineKeymap = new Keymap(Keymap::kKeymapTypeGame, "parallaction", "The Big Red Adventure");
else
return AdvancedMetaEngine::initKeymaps(target);
Action *act;
act = new Action(kStandardActionLeftClick, _("Left click"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
engineKeymap->addAction(act);
act = new Action(kStandardActionRightClick, _("Right click"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
engineKeymap->addAction(act);
if (gameId == "nippon") {
act = new Action(kStandardActionLoad, _("Load"));
act->setKeyEvent(KeyState(KEYCODE_l, 'l'));
act->addDefaultInputMapping("l");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
engineKeymap->addAction(act);
act = new Action(kStandardActionSave, _("Save"));
act->setKeyEvent(KeyState(KEYCODE_s, 's'));
act->addDefaultInputMapping("s");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
engineKeymap->addAction(act);
} else if (gameId == "bra") {
act = new Action(kStandardActionOpenMainMenu, _("Game menu"));
act->setKeyEvent(KeyState(KEYCODE_F5, ASCII_F5));
act->addDefaultInputMapping("F5");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
engineKeymap->addAction(act);
act = new Action("YES", _("Yes"));
act->setKeyEvent(KeyState(KEYCODE_y, 'y'));
act->addDefaultInputMapping("y");
act->addDefaultInputMapping("JOY_Y");
engineKeymap->addAction(act);
act = new Action("NO", _("No"));
act->setKeyEvent(KeyState(KEYCODE_n, 'n'));
act->addDefaultInputMapping("n");
act->addDefaultInputMapping("JOY_X");
engineKeymap->addAction(act);
}
return Keymap::arrayOf(engineKeymap);
}
SaveStateList ParallactionMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames = saveFileMan->listSavefiles(getSavegameFilePattern(target));
SaveStateList saveList;
for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
// Obtain the last 2 digits of the filename, since they correspond to the save slot
int slotNum = atoi(file->c_str() + file->size() - 2);
if (slotNum >= 0 && slotNum <= 99) {
Common::InSaveFile *in = saveFileMan->openForLoading(*file);
if (in) {
Common::String saveDesc = in->readLine();
saveList.push_back(SaveStateDescriptor(this, slotNum, saveDesc));
delete in;
}
}
}
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
int ParallactionMetaEngine::getMaximumSaveSlot() const { return 99; }
bool ParallactionMetaEngine::removeSaveState(const char *target, int slot) const {
return g_system->getSavefileManager()->removeSavefile(getSavegameFile(slot, target));
}
#if PLUGIN_ENABLED_DYNAMIC(PARALLACTION)
REGISTER_PLUGIN_DYNAMIC(PARALLACTION, PLUGIN_TYPE_ENGINE, ParallactionMetaEngine);
#else
REGISTER_PLUGIN_STATIC(PARALLACTION, PLUGIN_TYPE_ENGINE, ParallactionMetaEngine);
#endif

View File

@@ -0,0 +1,46 @@
MODULE := engines/parallaction
MODULE_OBJS := \
adlib.o \
balloons.o \
callables_br.o \
callables_ns.o \
debug.o \
dialogue.o \
disk_br.o \
disk_ns.o \
exec.o \
exec_br.o \
exec_ns.o \
font.o \
gfxbase.o \
graphics.o \
gui.o \
gui_br.o \
gui_ns.o \
input.o \
inventory.o \
metaengine.o \
objects.o \
parallaction.o \
parallaction_br.o \
parallaction_ns.o \
parser.o \
parser_br.o \
parser_ns.o \
saveload.o \
sound_ns.o \
sound_br.o \
staticres.o \
walk.o
# This module can be built as a plugin
ifeq ($(ENABLE_PARALLACTION), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

View File

@@ -0,0 +1,458 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/textconsole.h"
#include "parallaction/parallaction.h"
#include "parallaction/objects.h"
#include "parallaction/parser.h"
namespace Parallaction {
Command::Command() {
_id = 0;
_flagsOn = 0;
_flagsOff = 0;
_valid = false;
_flags = 0;
_callable = 0;
_object = 0;
_counterValue = 0;
_zeta0 = 0;
_zeta1 = 0;
_zeta2 = 0;
_musicCommand = 0;
_musicParm = 0;
}
Animation::Animation() {
gfxobj = nullptr;
_frame = 0;
_z = 0;
}
Animation::~Animation() {
if (gfxobj) {
gfxobj->release();
}
}
void Animation::getFrameRect(Common::Rect &r) const {
r.setWidth(0); r.setHeight(0);
if (!gfxobj) {
return;
}
gfxobj->getRect(_frame, r);
r.translate(_left, _top);
}
bool Animation::hitFrameRect(int x, int y) const {
if (!gfxobj) {
return false;
}
Common::Rect r;
getFrameRect(r);
return r.contains(x, y);
}
int16 Animation::getBottom() const {
int bottom = _top;
if (gfxobj) {
Common::Rect r;
getFrameRect(r);
bottom = r.bottom;
}
return bottom;
}
void Animation::resetZ() {
setZ(getBottom());
}
uint16 Animation::getFrameNum() const {
if (!gfxobj) return 0;
return gfxobj->getNum();
}
byte* Animation::getFrameData() const {
if (!gfxobj) return nullptr;
return gfxobj->getData(_frame);
}
void Animation::setF(int16 value) {
int16 min = MIN(0, getFrameNum() - 1);
int16 max = MAX(0, getFrameNum() - 1);
_frame = CLIP(value, min, max);
}
void Animation::forceXYZF(int16 x, int16 y, int16 z, int16 f) {
_left = x;
_top = y;
_z = z;
_frame = f;
}
void Animation::getFoot(Common::Point &foot) {
Common::Rect rect;
gfxobj->getRect(_frame, rect);
foot.x = getX() + (rect.left + rect.width() / 2);
foot.y = getY() + (rect.top + rect.height());
}
void Animation::setFoot(const Common::Point &foot) {
Common::Rect rect;
gfxobj->getRect(_frame, rect);
setX(foot.x - (rect.left + rect.width() / 2));
setY(foot.y - (rect.top + rect.height()));
}
#define NUM_LOCALS 10
char _localNames[NUM_LOCALS][10];
Program::Program() {
_loopCounter = 0;
_locals = new LocalVariable[NUM_LOCALS];
_numLocals = 0;
_status = kProgramIdle;
_ip = 0;
_loopStart = 0;
}
Program::~Program() {
delete[] _locals;
}
int16 Program::findLocal(const char* name) {
for (uint16 _si = 0; _si < NUM_LOCALS; _si++) {
if (!scumm_stricmp(name, _localNames[_si]))
return _si;
}
return -1;
}
int16 Program::addLocal(const char *name, int16 value, int16 min, int16 max) {
assert(_numLocals < NUM_LOCALS);
Common::strlcpy(_localNames[_numLocals], name, 10);
_locals[_numLocals].setRange(min, max);
_locals[_numLocals].setValue(value);
return _numLocals++;
}
void LocalVariable::setValue(int16 value) {
if (value >= _max)
value = _min;
if (value < _min)
value = _max - 1;
_value = value;
}
void LocalVariable::setRange(int16 min, int16 max) {
_max = max;
_min = min;
}
int16 LocalVariable::getValue() const {
return _value;
}
Zone::Zone() {
_left = _top = _right = _bottom = 0;
_type = 0;
_flags = kFlagsNoName;
_label = nullptr;
// BRA specific
_index = INVALID_ZONE_INDEX;
_locationIndex = INVALID_LOCATION_INDEX;
}
Zone::~Zone() {
g_vm->_gfx->unregisterLabel(_label);
delete _label;
}
void Zone::translate(int16 x, int16 y) {
_left += x;
_right += x;
_top += y;
_bottom += y;
}
bool Zone::hitRect(int x, int y) const {
// The scripts of Nippon Safes are full of invalid rectangles, used to
// provide 'special' features.
if (_right < _left || _bottom < _top) {
return false;
}
Common::Rect r(_left, _top, _right + 1, _bottom + 1);
r.grow(-1);
return r.contains(x, y);
}
Dialogue::Dialogue() {
memset(_questions, 0, sizeof(_questions));
_numQuestions = 0;
}
Dialogue::~Dialogue() {
for (int i = 0; i < NUM_QUESTIONS; i++) {
delete _questions[i];
}
}
Question *Dialogue::findQuestion(const Common::String &name) const {
for (uint i = 0; _questions[i]; ++i) {
if (_questions[i]->_name == name) {
return _questions[i];
}
}
return nullptr;
}
void Dialogue::addQuestion(Question *q) {
assert(_numQuestions < NUM_QUESTIONS);
assert(q);
_questions[_numQuestions] = q;
_numQuestions++;
}
Answer::Answer() {
_mood = 0;
_noFlags = 0;
_yesFlags = 0;
_hasCounterCondition = false;
_counterValue = 0;
_counterOp = 0;
}
bool Answer::textIsNull() {
return (_text.equalsIgnoreCase("NULL"));
}
int Answer::speakerMood() {
return _mood & 0xF;
}
Question::Question(const Common::String &name) : _name(name), _mood(0) {
memset(_answers, 0, sizeof(_answers));
}
Question::~Question() {
for (uint32 i = 0; i < NUM_ANSWERS; i++) {
delete _answers[i];
}
}
bool Question::textIsNull() {
return (_text.equalsIgnoreCase("NULL"));
}
int Question::speakerMood() {
return _mood & 0xF;
}
int Question::balloonWinding() {
return _mood & 0x10;
}
Instruction::Instruction() {
_index = 0;
_flags = 0;
// common
_immediate = 0;
_endif = 0;
// BRA specific
_y = 0;
}
int16 ScriptVar::getValue() {
if (_flags & kParaImmediate) {
return _value;
}
if (_flags & kParaLocal) {
return _local->getValue();
}
if (_flags & kParaField) {
return _field->getValue();
}
if (_flags & kParaRandom) {
return (g_vm->_rnd.getRandomNumber(65536) * _value) >> 16;
}
error("Parameter is not an r-value");
return 0; // for compilers that don't support NORETURN
}
void ScriptVar::setValue(int16 value) {
if ((_flags & kParaLValue) == 0) {
error("Only l-value can be set");
}
if (_flags & kParaLocal) {
_local->setValue(value);
}
if (_flags & kParaField) {
_field->setValue(value);
}
}
void ScriptVar::setLocal(LocalVariable *local) {
_local = local;
_flags |= (kParaLocal | kParaLValue);
}
void ScriptVar::setField(Animation *anim, AnimationField::AccessorFunc accessor, AnimationField::MutatorFunc mutator) {
_field = new AnimationField(anim, accessor, mutator);
_flags |= (kParaField | kParaLValue);
}
void ScriptVar::setField(Animation *anim, AnimationField::AccessorFunc accessor) {
_field = new AnimationField(anim, accessor);
_flags |= kParaField;
}
void ScriptVar::setImmediate(int16 value) {
_value = value;
_flags |= kParaImmediate;
}
void ScriptVar::setRandom(int16 seed) {
_value = seed;
_flags |= kParaRandom;
}
ScriptVar::ScriptVar() {
_flags = 0;
_local = nullptr;
_value = 0;
_field = nullptr;
}
ScriptVar::~ScriptVar() {
delete _field;
}
Table::Table(uint32 size) : _size(size), _used(0), _disposeMemory(true) {
_data = (char**)calloc(size, sizeof(char *));
}
Table::Table(uint32 size, const char **data) : _size(size), _used(size), _disposeMemory(false) {
_data = const_cast<char **>(data);
}
Table::~Table() {
if (!_disposeMemory) return;
clear();
free(_data);
}
void Table::addData(const char* s) {
if (!(_used < _size))
error("Table overflow");
size_t ln = strlen(s) + 1;
char *data = (char *)malloc(ln);
Common::strcpy_s(data, ln, s);
_data[_used++] = data;
}
uint16 Table::lookup(const char* s) {
for (uint16 i = 0; i < _used; i++) {
if (!scumm_stricmp(_data[i], s)) return i + 1;
}
return notFound;
}
void Table::clear() {
for (uint32 i = 0; i < _used; i++)
free(_data[i]);
_used = 0;
}
const char *Table::item(uint index) const {
assert(index < _used);
return _data[index];
}
FixedTable::FixedTable(uint32 size, uint32 fixed) : Table(size), _numFixed(fixed) {
}
void FixedTable::clear() {
uint32 deleted = 0;
for (uint32 i = _numFixed; i < _used; i++) {
free(_data[i]);
_data[i] = nullptr;
deleted++;
}
_used -= deleted;
}
Table* createTableFromStream(uint32 size, Common::SeekableReadStream *stream) {
assert(stream);
Table *t = new Table(size);
assert(t);
Script s(stream, false);
s.readLineToken();
while (scumm_stricmp(_tokens[0], "ENDTABLE")) {
t->addData(_tokens[0]);
s.readLineToken();
}
delete stream;
return t;
}
} // namespace Parallaction

View File

@@ -0,0 +1,548 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_ZONE_H
#define PARALLACTION_ZONE_H
#include "common/list.h"
#include "common/ptr.h"
#include "parallaction/graphics.h"
namespace Parallaction {
struct Zone;
struct Animation;
struct Command;
struct Question;
struct Answer;
struct Instruction;
struct Program;
typedef Common::SharedPtr<Zone> ZonePtr;
typedef Common::List<ZonePtr> ZoneList;
typedef Common::SharedPtr<Animation> AnimationPtr;
typedef Common::List<AnimationPtr> AnimationList;
typedef Common::SharedPtr<Instruction> InstructionPtr;
typedef Common::Array<InstructionPtr> InstructionList;
typedef Common::List<Common::Point> PointList;
enum ZoneTypes {
kZoneExamine = 1, // zone displays comment if activated
kZoneDoor = 2, // zone activated on click (after some walk if needed)
kZoneGet = 3, // for pickable items
kZoneMerge = 4, // tags items which can be merged in inventory
kZoneTaste = 5, // NEVER USED
kZoneHear = 6, // NEVER USED: they ran out of time before integrating sfx
kZoneFeel = 7, // NEVER USED
kZoneSpeak = 8, // tags NPCs the character can talk with
kZoneNone = 9, // used to prevent parsing on peculiar Animations
kZoneTrap = 10, // zone activated when character enters
kZoneYou = 11, // marks the character
kZoneCommand = 12,
// BRA specific
kZonePath = 13, // defines nodes for assisting walk calculation routines
kZoneBox = 14
};
enum ZoneFlags {
kFlagsClosed = 1, // Zone: door is closed / switch is off
kFlagsActive = 2, // Zone/Animation: object is visible
kFlagsRemove = 4, // Zone/Animation: object is soon to be removed
kFlagsActing = 8, // Animation: script execution is active
kFlagsLocked = 0x10, // Zone: door or switch cannot be toggled
kFlagsFixed = 0x20, // Zone: Zone item cannot be picked up
kFlagsNoName = 0x40, // Zone with no name (used to prevent some kEvEnterZone events)
kFlagsNoMasked = 0x80, // Animation is to be drawn ignoring z buffer
kFlagsLooping = 0x100, // Animation: script is to be executed repeatedly
kFlagsAdded = 0x200, // NEVER USED in Nippon Safes
kFlagsCharacter = 0x400, //
kFlagsNoWalk = 0x800, // Zone: character doesn't need to walk towards object to interact
// BRA specific
kFlagsYourself = 0x1000, // BRA: marks zones used by the character on him/herself
kFlagsScaled = 0x2000,
kFlagsSelfuse = 0x4000, // BRA: marks zones to be preserved across location changes (see Parallaction::freeZones)
kFlagsIsAnimation = 0x1000000, // BRA: used in walk code (trap check), to tell is a Zone is an Animation
kFlagsAnimLinked = 0x2000000
};
enum CommandFlags : uint {
kFlagsAll = 0xFFFFFFFFU,
kFlagsVisited = 1,
kFlagsExit = 0x10000000,
kFlagsEnter = 0x20000000,
kFlagsGlobal = 0x40000000,
// BRA specific
kFlagsTestTrue = 2
};
struct Command {
uint16 _id;
uint32 _flagsOn;
uint32 _flagsOff;
bool _valid;
Command();
// Common fields
uint32 _flags;
ZonePtr _zone;
Common::String _zoneName;
Common::String _string;
uint16 _callable;
uint16 _object;
Common::Point _move;
// BRA specific
Common::Point _startPos;
Common::Point _startPos2;
Common::String _counterName;
int _counterValue;
int _zeta0;
int _zeta1;
int _zeta2;
Common::String _characterName;
Common::String _string2;
int _musicCommand;
int _musicParm;
};
typedef Common::SharedPtr<Command> CommandPtr;
typedef Common::List<CommandPtr> CommandList;
#define NUM_QUESTIONS 40
#define NUM_ANSWERS 20
struct Answer {
Common::String _text;
uint16 _mood;
Common::String _followingName;
CommandList _commands;
uint32 _noFlags;
uint32 _yesFlags;
// BRA specific
bool _hasCounterCondition;
Common::String _counterName;
int _counterValue;
int _counterOp;
Answer();
bool textIsNull();
int speakerMood();
};
struct Question {
Common::String _name;
Common::String _text;
uint16 _mood;
Answer* _answers[NUM_ANSWERS];
Question(const Common::String &name);
~Question();
bool textIsNull();
int speakerMood();
int balloonWinding();
};
struct Dialogue {
Question *_questions[NUM_QUESTIONS];
uint _numQuestions;
Question *findQuestion(const Common::String &name) const;
void addQuestion(Question *q);
Dialogue();
~Dialogue();
};
#define MAX_WALKPOINT_LISTS 20
#define FREE_HEAR_CHANNEL -1
#define MUSIC_HEAR_CHANNEL -2
struct TypeData {
// common
GfxObj *_gfxobj; // get, examine, door
Common::String _filename; // speak, examine, hear
// get
uint32 _getIcon;
// speak
Dialogue *_speakDialogue;
// examine
Common::String _examineText;
// door
Common::String _doorLocation;
Common::Point _doorStartPos;
uint16 _doorStartFrame;
Common::Point _doorStartPos2_br;
uint16 _doorStartFrame2_br;
// hear
int _hearChannel;
int _hearFreq;
// merge
uint32 _mergeObj1;
uint32 _mergeObj2;
uint32 _mergeObj3;
// path
int _pathNumLists;
PointList _pathLists[MAX_WALKPOINT_LISTS];
TypeData() {
_gfxobj = 0;
_getIcon = 0;
_speakDialogue = 0;
_doorStartFrame = 0;
_doorStartPos.x = -1000;
_doorStartPos.y = -1000;
_doorStartFrame2_br = 0;
_doorStartPos2_br.x = -1000;
_doorStartPos2_br.y = -1000;
_hearChannel = FREE_HEAR_CHANNEL;
_hearFreq = -1;
_mergeObj1 = 0;
_mergeObj2 = 0;
_mergeObj3 = 0;
_pathNumLists = 0;
}
~TypeData() {
if (_gfxobj) {
_gfxobj->release();
}
delete _speakDialogue;
}
};
#define ACTIONTYPE(z) ((z)->_type & 0xFFFF)
#define ITEMTYPE(z) ((z)->_type & 0xFFFF0000)
#define PACK_ZONETYPE(zt,it) (((zt) & 0xFFFF) | (((it) & 0xFFFF) << 16))
#define ZONENAME_LENGTH 32
#define INVALID_LOCATION_INDEX ((uint32)-1)
#define INVALID_ZONE_INDEX ((uint32)-1)
struct Zone {
private:
int16 _right;
int16 _bottom;
protected:
int16 _left;
int16 _top;
public:
char _name[ZONENAME_LENGTH];
uint32 _type;
uint32 _flags;
GfxObj *_label;
TypeData u;
CommandList _commands;
Common::Point _moveTo;
// BRA specific
uint _index;
uint _locationIndex;
Common::String _linkedName;
AnimationPtr _linkedAnim;
Zone();
virtual ~Zone();
void translate(int16 x, int16 y);
bool hitRect(int x, int y) const;
void setRect(int16 left, int16 top, int16 right, int16 bottom) {
setX(left);
setY(top);
_right = right;
_bottom = bottom;
}
void getRect(Common::Rect& r) {
r.left = _left; r.right = _right;
r.top = _top; r.bottom = _bottom;
}
// getters/setters
virtual int16 getX() { return _left; }
virtual void setX(int16 value) { _left = value; }
virtual int16 getY() { return _top; }
virtual void setY(int16 value) { _top = value; }
};
struct LocalVariable {
protected:
int16 _value;
int16 _min;
int16 _max;
public:
LocalVariable() {
_value = 0;
_min = -10000;
_max = 10000;
}
void setRange(int16 min, int16 max);
int16 getValue() const;
void setValue(int16 value);
};
enum ParaFlags {
kParaImmediate = 1, // instruction is using an immediate parameter
kParaLocal = 2, // instruction is using a local variable
kParaField = 0x10, // instruction is using an animation's field
kParaRandom = 0x100,
kParaLValue = 0x20
};
struct AnimationField {
typedef Common::Functor0Mem<int16, Animation> Accessor;
typedef Common::Functor1Mem<int16, void, Animation> Mutator;
typedef Accessor::FuncType AccessorFunc;
typedef Mutator::FuncType MutatorFunc;
protected:
Accessor *_accessor;
Mutator *_mutator;
public:
AnimationField(Animation* instance, AccessorFunc accessor, MutatorFunc mutator) {
_accessor = new Accessor(instance, accessor);
_mutator = new Mutator(instance, mutator);
}
AnimationField(Animation* instance, AccessorFunc accessor) {
_accessor = new Accessor(instance, accessor);
_mutator = 0;
}
~AnimationField() {
delete _accessor;
delete _mutator;
}
int16 getValue() const {
assert(_accessor);
return (*_accessor)();
}
void setValue(int16 value) {
assert(_mutator);
(*_mutator)(value);
}
};
struct ScriptVar {
uint32 _flags;
int16 _value;
LocalVariable* _local;
AnimationField* _field;
ScriptVar();
~ScriptVar();
int16 getValue();
void setValue(int16 value);
void setLocal(LocalVariable *local);
void setField(Animation *anim, AnimationField::AccessorFunc accessor, AnimationField::MutatorFunc mutator);
void setField(Animation *anim, AnimationField::AccessorFunc accessor);
void setImmediate(int16 value);
void setRandom(int16 seed);
};
enum InstructionFlags {
kInstMod = 4,
kInstMaskedPut = 8,
kInstUnk20 = 0x20
};
struct Instruction {
uint32 _index;
uint32 _flags;
// common
AnimationPtr _a;
ZonePtr _z;
int16 _immediate;
ScriptVar _opA;
ScriptVar _opB;
// BRA specific
byte _colors[3];
ScriptVar _opC;
Common::String _text;
Common::String _text2;
int _y;
uint32 _endif;
Instruction();
};
enum {
kProgramIdle, // awaiting execution
kProgramRunning, // running
kProgramDone // execution completed
};
struct Program {
AnimationPtr _anim;
LocalVariable *_locals;
uint16 _loopCounter;
uint16 _numLocals;
uint32 _ip;
uint32 _loopStart;
InstructionList _instructions;
uint32 _status;
Program();
~Program();
int16 findLocal(const char* name);
int16 addLocal(const char *name, int16 value = 0, int16 min = -10000, int16 max = 10000);
};
typedef Common::SharedPtr<Program> ProgramPtr;
typedef Common::List<ProgramPtr> ProgramList;
struct Animation : public Zone {
protected:
int16 _frame;
int16 _z;
public:
GfxObj *gfxobj;
Common::String _scriptName;
Animation();
~Animation() override;
uint16 getFrameNum() const;
byte* getFrameData() const;
void resetZ();
bool hitFrameRect(int x, int y) const;
void getFrameRect(Common::Rect &r) const;
int16 getBottom() const;
// HACK: this routine is only used to download initialisation
// parameter to a script used when moving sarcophagi around in
// the museum. It bypasses all the consistency checks that
// can be performed by the individual setters. See the comment
// in startMovingSarcophagus() in callables_ns.cpp
void forceXYZF(int16 x, int16 y, int16 z, int16 f);
// getters/setters used by scripts
int16 getX() override { return _left; }
void setX(int16 value) override { _left = value; }
int16 getY() override { return _top; }
void setY(int16 value) override { _top = value; }
int16 getZ() { return _z; }
void setZ(int16 value) { _z = value; }
int16 getF() { return _frame; }
void setF(int16 value);
void getFoot(Common::Point &foot);
void setFoot(const Common::Point &foot);
};
class Table {
protected:
char **_data;
uint16 _size;
uint16 _used;
bool _disposeMemory;
public:
Table(uint32 size);
Table(uint32 size, const char** data);
virtual ~Table();
enum {
notFound = 0
};
uint count() const { return _used; }
const char *item(uint index) const;
virtual void addData(const char* s);
virtual void clear();
virtual uint16 lookup(const char* s);
};
class FixedTable : public Table {
uint16 _numFixed;
public:
FixedTable(uint32 size, uint32 fixed);
void clear() override;
};
Table* createTableFromStream(uint32 size, Common::SeekableReadStream *stream);
} // namespace Parallaction
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,697 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_PARALLACTION_H
#define PARALLACTION_PARALLACTION_H
#include "common/str.h"
#include "common/stack.h"
#include "common/array.h"
#include "common/func.h"
#include "common/random.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "common/text-to-speech.h"
#include "engines/engine.h"
#include "parallaction/input.h"
#include "parallaction/inventory.h"
#include "parallaction/objects.h"
#include "parallaction/disk.h"
#include "parallaction/detection.h"
#define PATH_LEN 200
/**
* This is the namespace of the Parallaction engine.
*
* Status of this engine: ???
*
* Games using this engine:
* - Nippon Safes Inc. (complete)
* - The Big Red Adventure (work in progress)
*/
namespace Parallaction {
enum {
kDebugDisk = 1,
kDebugWalk,
kDebugParser,
kDebugDialogue,
kDebugGraphics,
kDebugExec,
kDebugInput,
kDebugAudio,
kDebugMenu,
kDebugInventory,
};
enum EngineFlags {
kEnginePauseJobs = (1 << 1),
kEngineWalking = (1 << 3),
kEngineChangeLocation = (1 << 4),
kEngineBlockInput = (1 << 5),
kEngineDragging = (1 << 6),
kEngineTransformedDonna = (1 << 7),
// BRA specific
kEngineReturn = (1 << 10)
};
enum {
kEvNone = 0,
kEvSaveGame = 2000,
kEvLoadGame = 4000,
kEvIngameMenu = 8000
};
enum LanguageIndex {
kItalian = 0,
kFrench = 1,
kEnglish = 2,
kGerman = 3
};
extern uint32 g_engineFlags;
extern char g_saveData1[];
extern uint32 g_globalFlags;
extern const char *g_dinoName;
extern const char *g_donnaName;
extern const char *g_doughName;
extern const char *g_drkiName;
extern const char *g_minidinoName;
extern const char *g_minidonnaName;
extern const char *g_minidoughName;
extern const char *g_minidrkiName;
class Debugger;
class Gfx;
class Input;
class DialogueManager;
class MenuInputHelper;
class PathWalker_NS;
class PathWalker_BR;
class CommandExec;
class ProgramExec;
class SoundMan;
class SoundMan_ns;
class SoundMan_br;
class LocationParser_ns;
class LocationParser_br;
class ProgramParser_ns;
class ProgramParser_br;
class BalloonManager;
struct Location {
Common::Point _startPosition;
uint16 _startFrame;
char _name[100];
CommandList _aCommands;
CommandList _commands;
Common::String _comment;
Common::String _endComment;
ZoneList _zones;
AnimationList _animations;
ProgramList _programs;
bool _hasSound;
char _soundFile[50];
// NS specific
PointList _walkPoints;
Common::String _slideText[2];
// BRA specific
int _zeta0;
int _zeta1;
int _zeta2;
CommandList _escapeCommands;
Common::Point _followerStartPosition;
uint16 _followerStartFrame;
protected:
int _gameType;
bool keepZone_br(ZonePtr z);
bool keepZone_ns(ZonePtr z);
bool keepAnimation_ns(AnimationPtr a);
bool keepAnimation_br(AnimationPtr a);
template<class T>
void freeList(Common::List<T> &list, bool removeAll, Common::MemFunc1<bool, T, Location> filter);
public:
Location(int gameType);
~Location();
AnimationPtr findAnimation(const char *name);
ZonePtr findZone(const char *name);
void cleanup(bool removeAll);
void freeZones(bool removeAll);
int getScale(int z) const;
};
class CharacterName {
const char *_prefix;
const char *_suffix;
bool _dummy;
char _name[30];
char _baseName[30];
char _fullName[30];
static const char _prefixMini[];
static const char _suffixTras[];
static const char _empty[];
void dummify();
public:
CharacterName();
CharacterName(const char *name);
void bind(const char *name);
const char *getName() const;
const char *getBaseName() const;
const char *getFullName() const;
bool dummy() const;
};
struct Character {
AnimationPtr _ani;
GfxObj *_head;
GfxObj *_talk;
Character();
protected:
CharacterName _name;
public:
void setName(const char *name);
const char *getName() const;
const char *getBaseName() const;
const char *getFullName() const;
bool dummy() const;
};
#ifdef USE_TTS
struct CharacterVoiceData {
const char *characterName;
uint8 voiceID;
bool male;
};
static const CharacterVoiceData characterVoiceDatas[] = {
{ nullptr, 0, true }, // Used as the narrator for cutscene text
{ "dough", 1, true },
{ "donna", 0, false },
{ "dino", 2, true },
{ "drki", 3, true },
{ "ddd.talk", 1, true },
{ nullptr, 0, false }, // Donna in ddd.talk
{ nullptr, 2, true }, // Dino in ddd.talk
{ "police.talk", 4, true },
{ "vecchio.talk", 5, true },
{ "karaoke.talk", 6, true },
{ "suicida.talk", 7, true },
{ "direttore.talk", 8, true },
{ "guardiano.talk", 9, true },
{ "sento.talk", 10, true },
{ "giocatore.talk", 11, true },
{ "mona.talk", 12, true },
{ "monaco2.talk", 13, true },
{ "uccello.talk", 14, true },
{ "guru.talk", 15, true },
{ "monaco1.talk", 16, true },
{ "segretario.talk", 17, true },
{ "imperatore.talk", 18, true },
{ "secondo.talk", 19, true },
{ "taxista.talk", 20, true },
{ "bemutsu.talk", 21, true },
{ "negro.talk", 22, true },
{ "punk.talk", 23, true },
{ "chan.talk", 24, true },
{ "donna0.talk", 0, false },
{ "maxkos.talk", 25, true },
{ nullptr, 26, true }, // Second person in the maxkos.talk dialogue
{ "drki.talk", 3, true },
{ "barman.talk", 27, true },
{ "losco.talk", 28, true },
{ "mitsu.talk", 29, true },
{ "autoradio.talk", 30, true },
{ "passa1.talk", 31, true },
{ "passa2.talk", 1, false },
{ "passa3.talk", 32, true },
{ "giornalaio.talk", 33, true },
{ "pazza1.talk", 2, false },
{ "pazza2.talk", 3, false },
{ "pazza3.talk", 4, false },
{ "citofono.talk", 34, true },
{ "perdelook.talk", 0, false },
{ "tele.talk", 35, true },
{ "dough.talk", 1, true },
{ "donna.talk", 0, false },
{ "guanti.talk", 36, true },
{ "cuoco.talk", 37, true },
{ "punks.talk", 38, true },
{ nullptr, 39, true }, // Second person in the punks.talk dialogue
{ "punko.talk", 40, true },
{ "hotdog.talk", 41, true },
{ "cameriera.talk", 5, false },
{ "rice1.talk", 42, true },
{ nullptr, 43, true }, // Second person in the rice1.talk dialogue
{ "segretaria.talk", 6, false },
{ "robot.talk", 44, true },
{ "portiere.talk", 45, true },
{ "figaro.talk", 46, true },
{ "ominos.talk", 47, true },
{ "cassiera.talk", 7, false },
{ "dino.talk", 2, true }
};
#endif
enum PlayableCharacterVoiceID {
kDoug = 1,
kDonna = 2,
kDino = 3,
kDrki = 4
};
static const int kNarratorVoiceID = 0;
class SaveLoad;
#define NUM_LOCATIONS 120
class Parallaction : public Engine {
friend class Debugger;
public:
int getGameType() const;
uint32 getFeatures() const;
Common::Language getLanguage() const;
Common::Platform getPlatform() const;
protected: // members
bool detectGame();
private:
const PARALLACTIONGameDescription *_gameDescription;
uint16 _language;
public:
Parallaction(OSystem *syst, const PARALLACTIONGameDescription *gameDesc);
~Parallaction() override;
// Engine APIs
virtual Common::Error init();
virtual Common::Error go() = 0;
Common::Error run() override {
Common::Error err;
err = init();
if (err.getCode() != Common::kNoError)
return err;
return go();
}
bool hasFeature(EngineFeature f) const override;
void pauseEngineIntern(bool pause) override;
// info
int32 _screenWidth;
int32 _screenHeight;
int32 _screenSize;
int _gameType;
// subsystems
Gfx *_gfx;
Disk *_disk;
Input *_input;
SaveLoad *_saveLoad;
MenuInputHelper *_menuHelper;
Common::RandomSource _rnd;
SoundMan *_soundMan;
// fonts
Font *_labelFont;
Font *_menuFont;
Font *_introFont;
Font *_dialogueFont;
// game utilities
Table *_globalFlagsNames;
Table *_objectsNames;
GfxObj *_objects;
Table *_callableNames;
Table *_localFlagNames;
CommandExec *_cmdExec;
ProgramExec *_programExec;
BalloonManager *_balloonMan;
DialogueManager *_dialogueMan;
InventoryRenderer *_inventoryRenderer;
Inventory *_inventory; // inventory for the current character
// game data
Character _char;
uint32 _localFlags[NUM_LOCATIONS];
char _locationNames[NUM_LOCATIONS][32];
int16 _currentLocationIndex;
uint16 _numLocations;
Location _location;
ZonePtr _activeZone;
char _characterName1[50]; // only used in changeCharacter
int _characterVoiceID;
ZonePtr _zoneTrap;
ZonePtr _commentZone;
Common::String _newLocationName;
protected:
void runGame();
void runGameFrame(int event);
void runGuiFrame();
void cleanupGui();
void runDialogueFrame();
void exitDialogueMode();
void runCommentFrame();
void enterCommentMode(ZonePtr z);
void exitCommentMode();
void updateView();
void drawAnimation(AnimationPtr anim);
void drawZone(ZonePtr zone);
void updateZones();
void doLocationEnterTransition();
void allocateLocationSlot(const char *name);
void finalizeLocationParsing();
void showLocationComment(const Common::String &text, bool end);
void destroyDialogueManager();
public:
void beep();
void pauseJobs();
void resumeJobs();
uint getInternLanguage();
void setInternLanguage(uint id);
void enterDialogueMode(ZonePtr z);
void scheduleLocationSwitch(const char *location);
void showSlide(const char *name, int x = 0, int y = 0);
public:
void setLocationFlags(uint32 flags);
void clearLocationFlags(uint32 flags);
void toggleLocationFlags(uint32 flags);
uint32 getLocationFlags();
bool checkSpecialZoneBox(ZonePtr z, uint32 type, uint x, uint y);
bool checkZoneBox(ZonePtr z, uint32 type, uint x, uint y);
bool checkZoneType(ZonePtr z, uint32 type);
bool checkLinkedAnimBox(ZonePtr z, uint32 type, uint x, uint y);
ZonePtr hitZone(uint32 type, uint16 x, uint16 y);
void runZone(ZonePtr z);
bool pickupItem(ZonePtr z);
void updateDoor(ZonePtr z, bool close);
void showZone(ZonePtr z, bool visible);
void highlightInventoryItem(ItemPosition pos);
int16 getHoverInventoryItem(int16 x, int16 y);
int addInventoryItem(ItemName item);
int addInventoryItem(ItemName item, uint32 value);
void dropItem(uint16 v);
bool isItemInInventory(int32 v);
const InventoryItem* getInventoryItem(int16 pos);
int16 getInventoryItemIndex(int16 pos);
void openInventory();
void closeInventory();
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const;
void setTTSVoice(int id) const;
void stopTextToSpeech() const;
virtual void parseLocation(const char* name) = 0;
virtual void changeLocation() = 0;
virtual void changeCharacter(const char *name) = 0;
virtual void callFunction(uint index, void* parm) = 0;
virtual void runPendingZones() = 0;
virtual void cleanupGame() = 0;
virtual void updateWalkers() = 0;
virtual void scheduleWalk(int16 x, int16 y, bool fromUser) = 0;
virtual DialogueManager *createDialogueManager(ZonePtr z) = 0;
virtual bool processGameEvent(int event) = 0;
};
class Parallaction_ns : public Parallaction {
public:
Parallaction_ns(OSystem* syst, const PARALLACTIONGameDescription *gameDesc);
~Parallaction_ns() override;
// Engine APIs
Common::Error init() override;
Common::Error go() override;
SoundMan_ns* _soundManI;
uint16 _score;
Common::String _password;
bool _endCredits;
public:
void parseLocation(const char *filename) override;
void changeLocation() override;
void changeCharacter(const char *name) override;
void callFunction(uint index, void* parm) override;
void runPendingZones() override;
void cleanupGame() override;
void updateWalkers() override;
void scheduleWalk(int16 x, int16 y, bool fromUser) override;
DialogueManager *createDialogueManager(ZonePtr z) override;
bool processGameEvent(int event) override;
void cleanInventory(bool keepVerbs);
void changeBackground(const char *background, const char *mask = 0, const char *path = 0);
private:
bool _inTestResult;
LocationParser_ns *_locationParser;
ProgramParser_ns *_programParser;
private:
void initFonts();
void freeFonts();
void initResources();
void initInventory();
void destroyInventory();
void setupBalloonManager();
void startGui();
void startCreditSequence();
void startEndPartSequence();
void loadProgram(AnimationPtr a, const char *filename);
void freeLocation(bool removeAll);
void freeCharacter();
void destroyTestResultLabels();
void startMovingSarcophagus(ZonePtr sarc);
void stopMovingSarcophagus();
// callables data
typedef void (Parallaction_ns::*Callable)(void *);
const Callable *_callables;
ZonePtr _moveSarcGetZone;
ZonePtr _moveSarcExaZone;
ZonePtr _moveSarcGetZones[5];
ZonePtr _moveSarcExaZones[5];
uint16 num_foglie;
int16 _sarcophagusDeltaX;
bool _movingSarcophagus; // sarcophagus stuff to be saved
uint16 _freeSarcophagusSlotX; // sarcophagus stuff to be saved
AnimationPtr _rightHandAnim;
bool _intro;
static const Callable _dosCallables[25];
static const Callable _amigaCallables[25];
GfxObj *_testResultLabels[2];
PathWalker_NS *_walker;
// common callables
void _c_play_boogie(void *);
void _c_startIntro(void *);
void _c_endIntro(void *);
void _c_moveSheet(void *);
void _c_sketch(void *);
void _c_shade(void *);
void _c_score(void *);
void _c_fade(void *);
void _c_moveSarc(void *);
void _c_contaFoglie(void *);
void _c_zeroFoglie(void *);
void _c_trasformata(void *);
void _c_offMouse(void *);
void _c_onMouse(void *);
void _c_setMask(void *);
void _c_endComment(void *);
void _c_frankenstein(void *);
void _c_finito(void *);
void _c_ridux(void *);
void _c_testResult(void *);
// dos specific callables
void _c_null(void *);
// amiga specific callables
void _c_projector(void *);
void _c_HBOff(void *);
void _c_offSound(void *);
void _c_startMusic(void *);
void _c_closeMusic(void *);
void _c_HBOn(void *);
};
#define NUM_ZONES 100
class Parallaction_br : public Parallaction {
public:
Parallaction_br(OSystem* syst, const PARALLACTIONGameDescription *gameDesc);
~Parallaction_br() override;
Common::Error init() override;
Common::Error go() override;
public:
void parseLocation(const char* name) override;
void changeLocation() override;
void changeCharacter(const char *name) override;
void callFunction(uint index, void* parm) override;
void runPendingZones() override;
void cleanupGame() override;
void updateWalkers() override;
void scheduleWalk(int16 x, int16 y, bool fromUser) override;
DialogueManager *createDialogueManager(ZonePtr z) override;
bool processGameEvent(int event) override;
void setupSubtitles(const char *s, const char *s2, int y);
void clearSubtitles();
Inventory *findInventory(const char *name);
void linkUnlinkedZoneAnimations();
void testCounterCondition(const Common::String &name, int op, int value);
void restoreOrSaveZoneFlags(ZonePtr z, bool restore);
public:
bool counterExists(const Common::String &name);
int getCounterValue(const Common::String &name);
void setCounterValue(const Common::String &name, int value);
void setFollower(const Common::String &name);
int getSfxStatus();
int getMusicStatus();
void enableSfx(bool enable);
void enableMusic(bool enable);
const char **_audioCommandsNamesRes;
static const char *_partNames[];
int _part;
int _nextPart;
#if 0 // disabled since I couldn't find any references to lip sync in the scripts
int16 _lipSyncVal;
uint _subtitleLipSync;
#endif
int _subtitleY;
GfxObj *_subtitle[2];
ZonePtr _activeZone2;
uint32 _zoneFlags[NUM_LOCATIONS][NUM_ZONES];
private:
LocationParser_br *_locationParser;
ProgramParser_br *_programParser;
SoundMan_br *_soundManI;
Inventory *_dinoInventory;
Inventory *_donnaInventory;
Inventory *_dougInventory;
int32 _counters[32];
Table *_countersNames;
private:
void initResources();
void initInventory();
void destroyInventory();
void cleanInventory(bool keepVerbs);
void setupBalloonManager();
void initFonts();
void freeFonts();
void freeLocation(bool removeAll);
void loadProgram(AnimationPtr a, const char *filename);
void startGui(bool showSplash);
void startIngameMenu();
void freeCharacter();
typedef void (Parallaction_br::*Callable)(void *);
const Callable *_callables;
static const Callable _dosCallables[6];
static const Callable _amigaCallables[6];
Common::String _followerName;
AnimationPtr _follower;
PathWalker_BR *_walker;
int _ferrcycleMode;
// dos callables
void _c_null(void *);
void _c_blufade(void *);
void _c_resetpalette(void *);
void _c_ferrcycle(void *);
void _c_lipsinc(void *);
void _c_albcycle(void *);
void _c_password(void *);
};
extern Parallaction *g_vm;
} // End of namespace Parallaction
#endif

View File

@@ -0,0 +1,620 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "common/util.h"
#include "common/textconsole.h"
#include "parallaction/parallaction.h"
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parser.h"
#include "parallaction/saveload.h"
#include "parallaction/sound.h"
#include "parallaction/walk.h"
namespace Parallaction {
const char *Parallaction_br::_partNames[] = {
"PART0",
"PART1",
"PART2",
"PART3",
"PART4"
};
Parallaction_br::Parallaction_br(OSystem* syst, const PARALLACTIONGameDescription *gameDesc) : Parallaction(syst, gameDesc),
_locationParser(nullptr), _programParser(nullptr), _soundManI(nullptr) {
_audioCommandsNamesRes = nullptr;
_part = 0;
_nextPart = 0;
_subtitleY = 0;
_subtitle[0] = nullptr;
_subtitle[1] = nullptr;
_countersNames = nullptr;
_callables = nullptr;
_walker = nullptr;
}
Common::Error Parallaction_br::init() {
_screenWidth = 640;
_screenHeight = 400;
if (getPlatform() == Common::kPlatformDOS) {
if (getFeatures() & GF_DEMO) {
_disk = new DosDemoDisk_br(this);
} else {
_disk = new DosDisk_br(this);
}
_disk->setLanguage(2); // NOTE: language is now hardcoded to English. Original used command-line parameters.
_soundManI = new DosSoundMan_br(this);
} else {
_disk = new AmigaDisk_br(this);
_disk->setLanguage(2); // NOTE: language is now hardcoded to English. Original used command-line parameters.
_soundManI = new AmigaSoundMan_br(this);
}
_disk->init();
_soundMan = new SoundMan(_soundManI);
initResources();
initFonts();
_locationParser = new LocationParser_br(this);
_locationParser->init();
_programParser = new ProgramParser_br(this);
_programParser->init();
_cmdExec = new CommandExec_br(this);
_programExec = new ProgramExec_br(this);
_walker = new PathWalker_BR(this);
_part = -1;
_nextPart = -1;
_subtitle[0] = nullptr;
_subtitle[1] = nullptr;
memset(_zoneFlags, 0, sizeof(_zoneFlags));
_countersNames = nullptr;
_saveLoad = new SaveLoad_br(this, _saveFileMan);
initInventory();
setupBalloonManager();
Parallaction::init();
return Common::kNoError;
}
Parallaction_br::~Parallaction_br() {
freeFonts();
freeCharacter();
destroyInventory();
delete _objects;
delete _locationParser;
delete _programParser;
_location._animations.remove(_char._ani);
delete _walker;
}
void Parallaction_br::callFunction(uint index, void* parm) {
assert(index < 6); // magic value 6 is maximum # of callables for Big Red Adventure
(this->*_callables[index])(parm);
}
bool Parallaction_br::processGameEvent(int event) {
if (event == kEvNone) {
return true;
}
bool c = true;
_input->stopHovering();
switch (event) {
case kEvIngameMenu:
startIngameMenu();
c = false;
break;
default:
break;
}
_input->setArrowCursor();
return c;
}
Common::Error Parallaction_br::go() {
bool splash = true;
while (!shouldQuit()) {
if (getFeatures() & GF_DEMO) {
scheduleLocationSwitch("camalb");
_nextPart = 1;
_input->_inputMode = Input::kInputModeGame;
} else {
_input->setMenuPointer();
startGui(splash);
// don't show splash after first time
splash = false;
}
// initCharacter();
while (((g_engineFlags & kEngineReturn) == 0) && (!shouldQuit())) {
runGame();
}
g_engineFlags &= ~kEngineReturn;
cleanupGame();
}
return Common::kNoError;
}
void Parallaction_br::freeFonts() {
delete _menuFont;
_menuFont = nullptr;
delete _dialogueFont;
_dialogueFont = nullptr;
// no need to delete _labelFont, since it is using the same buffer as _menuFont
_labelFont = nullptr;
}
void Parallaction_br::runPendingZones() {
ZonePtr z;
_cmdExec->runSuspended();
if (_activeZone) {
z = _activeZone; // speak Zone or sound
_activeZone.reset();
if (ACTIONTYPE(z) == kZoneSpeak && z->u._speakDialogue) {
enterDialogueMode(z);
} else {
runZone(z); // FIXME: BRA doesn't handle sound yet
}
}
if (_activeZone2) {
z = _activeZone2; // speak Zone or sound
_activeZone2.reset();
if (ACTIONTYPE(z) == kZoneSpeak && z->u._speakDialogue) {
enterDialogueMode(z);
} else {
runZone(z); // FIXME: BRA doesn't handle sound yet
}
}
}
void Parallaction_br::freeCharacter() {
_gfx->freeCharacterObjects();
delete _char._talk;
delete _char._ani->gfxobj;
_char._talk = nullptr;
_char._ani->gfxobj = nullptr;
}
void Parallaction_br::freeLocation(bool removeAll) {
// free open location stuff
clearSubtitles();
_localFlagNames->clear();
_gfx->freeLocationObjects();
// save zone and animation flags
ZoneList::iterator zit = _location._zones.begin();
for ( ; zit != _location._zones.end(); ++zit) {
restoreOrSaveZoneFlags(*zit, false);
}
AnimationList::iterator ait = _location._animations.begin();
for ( ; ait != _location._animations.end(); ++ait) {
restoreOrSaveZoneFlags(*ait, false);
}
_location._animations.remove(_char._ani);
_location.cleanup(removeAll);
_location._animations.push_front(_char._ani);
}
void Parallaction_br::cleanupGame() {
freeLocation(true);
freeCharacter();
delete _globalFlagsNames;
delete _objectsNames;
delete _countersNames;
_globalFlagsNames = nullptr;
_objectsNames = nullptr;
_countersNames = nullptr;
_numLocations = 0;
g_globalFlags = 0;
memset(_localFlags, 0, sizeof(_localFlags));
memset(_locationNames, 0, sizeof(_locationNames));
memset(_zoneFlags, 0, sizeof(_zoneFlags));
}
void Parallaction_br::changeLocation() {
if (_newLocationName.empty()) {
return;
}
if (_nextPart != -1) {
cleanupGame();
// more cleanup needed for part changes (see also saveload)
g_globalFlags = 0;
cleanInventory(true);
Common::strcpy_s(_characterName1, "null");
_part = _nextPart;
if (getFeatures() & GF_DEMO) {
assert(_part == 1);
} else {
assert(_part >= 0 && _part <= 4);
}
_disk->selectArchive(_partNames[_part]);
memset(_counters, 0, sizeof(_counters));
_globalFlagsNames = _disk->loadTable("global");
_objectsNames = _disk->loadTable("objects");
_countersNames = _disk->loadTable("counters");
// TODO: maybe handle this into Disk
delete _objects;
if (getPlatform() == Common::kPlatformDOS) {
_objects = _disk->loadObjects("icone.ico");
} else {
_objects = _disk->loadObjects("icons.ico", _part);
}
parseLocation("common.slf");
}
freeLocation(false);
// load new location
Common::strlcpy(_location._name, _newLocationName.c_str(), 100);
parseLocation(_location._name);
if (_location._startPosition.x != -1000) {
_char._ani->setFoot(_location._startPosition);
_char._ani->setF(_location._startFrame);
}
// re-link the follower animation
setFollower(_followerName);
if (_follower) {
Common::Point p = _location._followerStartPosition;
if (p.x == -1000) {
_char._ani->getFoot(p);
}
_follower->setFoot(p);
_follower->setF(_location._followerStartFrame);
}
_location._startPosition.x = -1000;
_location._startPosition.y = -1000;
_location._followerStartPosition.x = -1000;
_location._followerStartPosition.y = -1000;
_gfx->setScrollPosX(0);
_gfx->setScrollPosY(0);
if (_char._ani->gfxobj) {
Common::Point foot;
_char._ani->getFoot(foot);
if (foot.x > 550)
_gfx->setScrollPosX(320);
if (foot.y > 350)
_gfx->setScrollPosY(foot.y - 350);
}
// kFlagsRemove is cleared because the character is visible by default.
// Commands can hide the character, anyway.
_char._ani->_flags &= ~kFlagsRemove;
_cmdExec->run(_location._commands);
doLocationEnterTransition();
_cmdExec->run(_location._aCommands);
// NOTE: music should not started here!
// TODO: implement the music commands which control music execution
_soundMan->execute(SC_PLAYMUSIC);
g_engineFlags &= ~kEngineChangeLocation;
_newLocationName.clear();
_nextPart = -1;
}
// FIXME: Parallaction_br::parseLocation() is now a verbatim copy of the same routine from Parallaction_ns.
void Parallaction_br::parseLocation(const char *filename) {
debugC(1, kDebugParser, "parseLocation('%s')", filename);
// find a new available slot
allocateLocationSlot(filename);
Script *script = _disk->loadLocation(filename);
// parse the text file
LocationParserOutput_br out;
_locationParser->parse(script, &out);
assert(out._info);
delete script;
bool visited = getLocationFlags() & kFlagsVisited;
// load background, mask and path
_disk->loadScenery(*out._info,
out._backgroundName.empty() ? nullptr : out._backgroundName.c_str(),
out._maskName.empty() ? nullptr : out._maskName.c_str(),
out._pathName.empty() ? nullptr : out._pathName.c_str());
// assign background
_gfx->setBackground(kBackgroundLocation, out._info);
// process zones
ZoneList::iterator zit = _location._zones.begin();
for ( ; zit != _location._zones.end(); ++zit) {
ZonePtr z = *zit;
// restore the flags if the location has already been visited
restoreOrSaveZoneFlags(z, visited);
// (re)link the bounding animation if needed
if (z->_flags & kFlagsAnimLinked) {
z->_linkedAnim = _location.findAnimation(z->_linkedName.c_str());
}
bool visible = (z->_flags & kFlagsRemove) == 0;
if (visible) {
showZone(z, visible);
}
}
// load the character (must be done before animations are processed)
if (!out._characterName.empty()) {
changeCharacter(out._characterName.c_str());
}
// process animations
AnimationList::iterator ait = _location._animations.begin();
for ( ; ait != _location._animations.end(); ++ait) {
// restore the flags if the location has already been visited
restoreOrSaveZoneFlags(*ait, visited);
// load the script
if (!(*ait)->_scriptName.empty()) {
loadProgram(*ait, (*ait)->_scriptName.c_str());
}
}
debugC(1, kDebugParser, "parseLocation('%s') done", filename);
return;
}
void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) {
debugC(1, kDebugParser, "loadProgram(Animation: %s, script: %s)", a->_name, filename);
Script *script = _disk->loadScript(filename);
ProgramPtr program(new Program);
program->_anim = a;
_programParser->parse(script, program);
delete script;
_location._programs.push_back(program);
debugC(1, kDebugParser, "loadProgram() done");
return;
}
void Parallaction_br::linkUnlinkedZoneAnimations() {
ZoneList::iterator zit = _location._zones.begin();
for ( ; zit != _location._zones.end(); ++zit) {
if ((*zit)->_flags & kFlagsActive) {
(*zit)->_linkedAnim = _location.findAnimation((*zit)->_linkedName.c_str());
}
}
}
void Parallaction_br::changeCharacter(const char *name) {
const char *charName = _char.getName();
if (scumm_stricmp(charName, name)) {
freeCharacter();
debugC(1, kDebugExec, "changeCharacter(%s)", name);
_char.setName(name);
_char._ani->gfxobj = _gfx->loadCharacterAnim(name);
_char._talk = _disk->loadTalk(name);
_inventory = findInventory(name);
_inventoryRenderer->setInventory(_inventory);
_input->setCharacterPointer(name);
}
_char._ani->_flags |= kFlagsActive;
}
bool Parallaction_br::counterExists(const Common::String &name) {
return Table::notFound != _countersNames->lookup(name.c_str());
}
int Parallaction_br::getCounterValue(const Common::String &name) {
int index = _countersNames->lookup(name.c_str());
if (index != Table::notFound) {
return _counters[index - 1];
}
return 0;
}
void Parallaction_br::setCounterValue(const Common::String &name, int value) {
int index = _countersNames->lookup(name.c_str());
if (index != Table::notFound) {
_counters[index - 1] = value;
}
}
void Parallaction_br::testCounterCondition(const Common::String &name, int op, int value) {
int index = _countersNames->lookup(name.c_str());
if (index == Table::notFound) {
clearLocationFlags(kFlagsTestTrue);
return;
}
int c = _counters[index - 1];
// these definitions must match those in parser_br.cpp
#define CMD_TEST 25
#define CMD_TEST_GT 26
#define CMD_TEST_LT 27
bool res = false;
switch (op) {
case CMD_TEST:
res = (c == value);
break;
case CMD_TEST_GT:
res = (c > value);
break;
case CMD_TEST_LT:
res = (c < value);
break;
default:
error("unknown operator in testCounterCondition");
}
if (res) {
setLocationFlags(kFlagsTestTrue);
} else {
clearLocationFlags(kFlagsTestTrue);
}
}
void Parallaction_br::updateWalkers() {
_walker->walk();
}
void Parallaction_br::scheduleWalk(int16 x, int16 y, bool fromUser) {
AnimationPtr a = _char._ani;
if ((a->_flags & kFlagsRemove) || (a->_flags & kFlagsActive) == 0) {
return;
}
_walker->setCharacterPath(a, x, y);
if (!fromUser) {
_walker->stopFollower();
} else {
if (_follower) {
_walker->setFollowerPath(_follower, x, y);
}
}
g_engineFlags |= kEngineWalking;
}
void Parallaction_br::setFollower(const Common::String &name) {
if (name.empty()) {
_followerName.clear();
_follower.reset();
} else {
_followerName = name;
_follower = _location.findAnimation(name.c_str());
}
}
void Parallaction_br::restoreOrSaveZoneFlags(ZonePtr z, bool restore) {
if ((z->_locationIndex == INVALID_LOCATION_INDEX) || (z->_index == INVALID_ZONE_INDEX)) {
return;
}
if (restore) {
z->_flags = _zoneFlags[z->_locationIndex][z->_index];
} else {
_zoneFlags[z->_locationIndex][z->_index] = z->_flags;
}
}
int Parallaction_br::getSfxStatus() {
if (!_soundManI) {
return -1;
}
return _soundManI->isSfxEnabled() ? 1 : 0;
}
int Parallaction_br::getMusicStatus() {
if (!_soundManI) {
return -1;
}
return _soundManI->isMusicEnabled() ? 1 : 0;
}
void Parallaction_br::enableSfx(bool enable) {
if (_soundManI) {
_soundManI->enableSfx(enable);
}
}
void Parallaction_br::enableMusic(bool enable) {
if (_soundManI) {
_soundManI->enableMusic(enable);
}
}
} // namespace Parallaction

View File

@@ -0,0 +1,638 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
#include "parallaction/parallaction.h"
#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parser.h"
#include "parallaction/saveload.h"
#include "parallaction/sound.h"
#include "parallaction/walk.h"
namespace Parallaction {
#ifdef USE_TTS
static const char *openingCreditsFirstLine[] = {
"Programmatore Assoluto: Paolo Costabel", // Italian
"Programmeur Absolu: Paolo Costabel", // French
"Absolute Programmer: Paolo Costabel", // English
"Absoluter Programmierer: Paolo Costabel" // German
};
// Transcribed for TTS
// First four names of the end credits are best voiced as one block; the remaining credits
// are voiced one at a time elsewhere
static const char *endCreditsFirstSection[] = {
// Italian
"Il Dottor Buoz\n"
"Lo Studente\n"
"Il Poliziotto\n"
"Secondo il Secondino",
// French
"Le Professeur Buoz\n"
"Le \311tudiant\n"
"Le Police\n"
"Secondo le Garde",
// English
"Doctor Buoz\n"
"Student\n"
"Police\n"
"Secondo the Warder",
// German
"Professor Buoz\n"
"Der Student\n"
"Die Polizei\n"
"Secondo der W\344chter"
};
#endif
#define INITIAL_FREE_SARCOPHAGUS_SLOT_X 200
class LocationName {
Common::String _slide;
Common::String _character;
Common::String _location;
bool _hasCharacter;
bool _hasSlide;
Common::String _buf;
public:
LocationName() {
_hasSlide = false;
_hasCharacter = false;
}
void bind(const char*);
const char *location() const {
return _location.c_str();
}
bool hasCharacter() const {
return _hasCharacter;
}
const char *character() const {
return _character.c_str();
}
bool hasSlide() const {
return _hasSlide;
}
const char *slide() const {
return _slide.c_str();
}
const char *c_str() const {
return _buf.c_str();
}
};
/*
bind accept the following input formats:
1 - [S].slide.[L]{.[C]}
2 - [L]{.[C]}
where:
[S] is the slide to be shown
[L] is the location to switch to (immediately in case 2, or right after slide [S] in case 1)
[C] is the character to be selected, and is optional
The routine tells one form from the other by searching for the '.slide.'
NOTE: there exists one script in which [L] is not used in the case 1, but its use
is commented out, and would definitely crash the current implementation.
*/
void LocationName::bind(const char *s) {
_buf = s;
_hasSlide = false;
_hasCharacter = false;
Common::StringArray list;
char *tok = strtok(_buf.begin(), ".");
while (tok) {
list.push_back(tok);
tok = strtok(nullptr, ".");
}
if (list.size() < 1 || list.size() > 4)
error("changeLocation: ill-formed location name '%s'", s);
if (list.size() > 1) {
if (list[1] == "slide") {
_hasSlide = true;
_slide = list[0];
list.remove_at(0); // removes slide name
list.remove_at(0); // removes 'slide'
}
if (list.size() == 2) {
_hasCharacter = true;
_character = list[1];
}
}
_location = list[0];
_buf = s; // kept as reference
}
Parallaction_ns::Parallaction_ns(OSystem* syst, const PARALLACTIONGameDescription *gameDesc) : Parallaction(syst, gameDesc),
_locationParser(nullptr), _programParser(nullptr), _walker(nullptr) {
_soundManI = nullptr;
_score = 0;
_inTestResult = 0;
_callables = nullptr;
num_foglie = 0;
_sarcophagusDeltaX = 0;
_movingSarcophagus = 0;
_freeSarcophagusSlotX = 0;
_intro = 0;
_testResultLabels[0] = nullptr;
_testResultLabels[1] = nullptr;
}
Common::Error Parallaction_ns::init() {
_screenWidth = 320;
_screenHeight = 200;
if (getPlatform() == Common::kPlatformDOS) {
_disk = new DosDisk_ns(this);
} else {
if (getFeatures() & GF_DEMO) {
Common::strcpy_s(_location._name, "fognedemo");
}
_disk = new AmigaDisk_ns(this);
}
_disk->init();
if (getPlatform() == Common::kPlatformDOS) {
_soundManI = new DosSoundMan_ns(this);
_soundManI->setMusicVolume(ConfMan.getInt("music_volume"));
} else {
_soundManI = new AmigaSoundMan_ns(this);
}
_soundMan = new SoundMan(_soundManI);
initResources();
initFonts();
_locationParser = new LocationParser_ns(this);
_locationParser->init();
_programParser = new ProgramParser_ns(this);
_programParser->init();
_cmdExec = new CommandExec_ns(this);
_programExec = new ProgramExec_ns(this);
_walker = new PathWalker_NS(this);
_sarcophagusDeltaX = 0;
_movingSarcophagus = false;
_freeSarcophagusSlotX = INITIAL_FREE_SARCOPHAGUS_SLOT_X;
num_foglie = 0;
_intro = false;
_inTestResult = false;
_endCredits = false;
_location._animations.push_front(_char._ani);
_saveLoad = new SaveLoad_ns(this, _saveFileMan);
initInventory();
setupBalloonManager();
_score = 1;
_testResultLabels[0] = nullptr;
_testResultLabels[1] = nullptr;
Parallaction::init();
return Common::kNoError;
}
Parallaction_ns::~Parallaction_ns() {
freeFonts();
// TODO: we may want to add a ~Character instead
freeCharacter();
_char._ani.reset();
destroyInventory();
delete _locationParser;
delete _programParser;
freeLocation(true);
_location._animations.remove(_char._ani);
delete _walker;
destroyTestResultLabels();
}
void Parallaction_ns::destroyTestResultLabels() {
for (int i = 0; i < 2; ++i) {
_gfx->unregisterLabel(_testResultLabels[i]);
delete _testResultLabels[i];
_testResultLabels[i] = nullptr;
}
}
void Parallaction_ns::freeFonts() {
delete _dialogueFont;
delete _labelFont;
delete _menuFont;
delete _introFont;
_menuFont = nullptr;
_dialogueFont = nullptr;
_labelFont = nullptr;
_introFont = nullptr;
}
void Parallaction_ns::callFunction(uint index, void* parm) {
assert(index < 25); // magic value 25 is maximum # of callables for Nippon Safes
(this->*_callables[index])(parm);
}
bool Parallaction_ns::processGameEvent(int event) {
if (event == kEvNone) {
return true;
}
bool c = true;
_input->stopHovering();
switch (event) {
case kEvSaveGame:
_saveLoad->saveGame();
break;
case kEvLoadGame:
_saveLoad->loadGame();
break;
default:
break;
}
_input->setArrowCursor();
_input->setMouseState(MOUSE_ENABLED_SHOW);
return c;
}
Common::Error Parallaction_ns::go() {
_saveLoad->renameOldSavefiles();
_globalFlagsNames = _disk->loadTable("global");
startGui();
while (!shouldQuit()) {
runGame();
}
return Common::kNoError;
}
void Parallaction_ns::changeBackground(const char* background, const char* mask, const char* path) {
Palette pal;
uint16 v2 = 0;
if (!scumm_stricmp(background, "final")) {
_gfx->clearScreen();
for (uint16 _si = 0; _si < 32; _si++) {
pal.setEntry(_si, v2, v2, v2);
v2 += 4;
}
_system->delayMillis(20);
_gfx->setPalette(pal);
_gfx->updateScreen();
return;
}
if (path == nullptr) {
path = mask;
}
BackgroundInfo *info = new BackgroundInfo;
_disk->loadScenery(*info, background, mask, path);
_gfx->setBackground(kBackgroundLocation, info);
}
void Parallaction_ns::runPendingZones() {
if (_activeZone) {
ZonePtr z = _activeZone; // speak Zone or sound
_activeZone.reset();
runZone(z);
}
}
// changeLocation handles transitions between locations, and is able to display slides
// between one and the other.
//
void Parallaction_ns::changeLocation() {
if (_newLocationName.empty()) {
return;
}
char location[200];
Common::strlcpy(location, _newLocationName.c_str(), 200);
Common::strlcpy(_location._name, _newLocationName.c_str(), 100);
debugC(1, kDebugExec, "changeLocation(%s)", location);
MouseTriState oldMouseState = _input->getMouseState();
_input->setMouseState(MOUSE_DISABLED);
if (!_intro) {
// prevent music changes during the introduction
_soundManI->playLocationMusic(location);
}
_input->stopHovering();
// this is still needed to remove the floatingLabel
_gfx->freeLabels();
_zoneTrap.reset();
_input->setArrowCursor();
_gfx->showGfxObj(_char._ani->gfxobj, false);
LocationName locname;
locname.bind(location);
freeLocation(false);
if (locname.hasSlide()) {
showSlide(locname.slide());
GfxObj *label = _gfx->createLabel(_menuFont, _location._slideText[0].c_str(), 1);
_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 14);
_gfx->updateScreen();
_input->waitForButtonEvent(kMouseLeftUp);
_gfx->unregisterLabel(label);
delete label;
if (!locname.hasCharacter()) {
setTTSVoice(_characterVoiceID);
}
}
if (locname.hasCharacter()) {
changeCharacter(locname.character());
}
Common::strlcpy(g_saveData1, locname.location(), 30);
parseLocation(g_saveData1);
if (_location._startPosition.x != -1000) {
_char._ani->setX(_location._startPosition.x);
_char._ani->setY(_location._startPosition.y);
_char._ani->setF(_location._startFrame);
_location._startPosition.y = -1000;
_location._startPosition.x = -1000;
}
_gfx->setBlackPalette();
_gfx->updateScreen();
// BUG #3459: kEngineChangeLocation flag must be cleared *before* commands
// and acommands are executed, so that it can be set again if needed.
g_engineFlags &= ~kEngineChangeLocation;
_cmdExec->run(_location._commands);
doLocationEnterTransition();
_cmdExec->run(_location._aCommands);
if (_location._hasSound)
_soundManI->playSfx(_location._soundFile, 0, true);
if (!_intro) {
_input->setMouseState(oldMouseState);
// WORKAROUND: Fix a script bug in the Multilingual DOS version of
// Nippon Safes: the mouse cursor is incorrectly hidden outside the
// cave at the end of the game. Fix it here.
if (!strcmp(_location._name, "ingressocav"))
_input->setMouseState(MOUSE_ENABLED_SHOW);
#ifdef USE_TTS
else if (!strcmp(_location._name, "final")) { // End credits
setTTSVoice(kNarratorVoiceID);
sayText(endCreditsFirstSection[getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
_endCredits = true;
}
#endif
}
#ifdef USE_TTS
if (!strcmp(_location._name, "title1")) { // First screen of opening credits
setTTSVoice(kNarratorVoiceID);
sayText(openingCreditsFirstLine[getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
}
#endif
debugC(1, kDebugExec, "changeLocation() done");
_newLocationName.clear();
}
void Parallaction_ns::parseLocation(const char *filename) {
debugC(1, kDebugParser, "parseLocation('%s')", filename);
allocateLocationSlot(filename);
Script *script = _disk->loadLocation(filename);
// TODO: the following two lines are specific to Nippon Safes
// and should be moved into something like 'initializeParsing()'
_location._hasSound = false;
_locationParser->parse(script);
delete script;
// this loads animation scripts
AnimationList::iterator it = _location._animations.begin();
for ( ; it != _location._animations.end(); ++it) {
if (!(*it)->_scriptName.empty()) {
loadProgram(*it, (*it)->_scriptName.c_str());
}
}
debugC(1, kDebugParser, "parseLocation('%s') done", filename);
return;
}
void Parallaction_ns::changeCharacter(const char *name) {
debugC(1, kDebugExec, "changeCharacter(%s)", name);
_char.setName(name);
if (!scumm_stricmp(_char.getFullName(), _characterName1)) {
debugC(3, kDebugExec, "changeCharacter: nothing done");
return;
}
freeCharacter();
_char._ani->gfxobj = _gfx->loadCharacterAnim(_char.getFullName());
if (scumm_stricmp(_char.getBaseName(), g_doughName) == 0) {
_characterVoiceID = kDoug;
} else if (scumm_stricmp(_char.getBaseName(), g_donnaName) == 0) {
_characterVoiceID = kDonna;
} else if (scumm_stricmp(_char.getBaseName(), g_dinoName) == 0) {
_characterVoiceID = kDino;
} else {
_characterVoiceID = kDrki;
}
setTTSVoice(_characterVoiceID);
if (!_char.dummy()) {
_char._head = _disk->loadHead(_char.getBaseName());
_char._talk = _disk->loadTalk(_char.getBaseName());
_objects = _disk->loadObjects(_char.getBaseName());
_objectsNames = _disk->loadTable(_char.getBaseName());
if (!_intro) {
// prevent music changes during the introduction
_soundManI->playCharacterMusic(_char.getBaseName());
}
// The original engine used to reload 'common' only on loadgames. We are reloading here since 'common'
// contains character specific stuff. This causes crashes like bug #3440, because parseLocation tries
// to reload scripts but the data archive selected is occasionally wrong. This has been solved by having
// parseLocation only load scripts when they aren't already loaded - which it should have done since the
// beginning nevertheless.
if (!(getFeatures() & GF_DEMO))
parseLocation("common");
}
Common::strcpy_s(_characterName1, _char.getFullName());
debugC(3, kDebugExec, "changeCharacter: switch completed");
return;
}
void Parallaction_ns::freeCharacter() {
_gfx->freeCharacterObjects();
delete _char._talk;
delete _char._head;
delete _char._ani->gfxobj;
delete _objects;
delete _objectsNames;
_char._talk = nullptr;
_char._head = nullptr;
_char._ani->gfxobj = nullptr;
_objects = nullptr;
_objectsNames = nullptr;
}
void Parallaction_ns::freeLocation(bool removeAll) {
debugC(2, kDebugExec, "freeLocation");
_soundManI->stopSfx(0);
_soundManI->stopSfx(1);
_soundManI->stopSfx(2);
_soundManI->stopSfx(3);
_localFlagNames->clear();
_gfx->freeLocationObjects();
_location._animations.remove(_char._ani);
_location.cleanup(removeAll);
_location._animations.push_front(_char._ani);
}
void Parallaction_ns::cleanupGame() {
_soundManI->stopMusic();
_inTestResult = false;
g_engineFlags &= ~kEngineTransformedDonna;
_numLocations = 0;
g_globalFlags = 0;
memset(_localFlags, 0, sizeof(_localFlags));
memset(_locationNames, 0, sizeof(_locationNames));
_location.freeZones(true);
_score = 0;
_freeSarcophagusSlotX = INITIAL_FREE_SARCOPHAGUS_SLOT_X;
_movingSarcophagus = false;
}
void Parallaction_ns::updateWalkers() {
_walker->walk();
}
void Parallaction_ns::scheduleWalk(int16 x, int16 y, bool fromUser) {
AnimationPtr a = _char._ani;
if ((a->_flags & kFlagsRemove) || (a->_flags & kFlagsActive) == 0) {
return;
}
_walker->buildPath(a, x, y);
g_engineFlags |= kEngineWalking;
}
}// namespace Parallaction

View File

@@ -0,0 +1,262 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/textconsole.h"
#include "parallaction/parallaction.h"
#include "parallaction/parser.h"
namespace Parallaction {
#define MAX_TOKENS 50
int _numTokens;
char _tokens[MAX_TOKENS][MAX_TOKEN_LEN];
Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {}
Script::~Script() {
if (_disposeSource)
delete _input;
}
/*
* readLineIntern read a text line and prepares it for
* parsing, by stripping the leading whitespace and
* changing tabs to spaces. It will stop on a CR, LF, or
* SUB (0x1A), which may all occur at the end of a script
* line.
* Returns an empty string (length = 0) when a line
* has no printable text in it.
*/
char *Script::readLineIntern(char *buf, size_t bufSize) {
uint i = 0;
for ( ; i < bufSize; ) {
char c = _input->readSByte();
if (_input->eos())
break;
// break if EOL
if (c == '\n' || c == '\r' || c == (char)0x1A)
break;
if (c == '\t')
c = ' ';
if ((c != ' ') || (i > 0)) {
buf[i] = c;
i++;
}
}
_line++;
if (i == bufSize) {
warning("overflow in readLineIntern (line %i)", _line);
}
if (i == 0 && _input->eos()) {
return nullptr;
}
buf[i] = '\0';
return buf;
}
bool isCommentLine(char *text) {
return text[0] == '#';
}
bool isStartOfCommentBlock(char *text) {
return (text[0] == '[');
}
bool isEndOfCommentBlock(char *text) {
return (text[0] == ']');
}
char *Script::readLine(char *buf, size_t bufSize) {
bool inBlockComment = false;
bool ignoreLine = true;
char *line = nullptr;
do {
line = readLineIntern(buf, bufSize);
if (line == nullptr) {
return nullptr;
}
if (line[0] == '\0')
continue;
ignoreLine = false;
line = Common::ltrim(line);
if (isCommentLine(line)) {
// ignore this line
ignoreLine = true;
} else
if (isStartOfCommentBlock(line)) {
// mark this and the following lines as comment
inBlockComment = true;
} else
if (isEndOfCommentBlock(line)) {
// comment is finished, so stop ignoring
inBlockComment = false;
// the current line must be skipped, though,
// as it contains the end-of-comment marker
ignoreLine = true;
}
} while (inBlockComment || ignoreLine);
return line;
}
void Script::clearTokens() {
memset(_tokens, 0, sizeof(_tokens));
_numTokens = 0;
}
void Script::skip(const char* endToken) {
while (scumm_stricmp(_tokens[0], endToken)) {
readLineToken(true);
}
}
//
// Scans 's' until one of the stop-chars in 'brk' is found, building a token.
// If the routine encounters quotes, it will extract the contained text and
// make a proper token. When scanning inside quotes, 'brk' is ignored and
// only newlines are considered stop-chars.
//
// The routine returns the unparsed portion of the input string 's'.
//
char *Script::parseNextToken(char *s, char *tok, uint16 count, const char *brk) {
enum STATES { NORMAL, QUOTED };
STATES state = NORMAL;
while (count > 0) {
switch (state) {
case NORMAL:
if (*s == '\0') {
*tok = '\0';
return s;
} else
if (strchr(brk, *s)) {
*tok = '\0';
return ++s;
} else
if (*s == '"') {
state = QUOTED;
s++;
} else {
*tok++ = *s++;
count--;
}
break;
case QUOTED:
if (*s == '\0') {
*tok = '\0';
return s;
} else
if (*s == '"') {
*tok = '\0';
return ++s;
} else {
*tok++ = *s++;
count--;
}
break;
default:
break;
}
}
*tok = '\0';
// TODO: if execution flows here, make *REALLY* sure everything has been parsed
// out of the input string. This is what is supposed to happen, but never ever
// allocated time to properly check.
return tok;
}
uint16 Script::readLineToken(bool errorOnEOF) {
char buf[200];
char *line = readLine(buf, 200);
if (!line) {
if (errorOnEOF)
error("unexpected end of file while parsing");
else
return 0;
}
clearTokens();
while (*line && _numTokens < MAX_TOKENS) {
line = parseNextToken(line, _tokens[_numTokens], MAX_TOKEN_LEN, " ");
line = Common::ltrim(line);
_numTokens++;
}
return _numTokens;
}
void Parser::reset() {
_currentOpcodes = nullptr;
_currentStatements = nullptr;
_lookup = 0;
_statements.clear();
_opcodes.clear();
}
void Parser::pushTables(OpcodeSet *opcodes, Table *statements) {
_opcodes.push(_currentOpcodes);
_statements.push(_currentStatements);
_currentOpcodes = opcodes;
_currentStatements = statements;
}
void Parser::popTables() {
assert(_opcodes.size() > 0);
_currentOpcodes = _opcodes.pop();
_currentStatements = _statements.pop();
}
void Parser::parseStatement() {
assert(_currentOpcodes != nullptr);
_lookup = _currentStatements->lookup(_tokens[0]);
debugC(9, kDebugParser, "parseStatement: %s (lookup = %i)", _tokens[0], _lookup);
(*(*_currentOpcodes)[_lookup])();
}
} // End of namespace Parallaction

View File

@@ -0,0 +1,416 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_PARSER_H
#define PARALLACTION_PARSER_H
#include "common/stream.h"
#include "common/stack.h"
#include "parallaction/objects.h"
namespace Parallaction {
#define MAX_TOKEN_LEN 50
extern int _numTokens;
extern char _tokens[][MAX_TOKEN_LEN];
class Script {
Common::ReadStream *_input;
bool _disposeSource;
uint _line; // for debug messages
void clearTokens();
char *parseNextToken(char *s, char *tok, uint16 count, const char *brk);
char *readLineIntern(char *buf, size_t bufSize);
public:
Script(Common::ReadStream *, bool _disposeSource = false);
~Script();
char *readLine(char *buf, size_t bufSize);
uint16 readLineToken(bool errorOnEOF = false);
void skip(const char* endToken);
uint getLine() { return _line; }
};
typedef Common::Functor0<void> Opcode;
typedef Common::Array<const Opcode *> OpcodeSet;
class Parser {
public:
Parser() { reset(); }
~Parser() { reset(); }
uint _lookup;
Common::Stack<OpcodeSet *> _opcodes;
Common::Stack<Table *> _statements;
OpcodeSet *_currentOpcodes;
Table *_currentStatements;
void reset();
void pushTables(OpcodeSet *opcodes, Table* statements);
void popTables();
void parseStatement();
};
#define DECLARE_UNQUALIFIED_ZONE_PARSER(sig) void locZoneParse_##sig()
#define DECLARE_UNQUALIFIED_ANIM_PARSER(sig) void locAnimParse_##sig()
#define DECLARE_UNQUALIFIED_COMMAND_PARSER(sig) void cmdParse_##sig()
#define DECLARE_UNQUALIFIED_LOCATION_PARSER(sig) void locParse_##sig()
#define DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(sig) void instParse_##sig()
#define MAX_FORWARDS 50
class Parallaction_ns;
class Parallaction_br;
class LocationParser_ns {
protected:
Parallaction_ns* _vm;
Script *_script;
Parser *_parser;
Table *_zoneTypeNames;
Table *_zoneFlagNames;
uint _zoneProg;
// location parser
OpcodeSet _locationParsers;
OpcodeSet _locationZoneParsers;
OpcodeSet _locationAnimParsers;
OpcodeSet _commandParsers;
Table *_commandsNames;
Table *_locationStmt;
Table *_locationZoneStmt;
Table *_locationAnimStmt;
struct ParserContext {
bool end;
const char *filename;
ZonePtr z;
AnimationPtr a;
int nextToken;
CommandList *list;
bool endcommands;
CommandPtr cmd;
} ctxt;
void warning_unexpected();
DECLARE_UNQUALIFIED_LOCATION_PARSER(endlocation);
DECLARE_UNQUALIFIED_LOCATION_PARSER(location);
DECLARE_UNQUALIFIED_LOCATION_PARSER(disk);
DECLARE_UNQUALIFIED_LOCATION_PARSER(nodes);
DECLARE_UNQUALIFIED_LOCATION_PARSER(zone);
DECLARE_UNQUALIFIED_LOCATION_PARSER(animation);
DECLARE_UNQUALIFIED_LOCATION_PARSER(localflags);
DECLARE_UNQUALIFIED_LOCATION_PARSER(commands);
DECLARE_UNQUALIFIED_LOCATION_PARSER(acommands);
DECLARE_UNQUALIFIED_LOCATION_PARSER(flags);
DECLARE_UNQUALIFIED_LOCATION_PARSER(comment);
DECLARE_UNQUALIFIED_LOCATION_PARSER(endcomment);
DECLARE_UNQUALIFIED_LOCATION_PARSER(sound);
DECLARE_UNQUALIFIED_LOCATION_PARSER(music);
DECLARE_UNQUALIFIED_ZONE_PARSER(limits);
DECLARE_UNQUALIFIED_ZONE_PARSER(moveto);
DECLARE_UNQUALIFIED_ZONE_PARSER(type);
DECLARE_UNQUALIFIED_ZONE_PARSER(commands);
DECLARE_UNQUALIFIED_ZONE_PARSER(label);
DECLARE_UNQUALIFIED_ZONE_PARSER(flags);
DECLARE_UNQUALIFIED_ZONE_PARSER(endzone);
DECLARE_UNQUALIFIED_ZONE_PARSER(null);
DECLARE_UNQUALIFIED_ANIM_PARSER(script);
DECLARE_UNQUALIFIED_ANIM_PARSER(commands);
DECLARE_UNQUALIFIED_ANIM_PARSER(type);
DECLARE_UNQUALIFIED_ANIM_PARSER(label);
DECLARE_UNQUALIFIED_ANIM_PARSER(flags);
DECLARE_UNQUALIFIED_ANIM_PARSER(file);
DECLARE_UNQUALIFIED_ANIM_PARSER(position);
DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);
DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation);
DECLARE_UNQUALIFIED_COMMAND_PARSER(flags);
DECLARE_UNQUALIFIED_COMMAND_PARSER(animation);
DECLARE_UNQUALIFIED_COMMAND_PARSER(zone);
DECLARE_UNQUALIFIED_COMMAND_PARSER(location);
DECLARE_UNQUALIFIED_COMMAND_PARSER(invObject);
DECLARE_UNQUALIFIED_COMMAND_PARSER(call);
DECLARE_UNQUALIFIED_COMMAND_PARSER(simple);
DECLARE_UNQUALIFIED_COMMAND_PARSER(move);
DECLARE_UNQUALIFIED_COMMAND_PARSER(endcommands);
public:
virtual void parseGetData(ZonePtr z);
virtual void parseExamineData(ZonePtr z);
virtual void parseDoorData(ZonePtr z);
virtual void parseMergeData(ZonePtr z);
virtual void parseHearData(ZonePtr z);
virtual void parseSpeakData(ZonePtr z);
virtual void parseNoneData(ZonePtr z);
protected:
Common::String parseComment();
Common::String parseDialogueString();
Dialogue *parseDialogue();
virtual Answer *parseAnswer();
void parseAnswerFlags(Answer *answer);
void parseAnswerBody(Answer *answer);
void parseQuestion(Question *q);
uint32 buildZoneType(const char *t0, const char* t1);
void parseZone(ZoneList &list, char *name);
virtual void parseZoneTypeBlock(ZonePtr z);
void parsePointList(PointList &list);
void parseAnimation(AnimationList &list, char *name);
void parseCommands(CommandList&);
void parseCommandFlags();
void parseCommandFlag(CommandPtr cmd, const char *flag, Table *table);
void createCommand(uint id);
void addCommand();
void clearSet(OpcodeSet &opcodes) {
for (Common::Array<const Opcode *>::iterator i = opcodes.begin(); i != opcodes.end(); ++i)
delete *i;
opcodes.clear();
}
public:
LocationParser_ns(Parallaction_ns *vm) : _vm(vm), _commandsNames(0), _locationStmt(0),
_locationZoneStmt(0), _locationAnimStmt(0) {
_script = 0;
_parser = 0;
_zoneTypeNames = 0;
_zoneFlagNames = 0;
_zoneProg = 0;
}
virtual void init();
virtual ~LocationParser_ns() {
delete _parser;
delete _commandsNames;
delete _locationStmt;
delete _locationZoneStmt;
delete _locationAnimStmt;
delete _zoneTypeNames;
delete _zoneFlagNames;
clearSet(_commandParsers);
clearSet(_locationAnimParsers);
clearSet(_locationZoneParsers);
clearSet(_locationParsers);
}
void parse(Script *script);
};
struct LocationParserOutput_br {
BackgroundInfo *_info;
Common::String _characterName;
Common::String _backgroundName;
Common::String _maskName;
Common::String _pathName;
};
class LocationParser_br : public LocationParser_ns {
protected:
Parallaction_br* _vm;
Table *_audioCommandsNames;
LocationParserOutput_br *_out;
DECLARE_UNQUALIFIED_LOCATION_PARSER(location);
DECLARE_UNQUALIFIED_LOCATION_PARSER(zone);
DECLARE_UNQUALIFIED_LOCATION_PARSER(animation);
DECLARE_UNQUALIFIED_LOCATION_PARSER(localflags);
DECLARE_UNQUALIFIED_LOCATION_PARSER(flags);
DECLARE_UNQUALIFIED_LOCATION_PARSER(comment);
DECLARE_UNQUALIFIED_LOCATION_PARSER(endcomment);
DECLARE_UNQUALIFIED_LOCATION_PARSER(sound);
DECLARE_UNQUALIFIED_LOCATION_PARSER(music);
DECLARE_UNQUALIFIED_LOCATION_PARSER(redundant);
DECLARE_UNQUALIFIED_LOCATION_PARSER(ifchar);
DECLARE_UNQUALIFIED_LOCATION_PARSER(character);
DECLARE_UNQUALIFIED_LOCATION_PARSER(mask);
DECLARE_UNQUALIFIED_LOCATION_PARSER(path);
DECLARE_UNQUALIFIED_LOCATION_PARSER(escape);
DECLARE_UNQUALIFIED_LOCATION_PARSER(zeta);
DECLARE_UNQUALIFIED_LOCATION_PARSER(null);
DECLARE_UNQUALIFIED_COMMAND_PARSER(ifchar);
DECLARE_UNQUALIFIED_COMMAND_PARSER(endif);
DECLARE_UNQUALIFIED_COMMAND_PARSER(location);
DECLARE_UNQUALIFIED_COMMAND_PARSER(toggle);
DECLARE_UNQUALIFIED_COMMAND_PARSER(string);
DECLARE_UNQUALIFIED_COMMAND_PARSER(math);
DECLARE_UNQUALIFIED_COMMAND_PARSER(test);
DECLARE_UNQUALIFIED_COMMAND_PARSER(music);
DECLARE_UNQUALIFIED_COMMAND_PARSER(zeta);
DECLARE_UNQUALIFIED_COMMAND_PARSER(swap);
DECLARE_UNQUALIFIED_COMMAND_PARSER(give);
DECLARE_UNQUALIFIED_COMMAND_PARSER(text);
DECLARE_UNQUALIFIED_COMMAND_PARSER(unary);
DECLARE_UNQUALIFIED_ZONE_PARSER(limits);
DECLARE_UNQUALIFIED_ZONE_PARSER(moveto);
DECLARE_UNQUALIFIED_ZONE_PARSER(type);
DECLARE_UNQUALIFIED_ANIM_PARSER(file);
DECLARE_UNQUALIFIED_ANIM_PARSER(position);
DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);
DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation);
void parseZoneTypeBlock(ZonePtr z) override;
public:
virtual void parsePathData(ZonePtr z);
void parseGetData(ZonePtr z) override;
void parseDoorData(ZonePtr z) override;
void parseHearData(ZonePtr z) override;
void parseNoneData(ZonePtr z) override;
protected:
void parseAnswerCounter(Answer *answer);
Answer *parseAnswer() override;
public:
LocationParser_br(Parallaction_br *vm) : LocationParser_ns((Parallaction_ns*)vm), _vm(vm),
_audioCommandsNames(0), _out(0) {
}
void init() override;
~LocationParser_br() override {
delete _audioCommandsNames;
}
void parse(Script *script, LocationParserOutput_br *out);
};
class ProgramParser_ns {
protected:
Parallaction_ns *_vm;
Parser *_parser;
Script *_script;
ProgramPtr _program;
// program parser
OpcodeSet _instructionParsers;
Table *_instructionNames;
uint32 _currentInstruction; // index of the instruction being parsed
struct ParserContext {
bool end;
AnimationPtr a;
InstructionPtr inst;
LocalVariable *locals;
} ctxt;
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(defLocal);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(animation);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(loop);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(x);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(y);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(z);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(f);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(inc);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(set);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(move);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(put);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(call);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(sound);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(null);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(endscript);
void parseInstruction();
void parseLValue(ScriptVar &var, const char *str);
virtual void parseRValue(ScriptVar &var, const char *str);
void clearSet(OpcodeSet &opcodes) {
for (Common::Array<const Opcode *>::iterator i = opcodes.begin(); i != opcodes.end(); ++i)
delete *i;
opcodes.clear();
}
public:
ProgramParser_ns(Parallaction_ns *vm) : _vm(vm), _parser(0), _instructionNames(0), _script(0), _currentInstruction(0) {
}
virtual void init();
virtual ~ProgramParser_ns() {
delete _parser;
delete _instructionNames;
clearSet(_instructionParsers);
}
virtual void parse(Script *script, ProgramPtr program);
};
class ProgramParser_br : public ProgramParser_ns {
protected:
Parallaction_br *_vm;
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(zone);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(color);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(mask);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(print);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(text);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(if_op);
DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(endif);
int32 _openIfStatement;
void beginIfStatement();
void endIfStatement();
void parseRValue(ScriptVar &var, const char *str) override;
public:
ProgramParser_br(Parallaction_br *vm) : ProgramParser_ns((Parallaction_ns*)vm), _vm(vm), _openIfStatement(0) {
}
void init() override;
void parse(Script *script, ProgramPtr program) override;
};
} // End of namespace Parallaction
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,347 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/savefile.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "gui/saveload.h"
#include "gui/message.h"
#include "parallaction/parallaction.h"
#include "parallaction/saveload.h"
#include "parallaction/sound.h"
/* Nippon Safes savefiles are called 'nippon.000' to 'nippon.099'.
*
* A special savefile named 'nippon.999' holds information on whether the user completed one or more parts of the game.
*/
#define NUM_SAVESLOTS 100
#define SPECIAL_SAVESLOT 999
namespace Parallaction {
Common::String SaveLoad::genSaveFileName(uint slot) {
assert(slot < NUM_SAVESLOTS || slot == SPECIAL_SAVESLOT);
char s[20];
Common::sprintf_s(s, "%s.%.3u", _saveFilePrefix.c_str(), slot);
return Common::String(s);
}
Common::InSaveFile *SaveLoad::getInSaveFile(uint slot) {
Common::String name = genSaveFileName(slot);
return _saveFileMan->openForLoading(name);
}
Common::OutSaveFile *SaveLoad::getOutSaveFile(uint slot) {
Common::String name = genSaveFileName(slot);
return _saveFileMan->openForSaving(name);
}
void SaveLoad_ns::doLoadGame(uint16 slot) {
_vm->cleanupGame();
Common::InSaveFile *f = getInSaveFile(slot);
if (!f) return;
Common::String s, character, location;
// scrap the line with the savefile name
f->readLine();
character = f->readLine();
location = f->readLine();
s = f->readLine();
_vm->_location._startPosition.x = atoi(s.c_str());
s = f->readLine();
_vm->_location._startPosition.y = atoi(s.c_str());
s = f->readLine();
_vm->_score = atoi(s.c_str());
s = f->readLine();
g_globalFlags = atoi(s.c_str());
s = f->readLine();
_vm->_numLocations = atoi(s.c_str());
uint16 _si;
for (_si = 0; _si < _vm->_numLocations; _si++) {
s = f->readLine();
Common::strlcpy(_vm->_locationNames[_si], s.c_str(), 32);
s = f->readLine();
_vm->_localFlags[_si] = atoi(s.c_str());
}
_vm->cleanInventory(false);
ItemName name;
uint32 value;
for (_si = 0; _si < 30; _si++) {
s = f->readLine();
value = atoi(s.c_str());
s = f->readLine();
name = atoi(s.c_str());
_vm->addInventoryItem(name, value);
}
delete f;
// force reload of character to solve inventory
// bugs, but it's a good maneuver anyway
Common::strcpy_s(_vm->_characterName1, "null");
char tmp[PATH_LEN];
Common::sprintf_s(tmp, "%s.%s" , location.c_str(), character.c_str());
_vm->scheduleLocationSwitch(tmp);
}
void SaveLoad_ns::doSaveGame(uint16 slot, const char* name) {
Common::OutSaveFile *f = getOutSaveFile(slot);
if (f == nullptr) {
Common::U32String buf = Common::U32String::format(_("Can't save game in slot %i\n\n"), slot);
GUI::MessageDialog dialog(buf);
dialog.runModal();
return;
}
char s[200];
memset(s, 0, sizeof(s));
if (!name || name[0] == '\0') {
Common::sprintf_s(s, "default_%i", slot);
} else {
strncpy(s, name, 199);
}
f->writeString(s);
f->writeString("\n");
Common::sprintf_s(s, "%s\n", _vm->_char.getFullName());
f->writeString(s);
Common::sprintf_s(s, "%s\n", g_saveData1);
f->writeString(s);
Common::sprintf_s(s, "%d\n", _vm->_char._ani->getX());
f->writeString(s);
Common::sprintf_s(s, "%d\n", _vm->_char._ani->getY());
f->writeString(s);
Common::sprintf_s(s, "%d\n", _vm->_score);
f->writeString(s);
Common::sprintf_s(s, "%u\n", g_globalFlags);
f->writeString(s);
Common::sprintf_s(s, "%d\n", _vm->_numLocations);
f->writeString(s);
for (uint16 _si = 0; _si < _vm->_numLocations; _si++) {
Common::sprintf_s(s, "%s\n%u\n", _vm->_locationNames[_si], _vm->_localFlags[_si]);
f->writeString(s);
}
const InventoryItem *item;
for (uint16 _si = 0; _si < 30; _si++) {
item = _vm->getInventoryItem(_si);
Common::sprintf_s(s, "%u\n%d\n", item->_id, item->_index);
f->writeString(s);
}
delete f;
}
int SaveLoad::selectSaveFile(Common::String &selectedName, bool saveMode, const Common::U32String &caption, const Common::U32String &button) {
GUI::SaveLoadChooser slc(caption, button, saveMode);
selectedName.clear();
int idx = slc.runModalWithCurrentTarget();
if (idx >= 0) {
selectedName = slc.getResultString();
}
return idx;
}
bool SaveLoad::loadGame() {
Common::String null;
int _di = selectSaveFile(null, false, _("Load file"), _("Load"));
if (_di == -1) {
return false;
}
doLoadGame(_di);
GUI::TimedMessageDialog dialog(_("Loading game..."), 1500);
dialog.runModal();
return true;
}
bool SaveLoad::saveGame() {
Common::String saveName;
int slot = selectSaveFile(saveName, true, _("Save file"), _("Save"));
if (slot == -1) {
return false;
}
doSaveGame(slot, saveName.c_str());
GUI::TimedMessageDialog dialog(_("Saving game..."), 1500);
dialog.runModal();
return true;
}
bool SaveLoad_ns::saveGame() {
// NOTE: shouldn't this check be done before, so that the
// user can't even select 'save'?
if (!scumm_stricmp(_vm->_location._name, "caveau")) {
return false;
}
return SaveLoad::saveGame();
}
void SaveLoad_ns::setPartComplete(const char *part) {
Common::String s;
bool alreadyPresent = false;
Common::InSaveFile *inFile = getInSaveFile(SPECIAL_SAVESLOT);
if (inFile) {
s = inFile->readLine();
delete inFile;
if (s.contains(part)) {
alreadyPresent = true;
}
}
if (!alreadyPresent) {
Common::OutSaveFile *outFile = getOutSaveFile(SPECIAL_SAVESLOT);
outFile->writeString(s);
outFile->writeString(part);
outFile->finalize();
delete outFile;
}
}
void SaveLoad_ns::getGamePartProgress(bool *complete, int size) {
assert(complete && size >= 3);
Common::InSaveFile *inFile = getInSaveFile(SPECIAL_SAVESLOT);
Common::String s = inFile->readLine();
delete inFile;
complete[0] = s.contains("dino");
complete[1] = s.contains("donna");
complete[2] = s.contains("dough");
}
static bool askRenameOldSavefiles() {
GUI::MessageDialog dialog0(
_("ScummVM found that you have old saved games for Nippon Safes that should be renamed.\n"
"The old names are no longer supported, so you will not be able to load your games if you don't convert them.\n\n"
"Press OK to convert them now, otherwise you will be asked next time.\n"), _("OK"), _("Cancel"));
return (dialog0.runModal() == GUI::kMessageOK);
}
void SaveLoad_ns::renameOldSavefiles() {
Common::StringArray oldFilenames = _saveFileMan->listSavefiles("game.*");
uint numOldSaves = oldFilenames.size();
bool rename = false;
uint success = 0, id;
Common::String oldName, newName;
for (uint i = 0; i < oldFilenames.size(); ++i) {
oldName = oldFilenames[i];
int e = sscanf(oldName.c_str(), "game.%u", &id);
if (e != 1) {
// this wasn't a savefile, so adjust numOldSaves accordingly
--numOldSaves;
continue;
}
if (!rename) {
rename = askRenameOldSavefiles();
}
if (!rename) {
// return immediately if the user doesn't want to rename the files
return;
}
newName = genSaveFileName(id);
if (_saveFileMan->renameSavefile(oldName, newName)) {
success++;
} else {
warning("Error %i (%s) occurred while renaming %s to %s", _saveFileMan->getError().getCode(),
_saveFileMan->getErrorDesc().c_str(), oldName.c_str(), newName.c_str());
}
}
if (numOldSaves == 0) {
// there were no old savefiles: nothing to notify
return;
}
Common::U32String msg;
if (success == numOldSaves) {
msg = _("ScummVM successfully converted all your saved games.");
} else {
msg = _("ScummVM printed some warnings in your console window and can't guarantee all your files have been converted.\n\n"
"Please report to the team.");
}
GUI::MessageDialog dialog1(msg);
dialog1.runModal();
}
void SaveLoad_br::doLoadGame(uint16 slot) {
// TODO: implement loadgame
}
void SaveLoad_br::doSaveGame(uint16 slot, const char* name) {
// TODO: implement savegame
}
void SaveLoad_br::getGamePartProgress(bool *complete, int size) {
assert(complete && size >= 3);
// TODO: implement progress loading
complete[0] = true;
complete[1] = true;
complete[2] = true;
}
void SaveLoad_br::setPartComplete(const char *part) {
// TODO: implement progress saving
}
} // namespace Parallaction

View File

@@ -0,0 +1,86 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_SAVELOAD_H
#define PARALLACTION_SAVELOAD_H
namespace Parallaction {
struct Character;
class SaveLoad {
protected:
Common::SaveFileManager *_saveFileMan;
Common::String _saveFilePrefix;
Common::String genSaveFileName(uint slot);
Common::InSaveFile *getInSaveFile(uint slot);
Common::OutSaveFile *getOutSaveFile(uint slot);
int selectSaveFile(Common::String &selectedName, bool saveMode, const Common::U32String &caption, const Common::U32String &button);
int buildSaveFileList(Common::StringArray& l);
virtual void doLoadGame(uint16 slot) = 0;
virtual void doSaveGame(uint16 slot, const char* name) = 0;
public:
SaveLoad(Common::SaveFileManager* saveFileMan, const char *prefix) : _saveFileMan(saveFileMan), _saveFilePrefix(prefix) { }
virtual ~SaveLoad() { }
virtual bool loadGame();
virtual bool saveGame();
virtual void getGamePartProgress(bool *complete, int size) = 0;
virtual void setPartComplete(const char *part) = 0;
virtual void renameOldSavefiles() { }
};
class SaveLoad_ns : public SaveLoad {
Parallaction_ns *_vm;
protected:
void renameOldSavefiles() override;
void doLoadGame(uint16 slot) override;
void doSaveGame(uint16 slot, const char* name) override;
public:
SaveLoad_ns(Parallaction_ns *vm, Common::SaveFileManager *saveFileMan) : SaveLoad(saveFileMan, "nippon"), _vm(vm) { }
bool saveGame() override;
void getGamePartProgress(bool *complete, int size) override;
void setPartComplete(const char *part) override;
};
class SaveLoad_br : public SaveLoad {
// Parallaction_br *_vm;
void doLoadGame(uint16 slot) override;
void doSaveGame(uint16 slot, const char* name) override;
public:
SaveLoad_br(Parallaction_br *vm, Common::SaveFileManager *saveFileMan) : SaveLoad(saveFileMan, "bra") { }
void getGamePartProgress(bool *complete, int size) override;
void setPartComplete(const char *part) override;
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,258 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_MUSIC_H
#define PARALLACTION_MUSIC_H
#include "common/util.h"
#include "common/mutex.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/iff_sound.h"
#define PATH_LEN 200
class MidiParser;
class MidiDriver;
namespace Parallaction {
class Parallaction_ns;
class MidiPlayer;
class Parallaction_br;
class MidiPlayer_MSC;
MidiDriver *createAdLibDriver();
class SoundManImpl {
public:
virtual void execute(int command, const char *parm = 0) = 0;
virtual ~SoundManImpl() { }
};
class SoundMan {
SoundManImpl *_impl;
public:
SoundMan(SoundManImpl *impl) : _impl(impl) { }
virtual ~SoundMan() { delete _impl; }
void execute(int command, int32 parm) {
char n[12];
Common::sprintf_s(n, "%i", parm);
execute(command, n);
}
void execute(int command, const char *parm = 0) {
if (_impl) {
_impl->execute(command, parm);
}
}
};
enum {
// soundMan commands
SC_PLAYMUSIC,
SC_STOPMUSIC,
SC_SETMUSICTYPE,
SC_SETMUSICFILE,
SC_PLAYSFX,
SC_STOPSFX,
SC_SETSFXCHANNEL,
SC_SETSFXLOOPING,
SC_SETSFXVOLUME,
SC_SETSFXRATE,
SC_PAUSE
};
struct Channel {
Audio::AudioStream *stream;
Audio::SoundHandle handle;
uint32 volume;
};
class SoundMan_ns : public SoundManImpl {
public:
enum {
MUSIC_ANY,
MUSIC_CHARACTER,
MUSIC_LOCATION
};
protected:
Parallaction_ns *_vm;
Audio::Mixer *_mixer;
char _musicFile[PATH_LEN];
bool _sfxLooping;
int _sfxVolume;
int _sfxRate;
uint _sfxChannel;
int _musicType;
public:
SoundMan_ns(Parallaction_ns *vm);
~SoundMan_ns() override {}
virtual void playSfx(const char *filename, uint channel, bool looping, int volume = -1) { }
virtual void stopSfx(uint channel) { }
void setMusicFile(const char *filename);
virtual void playMusic() = 0;
virtual void stopMusic() = 0;
virtual void playCharacterMusic(const char *character) = 0;
virtual void playLocationMusic(const char *location) = 0;
virtual void pause(bool p) { }
void execute(int command, const char *parm) override;
void setMusicVolume(int value);
};
class DosSoundMan_ns : public SoundMan_ns {
MidiPlayer *_midiPlayer;
bool _playing;
bool isLocationSilent(const char *locationName);
bool locationHasOwnSoftMusic(const char *locationName);
public:
DosSoundMan_ns(Parallaction_ns *vm);
~DosSoundMan_ns() override;
void playMusic() override;
void stopMusic() override;
void playCharacterMusic(const char *character) override;
void playLocationMusic(const char *location) override;
void pause(bool p) override;
};
#define NUM_SFX_CHANNELS 4
class AmigaSoundMan_ns : public SoundMan_ns {
Audio::AudioStream *_musicStream;
Audio::SoundHandle _musicHandle;
uint32 beepSoundBufferSize;
int8 *beepSoundBuffer;
Channel _channels[NUM_SFX_CHANNELS];
Audio::AudioStream *loadChannelData(const char *filename, Channel *ch, bool looping);
public:
AmigaSoundMan_ns(Parallaction_ns *vm);
~AmigaSoundMan_ns() override;
void playMusic() override;
void stopMusic() override;
void playSfx(const char *filename, uint channel, bool looping, int volume) override;
void stopSfx(uint channel) override;
void playCharacterMusic(const char *character) override;
void playLocationMusic(const char *location) override;
};
class DummySoundMan : public SoundManImpl {
public:
void execute(int command, const char *parm) override { }
};
class SoundMan_br : public SoundManImpl {
protected:
Parallaction_br *_vm;
Audio::Mixer *_mixer;
Common::String _musicFile;
bool _sfxLooping;
int _sfxVolume;
int _sfxRate;
uint _sfxChannel;
bool _musicEnabled;
bool _sfxEnabled;
Channel _channels[NUM_SFX_CHANNELS];
virtual void playMusic() = 0;
virtual void stopMusic() = 0;
virtual void pause(bool p) = 0;
public:
SoundMan_br(Parallaction_br *vm);
~SoundMan_br() override;
virtual void playSfx(const char *filename, uint channel, bool looping, int volume = -1) { }
void stopSfx(uint channel);
void stopAllSfx();
void execute(int command, const char *parm) override;
void setMusicFile(const char *parm);
void enableSfx(bool enable);
void enableMusic(bool enable);
bool isSfxEnabled() const;
bool isMusicEnabled() const;
};
class DosSoundMan_br : public SoundMan_br {
MidiPlayer_MSC *_midiPlayer;
Audio::AudioStream *loadChannelData(const char *filename, Channel *ch, bool looping);
public:
DosSoundMan_br(Parallaction_br *vm);
~DosSoundMan_br() override;
void playMusic() override;
void stopMusic() override;
void pause(bool p) override;
void playSfx(const char *filename, uint channel, bool looping, int volume) override;
};
class AmigaSoundMan_br : public SoundMan_br {
Audio::AudioStream *_musicStream;
Audio::SoundHandle _musicHandle;
Audio::AudioStream *loadChannelData(const char *filename, Channel *ch, bool looping);
public:
AmigaSoundMan_br(Parallaction_br *vm);
~AmigaSoundMan_br() override;
void playMusic() override;
void stopMusic() override;
void pause(bool p) override;
void playSfx(const char *filename, uint channel, bool looping, int volume) override;
};
} // namespace Parallaction
#endif

View File

@@ -0,0 +1,583 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/mixer.h"
#include "audio/midiparser.h"
#include "audio/midiplayer.h"
#include "audio/mods/protracker.h"
#include "audio/decoders/raw.h"
#include "parallaction/disk.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
namespace Parallaction {
/*
* List of calls to the original music driver.
*
*
* 1 set music buffer segment
* 2 set music buffer offset
* 3 set music buffer size
* 4 play/resume
* 5 stop
* 6 pause
* 7 set channel volume
* 8 set fade in flag
* 9 set fade out flag
* 10 set global volume
* 11 shutdown
* 12 get status (stopped, playing, paused)
* 13 set fade volume change rate
* 14 get global volume
* 15 get fade in flag
* 16 get fade out flag
* 17 set tempo
* 18 get tempo
* 19 get fade volume change rate
* 20 get looping flag
* 21 set looping flag
* 22 get version??
* 23 get version??
* 24 get busy flag (dsp has pending data)
*/
class MidiParser_MSC : public MidiParser {
protected:
void parseNextEvent(EventInfo &info) override;
bool loadMusic(const byte *data, uint32 size) override;
uint8 read1(const byte *&data) {
return *data++;
}
void parseMetaEvent(EventInfo &info);
void parseMidiEvent(EventInfo &info);
bool byte_11C5A;
uint8 _beats;
uint8 _lastEvent;
const byte *_trackEnd;
public:
MidiParser_MSC() : byte_11C5A(false), _beats(0), _lastEvent(0), _trackEnd(nullptr) {
}
};
void MidiParser_MSC::parseMetaEvent(EventInfo &info) {
const byte *playPos = _position._subtracks[0]._playPos;
uint8 type = read1(playPos);
uint8 len = read1(playPos);
info.ext.type = type;
info.length = len;
info.ext.data = nullptr;
if (type == 0x51) {
info.ext.data = playPos;
} else {
warning("unknown meta event 0x%02X", type);
info.ext.type = 0;
}
playPos += len;
_position._subtracks[0]._playPos = playPos;
}
void MidiParser_MSC::parseMidiEvent(EventInfo &info) {
const byte *playPos = _position._subtracks[0]._playPos;
uint8 type = info.command();
switch (type) {
case 0x8:
case 0x9:
case 0xA:
case 0xB:
case 0xE:
info.basic.param1 = read1(playPos);
info.basic.param2 = read1(playPos);
break;
case 0xC:
case 0xD:
info.basic.param1 = read1(playPos);
info.basic.param2 = 0;
break;
default:
warning("Unexpected midi event 0x%02X in midi data", info.event);
}
//if ((type == 0xB) && (info.basic.param1 == 64)) info.basic.param2 = 127;
_position._subtracks[0]._playPos = playPos;
}
void MidiParser_MSC::parseNextEvent(EventInfo &info) {
const byte *playPos = _position._subtracks[0]._playPos;
info.start = playPos;
if (playPos >= _trackEnd) {
// fake an end-of-track meta event
info.delta = 0;
info.event = 0xFF;
info.ext.type = 0x2F;
info.length = 0;
return;
}
info.length = 0;
info.delta = readVLQ(playPos);
info.event = read1(playPos);
if (info.event == 0xFF) {
_position._subtracks[0]._playPos = playPos;
parseMetaEvent(info);
return;
}
if (info.event < 0x80) {
playPos--;
info.event = _lastEvent;
}
_position._subtracks[0]._playPos = playPos;
parseMidiEvent(info);
_lastEvent = info.event;
}
bool MidiParser_MSC::loadMusic(const byte *data, uint32 size) {
unloadMusic();
const byte *pos = data;
if (memcmp("MSCt", pos, 4)) {
warning("Expected header not found in music file");
return false;
}
pos += 4;
_beats = read1(pos);
_ppqn = read2low(pos);
if (byte_11C5A) {
// do something with byte_11C4D
}
_lastEvent = 0;
_trackEnd = data + size;
_numTracks = 1;
_numSubtracks[0] = 1;
_tracks[0][0] = pos;
setTempo(500000);
setTrack(0);
return true;
}
MidiParser *createParser_MSC() {
return new MidiParser_MSC;
}
class MidiPlayer_MSC : public Audio::MidiPlayer {
public:
MidiPlayer_MSC();
void play(Common::SeekableReadStream *stream);
virtual void pause(bool p);
void pause() override { assert(0); } // overridden
void setVolume(int volume) override;
void onTimer() override;
// MidiDriver_BASE interface
void send(uint32 b) override;
private:
void setVolumeInternal(int volume);
bool _paused;
};
MidiPlayer_MSC::MidiPlayer_MSC()
: _paused(false) {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
const MusicType musicType = MidiDriver::getMusicType(dev);
if (musicType == MT_ADLIB) {
_driver = createAdLibDriver();
} else {
_driver = MidiDriver::createMidi(dev);
}
assert(_driver);
int ret = _driver->open();
if (ret == 0) {
_driver->setTimerCallback(this, &timerCallback);
}
}
void MidiPlayer_MSC::play(Common::SeekableReadStream *stream) {
Common::StackLock lock(_mutex);
stop();
if (!stream)
return;
int size = stream->size();
_midiData = (uint8 *)malloc(size);
if (_midiData) {
stream->read(_midiData, size);
delete stream;
_parser = createParser_MSC();
_parser->loadMusic(_midiData, size);
_parser->setTrack(0);
_parser->setMidiDriver(this);
_parser->setTimerRate(_driver->getBaseTempo());
_isLooping = true;
_isPlaying = true;
}
}
void MidiPlayer_MSC::pause(bool p) {
_paused = p;
setVolumeInternal(_paused ? 0 : _masterVolume);
}
void MidiPlayer_MSC::onTimer() {
Common::StackLock lock(_mutex);
if (!_paused && _isPlaying && _parser) {
_parser->onTimer();
}
}
void MidiPlayer_MSC::setVolume(int volume) {
_masterVolume = CLIP(volume, 0, 255);
setVolumeInternal(_masterVolume);
}
void MidiPlayer_MSC::setVolumeInternal(int volume) {
Common::StackLock lock(_mutex);
for (int i = 0; i < kNumChannels; ++i) {
if (_channelsTable[i]) {
_channelsTable[i]->volume(_channelsVolume[i] * volume / 255);
}
}
}
void MidiPlayer_MSC::send(uint32 b) {
// FIXME/TODO: Unlike Audio::MidiPlayer::send(), this code
// does not handle All Note Off. Is this on purpose?
// If not, we could simply remove this method, and use the
// inherited one.
const byte ch = b & 0x0F;
byte param2 = (b >> 16) & 0xFF;
switch (b & 0xFFF0) {
case 0x07B0: // volume change
_channelsVolume[ch] = param2;
break;
default:
break;
}
sendToChannel(ch, b);
}
DosSoundMan_br::DosSoundMan_br(Parallaction_br *vm) : SoundMan_br(vm) {
_midiPlayer = new MidiPlayer_MSC();
assert(_midiPlayer);
}
DosSoundMan_br::~DosSoundMan_br() {
delete _midiPlayer;
}
Audio::AudioStream *DosSoundMan_br::loadChannelData(const char *filename, Channel *ch, bool looping) {
Common::SeekableReadStream *stream = _vm->_disk->loadSound(filename);
uint32 dataSize = stream->size();
byte *data = (byte *)malloc(dataSize);
if (stream->read(data, dataSize) != dataSize)
error("DosSoundMan_br::loadChannelData: Read failed");
delete stream;
// TODO: Confirm sound rate
int rate = 11025;
ch->stream = Audio::makeLoopingAudioStream(
Audio::makeRawStream(data, dataSize, rate, Audio::FLAG_UNSIGNED),
looping ? 0 : 1);
return ch->stream;
}
void DosSoundMan_br::playSfx(const char *filename, uint channel, bool looping, int volume) {
stopSfx(channel);
if (!_sfxEnabled) {
return;
}
debugC(1, kDebugAudio, "DosSoundMan_br::playSfx(%s, %u, %i, %i)", filename, channel, looping, volume);
Channel *ch = &_channels[channel];
Audio::AudioStream *input = loadChannelData(filename, ch, looping);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &ch->handle, input, -1, volume);
}
void DosSoundMan_br::playMusic() {
if (_musicFile.empty()) {
return;
}
if (!_musicEnabled) {
return;
}
Common::SeekableReadStream *s = _vm->_disk->loadMusic(_musicFile.c_str());
assert(s);
_midiPlayer->play(s);
}
void DosSoundMan_br::stopMusic() {
_midiPlayer->stop();
}
void DosSoundMan_br::pause(bool p) {
_midiPlayer->pause(p);
}
AmigaSoundMan_br::AmigaSoundMan_br(Parallaction_br *vm) : SoundMan_br(vm) {
_musicStream = nullptr;
}
AmigaSoundMan_br::~AmigaSoundMan_br() {
stopMusic();
}
Audio::AudioStream *AmigaSoundMan_br::loadChannelData(const char *filename, Channel *ch, bool looping) {
Common::SeekableReadStream *stream = _vm->_disk->loadSound(filename);
Audio::AudioStream *input = nullptr;
if (_vm->getFeatures() & GF_DEMO) {
uint32 dataSize = stream->size();
int8 *data = (int8 *)malloc(dataSize);
if (stream->read(data, dataSize) != dataSize)
error("DosSoundMan_br::loadChannelData: Read failed");
// TODO: Confirm sound rate
int rate = 11025;
input = Audio::makeRawStream((byte *)data, dataSize, rate, 0);
} else {
input = Audio::make8SVXStream(*stream, looping);
}
delete stream;
ch->stream = input;
return input;
}
void AmigaSoundMan_br::playSfx(const char *filename, uint channel, bool looping, int volume) {
if (channel >= NUM_SFX_CHANNELS) {
warning("unknown sfx channel");
return;
}
stopSfx(channel);
if (!_sfxEnabled) {
return;
}
debugC(1, kDebugAudio, "AmigaSoundMan_ns::playSfx(%s, %i)", filename, channel);
Channel *ch = &_channels[channel];
Audio::AudioStream *input = loadChannelData(filename, ch, looping);
if (volume == -1) {
volume = ch->volume;
}
_mixer->playStream(Audio::Mixer::kSFXSoundType, &ch->handle, input, -1, volume);
}
void AmigaSoundMan_br::playMusic() {
stopMusic();
if (!_musicEnabled) {
return;
}
debugC(1, kDebugAudio, "AmigaSoundMan_ns::playMusic()");
Common::SeekableReadStream *stream = _vm->_disk->loadMusic(_musicFile.c_str());
// NOTE: Music files don't always exist
if (!stream)
return;
_musicStream = Audio::makeProtrackerStream(stream);
delete stream;
debugC(3, kDebugAudio, "AmigaSoundMan_ns::playMusic(): created new music stream");
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, _musicStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, false);
}
void AmigaSoundMan_br::stopMusic() {
debugC(1, kDebugAudio, "AmigaSoundMan_ns::stopMusic()");
if (_mixer->isSoundHandleActive(_musicHandle)) {
_mixer->stopHandle(_musicHandle);
delete _musicStream;
_musicStream = nullptr;
}
}
void AmigaSoundMan_br::pause(bool p) {
_mixer->pauseHandle(_musicHandle, p);
}
SoundMan_br::SoundMan_br(Parallaction_br *vm) : _vm(vm) {
_mixer = _vm->_mixer;
_musicEnabled = true;
_sfxEnabled = true;
_sfxLooping = false;
_sfxVolume = 0;
_sfxRate = 0;
_sfxChannel = 0;
}
SoundMan_br::~SoundMan_br() {
stopAllSfx();
}
void SoundMan_br::stopAllSfx() {
stopSfx(0);
stopSfx(1);
stopSfx(2);
stopSfx(3);
}
void SoundMan_br::setMusicFile(const char *name) {
stopMusic();
_musicFile = name;
}
void SoundMan_br::stopSfx(uint channel) {
if (channel >= NUM_SFX_CHANNELS) {
warning("unknown sfx channel");
return;
}
debugC(1, kDebugAudio, "SoundMan_br::stopSfx(%i)", channel);
_mixer->stopHandle(_channels[channel].handle);
_channels[channel].stream = nullptr;
}
void SoundMan_br::execute(int command, const char *parm) {
uint32 n = parm ? strtoul(parm, nullptr, 10) : 0;
bool b = (n == 1) ? true : false;
switch (command) {
case SC_PLAYMUSIC:
playMusic();
break;
case SC_STOPMUSIC:
stopMusic();
break;
case SC_SETMUSICFILE:
if (!parm)
error("no parameter passed to SC_SETMUSICFILE");
setMusicFile(parm);
break;
case SC_PLAYSFX:
if (!parm)
error("no parameter passed to SC_PLAYSFX");
playSfx(parm, _sfxChannel, _sfxLooping, _sfxVolume);
break;
case SC_STOPSFX:
stopSfx(n);
break;
case SC_SETSFXCHANNEL:
_sfxChannel = n;
break;
case SC_SETSFXLOOPING:
_sfxLooping = b;
break;
case SC_SETSFXVOLUME:
_sfxVolume = n;
break;
case SC_PAUSE:
pause(b);
break;
default:
break;
}
}
void SoundMan_br::enableSfx(bool enable) {
if (!enable) {
stopAllSfx();
}
_sfxEnabled = enable;
}
void SoundMan_br::enableMusic(bool enable) {
if (enable) {
playMusic();
} else {
stopMusic();
}
_musicEnabled = enable;
}
bool SoundMan_br::isSfxEnabled() const {
return _sfxEnabled;
}
bool SoundMan_br::isMusicEnabled() const {
return _musicEnabled;
}
} // namespace Parallaction

View File

@@ -0,0 +1,386 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "audio/mixer.h"
#include "audio/midiparser.h"
#include "audio/midiplayer.h"
#include "audio/mods/protracker.h"
#include "audio/decoders/raw.h"
#include "parallaction/sound.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
class MidiPlayer : public Audio::MidiPlayer {
public:
MidiPlayer();
void play(Common::SeekableReadStream *stream);
void pause(bool p);
void pause() override { assert(0); } // overridden
void onTimer() override;
private:
bool _paused;
};
MidiPlayer::MidiPlayer()
: _paused(false) {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
_driver = MidiDriver::createMidi(dev);
assert(_driver);
int ret = _driver->open();
if (ret == 0) {
_driver->setTimerCallback(this, &timerCallback);
}
}
void MidiPlayer::play(Common::SeekableReadStream *stream) {
Common::StackLock lock(_mutex);
stop();
if (!stream)
return;
int size = stream->size();
_midiData = (uint8 *)malloc(size);
if (_midiData) {
stream->read(_midiData, size);
delete stream;
_parser = MidiParser::createParser_SMF();
_parser->loadMusic(_midiData, size);
_parser->setTrack(0);
_parser->setMidiDriver(this);
_parser->setTimerRate(_driver->getBaseTempo());
_isLooping = true;
_isPlaying = true;
}
}
void MidiPlayer::pause(bool p) {
_paused = p;
for (int i = 0; i < kNumChannels; ++i) {
if (_channelsTable[i]) {
_channelsTable[i]->volume(_paused ? 0 : _channelsVolume[i] * _masterVolume / 255);
}
}
}
void MidiPlayer::onTimer() {
Common::StackLock lock(_mutex);
if (!_paused && _isPlaying && _parser) {
_parser->onTimer();
}
}
DosSoundMan_ns::DosSoundMan_ns(Parallaction_ns *vm) : SoundMan_ns(vm), _playing(false) {
_midiPlayer = new MidiPlayer();
}
DosSoundMan_ns::~DosSoundMan_ns() {
debugC(1, kDebugAudio, "DosSoundMan_ns_ns::playMusic()");
delete _midiPlayer;
}
bool DosSoundMan_ns::isLocationSilent(const char *locationName) {
// these are the prefixes for location names with no background midi music
const char *noMusicPrefix[] = { "museo", "intgrottadopo", "caveau", "estgrotta", "plaza1", "endtgz", "common", nullptr };
Common::String s(locationName);
for (int i = 0; noMusicPrefix[i]; i++) {
if (s.hasPrefix(noMusicPrefix[i])) {
return true;
}
}
return false;
}
void DosSoundMan_ns::playMusic() {
debugC(1, kDebugAudio, "DosSoundMan_ns_ns::playMusic()");
if (isLocationSilent(_vm->_location._name)) {
// just stop the music if this location is silent
_midiPlayer->stop();
return;
}
Common::SeekableReadStream *stream = _vm->_disk->loadMusic(_musicFile);
_midiPlayer->play(stream);
_midiPlayer->setVolume(255);
_playing = true;
}
void DosSoundMan_ns::stopMusic() {
_musicFile[0] = 0;
_midiPlayer->stop();
_playing = false;
}
void DosSoundMan_ns::pause(bool p) {
SoundMan_ns::pause(p);
_midiPlayer->pause(p);
}
bool DosSoundMan_ns::locationHasOwnSoftMusic(const char *locationName) {
return !scumm_stricmp(locationName, "night") || !scumm_stricmp(locationName, "intsushi");
}
void DosSoundMan_ns::playCharacterMusic(const char *character) {
if (!character || locationHasOwnSoftMusic(_vm->_location._name)) {
return;
}
char *name = const_cast<char *>(character);
const char *newMusicFile = nullptr;
if (!scumm_stricmp(name, g_dinoName)) {
newMusicFile = "dino";
} else
if (!scumm_stricmp(name, g_donnaName)) {
newMusicFile = "donna";
} else
if (!scumm_stricmp(name, g_doughName)) {
newMusicFile = "nuts";
} else {
warning("unknown character '%s' in DosSoundMan_ns_ns::playCharacterMusic", character);
return;
}
if (!_playing || (newMusicFile && scumm_stricmp(newMusicFile, _musicFile))) {
// avoid restarting the same piece
setMusicFile(newMusicFile);
playMusic();
debugC(2, kDebugExec, "changeLocation: started character specific music (%s)", newMusicFile);
}
}
void DosSoundMan_ns::playLocationMusic(const char *location) {
if (locationHasOwnSoftMusic(location)) {
setMusicFile("soft");
playMusic();
debugC(2, kDebugExec, "changeLocation: started music 'soft'");
} else
if (isLocationSilent(location)) {
stopMusic();
debugC(2, kDebugExec, "changeLocation: music stopped");
} else {
playCharacterMusic(_vm->_char.getBaseName());
}
}
#pragma mark Amiga sound manager code
#define AMIGABEEP_SIZE 16
#define NUM_REPEATS 60
static int8 res_amigaBeep[AMIGABEEP_SIZE] = {
0, 20, 40, 60, 80, 60, 40, 20, 0, -20, -40, -60, -80, -60, -40, -20
};
AmigaSoundMan_ns::AmigaSoundMan_ns(Parallaction_ns *vm) : SoundMan_ns(vm) {
_musicStream = nullptr;
// initialize the waveform for the 'beep' sound
beepSoundBufferSize = AMIGABEEP_SIZE * NUM_REPEATS;
beepSoundBuffer = new int8[beepSoundBufferSize];
int8 *odata = beepSoundBuffer;
for (int i = 0; i < NUM_REPEATS; i++) {
memcpy(odata, res_amigaBeep, AMIGABEEP_SIZE);
odata += AMIGABEEP_SIZE;
}
}
AmigaSoundMan_ns::~AmigaSoundMan_ns() {
stopMusic();
stopSfx(0);
stopSfx(1);
stopSfx(2);
stopSfx(3);
delete[] beepSoundBuffer;
}
Audio::AudioStream *AmigaSoundMan_ns::loadChannelData(const char *filename, Channel *ch, bool looping) {
Audio::AudioStream *input = nullptr;
if (!scumm_stricmp("beep", filename)) {
int rate = 11934;
ch->volume = 160;
input = Audio::makeRawStream((byte *)beepSoundBuffer, beepSoundBufferSize, rate, 0, DisposeAfterUse::NO);
} else {
Common::SeekableReadStream *stream = _vm->_disk->loadSound(filename);
input = Audio::make8SVXStream(*stream, looping);
delete stream;
}
ch->stream = input;
return input;
}
void AmigaSoundMan_ns::playSfx(const char *filename, uint channel, bool looping, int volume) {
if (channel >= NUM_SFX_CHANNELS) {
warning("unknown sfx channel");
return;
}
stopSfx(channel);
debugC(1, kDebugAudio, "AmigaSoundMan_ns::playSfx(%s, %i)", filename, channel);
Channel *ch = &_channels[channel];
Audio::AudioStream *input = loadChannelData(filename, ch, looping);
if (volume == -1) {
volume = ch->volume;
}
_mixer->playStream(Audio::Mixer::kSFXSoundType, &ch->handle, input, -1, volume);
}
void AmigaSoundMan_ns::stopSfx(uint channel) {
if (channel >= NUM_SFX_CHANNELS) {
warning("unknown sfx channel");
return;
}
debugC(1, kDebugAudio, "AmigaSoundMan_ns::stopSfx(%i)", channel);
_mixer->stopHandle(_channels[channel].handle);
_channels[channel].stream = nullptr;
}
void AmigaSoundMan_ns::playMusic() {
stopMusic();
debugC(1, kDebugAudio, "AmigaSoundMan_ns::playMusic()");
Common::SeekableReadStream *stream = _vm->_disk->loadMusic(_musicFile);
_musicStream = Audio::makeProtrackerStream(stream);
delete stream;
debugC(3, kDebugAudio, "AmigaSoundMan_ns::playMusic(): created new music stream");
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, _musicStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, false);
}
void AmigaSoundMan_ns::stopMusic() {
debugC(1, kDebugAudio, "AmigaSoundMan_ns::stopMusic()");
if (_mixer->isSoundHandleActive(_musicHandle)) {
_mixer->stopHandle(_musicHandle);
delete _musicStream;
_musicStream = nullptr;
}
}
void AmigaSoundMan_ns::playCharacterMusic(const char *character) {
}
void AmigaSoundMan_ns::playLocationMusic(const char *location) {
}
SoundMan_ns::SoundMan_ns(Parallaction_ns *vm) : _vm(vm) {
_mixer = _vm->_mixer;
_sfxLooping = false;
_sfxVolume = 0;
_sfxRate = 0;
_sfxChannel = 0;
_musicType = 0;
}
void SoundMan_ns::setMusicVolume(int value) {
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, value);
}
void SoundMan_ns::setMusicFile(const char *filename) {
Common::strlcpy(_musicFile, filename, PATH_LEN);
}
void SoundMan_ns::execute(int command, const char *parm = nullptr) {
uint32 n = strtoul(parm, nullptr, 10);
bool b = (n == 1) ? true : false;
switch (command) {
case SC_PLAYMUSIC:
if (_musicType == MUSIC_CHARACTER) playCharacterMusic(parm);
else if (_musicType == MUSIC_LOCATION) playLocationMusic(parm);
else playMusic();
break;
case SC_STOPMUSIC:
stopMusic();
break;
case SC_SETMUSICTYPE:
_musicType = n;
break;
case SC_SETMUSICFILE:
setMusicFile(parm);
break;
case SC_PLAYSFX:
playSfx(parm, _sfxChannel, _sfxLooping, _sfxVolume);
break;
case SC_STOPSFX:
stopSfx(n);
break;
case SC_SETSFXCHANNEL:
_sfxChannel = n;
break;
case SC_SETSFXLOOPING:
_sfxLooping = b;
break;
case SC_SETSFXVOLUME:
_sfxVolume = n;
break;
case SC_PAUSE:
pause(b);
break;
default:
break;
}
}
} // namespace Parallaction

View File

@@ -0,0 +1,255 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/parallaction.h"
namespace Parallaction {
byte Input::_resMouseArrow_NS[256] = {
0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00,
0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00,
0x11, 0x12, 0x12, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00,
0x11, 0x12, 0x11, 0x00, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x11, 0x00, 0x00, 0x00, 0x11, 0x12, 0x12, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* TODO: Scale this bitmap. Its 32x16, but the original scales it twice horizontally and 6 times
vertically (64x96).
TODO: The cursor data should be adjusted by adding 0x10 to each byte, because the bitmap
must be drawn using the background palette.
*/
byte Input::_resMouseArrow_BR_Amiga[512] = {
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02,
0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/*
This palette snippet is used for animations in Big Red Adventure.
*/
byte braAmigaFramesDefaultPalette[48] = {
0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0xFF, 0xE0, 0xCF, 0x7F, 0x7F, 0x7F, 0xD9, 0x9C, 0x84, 0x00,
0x9E, 0xF0, 0x91, 0xCC, 0x36, 0xFF, 0x6A, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x11, 0xB0, 0xEE,
0xF0, 0xFF, 0x17, 0x3D, 0x18, 0xAC, 0x3A, 0xB0, 0x00, 0x00, 0x7D, 0x00, 0x00, 0xFF, 0xA8, 0xFF,
};
const char *callableNamesRes_ns[] = {
"Projector",
"HBOff",
"StartIntro",
"EndIntro",
"MoveSheet",
"Sketch",
"Shade",
"Score",
"OffSound",
"StartMusic",
"CloseMusic",
"Fade",
"HBOn",
"MoveSarc",
"ContaFoglie",
"ZeroFoglie",
"Trasformata",
"OffMouse",
"OnMouse",
"SetMask",
"EndComment",
"Frankenstain",
"Finito",
"Ridux",
"TestResult"
};
const char *callableNamesRes_br[] = {
"blufade",
"resetpalette",
"ferrcycle",
"lipsinc",
"albycle",
"password"
};
const char *g_dinoName = "dino";
const char *g_donnaName = "donna";
const char *g_doughName = "dough";
const char *g_drkiName = "drki";
const char *g_minidinoName = "minidino";
const char *g_minidonnaName = "minidonna";
const char *g_minidoughName = "minidough";
const char *g_minidrkiName = "minidrki";
#define CALLABLE_NS(x) &Parallaction_ns::x
const Parallaction_ns::Callable Parallaction_ns::_dosCallables[] = {
CALLABLE_NS(_c_play_boogie),
CALLABLE_NS(_c_play_boogie),
CALLABLE_NS(_c_startIntro),
CALLABLE_NS(_c_endIntro),
CALLABLE_NS(_c_moveSheet),
CALLABLE_NS(_c_sketch),
CALLABLE_NS(_c_shade),
CALLABLE_NS(_c_score),
CALLABLE_NS(_c_null),
CALLABLE_NS(_c_null),
CALLABLE_NS(_c_null),
CALLABLE_NS(_c_fade),
CALLABLE_NS(_c_play_boogie),
CALLABLE_NS(_c_moveSarc),
CALLABLE_NS(_c_contaFoglie),
CALLABLE_NS(_c_zeroFoglie),
CALLABLE_NS(_c_trasformata),
CALLABLE_NS(_c_offMouse),
CALLABLE_NS(_c_onMouse),
CALLABLE_NS(_c_setMask),
CALLABLE_NS(_c_endComment),
CALLABLE_NS(_c_frankenstein),
CALLABLE_NS(_c_finito),
CALLABLE_NS(_c_ridux),
CALLABLE_NS(_c_testResult)
};
const Parallaction_ns::Callable Parallaction_ns::_amigaCallables[] = {
CALLABLE_NS(_c_projector),
CALLABLE_NS(_c_HBOff),
CALLABLE_NS(_c_startIntro),
CALLABLE_NS(_c_endIntro),
CALLABLE_NS(_c_moveSheet),
CALLABLE_NS(_c_sketch),
CALLABLE_NS(_c_shade),
CALLABLE_NS(_c_score),
CALLABLE_NS(_c_offSound),
CALLABLE_NS(_c_startMusic),
CALLABLE_NS(_c_closeMusic),
CALLABLE_NS(_c_fade),
CALLABLE_NS(_c_HBOn),
CALLABLE_NS(_c_moveSarc),
CALLABLE_NS(_c_contaFoglie),
CALLABLE_NS(_c_zeroFoglie),
CALLABLE_NS(_c_trasformata),
CALLABLE_NS(_c_offMouse),
CALLABLE_NS(_c_onMouse),
CALLABLE_NS(_c_setMask),
CALLABLE_NS(_c_endComment),
CALLABLE_NS(_c_frankenstein),
CALLABLE_NS(_c_finito),
CALLABLE_NS(_c_ridux),
CALLABLE_NS(_c_testResult)
};
#define CALLABLE_BR(x) &Parallaction_br::x
const Parallaction_br::Callable Parallaction_br::_dosCallables[] = {
CALLABLE_BR(_c_blufade),
CALLABLE_BR(_c_resetpalette),
CALLABLE_BR(_c_ferrcycle),
CALLABLE_BR(_c_lipsinc),
CALLABLE_BR(_c_albcycle),
CALLABLE_BR(_c_password)
};
const Parallaction_br::Callable Parallaction_br::_amigaCallables[] = {
CALLABLE_BR(_c_blufade),
CALLABLE_BR(_c_resetpalette),
CALLABLE_BR(_c_null),
CALLABLE_BR(_c_null),
CALLABLE_BR(_c_null),
CALLABLE_BR(_c_null)
};
void Parallaction_ns::initResources() {
_callableNames = new Table(ARRAYSIZE(callableNamesRes_ns), callableNamesRes_ns);
_localFlagNames = new FixedTable(NUM_LOCATIONS, 1);
_localFlagNames->addData("visited");
if (getPlatform() == Common::kPlatformDOS) {
_callables = _dosCallables;
} else {
_callables = _amigaCallables;
}
}
void Parallaction_br::initResources() {
_callableNames = new Table(ARRAYSIZE(callableNamesRes_br), callableNamesRes_br);
_localFlagNames = new FixedTable(NUM_LOCATIONS, 2);
_localFlagNames->addData("visited");
_localFlagNames->addData("testtrue");
if (getPlatform() == Common::kPlatformDOS) {
_callables = _dosCallables;
} else {
_callables = _amigaCallables;
}
}
} // namespace Parallaction

View File

@@ -0,0 +1,723 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "parallaction/exec.h"
#include "parallaction/parallaction.h"
#include "parallaction/walk.h"
namespace Parallaction {
enum {
WALK_LEFT = 0,
WALK_RIGHT = 1,
WALK_DOWN = 2,
WALK_UP = 3
};
struct WalkFrames {
int16 stillFrame[4];
int16 firstWalkFrame[4];
int16 numWalkFrames[4];
int16 frameRepeat[4];
};
WalkFrames _char20WalkFrames_NS = {
{ 0, 7, 14, 17 },
{ 1, 8, 15, 18 },
{ 6, 6, 2, 2 },
{ 2, 2, 4, 4 }
};
WalkFrames _char24WalkFrames_NS = {
{ 0, 9, 18, 21 },
{ 1, 10, 19, 22 },
{ 8, 8, 2, 2 },
{ 2, 2, 4, 4 }
};
static int getPathWidth() {
if (!g_vm->_gfx->_backgroundInfo->_path) {
warning("getPathWidth() _path is NULL!");
return 0;
} else
return g_vm->_gfx->_backgroundInfo->_path->w;
}
static int getPathHeight() {
if (!g_vm->_gfx->_backgroundInfo->_path) {
warning("getPathHeight() _path is NULL!");
return 0;
} else
return g_vm->_gfx->_backgroundInfo->_path->h;
}
static bool isPathClear(uint16 x, uint16 y) {
if (!g_vm->_gfx->_backgroundInfo->_path) {
warning("isPathClear() _path is NULL!");
return false;
} else
return (g_vm->_gfx->_backgroundInfo->_path->getValue(x, y) ? true : false);
}
// adjusts position towards nearest walkable point
void PathWalker_NS::correctPathPoint(Common::Point &to) {
if (isPathClear(to.x, to.y)) return;
int maxX = getPathWidth();
int maxY = getPathHeight();
int16 right = to.x;
int16 left = to.x;
do {
right++;
} while ((right < maxX) && !isPathClear(right, to.y));
do {
left--;
} while ((left > 0) && !isPathClear(left, to.y));
right = (right == maxX) ? 1000 : right - to.x;
left = (left == 0) ? 1000 : to.x - left;
int16 top = to.y;
int16 bottom = to.y;
do {
top--;
} while ((top > 0) && !isPathClear(to.x, top));
do {
bottom++;
} while ((bottom < maxY) && !isPathClear(to.x, bottom));
top = (top == 0) ? 1000 : to.y - top;
bottom = (bottom == maxY) ? 1000 : bottom - to.y;
int16 closeX = (right >= left) ? left : right;
int16 closeY = (top >= bottom) ? bottom : top;
int16 close = (closeX >= closeY) ? closeY : closeX;
if (close == right) {
to.x += right;
} else
if (close == left) {
to.x -= left;
} else
if (close == top) {
to.y -= top;
} else
if (close == bottom) {
to.y += bottom;
}
}
uint32 PathWalker_NS::buildSubPath(const Common::Point& pos, const Common::Point& stop) {
uint32 v28 = 0;
uint32 v2C = 0;
uint32 v34 = pos.sqrDist(stop); // square distance from current position and target
uint32 v30 = v34;
_subPath.clear();
Common::Point v20(pos);
while (true) {
PointList::iterator nearest = _vm->_location._walkPoints.end();
PointList::iterator locNode = _vm->_location._walkPoints.begin();
// scans location path nodes searching for the nearest Node
// which can't be farther than the target position
// otherwise no _closest_node is selected
while (locNode != _vm->_location._walkPoints.end()) {
Common::Point v8 = *locNode;
v2C = v8.sqrDist(stop);
v28 = v8.sqrDist(v20);
if (v2C < v34 && v28 < v30) {
v30 = v28;
nearest = locNode;
}
locNode++;
}
if (nearest == _vm->_location._walkPoints.end()) break;
v20 = *nearest;
v34 = v30 = v20.sqrDist(stop);
_subPath.push_back(*nearest);
}
return v34;
}
// x, y: mouse click (foot) coordinates
void PathWalker_NS::buildPath(AnimationPtr a, uint16 x, uint16 y) {
debugC(1, kDebugWalk, "PathBuilder::buildPath to (%i, %i)", x, y);
_a = a;
_walkPath.clear();
Common::Point to(x, y);
correctPathPoint(to);
debugC(1, kDebugWalk, "found closest path point at (%i, %i)", to.x, to.y);
Common::Point v48(to);
Common::Point v44(to);
uint16 v38 = walkFunc1(to, v44);
if (v38 == 1) {
// destination directly reachable
debugC(1, kDebugWalk, "direct move to (%i, %i)", to.x, to.y);
_walkPath.push_back(v48);
return;
}
// path is obstructed: look for alternative
_walkPath.push_back(v48);
Common::Point pos;
_a->getFoot(pos);
uint32 v34 = buildSubPath(pos, v48);
if (v38 != 0 && v34 > v38) {
// no alternative path (gap?)
_walkPath.clear();
_walkPath.push_back(v44);
return;
}
_walkPath.insert(_walkPath.begin(), _subPath.begin(), _subPath.end());
buildSubPath(pos, *_walkPath.begin());
_walkPath.insert(_walkPath.begin(), _subPath.begin(), _subPath.end());
}
// x,y : top left coordinates
//
// 0 : Point not reachable
// 1 : Point reachable in a straight line
// other values: square distance to target (point not reachable in a straight line)
uint16 PathWalker_NS::walkFunc1(const Common::Point &to, Common::Point& node) {
Common::Point arg(to);
Common::Point v4;
Common::Point foot;
_a->getFoot(foot);
Common::Point v8(foot);
while (foot != arg) {
if (foot.x < to.x && isPathClear(foot.x + 1, foot.y)) foot.x++;
if (foot.x > to.x && isPathClear(foot.x - 1, foot.y)) foot.x--;
if (foot.y < to.y && isPathClear(foot.x, foot.y + 1)) foot.y++;
if (foot.y > to.y && isPathClear(foot.x, foot.y - 1)) foot.y--;
if (foot == v8 && foot != arg) {
// foot couldn't move and still away from target
v4 = foot;
while (foot != arg) {
if (foot.x < to.x && !isPathClear(foot.x + 1, foot.y)) foot.x++;
if (foot.x > to.x && !isPathClear(foot.x - 1, foot.y)) foot.x--;
if (foot.y < to.y && !isPathClear(foot.x, foot.y + 1)) foot.y++;
if (foot.y > to.y && !isPathClear(foot.x, foot.y - 1)) foot.y--;
if (foot == v8 && foot != arg)
return 0;
v8 = foot;
}
node = v4;
return v4.sqrDist(to);
}
v8 = foot;
}
// there exists an unobstructed path
return 1;
}
void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) {
if ((pos.x < to.x) && (pos.x < getPathWidth()) && isPathClear(pos.x + 2, pos.y)) {
pos.x = (pos.x + 2 < to.x) ? pos.x + 2 : to.x;
}
if ((pos.x > to.x) && (pos.x > 0) && isPathClear(pos.x - 2, pos.y)) {
pos.x = (pos.x - 2 > to.x) ? pos.x - 2 : to.x;
}
if ((pos.y < to.y) && (pos.y < getPathHeight()) && isPathClear(pos.x, pos.y + 2)) {
pos.y = (pos.y + 2 <= to.y) ? pos.y + 2 : to.y;
}
if ((pos.y > to.y) && (pos.y > 0) && isPathClear(pos.x, pos.y - 2)) {
pos.y = (pos.y - 2 >= to.y) ? pos.y - 2 : to.y;
}
}
void PathWalker_NS::checkDoor(const Common::Point &foot) {
ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
if (z) {
if ((z->_flags & kFlagsClosed) == 0) {
_vm->_location._startPosition = z->u._doorStartPos;
_vm->_location._startFrame = z->u._doorStartFrame;
_vm->scheduleLocationSwitch(z->u._doorLocation.c_str());
_vm->_zoneTrap.reset();
} else {
_vm->_cmdExec->run(z->_commands, z);
}
}
z = _vm->hitZone(kZoneTrap, foot.x, foot.y);
if (z) {
_vm->setLocationFlags(kFlagsEnter);
_vm->_cmdExec->run(z->_commands, z);
_vm->clearLocationFlags(kFlagsEnter);
_vm->_zoneTrap = z;
} else
if (_vm->_zoneTrap) {
_vm->setLocationFlags(kFlagsExit);
_vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap);
_vm->clearLocationFlags(kFlagsExit);
_vm->_zoneTrap.reset();
}
}
void PathWalker_NS::finalizeWalk() {
g_engineFlags &= ~kEngineWalking;
Common::Point foot;
_a->getFoot(foot);
checkDoor(foot);
_walkPath.clear();
}
void PathWalker_NS::walk() {
if ((g_engineFlags & kEngineWalking) == 0) {
return;
}
Common::Point curPos;
_a->getFoot(curPos);
// update target, if previous was reached
PointList::iterator it = _walkPath.begin();
if (it != _walkPath.end()) {
if (*it == curPos) {
debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it).x, (*it).y);
it = _walkPath.erase(it);
}
}
// advance character towards the target
Common::Point targetPos;
if (it == _walkPath.end()) {
debugC(1, kDebugWalk, "walk reached last node");
finalizeWalk();
targetPos = curPos;
} else {
// targetPos is saved to help setting character direction
targetPos = *it;
Common::Point newPos(curPos);
clipMove(newPos, targetPos);
_a->setFoot(newPos);
if (newPos == curPos) {
debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle");
finalizeWalk();
targetPos = newPos; // when walking is interrupted, targetPos must be hacked so that a still frame can be selected
}
}
// targetPos is used to select the direction (and the walkFrame) of a character,
// since it doesn't cause the sudden changes in orientation that newPos would.
// Since newPos is 'adjusted' according to walkable areas, an imaginary line drawn
// from curPos to newPos is prone to abrutply change in direction, thus making the
// code select 'too different' frames when walking diagonally against obstacles,
// and yielding an annoying shaking effect in the character.
updateDirection(curPos, targetPos);
}
void PathWalker_NS::updateDirection(const Common::Point& pos, const Common::Point& to) {
Common::Point dist(to.x - pos.x, to.y - pos.y);
WalkFrames *frames = (_a->getFrameNum() == 20) ? &_char20WalkFrames_NS : &_char24WalkFrames_NS;
_step++;
if (dist.x == 0 && dist.y == 0) {
_a->setF(frames->stillFrame[_direction]);
return;
}
if (dist.x < 0)
dist.x = -dist.x;
if (dist.y < 0)
dist.y = -dist.y;
_direction = (dist.x > dist.y) ? ((to.x > pos.x) ? WALK_LEFT : WALK_RIGHT) : ((to.y > pos.y) ? WALK_DOWN : WALK_UP);
_a->setF(frames->firstWalkFrame[_direction] + (_step / frames->frameRepeat[_direction]) % frames->numWalkFrames[_direction]);
}
PathWalker_NS::PathWalker_NS(Parallaction *vm) : _direction(WALK_DOWN), _step(0), _vm(vm) {
}
bool PathWalker_BR::directPathExists(const Common::Point &from, const Common::Point &to) {
Common::Point copy(from);
Common::Point p(copy);
while (p != to) {
if (p.x < to.x && isPathClear(p.x + 1, p.y)) p.x++;
if (p.x > to.x && isPathClear(p.x - 1, p.y)) p.x--;
if (p.y < to.y && isPathClear(p.x, p.y + 1)) p.y++;
if (p.y > to.y && isPathClear(p.x, p.y - 1)) p.y--;
if (p == copy && p != to)
return false;
copy = p;
}
return true;
}
void PathWalker_BR::setCharacterPath(AnimationPtr a, uint16 x, uint16 y) {
_character._a = a;
_character._first = true;
_character._stillWalkingTowardsNode = true;
_character._walkDelay = 0;
buildPath(_character, x, y);
_character._active = true;
}
void PathWalker_BR::setFollowerPath(AnimationPtr a, uint16 x, uint16 y) {
_follower._a = a;
_follower._first = true;
_follower._stillWalkingTowardsNode = true;
_follower._walkDelay = 5;
buildPath(_follower, x - 50, y);
_follower._active = true;
}
void PathWalker_BR::stopFollower() {
if (_follower._active) {
uint32 frame = _follower._a->getF();
_follower._a->setF((frame/9) * 9);
}
_follower._a.reset();
_follower._active = false;
}
void PathWalker_BR::buildPath(State &s, uint16 x, uint16 y) {
Common::Point foot;
s._a->getFoot(foot);
debugC(1, kDebugWalk, "buildPath: try to build path from (%i, %i) to (%i, %i)", foot.x, foot.y, x, y);
s._walkPath.clear();
// look for easy path first
Common::Point dest(x, y);
if (directPathExists(foot, dest)) {
s._walkPath.push_back(dest);
debugC(3, kDebugWalk, "buildPath: direct path found");
return;
}
// look for short circuit cases
ZonePtr z0 = _vm->hitZone(kZonePath, x, y);
if (!z0) {
s._walkPath.push_back(dest);
debugC(3, kDebugWalk, "buildPath: corner case 0 (%i nodes)", s._walkPath.size());
return;
}
ZonePtr z1 = _vm->hitZone(kZonePath, foot.x, foot.y);
if (!z1 || z1 == z0) {
s._walkPath.push_back(dest);
debugC(3, kDebugWalk, "buildPath: corner case 1 (%i nodes)", s._walkPath.size());
return;
}
// build complex path
int id = atoi(z0->_name);
if (z1->u._pathLists[id].empty()) {
s._walkPath.clear();
debugC(3, kDebugWalk, "buildPath: no path found");
// If no path, trigger finalize and stop of walking...
s._stillWalkingTowardsNode = false;
return;
}
PointList::iterator b = z1->u._pathLists[id].begin();
PointList::iterator e = z1->u._pathLists[id].end();
for ( ; b != e; ++b) {
s._walkPath.push_front(*b);
}
s._walkPath.push_back(dest);
debugC(3, kDebugWalk, "buildPath: complex path (%i nodes)", s._walkPath.size());
}
void PathWalker_BR::finalizeWalk(State &s) {
g_engineFlags &= ~kEngineWalking;
Common::Point foot;
_character._a->getFoot(foot);
ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
if (z && ((z->_flags & kFlagsClosed) == 0)) {
_vm->_location._startPosition = z->u._doorStartPos; // foot pos
_vm->_location._startFrame = z->u._doorStartFrame;
// TODO: implement working follower. Must find out a location in which the code is
// used and which is stable enough.
if (_follower._active) {
_vm->_location._followerStartPosition = z->u._doorStartPos2_br; // foot pos
_vm->_location._followerStartFrame = z->u._doorStartFrame2_br;
} else {
_vm->_location._followerStartPosition.x = -1000;
_vm->_location._followerStartPosition.y = -1000;
_vm->_location._followerStartFrame = 0;
}
_vm->scheduleLocationSwitch(z->u._doorLocation.c_str());
_vm->_cmdExec->run(z->_commands, z);
}
#if 0
// TODO: Input::walkTo must be extended to support destination frame in addition to coordinates
if (g_engineFlags & FINAL_WALK_FRAME) { // this flag is set in readInput()
g_engineFlags &= ~FINAL_WALK_FRAME;
_ch._a->_frame = _moveToF; // from readInput()...
} else {
_ch._a->_frame = _dirFrame; // from walk()
}
_ch._a->setFoot(foot);
#endif
s._a->setF(s._dirFrame); // temporary solution
s._active = false;
}
void PathWalker_BR::walk() {
if ((g_engineFlags & kEngineWalking) == 0) {
return;
}
doWalk(_character);
doWalk(_follower);
Common::Point pos, foot;
_vm->_gfx->getScrollPos(pos);
_character._a->getFoot(foot);
int32 dx = 0, dy = 0;
if (foot.x > pos.x + 600) {
dx = 78*4;
} else
if (foot.x < pos.x + 40) {
dx = -78*4;
}
if (foot.y > pos.y + 350) {
dy = 100;
} else
if (foot.y < pos.y + 80) {
dy = -100;
}
_vm->_gfx->initiateScroll(dx, dy);
}
void PathWalker_BR::checkTrap(const Common::Point &p) {
ZonePtr z = _vm->hitZone(kZoneTrap, p.x, p.y);
if (z && z != _vm->_zoneTrap) {
if (z->_flags & kFlagsIsAnimation) {
z->_flags |= kFlagsActing;
} else {
_vm->setLocationFlags(kFlagsExit);
_vm->_cmdExec->run(z->_commands, z);
_vm->clearLocationFlags(kFlagsExit);
}
}
if (_vm->_zoneTrap && _vm->_zoneTrap != z) {
if (_vm->_zoneTrap->_flags & kFlagsIsAnimation) {
_vm->_zoneTrap->_flags &= ~kFlagsActing;
} else {
_vm->setLocationFlags(kFlagsEnter);
_vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap);
_vm->clearLocationFlags(kFlagsEnter);
}
}
_vm->_zoneTrap = z;
}
void PathWalker_BR::doWalk(State &s) {
if (!s._active) {
return;
}
if (s._walkDelay > 0) {
s._walkDelay--;
if (s._walkDelay == 0 && !s._a->_scriptName.empty()) {
// stop script and reset
s._a->_flags &= ~kFlagsActing;
// _vm->_programExec->resetProgram(s._a->_scriptName);
}
return;
}
if (!s._stillWalkingTowardsNode) {
if (!s._walkPath.empty())
s._walkPath.erase(s._walkPath.begin());
if (s._walkPath.empty()) {
finalizeWalk(s);
debugC(3, kDebugWalk, "PathWalker_BR::doWalk, walk completed (no more nodes)");
return;
} else {
debugC(3, kDebugWalk, "PathWalker_BR::doWalk, reached a walkpath node, %i left", s._walkPath.size());
}
}
s._a->getFoot(s._startFoot);
uint scale = _vm->_location.getScale(s._startFoot.y);
int xStep = (scale * 16) / 100 + 1;
int yStep = (scale * 10) / 100 + 1;
/* WORKAROUND: in the balloon scene, the position of the balloon (which is implemented as a
Character) is controlled by the user (for movement, via this walking code) and by the scripts
(to simulate the balloon floating in the air, in a neverending loop that alters the position
coordinates).
When the two step sizes are equal in magnitude and opposite in direction, then the walk code
enters an infinite loop without giving control back to the user (this happens quite frequently
when navigating the balloon near the borders of the screen, where the calculated step is
forcibly small because of clipping). Since the "floating" script (part1/scripts/mongolo.scr)
uses increments of 3 for both x and y, we tweak the calculated steps accordingly here. */
if (xStep == 3) xStep--;
if (yStep == 3) yStep--;
debugC(9, kDebugWalk, "calculated step: (%i, %i)", xStep, yStep);
s._stillWalkingTowardsNode = false;
s._step++;
s._step %= 8;
int maxX = _vm->_gfx->_backgroundInfo->width;
int minX = 0;
int maxY = _vm->_gfx->_backgroundInfo->height;
int minY = 0;
int walkFrame = s._step;
s._dirFrame = 0;
Common::Point newpos(s._startFoot), delta;
assert (!s._walkPath.empty());
Common::Point p(*s._walkPath.begin());
if (s._startFoot.y < p.y && (s._startFoot.y + yStep) < maxY && isPathClear(s._startFoot.x, s._startFoot.y + yStep)) {
if (yStep + s._startFoot.y <= p.y) {
s._stillWalkingTowardsNode = true;
delta.y = yStep;
newpos.y = yStep + s._startFoot.y;
} else {
delta.y = p.y - s._startFoot.y;
newpos.y = p.y;
}
s._dirFrame = 9;
} else
if (s._startFoot.y > p.y && (s._startFoot.y - yStep) > minY && isPathClear(s._startFoot.x, s._startFoot.y - yStep)) {
if (s._startFoot.y - yStep >= p.y) {
s._stillWalkingTowardsNode = true;
delta.y = yStep;
newpos.y = s._startFoot.y - yStep;
} else {
delta.y = s._startFoot.y - p.y;
newpos.y = p.y;
}
s._dirFrame = 0;
}
if (s._startFoot.x < p.x && (s._startFoot.x + xStep) < maxX && isPathClear(s._startFoot.x + xStep, s._startFoot.y)) {
if (s._startFoot.x + xStep <= p.x) {
s._stillWalkingTowardsNode = true;
delta.x = xStep;
newpos.x = xStep + s._startFoot.x;
} else {
delta.x = p.x - s._startFoot.x;
newpos.x = p.x;
}
if (delta.y < delta.x) {
s._dirFrame = 18; // right
}
} else
if (s._startFoot.x > p.x && (s._startFoot.x - xStep) > minX && isPathClear(s._startFoot.x - xStep, s._startFoot.y)) {
if (s._startFoot.x - xStep >= p.x) {
s._stillWalkingTowardsNode = true;
delta.x = xStep;
newpos.x = s._startFoot.x - xStep;
} else {
delta.x = s._startFoot.x - p.x;
newpos.x = p.x;
}
if (delta.y < delta.x) {
s._dirFrame = 27; // left
}
}
debugC(9, kDebugWalk, "foot (%i, %i) dest (%i, %i) deltas = %i/%i ", s._startFoot.x, s._startFoot.y, p.x, p.y, delta.x, delta.y);
if (s._stillWalkingTowardsNode) {
debugC(9, kDebugWalk, "PathWalker_BR::doWalk, foot moved from (%i, %i) to (%i, %i)", s._startFoot.x, s._startFoot.y, newpos.x, newpos.y);
s._a->setF(walkFrame + s._dirFrame + 1);
s._startFoot.x = newpos.x;
s._startFoot.y = newpos.y;
s._a->setFoot(s._startFoot);
s._a->setZ(newpos.y);
}
if (s._stillWalkingTowardsNode || !s._walkPath.empty()) {
Common::Point p2;
s._a->getFoot(p2);
checkTrap(p2);
debugC(3, kDebugWalk, "PathWalker_BR::doWalk, stepped to (%i, %i)", p2.x, p2.y);
} else {
debugC(3, kDebugWalk, "PathWalker_BR::doWalk, case 2");
finalizeWalk(s);
}
}
PathWalker_BR::PathWalker_BR(Parallaction *vm) : _vm(vm) {
_character._active = false;
_character._step = 0;
_follower._active = false;
_follower._step = 0;
}
} // namespace Parallaction

View File

@@ -0,0 +1,98 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARALLACTION_WALK_H
#define PARALLACTION_WALK_H
#include "common/ptr.h"
#include "common/list.h"
#include "parallaction/objects.h"
namespace Parallaction {
struct Character;
class PathWalker_NS {
AnimationPtr _a;
PointList _walkPath;
int16 _direction, _step;
// builder routines
PointList _subPath;
void correctPathPoint(Common::Point &to);
uint32 buildSubPath(const Common::Point& pos, const Common::Point& stop);
uint16 walkFunc1(const Common::Point &to, Common::Point& node);
// walker routines
void finalizeWalk();
void clipMove(Common::Point& pos, const Common::Point& to);
void checkDoor(const Common::Point &foot);
void updateDirection(const Common::Point& pos, const Common::Point& to);
Parallaction *_vm;
public:
PathWalker_NS(Parallaction *vm);
void buildPath(AnimationPtr a, uint16 x, uint16 y);
void walk();
};
class PathWalker_BR {
struct State {
bool _active;
AnimationPtr _a;
int _walkDelay;
bool _stillWalkingTowardsNode;
Common::Point _startFoot;
bool _first;
int _step;
int _dirFrame;
PointList _walkPath;
};
State _character;
State _follower;
void finalizeWalk(State &s);
bool directPathExists(const Common::Point &from, const Common::Point &to);
void buildPath(State &s, uint16 x, uint16 y);
void doWalk(State &s);
void checkTrap(const Common::Point &p);
Parallaction *_vm;
public:
PathWalker_BR(Parallaction *vm);
~PathWalker_BR() { }
void setCharacterPath(AnimationPtr a, uint16 x, uint16 y);
void setFollowerPath(AnimationPtr a, uint16 x, uint16 y);
void stopFollower();
void walk();
};
}
#endif