Initial commit
This commit is contained in:
486
engines/freescape/language/8bitDetokeniser.cpp
Normal file
486
engines/freescape/language/8bitDetokeniser.cpp
Normal file
@@ -0,0 +1,486 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on Phantasma code by Thomas Harte (2013),
|
||||
// available at https://github.com/TomHarte/Phantasma/ (MIT)
|
||||
// which was implemented based on John Elliott's reverse engineering of Driller (2001)
|
||||
// https://web.archive.org/web/20200116141513/http://www.seasip.demon.co.uk/ZX/Driller/
|
||||
|
||||
#include "freescape/freescape.h"
|
||||
#include "freescape/language/8bitDetokeniser.h"
|
||||
|
||||
namespace Freescape {
|
||||
|
||||
uint8 k8bitVariableShield = 63;
|
||||
|
||||
Common::String detokenise8bitCondition(Common::Array<uint16> &tokenisedCondition, FCLInstructionVector &instructions, bool isAmigaAtari) {
|
||||
Common::String detokenisedStream;
|
||||
Common::Array<uint8>::size_type bytePointer = 0;
|
||||
Common::Array<uint8>::size_type sizeOfTokenisedContent = tokenisedCondition.size();
|
||||
|
||||
if (sizeOfTokenisedContent == 0)
|
||||
error("No tokenised content");
|
||||
|
||||
// on the 8bit platforms, all instructions have a conditional flag;
|
||||
// we'll want to convert them into runs of "if shot? then", "if collided? then" or "if timer? then",
|
||||
// and we'll want to start that from the top
|
||||
FCLInstructionVector *conditionalInstructions = new FCLInstructionVector();
|
||||
FCLInstruction currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
|
||||
// this lookup table tells us how many argument bytes to read per opcode
|
||||
uint8 argumentsRequiredByOpcode[49] =
|
||||
{0, 3, 1, 1, 1, 1, 2, 2,
|
||||
2, 1, 1, 2, 1, 1, 2, 1,
|
||||
1, 2, 2, 1, 2, 0, 0, 0,
|
||||
1, 1, 0, 1, 1, 1, 1, 1,
|
||||
2, 2, 1, 1, 1, 1, 0, 0,
|
||||
0, 1, 0, 0, 0, 0, 2, 2,
|
||||
1};
|
||||
|
||||
if (sizeOfTokenisedContent > 0)
|
||||
detokenisedStream += Common::String::format("CONDITION FLAG: %x\n", tokenisedCondition[0]);
|
||||
uint16 newConditional = 0;
|
||||
uint16 oldConditional = 0;
|
||||
|
||||
while (bytePointer < sizeOfTokenisedContent) {
|
||||
// get the conditional type of the next operation
|
||||
uint8 conditionalByte = tokenisedCondition[bytePointer] & 0xc0;
|
||||
//detokenisedStream += Common::String::format("CONDITION FLAG: %x\n", conditionalByte);
|
||||
newConditional = 0;
|
||||
|
||||
if (conditionalByte == 0x40)
|
||||
newConditional = kConditionalTimeout;
|
||||
else if (conditionalByte == 0x80)
|
||||
newConditional = kConditionalShot;
|
||||
else if (conditionalByte == 0xc0)
|
||||
newConditional = kConditionalActivated;
|
||||
else
|
||||
newConditional = kConditionalCollided;
|
||||
|
||||
// if the conditional type has changed then end the old conditional,
|
||||
// if we were in one, and begin a new one
|
||||
if (bytePointer == 0 || newConditional != oldConditional) {
|
||||
oldConditional = newConditional;
|
||||
FCLInstruction branch;
|
||||
branch = FCLInstruction(Token::CONDITIONAL);
|
||||
|
||||
if (bytePointer > 0) {
|
||||
detokenisedStream += "ENDIF\n";
|
||||
assert(conditionalInstructions->size() > 0);
|
||||
// Allocate the next vector of instructions
|
||||
conditionalInstructions = new FCLInstructionVector();
|
||||
}
|
||||
|
||||
branch.setBranches(conditionalInstructions, nullptr);
|
||||
branch.setSource(oldConditional); // conditional flag
|
||||
instructions.push_back(branch);
|
||||
|
||||
detokenisedStream += "IF ";
|
||||
|
||||
if (oldConditional & kConditionalShot)
|
||||
detokenisedStream += "SHOT? ";
|
||||
else if (oldConditional & kConditionalTimeout)
|
||||
detokenisedStream += "TIMER? ";
|
||||
else if (oldConditional & kConditionalCollided)
|
||||
detokenisedStream += "COLLIDED? ";
|
||||
else if (oldConditional & kConditionalActivated)
|
||||
detokenisedStream += "ACTIVATED? ";
|
||||
else
|
||||
error("Invalid conditional: %x", oldConditional);
|
||||
|
||||
detokenisedStream += "THEN\n";
|
||||
}
|
||||
|
||||
// get the actual operation
|
||||
uint16 opcode = tokenisedCondition[bytePointer] & 0x3f;
|
||||
bytePointer++;
|
||||
|
||||
// figure out how many argument bytes we're going to need,
|
||||
// check we have enough bytes left to read
|
||||
if (opcode > 48) {
|
||||
debugC(1, kFreescapeDebugParser, "%s", detokenisedStream.c_str());
|
||||
error("ERROR: failed to read opcode: %x", opcode);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8 numberOfArguments = argumentsRequiredByOpcode[opcode];
|
||||
if (bytePointer + numberOfArguments > sizeOfTokenisedContent)
|
||||
break;
|
||||
|
||||
// generate the string
|
||||
switch (opcode) {
|
||||
default:
|
||||
detokenisedStream += "<UNKNOWN 8 bit: ";
|
||||
detokenisedStream += Common::String::format("%x", (int)opcode);
|
||||
detokenisedStream += " > ";
|
||||
debugC(1, kFreescapeDebugParser, "%s", detokenisedStream.c_str());
|
||||
error("ERROR: failed to read opcode: %x", opcode);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
detokenisedStream += "NOP ";
|
||||
currentInstruction = FCLInstruction(Token::NOP);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
break; // NOP
|
||||
case 1: // add three-byte value to score
|
||||
{
|
||||
int32 additionValue =
|
||||
tokenisedCondition[bytePointer] |
|
||||
(tokenisedCondition[bytePointer + 1] << 8) |
|
||||
(tokenisedCondition[bytePointer + 2] << 16);
|
||||
detokenisedStream += "ADDVAR";
|
||||
detokenisedStream += Common::String::format("(%d, v%d)", additionValue, k8bitVariableScore);
|
||||
currentInstruction = FCLInstruction(Token::ADDVAR);
|
||||
currentInstruction.setSource(k8bitVariableScore);
|
||||
currentInstruction.setDestination(additionValue);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer += 3;
|
||||
numberOfArguments = 0;
|
||||
} break;
|
||||
case 2: // add one-byte value to energy
|
||||
detokenisedStream += "ADDVAR ";
|
||||
detokenisedStream += Common::String::format("(%d, v%d)", (int8)tokenisedCondition[bytePointer], k8bitVariableEnergy);
|
||||
currentInstruction = FCLInstruction(Token::ADDVAR);
|
||||
currentInstruction.setSource(k8bitVariableEnergy);
|
||||
currentInstruction.setDestination((int8)tokenisedCondition[bytePointer]);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
case 19: // add one-byte value to shield
|
||||
detokenisedStream += "ADDVAR ";
|
||||
detokenisedStream += Common::String::format("(%d, v%d)", (int8)tokenisedCondition[bytePointer], k8bitVariableShield);
|
||||
currentInstruction = FCLInstruction(Token::ADDVAR);
|
||||
currentInstruction.setSource(k8bitVariableShield);
|
||||
currentInstruction.setDestination((int8)tokenisedCondition[bytePointer]);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
case 3:
|
||||
detokenisedStream += "TOGVIS (";
|
||||
currentInstruction = FCLInstruction(Token::TOGVIS);
|
||||
currentInstruction.setSource(0);
|
||||
currentInstruction.setDestination(0);
|
||||
break; // these all come in unary and binary versions,
|
||||
case 7:
|
||||
case 4:
|
||||
detokenisedStream += "VIS (";
|
||||
currentInstruction = FCLInstruction(Token::VIS);
|
||||
currentInstruction.setSource(0);
|
||||
currentInstruction.setDestination(0);
|
||||
break; // hence each getting two case statement entries
|
||||
case 8:
|
||||
case 5:
|
||||
detokenisedStream += "INVIS (";
|
||||
currentInstruction = FCLInstruction(Token::INVIS);
|
||||
currentInstruction.setSource(0);
|
||||
currentInstruction.setDestination(0);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
detokenisedStream += "ADDVAR (1, v";
|
||||
detokenisedStream += Common::String::format("%d)", tokenisedCondition[bytePointer]);
|
||||
currentInstruction = FCLInstruction(Token::ADDVAR);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setDestination(1);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
case 10:
|
||||
detokenisedStream += "SUBVAR (1, v";
|
||||
detokenisedStream += Common::String::format("%d)", tokenisedCondition[bytePointer]);
|
||||
currentInstruction = FCLInstruction(Token::SUBVAR);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setDestination(1);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 11: // end condition if a variable doesn't have a particular value
|
||||
detokenisedStream += "IF VAR!=? ";
|
||||
detokenisedStream += Common::String::format("(v%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
||||
detokenisedStream += " THEN END ENDIF";
|
||||
currentInstruction = FCLInstruction(Token::VARNOTEQ);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setDestination(tokenisedCondition[bytePointer + 1]);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer += 2;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
case 14: // end condition if a bit doesn't have a particular value
|
||||
detokenisedStream += "IF BIT!=? ";
|
||||
detokenisedStream += Common::String::format("(%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
||||
detokenisedStream += " THEN END ENDIF";
|
||||
currentInstruction = FCLInstruction(Token::BITNOTEQ);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setDestination(tokenisedCondition[bytePointer + 1]);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer += 2;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
case 30: // end condition if an object is invisible
|
||||
detokenisedStream += "IF INVIS? ";
|
||||
detokenisedStream += Common::String::format("(%d)", (int)tokenisedCondition[bytePointer]);
|
||||
detokenisedStream += " THEN END ENDIF";
|
||||
currentInstruction = FCLInstruction(Token::INVISQ);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setDestination(true); // invisible
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
case 31: // end condition if an object is visible
|
||||
detokenisedStream += "IF VIS? ";
|
||||
detokenisedStream += Common::String::format("(%d)", (int)tokenisedCondition[bytePointer]);
|
||||
detokenisedStream += " THEN END ENDIF";
|
||||
currentInstruction = FCLInstruction(Token::INVISQ);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setDestination(false); // visible
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 32: // end condition if an object is visible in another area
|
||||
detokenisedStream += "IF RINVIS? ";
|
||||
detokenisedStream += Common::String::format("(%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
||||
detokenisedStream += " THEN END ENDIF";
|
||||
currentInstruction = FCLInstruction(Token::INVISQ);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setAdditional(tokenisedCondition[bytePointer + 1]);
|
||||
currentInstruction.setDestination(true); // invisible
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer += 2;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 33: // end condition if an object is invisible in another area
|
||||
detokenisedStream += "IF RVIS? ";
|
||||
detokenisedStream += Common::String::format("(%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
||||
detokenisedStream += " THEN END ENDIF";
|
||||
currentInstruction = FCLInstruction(Token::INVISQ);
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
currentInstruction.setAdditional(tokenisedCondition[bytePointer + 1]);
|
||||
currentInstruction.setDestination(false); // visible
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer += 2;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 34: // show a message on screen
|
||||
detokenisedStream += "PRINT (";
|
||||
currentInstruction = FCLInstruction(Token::PRINT);
|
||||
break;
|
||||
|
||||
case 35:
|
||||
detokenisedStream += "SCREEN (";
|
||||
currentInstruction = FCLInstruction(Token::SCREEN);
|
||||
break;
|
||||
|
||||
case 36: // Only used in Dark Side to keep track of cristals and letters collected
|
||||
detokenisedStream += "SETFLAGS (";
|
||||
currentInstruction = FCLInstruction(Token::SETFLAGS);
|
||||
break;
|
||||
|
||||
case 37:
|
||||
detokenisedStream += "STARTANIM (";
|
||||
currentInstruction = FCLInstruction(Token::STARTANIM);
|
||||
break;
|
||||
|
||||
case 41: // Not sure about this one
|
||||
detokenisedStream += "LOOP (";
|
||||
currentInstruction = FCLInstruction(Token::LOOP);
|
||||
break;
|
||||
|
||||
case 42: // Not sure about this one
|
||||
detokenisedStream += "AGAIN";
|
||||
currentInstruction = FCLInstruction(Token::AGAIN);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
detokenisedStream += "SETBIT (";
|
||||
currentInstruction = FCLInstruction(Token::SETBIT);
|
||||
break;
|
||||
case 13:
|
||||
detokenisedStream += "CLRBIT (";
|
||||
currentInstruction = FCLInstruction(Token::CLEARBIT);
|
||||
break;
|
||||
|
||||
case 15:
|
||||
detokenisedStream += "SOUND (";
|
||||
currentInstruction = FCLInstruction(Token::SOUND);
|
||||
currentInstruction.setAdditional(false);
|
||||
break;
|
||||
case 17:
|
||||
case 16:
|
||||
detokenisedStream += "DESTROY (";
|
||||
currentInstruction = FCLInstruction(Token::DESTROY);
|
||||
break;
|
||||
case 18:
|
||||
detokenisedStream += "GOTO (";
|
||||
currentInstruction = FCLInstruction(Token::GOTO);
|
||||
break;
|
||||
|
||||
case 21:
|
||||
detokenisedStream += "SWAPJET";
|
||||
currentInstruction = FCLInstruction(Token::SWAPJET);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
/*
|
||||
case 22:
|
||||
case 23:
|
||||
case 24:
|
||||
UNUSED
|
||||
*/
|
||||
|
||||
case 26:
|
||||
detokenisedStream += "REDRAW";
|
||||
currentInstruction = FCLInstruction(Token::REDRAW);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
break;
|
||||
case 27:
|
||||
detokenisedStream += "DELAY (";
|
||||
currentInstruction = FCLInstruction(Token::DELAY);
|
||||
break;
|
||||
case 28:
|
||||
detokenisedStream += "SYNCSND (";
|
||||
currentInstruction = FCLInstruction(Token::SOUND);
|
||||
currentInstruction.setAdditional(true);
|
||||
break;
|
||||
case 29:
|
||||
detokenisedStream += "TOGGLEBIT (";
|
||||
currentInstruction = FCLInstruction(Token::TOGGLEBIT);
|
||||
break;
|
||||
|
||||
case 25:
|
||||
// this should toggle border colour or the room palette
|
||||
detokenisedStream += "SPFX (";
|
||||
currentInstruction = FCLInstruction(Token::SPFX);
|
||||
if (isAmigaAtari) {
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer] >> 8);
|
||||
currentInstruction.setDestination(tokenisedCondition[bytePointer] & 0xff);
|
||||
} else {
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer] >> 4);
|
||||
currentInstruction.setDestination(tokenisedCondition[bytePointer] & 0xf);
|
||||
}
|
||||
detokenisedStream += Common::String::format("%d, %d)", currentInstruction._source, currentInstruction._destination);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
bytePointer++;
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 20:
|
||||
detokenisedStream += "SETVAR (v";
|
||||
currentInstruction = FCLInstruction(Token::SETVAR);
|
||||
break;
|
||||
|
||||
case 44:
|
||||
detokenisedStream += "ELSE ";
|
||||
currentInstruction = FCLInstruction(Token::ELSE);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 45:
|
||||
detokenisedStream += "ENDIF ";
|
||||
currentInstruction = FCLInstruction(Token::ENDIF);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
numberOfArguments = 0;
|
||||
break;
|
||||
|
||||
case 46:
|
||||
detokenisedStream += "IFGTE (v";
|
||||
currentInstruction = FCLInstruction(Token::IFGTEQ);
|
||||
break;
|
||||
|
||||
case 47:
|
||||
detokenisedStream += "IFLTE (v";
|
||||
currentInstruction = FCLInstruction(Token::IFLTEQ);
|
||||
break;
|
||||
|
||||
case 48:
|
||||
detokenisedStream += "EXECUTE (";
|
||||
currentInstruction = FCLInstruction(Token::EXECUTE);
|
||||
break;
|
||||
}
|
||||
|
||||
// if there are any regular arguments to add, do so
|
||||
if (numberOfArguments) {
|
||||
for (uint8 argumentNumber = 0; argumentNumber < numberOfArguments; argumentNumber++) {
|
||||
if (argumentNumber == 0)
|
||||
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
||||
else if (argumentNumber == 1)
|
||||
currentInstruction.setDestination(tokenisedCondition[bytePointer]);
|
||||
else
|
||||
error("Unexpected number of arguments!");
|
||||
|
||||
detokenisedStream += Common::String::format("%d", (int)tokenisedCondition[bytePointer]);
|
||||
bytePointer++;
|
||||
|
||||
if (argumentNumber < numberOfArguments - 1)
|
||||
detokenisedStream += ", ";
|
||||
}
|
||||
|
||||
detokenisedStream += ")";
|
||||
assert(currentInstruction.getType() != Token::UNKNOWN);
|
||||
conditionalInstructions->push_back(currentInstruction);
|
||||
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
||||
}
|
||||
|
||||
// throw in a newline
|
||||
detokenisedStream += "\n";
|
||||
}
|
||||
|
||||
// This fails in Castle Master
|
||||
//assert(conditionalInstructions->size() > 0);
|
||||
|
||||
return detokenisedStream;
|
||||
}
|
||||
|
||||
} // End of namespace Freescape
|
||||
58
engines/freescape/language/8bitDetokeniser.h
Normal file
58
engines/freescape/language/8bitDetokeniser.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/* 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 FREESCAPE_8BITDETOKENIZER_H
|
||||
#define FREESCAPE_8BITDETOKENIZER_H
|
||||
|
||||
#include "freescape/language/instruction.h"
|
||||
|
||||
namespace Freescape {
|
||||
|
||||
enum {
|
||||
k8bitGameBitTravelRock = 30
|
||||
};
|
||||
|
||||
enum {
|
||||
k8bitVariableCrawling = 30,
|
||||
k8bitVariableSpiritsDestroyed = 28,
|
||||
k8bitVariableEnergy = 62,
|
||||
k8bitVariableScore = 61,
|
||||
k8bitVariableShieldDrillerTank = 60,
|
||||
k8bitVariableEnergyDrillerTank = 59,
|
||||
k8bitVariableShieldDrillerJet = 58,
|
||||
k8bitVariableEnergyDrillerJet = 57,
|
||||
k8bitMaxVariable = 64
|
||||
};
|
||||
|
||||
enum {
|
||||
kConditionalShot = 1 << 0,
|
||||
kConditionalTimeout = 1 << 1,
|
||||
kConditionalCollided = 1 << 2,
|
||||
kConditionalActivated = 1 << 3,
|
||||
};
|
||||
|
||||
extern uint8 k8bitVariableShield;
|
||||
|
||||
Common::String detokenise8bitCondition(Common::Array<uint16> &tokenisedCondition, FCLInstructionVector &instructions, bool enableActivated);
|
||||
|
||||
} // End of namespace Freescape
|
||||
|
||||
#endif // FREESCAPE_8BITDETOKENIZER_H
|
||||
829
engines/freescape/language/instruction.cpp
Normal file
829
engines/freescape/language/instruction.cpp
Normal file
@@ -0,0 +1,829 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on Phantasma code by Thomas Harte (2013),
|
||||
// available at https://github.com/TomHarte/Phantasma/ (MIT)
|
||||
|
||||
#include "freescape/freescape.h"
|
||||
#include "freescape/language/8bitDetokeniser.h"
|
||||
#include "freescape/sweepAABB.h"
|
||||
|
||||
namespace Freescape {
|
||||
|
||||
FCLInstructionVector *duplicateCondition(FCLInstructionVector *condition) {
|
||||
if (!condition)
|
||||
return nullptr;
|
||||
|
||||
FCLInstructionVector *copy = new FCLInstructionVector();
|
||||
for (uint i = 0; i < condition->size(); i++) {
|
||||
copy->push_back((*condition)[i].duplicate());
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
FCLInstruction FCLInstruction::duplicate() {
|
||||
FCLInstruction copy(_type);
|
||||
copy.setSource(_source);
|
||||
copy.setDestination(_destination);
|
||||
copy.setAdditional(_additional);
|
||||
|
||||
copy._thenInstructions = duplicateCondition(_thenInstructions);
|
||||
copy._elseInstructions = duplicateCondition(_elseInstructions);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
FCLInstruction::FCLInstruction(Token::Type type_) {
|
||||
_source = 0;
|
||||
_destination = 0;
|
||||
_additional = 0;
|
||||
_type = type_;
|
||||
_thenInstructions = nullptr;
|
||||
_elseInstructions = nullptr;
|
||||
}
|
||||
|
||||
FCLInstruction::FCLInstruction() {
|
||||
_source = 0;
|
||||
_destination = 0;
|
||||
_additional = 0;
|
||||
_type = Token::UNKNOWN;
|
||||
_thenInstructions = nullptr;
|
||||
_elseInstructions = nullptr;
|
||||
}
|
||||
|
||||
void FCLInstruction::setSource(int32 source_) {
|
||||
_source = source_;
|
||||
}
|
||||
|
||||
void FCLInstruction::setAdditional(int32 additional_) {
|
||||
_additional = additional_;
|
||||
}
|
||||
|
||||
void FCLInstruction::setDestination(int32 destination_) {
|
||||
_destination = destination_;
|
||||
}
|
||||
|
||||
void FCLInstruction::setBranches(FCLInstructionVector *thenBranch, FCLInstructionVector *elseBranch) {
|
||||
_thenInstructions = thenBranch;
|
||||
_elseInstructions = elseBranch;
|
||||
}
|
||||
|
||||
Token::Type FCLInstruction::getType() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeEntranceConditions(Entrance *entrance) {
|
||||
if (!entrance->_conditionSource.empty()) {
|
||||
_firstSound = true;
|
||||
_syncSound = false;
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Executing entrance condition with collision flag: %s", entrance->_conditionSource.c_str());
|
||||
executeCode(entrance->_condition, false, true, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool FreescapeEngine::executeObjectConditions(GeometricObject *obj, bool shot, bool collided, bool activated) {
|
||||
bool executed = false;
|
||||
assert(obj != nullptr);
|
||||
if (!obj->_conditionSource.empty()) {
|
||||
_firstSound = true;
|
||||
_syncSound = false;
|
||||
_objExecutingCodeSize = collided ? obj->getSize() : Math::Vector3d();
|
||||
if (collided) {
|
||||
if (!isCastle())
|
||||
clearGameBit(31); // We collided with something that has code
|
||||
debugC(1, kFreescapeDebugCode, "Executing with collision flag: %s", obj->_conditionSource.c_str());
|
||||
} else if (shot)
|
||||
debugC(1, kFreescapeDebugCode, "Executing with shot flag: %s", obj->_conditionSource.c_str());
|
||||
else if (activated) {
|
||||
if (isCastle()) // TODO: add a 3DCK check here
|
||||
clearTemporalMessages();
|
||||
debugC(1, kFreescapeDebugCode, "Executing with activated flag: %s", obj->_conditionSource.c_str());
|
||||
} else
|
||||
error("Neither shot or collided flag is set!");
|
||||
executed = executeCode(obj->_condition, shot, collided, false, activated); // TODO: check this last parameter
|
||||
}
|
||||
if (activated && !executed)
|
||||
if (!_noEffectMessage.empty())
|
||||
insertTemporaryMessage(_noEffectMessage, _countdown - 2);
|
||||
|
||||
return executed;
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeLocalGlobalConditions(bool shot, bool collided, bool timer) {
|
||||
debugC(1, kFreescapeDebugCode, "Executing room conditions");
|
||||
Common::Array<FCLInstructionVector> conditions = _currentArea->_conditions;
|
||||
Common::Array<Common::String> conditionSources = _currentArea->_conditionSources;
|
||||
|
||||
for (uint i = 0; i < conditions.size(); i++) {
|
||||
debugC(1, kFreescapeDebugCode, "%s", conditionSources[i].c_str());
|
||||
executeCode(conditions[i], shot, collided, timer, false);
|
||||
}
|
||||
|
||||
_executingGlobalCode = true;
|
||||
debugC(1, kFreescapeDebugCode, "Executing global conditions (%d)", _conditions.size());
|
||||
for (uint i = 0; i < _conditions.size(); i++) {
|
||||
debugC(1, kFreescapeDebugCode, "%s", _conditionSources[i].c_str());
|
||||
executeCode(_conditions[i], shot, collided, timer, false);
|
||||
}
|
||||
_executingGlobalCode = false;
|
||||
}
|
||||
|
||||
bool FreescapeEngine::executeCode(FCLInstructionVector &code, bool shot, bool collided, bool timer, bool activated) {
|
||||
int ip = 0;
|
||||
bool skip = false;
|
||||
int skipDepth = 0;
|
||||
int conditionalDepth = 0;
|
||||
bool executed = false;
|
||||
int loopIterations = 0;
|
||||
int loopHead = -1;
|
||||
int codeSize = code.size();
|
||||
|
||||
if (codeSize == 0) {
|
||||
assert(isCastle()); // Only seems to happen in Castle Master (magister room)
|
||||
debugC(1, kFreescapeDebugCode, "Code is empty!");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (ip <= codeSize - 1) {
|
||||
FCLInstruction &instruction = code[ip];
|
||||
debugC(1, kFreescapeDebugCode, "Executing ip: %d with type %d in code with size: %d. Skip flag is: %d", ip, instruction.getType(), codeSize, skip);
|
||||
|
||||
if (instruction.isConditional()) {
|
||||
conditionalDepth++;
|
||||
debugC(1, kFreescapeDebugCode, "Conditional depth increased to: %d", conditionalDepth);
|
||||
} else if (instruction.getType() == Token::ENDIF) {
|
||||
conditionalDepth--;
|
||||
debugC(1, kFreescapeDebugCode, "Conditional depth decreased to: %d", conditionalDepth);
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
if (instruction.getType() == Token::ELSE) {
|
||||
debugC(1, kFreescapeDebugCode, "Else found, skip depth: %d, conditional depth: %d", skipDepth, conditionalDepth);
|
||||
if (skipDepth == conditionalDepth - 1) {
|
||||
skip = false;
|
||||
}
|
||||
} else if (instruction.getType() == Token::ENDIF) {
|
||||
debugC(1, kFreescapeDebugCode, "Endif found, skip depth: %d, conditional depth: %d", skipDepth, conditionalDepth);
|
||||
if (skipDepth == conditionalDepth) {
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
debugC(1, kFreescapeDebugCode, "Instruction skipped!");
|
||||
ip++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instruction.getType() != Token::CONDITIONAL && !instruction.isConditional())
|
||||
executed = true;
|
||||
|
||||
switch (instruction.getType()) {
|
||||
default:
|
||||
error("Instruction %x at ip: %d not implemented!", instruction.getType(), ip);
|
||||
break;
|
||||
case Token::NOP:
|
||||
debugC(1, kFreescapeDebugCode, "Executing NOP at ip: %d", ip);
|
||||
break;
|
||||
|
||||
case Token::LOOP:
|
||||
loopHead = ip;
|
||||
loopIterations = instruction._source;
|
||||
debugC(1, kFreescapeDebugCode, "Starting loop with %d iterations at ip: %d", loopIterations, ip);
|
||||
break;
|
||||
|
||||
case Token::AGAIN:
|
||||
if (loopIterations > 1) {
|
||||
loopIterations--;
|
||||
ip = loopHead;
|
||||
debugC(1, kFreescapeDebugCode, "Looping again, %d iterations left, jumping to ip: %d", loopIterations, ip);
|
||||
} else if (loopIterations == 1) {
|
||||
loopIterations--;
|
||||
debugC(1, kFreescapeDebugCode, "Loop finished");
|
||||
} else {
|
||||
error("AGAIN found without a matching LOOP!");
|
||||
}
|
||||
break;
|
||||
|
||||
case Token::CONDITIONAL:
|
||||
if (checkConditional(instruction, shot, collided, timer, activated))
|
||||
executed = executeCode(*instruction._thenInstructions, shot, collided, timer, activated);
|
||||
// else branch is always empty
|
||||
assert(instruction._elseInstructions == nullptr);
|
||||
break;
|
||||
|
||||
case Token::VARNOTEQ:
|
||||
if (executeEndIfNotEqual(instruction)) {
|
||||
if (isCastle()) {
|
||||
skip = true;
|
||||
skipDepth = conditionalDepth - 1;
|
||||
} else
|
||||
ip = codeSize;
|
||||
}
|
||||
break;
|
||||
case Token::IFGTEQ:
|
||||
skip = !checkIfGreaterOrEqual(instruction);
|
||||
if (skip)
|
||||
skipDepth = conditionalDepth - 1;
|
||||
break;
|
||||
|
||||
case Token::IFLTEQ:
|
||||
skip = !checkIfLessOrEqual(instruction);
|
||||
if (skip)
|
||||
skipDepth = conditionalDepth - 1;
|
||||
break;
|
||||
|
||||
|
||||
case Token::ELSE:
|
||||
skip = !skip;
|
||||
if (skip)
|
||||
skipDepth = conditionalDepth - 1;
|
||||
break;
|
||||
|
||||
case Token::ENDIF:
|
||||
skip = false;
|
||||
break;
|
||||
|
||||
case Token::SWAPJET:
|
||||
executeSwapJet(instruction);
|
||||
break;
|
||||
case Token::ADDVAR:
|
||||
executeIncrementVariable(instruction);
|
||||
break;
|
||||
case Token::SUBVAR:
|
||||
executeDecrementVariable(instruction);
|
||||
break;
|
||||
case Token::SETVAR:
|
||||
executeSetVariable(instruction);
|
||||
break;
|
||||
case Token::GOTO:
|
||||
executeGoto(instruction);
|
||||
break;
|
||||
case Token::TOGVIS:
|
||||
executeToggleVisibility(instruction);
|
||||
break;
|
||||
case Token::INVIS:
|
||||
executeMakeInvisible(instruction);
|
||||
break;
|
||||
case Token::VIS:
|
||||
executeMakeVisible(instruction);
|
||||
break;
|
||||
case Token::DESTROY:
|
||||
executeDestroy(instruction);
|
||||
break;
|
||||
case Token::REDRAW:
|
||||
executeRedraw(instruction);
|
||||
break;
|
||||
case Token::EXECUTE:
|
||||
executeExecute(instruction);
|
||||
ip = codeSize;
|
||||
break;
|
||||
case Token::DELAY:
|
||||
executeDelay(instruction);
|
||||
break;
|
||||
case Token::SOUND:
|
||||
executeSound(instruction);
|
||||
break;
|
||||
case Token::SETBIT:
|
||||
executeSetBit(instruction);
|
||||
break;
|
||||
case Token::CLEARBIT:
|
||||
executeClearBit(instruction);
|
||||
break;
|
||||
case Token::TOGGLEBIT:
|
||||
executeToggleBit(instruction);
|
||||
break;
|
||||
case Token::PRINT:
|
||||
executePrint(instruction);
|
||||
break;
|
||||
case Token::SPFX:
|
||||
executeSPFX(instruction);
|
||||
break;
|
||||
case Token::SCREEN:
|
||||
// TODO
|
||||
break;
|
||||
case Token::SETFLAGS:
|
||||
// TODO
|
||||
break;
|
||||
case Token::STARTANIM:
|
||||
executeStartAnim(instruction);
|
||||
break;
|
||||
case Token::BITNOTEQ:
|
||||
if (executeEndIfBitNotEqual(instruction)) {
|
||||
if (isCastle()) {
|
||||
skip = true;
|
||||
skipDepth = conditionalDepth - 1;
|
||||
} else
|
||||
ip = codeSize;
|
||||
}
|
||||
break;
|
||||
case Token::INVISQ:
|
||||
if (executeEndIfVisibilityIsEqual(instruction)) {
|
||||
if (isCastle()) {
|
||||
skip = true;
|
||||
skipDepth = conditionalDepth - 1;
|
||||
} else
|
||||
ip = codeSize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ip++;
|
||||
}
|
||||
return executed;
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeRedraw(FCLInstruction &instruction) {
|
||||
debugC(1, kFreescapeDebugCode, "Redrawing screen");
|
||||
uint32 delay = (100 / 15) + 1;
|
||||
if (isEclipse2() && _currentArea->getAreaID() == _startArea && _gameStateControl == kFreescapeGameStateStart)
|
||||
delay = delay * 10;
|
||||
|
||||
if (isCastle() && isSpectrum() && getGameBit(31))
|
||||
delay = delay * 15; // Slow down redraws when the final cutscene is playing
|
||||
waitInLoop(delay);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeExecute(FCLInstruction &instruction) {
|
||||
uint16 objId = instruction._source;
|
||||
debugC(1, kFreescapeDebugCode, "Executing instructions from object %d", objId);
|
||||
Object *obj = _currentArea->objectWithID(objId);
|
||||
if (!obj) {
|
||||
obj = _areaMap[255]->objectWithID(objId);
|
||||
if (!obj) {
|
||||
obj = _areaMap[255]->entranceWithID(objId);
|
||||
if (!obj) {
|
||||
debugC(1, kFreescapeDebugCode, "WARNING: executing instructions from a non-existent object %d", objId);
|
||||
return;
|
||||
}
|
||||
assert(obj);
|
||||
FCLInstructionVector &condition = ((Entrance *)obj)->_condition;
|
||||
executeCode(condition, true, true, true, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
executeObjectConditions((GeometricObject *)obj, true, true, true);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeSound(FCLInstruction &instruction) {
|
||||
stopAllSounds(_movementSoundHandle);
|
||||
_firstSound = false;
|
||||
uint16 index = instruction._source;
|
||||
bool sync = instruction._additional;
|
||||
debugC(1, kFreescapeDebugCode, "Playing sound %d", index);
|
||||
playSound(index, sync, _soundFxHandle);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeDelay(FCLInstruction &instruction) {
|
||||
uint16 delay = instruction._source;
|
||||
debugC(1, kFreescapeDebugCode, "Delaying %d * 1/50 seconds", delay);
|
||||
waitInLoop(((20 * delay) / 15) + 1);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executePrint(FCLInstruction &instruction) {
|
||||
uint16 index = instruction._source - 1;
|
||||
debugC(1, kFreescapeDebugCode, "Printing message %d: \"%s\"", index, _messagesList[index].c_str());
|
||||
_currentAreaMessages.clear();
|
||||
_currentAreaMessages.push_back(_messagesList[index]);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeSPFX(FCLInstruction &instruction) {
|
||||
uint16 src = instruction._source;
|
||||
uint16 dst = instruction._destination;
|
||||
if (isAmiga() || isAtariST()) {
|
||||
uint8 r = 0;
|
||||
uint8 g = 0;
|
||||
uint8 b = 0;
|
||||
uint32 color = 0;
|
||||
|
||||
if (src & (1 << 7)) {
|
||||
uint16 v = 0;
|
||||
color = 0;
|
||||
// Extract the color to replace from the src/dst values
|
||||
v = (src & 0x77) << 8;
|
||||
v = v | (dst & 0x70);
|
||||
v = v >> 4;
|
||||
|
||||
// Convert the color to RGB
|
||||
r = (v & 0xf00) >> 8;
|
||||
r = r << 4 | r;
|
||||
r = r & 0xff;
|
||||
|
||||
g = (v & 0xf0) >> 4;
|
||||
g = g << 4 | g;
|
||||
g = g & 0xff;
|
||||
|
||||
b = v & 0xf;
|
||||
b = b << 4 | b;
|
||||
b = b & 0xff;
|
||||
|
||||
color = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
|
||||
_currentArea->remapColor(dst & 0x0f, color); // src & 0x77, dst & 0x0f
|
||||
} else if ((src & 0xf0) >> 4 == 1) {
|
||||
_gfx->readFromPalette(src & 0x0f, r, g, b);
|
||||
color = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
|
||||
for (int i = 1; i < 16; i++)
|
||||
_currentArea->remapColor(i, color);
|
||||
} else if ((src & 0x0f) == 1) {
|
||||
_gfx->readFromPalette(dst & 0x0f, r, g, b);
|
||||
color = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
|
||||
for (int i = 1; i < 16; i++)
|
||||
_currentArea->remapColor(i, color);
|
||||
}
|
||||
} else {
|
||||
debugC(1, kFreescapeDebugCode, "Switching palette from position %d to %d", src, dst);
|
||||
if (src == 0 && dst == 1) {
|
||||
|
||||
src = _currentArea->_usualBackgroundColor;
|
||||
dst = _currentArea->_underFireBackgroundColor;
|
||||
|
||||
if (_renderMode == Common::kRenderCGA)
|
||||
dst = 1;
|
||||
else if (isC64()) {
|
||||
src %= 16;
|
||||
dst %= 16;
|
||||
}
|
||||
|
||||
_currentArea->remapColor(src, dst);
|
||||
} else if (src == 0 && dst == 0)
|
||||
_currentArea->unremapColor(_currentArea->_usualBackgroundColor);
|
||||
else if (src == 15 && dst == 15) // Found in Total Eclipse (DOS)
|
||||
_currentArea->unremapColor(_currentArea->_usualBackgroundColor);
|
||||
else
|
||||
_currentArea->remapColor(src, dst);
|
||||
}
|
||||
_gfx->setColorRemaps(&_currentArea->_colorRemaps);
|
||||
executeRedraw(instruction);
|
||||
}
|
||||
|
||||
|
||||
bool FreescapeEngine::executeEndIfVisibilityIsEqual(FCLInstruction &instruction) {
|
||||
uint16 source = instruction._source;
|
||||
uint16 additional = instruction._additional;
|
||||
uint16 value = instruction._destination;
|
||||
|
||||
Object *obj = nullptr;
|
||||
if (additional == 0) {
|
||||
obj = _currentArea->objectWithID(source);
|
||||
if (!obj && isCastle())
|
||||
return (true == (value != 0));
|
||||
assert(obj);
|
||||
debugC(1, kFreescapeDebugCode, "End condition if visibility of obj with id %d is %d!", source, value);
|
||||
} else {
|
||||
debugC(1, kFreescapeDebugCode, "End condition if visibility of obj with id %d in area %d is %d!", additional, source, value);
|
||||
if (_areaMap.contains(source)) {
|
||||
obj = _areaMap[source]->objectWithID(additional);
|
||||
assert(obj);
|
||||
} else {
|
||||
assert(isDOS() && isDemo()); // Should only happen in the DOS demo
|
||||
return (value == false);
|
||||
}
|
||||
}
|
||||
|
||||
return (obj->isInvisible() == (value != 0));
|
||||
}
|
||||
|
||||
bool FreescapeEngine::checkConditional(FCLInstruction &instruction, bool shot, bool collided, bool timer, bool activated) {
|
||||
uint16 conditional = instruction._source;
|
||||
bool result = false;
|
||||
|
||||
if (conditional & kConditionalShot)
|
||||
result |= shot;
|
||||
if (conditional & kConditionalTimeout)
|
||||
result |= timer;
|
||||
if (conditional & kConditionalCollided)
|
||||
result |= collided;
|
||||
if (conditional & kConditionalActivated)
|
||||
result |= activated;
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Check if conditional %x is true: %d!", conditional, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FreescapeEngine::checkIfGreaterOrEqual(FCLInstruction &instruction) {
|
||||
assert(instruction._destination <= 128);
|
||||
|
||||
uint16 variable = instruction._source;
|
||||
int8 value = instruction._destination;
|
||||
debugC(1, kFreescapeDebugCode, "Check if variable %d with value %d is greater or equal to %d!", variable, (int8)_gameStateVars[variable], value);
|
||||
return ((int8)_gameStateVars[variable] >= value);
|
||||
}
|
||||
|
||||
bool FreescapeEngine::checkIfLessOrEqual(FCLInstruction &instruction) {
|
||||
assert(instruction._destination <= 128);
|
||||
|
||||
uint16 variable = instruction._source;
|
||||
int8 value = instruction._destination;
|
||||
debugC(1, kFreescapeDebugCode, "Check if variable %d with value %d is less or equal to %d!", variable, (int8)_gameStateVars[variable], value);
|
||||
return ((int8)_gameStateVars[variable] <= value);
|
||||
}
|
||||
|
||||
|
||||
bool FreescapeEngine::executeEndIfNotEqual(FCLInstruction &instruction) {
|
||||
uint16 variable = instruction._source;
|
||||
uint16 value = instruction._destination;
|
||||
debugC(1, kFreescapeDebugCode, "End condition if variable %d with value %d is not equal to %d!", variable, (int8)_gameStateVars[variable], value);
|
||||
return (_gameStateVars[variable] != value);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeIncrementVariable(FCLInstruction &instruction) {
|
||||
int32 variable = instruction._source;
|
||||
int32 increment = instruction._destination;
|
||||
_gameStateVars[variable] = _gameStateVars[variable] + increment;
|
||||
if (variable == k8bitVariableScore) {
|
||||
debugC(1, kFreescapeDebugCode, "Score incremented by %d up to %d", increment, _gameStateVars[variable]);
|
||||
} else if (variable == k8bitVariableEnergy) {
|
||||
if (_gameStateVars[variable] > _maxEnergy)
|
||||
_gameStateVars[variable] = _maxEnergy;
|
||||
else if (_gameStateVars[variable] < 0)
|
||||
_gameStateVars[variable] = 0;
|
||||
debugC(1, kFreescapeDebugCode, "Energy incremented by %d up to %d", increment, _gameStateVars[variable]);
|
||||
} else if (variable == k8bitVariableShield) {
|
||||
if (_gameStateVars[variable] > _maxShield)
|
||||
_gameStateVars[variable] = _maxShield;
|
||||
else if (_gameStateVars[variable] < 0)
|
||||
_gameStateVars[variable] = 0;
|
||||
|
||||
if (increment < 0 && !isCastle())
|
||||
flashScreen(_renderMode == Common::kRenderCGA ? 1 :_currentArea->_underFireBackgroundColor);
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Shield incremented by %d up to %d", increment, _gameStateVars[variable]);
|
||||
} else {
|
||||
debugC(1, kFreescapeDebugCode, "Variable %d by %d incremented up to %d!", variable, increment, _gameStateVars[variable]);
|
||||
}
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeDecrementVariable(FCLInstruction &instruction) {
|
||||
uint16 variable = instruction._source;
|
||||
uint16 decrement = instruction._destination;
|
||||
_gameStateVars[variable] = _gameStateVars[variable] - decrement;
|
||||
if (variable == k8bitVariableEnergy) {
|
||||
debugC(1, kFreescapeDebugCode, "Energy decrement by %d up to %d", decrement, _gameStateVars[variable]);
|
||||
} else
|
||||
debugC(1, kFreescapeDebugCode, "Variable %d by %d incremented up to %d!", variable, decrement, _gameStateVars[variable]);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeSetVariable(FCLInstruction &instruction) {
|
||||
uint16 variable = instruction._source;
|
||||
uint16 value = instruction._destination;
|
||||
_gameStateVars[variable] = value;
|
||||
if (variable == k8bitVariableEnergy)
|
||||
debugC(1, kFreescapeDebugCode, "Energy set to %d", value);
|
||||
else
|
||||
debugC(1, kFreescapeDebugCode, "Variable %d by set to %d!", variable, value);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeDestroy(FCLInstruction &instruction) {
|
||||
uint16 objectID = 0;
|
||||
uint16 areaID = _currentArea->getAreaID();
|
||||
|
||||
if (instruction._destination > 0) {
|
||||
objectID = instruction._destination;
|
||||
areaID = instruction._source;
|
||||
} else {
|
||||
objectID = instruction._source;
|
||||
}
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Destroying obj %d in area %d!", objectID, areaID);
|
||||
assert(_areaMap.contains(areaID));
|
||||
Object *obj = _areaMap[areaID]->objectWithID(objectID);
|
||||
assert(obj); // We know that an object should be there
|
||||
if (obj->isDestroyed())
|
||||
debugC(1, kFreescapeDebugCode, "WARNING: Destroying obj %d in area %d already destroyed!", objectID, areaID);
|
||||
|
||||
obj->destroy();
|
||||
obj->makeInvisible();
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeMakeInvisible(FCLInstruction &instruction) {
|
||||
uint16 objectID = 0;
|
||||
uint16 areaID = _currentArea->getAreaID();
|
||||
|
||||
if (instruction._destination > 0) {
|
||||
objectID = instruction._destination;
|
||||
areaID = instruction._source;
|
||||
} else {
|
||||
objectID = instruction._source;
|
||||
}
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Making obj %d invisible in area %d!", objectID, areaID);
|
||||
if (_areaMap.contains(areaID)) {
|
||||
Object *obj = _areaMap[areaID]->objectWithID(objectID);
|
||||
|
||||
if (!obj) {
|
||||
// Object is not in the area, but it should be invisible so we can return immediately
|
||||
return;
|
||||
/*obj = _areaMap[255]->objectWithID(objectID);
|
||||
if (!obj) {
|
||||
error("obj %d does not exists in area %d nor in the global one!", objectID, areaID);
|
||||
return;
|
||||
}
|
||||
_currentArea->addObjectFromArea(objectID, _areaMap[255]);
|
||||
obj = _areaMap[areaID]->objectWithID(objectID);*/
|
||||
}
|
||||
|
||||
assert(obj); // We assume the object was there
|
||||
obj->makeInvisible();
|
||||
} else {
|
||||
assert(isDriller() && isDOS() && isDemo());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeMakeVisible(FCLInstruction &instruction) {
|
||||
uint16 objectID = 0;
|
||||
uint16 areaID = _currentArea->getAreaID();
|
||||
|
||||
if (instruction._destination > 0) {
|
||||
objectID = instruction._destination;
|
||||
areaID = instruction._source;
|
||||
} else {
|
||||
objectID = instruction._source;
|
||||
}
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Making obj %d visible in area %d!", objectID, areaID);
|
||||
if (_areaMap.contains(areaID)) {
|
||||
Object *obj = _areaMap[areaID]->objectWithID(objectID);
|
||||
if (!obj) {
|
||||
obj = _areaMap[255]->objectWithID(objectID);
|
||||
if (!obj) {
|
||||
if (!isCastle() || !isDemo())
|
||||
error("obj %d does not exists in area %d nor in the global one!", objectID, areaID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj->getType() != kGroupType)
|
||||
_currentArea->addObjectFromArea(objectID, _areaMap[255]);
|
||||
else if (obj->_partOfGroup)
|
||||
_currentArea->addGroupFromArea(objectID, _areaMap[255]);
|
||||
obj = _areaMap[areaID]->objectWithID(objectID);
|
||||
assert(obj); // We know that an object should be there
|
||||
}
|
||||
|
||||
obj->makeVisible();
|
||||
if (!isDriller()) {
|
||||
Math::AABB boundingBox = createPlayerAABB(_position, _playerHeight);
|
||||
if (obj->_boundingBox.collides(boundingBox)) {
|
||||
_playerWasCrushed = true;
|
||||
_avoidRenderingFrames = 60 * 3;
|
||||
if (isEclipse())
|
||||
playSoundFx(2, true);
|
||||
_shootingFrames = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(isDOS() && isDemo()); // Should only happen in the DOS demo
|
||||
}
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeToggleVisibility(FCLInstruction &instruction) {
|
||||
uint16 objectID = 0;
|
||||
uint16 areaID = _currentArea->getAreaID();
|
||||
|
||||
if (instruction._destination > 0) {
|
||||
objectID = instruction._destination;
|
||||
areaID = instruction._source;
|
||||
} else {
|
||||
objectID = instruction._source;
|
||||
}
|
||||
|
||||
debugC(1, kFreescapeDebugCode, "Toggling obj %d visibility in area %d!", objectID, areaID);
|
||||
Object *obj = _areaMap[areaID]->objectWithID(objectID);
|
||||
if (obj)
|
||||
obj->toggleVisibility();
|
||||
else {
|
||||
obj = _areaMap[255]->objectWithID(objectID);
|
||||
if (!obj) {
|
||||
// This happens in Driller, the ketar hangar
|
||||
warning("ERROR!: obj %d does not exists in area %d nor in the global one!", objectID, areaID);
|
||||
return;
|
||||
}
|
||||
// If an object is not in the area, it is considered to be invisible
|
||||
_currentArea->addObjectFromArea(objectID, _areaMap[255]);
|
||||
obj = _areaMap[areaID]->objectWithID(objectID);
|
||||
assert(obj); // We know that an object should be there
|
||||
obj->makeVisible();
|
||||
}
|
||||
if (!obj->isInvisible()) {
|
||||
if (!isDriller()) {
|
||||
Math::AABB boundingBox = createPlayerAABB(_position, _playerHeight);
|
||||
if (obj->_boundingBox.collides(boundingBox)) {
|
||||
_playerWasCrushed = true;
|
||||
_avoidRenderingFrames = 60 * 3;
|
||||
_shootingFrames = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeGoto(FCLInstruction &instruction) {
|
||||
uint16 areaID = instruction._source;
|
||||
uint16 entranceID = instruction._destination;
|
||||
gotoArea(areaID, entranceID);
|
||||
_gotoExecuted = true;
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeSetBit(FCLInstruction &instruction) {
|
||||
uint16 index = instruction._source; // Starts at 1
|
||||
assert(index > 0 && index <= 32);
|
||||
setGameBit(index);
|
||||
debugC(1, kFreescapeDebugCode, "Setting bit %d", index);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeClearBit(FCLInstruction &instruction) {
|
||||
uint16 index = instruction._source; // Starts at 1
|
||||
assert(index > 0 && index <= 32);
|
||||
clearGameBit(index);
|
||||
debugC(1, kFreescapeDebugCode, "Clearing bit %d", index);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeToggleBit(FCLInstruction &instruction) {
|
||||
uint16 index = instruction._source; // Starts at 1
|
||||
assert(index > 0 && index <= 32);
|
||||
toggleGameBit(index);
|
||||
debugC(1, kFreescapeDebugCode, "Toggling bit %d", index);
|
||||
}
|
||||
|
||||
bool FreescapeEngine::executeEndIfBitNotEqual(FCLInstruction &instruction) {
|
||||
uint16 index = instruction._source;
|
||||
uint16 value = instruction._destination;
|
||||
assert(index <= 32);
|
||||
debugC(1, kFreescapeDebugCode, "End condition if bit %d is not equal to %d!", index, value);
|
||||
return (getGameBit(index) != value);
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeSwapJet(FCLInstruction &instruction) {
|
||||
//playSound(15, false);
|
||||
_flyMode = !_flyMode;
|
||||
uint16 areaID = _currentArea->getAreaID();
|
||||
|
||||
if (_flyMode) {
|
||||
debugC(1, kFreescapeDebugCode, "Swaping to ship mode");
|
||||
if (areaID == 27) {
|
||||
traverseEntrance(26);
|
||||
_lastPosition = _position;
|
||||
}
|
||||
_playerHeight = 2;
|
||||
_playerHeightNumber = -1;
|
||||
|
||||
// Save tank energy and shield
|
||||
_gameStateVars[k8bitVariableEnergyDrillerTank] = _gameStateVars[k8bitVariableEnergy];
|
||||
_gameStateVars[k8bitVariableShieldDrillerTank] = _gameStateVars[k8bitVariableShield];
|
||||
|
||||
// Restore ship energy and shield
|
||||
_gameStateVars[k8bitVariableEnergy] = _gameStateVars[k8bitVariableEnergyDrillerJet];
|
||||
_gameStateVars[k8bitVariableShield] = _gameStateVars[k8bitVariableShieldDrillerJet];
|
||||
} else {
|
||||
debugC(1, kFreescapeDebugCode, "Swaping to tank mode");
|
||||
_playerHeightNumber = 0;
|
||||
if (areaID == 27) {
|
||||
traverseEntrance(27);
|
||||
_lastPosition = _position;
|
||||
}
|
||||
|
||||
// Save shield energy and shield
|
||||
_gameStateVars[k8bitVariableEnergyDrillerJet] = _gameStateVars[k8bitVariableEnergy];
|
||||
_gameStateVars[k8bitVariableShieldDrillerJet] = _gameStateVars[k8bitVariableShield];
|
||||
|
||||
// Restore ship energy and shield
|
||||
_gameStateVars[k8bitVariableEnergy] = _gameStateVars[k8bitVariableEnergyDrillerTank];
|
||||
_gameStateVars[k8bitVariableShield] = _gameStateVars[k8bitVariableShieldDrillerTank];
|
||||
}
|
||||
// TODO: implement the rest of the changes (e.g. border)
|
||||
}
|
||||
|
||||
void FreescapeEngine::executeStartAnim(FCLInstruction &instruction) {
|
||||
uint16 objID = instruction._source;
|
||||
debugC(1, kFreescapeDebugCode, "Staring animation of object %d", objID);
|
||||
Object *obj = _currentArea->objectWithID(objID);
|
||||
assert(obj);
|
||||
Group *group = nullptr;
|
||||
if (obj->getType() == kGroupType) {
|
||||
group = (Group *)obj;
|
||||
} else {
|
||||
assert(obj->_partOfGroup);
|
||||
group = (Group *)obj->_partOfGroup;
|
||||
}
|
||||
debugC(1, kFreescapeDebugCode, "From group %d", group->getObjectID());
|
||||
if (!group->isDestroyed())
|
||||
group->start();
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace Freescape
|
||||
70
engines/freescape/language/instruction.h
Normal file
70
engines/freescape/language/instruction.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on Phantasma code by Thomas Harte (2013),
|
||||
// available at https://github.com/TomHarte/Phantasma/ (MIT)
|
||||
|
||||
#ifndef FREESCAPE_INSTRUCTION_H
|
||||
#define FREESCAPE_INSTRUCTION_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "freescape/language/token.h"
|
||||
|
||||
namespace Freescape {
|
||||
|
||||
class FCLInstruction;
|
||||
typedef Common::Array<FCLInstruction> FCLInstructionVector;
|
||||
|
||||
class FCLInstruction {
|
||||
public:
|
||||
FCLInstruction();
|
||||
FCLInstruction(Token::Type type);
|
||||
void setSource(int32 source);
|
||||
void setAdditional(int32 additional);
|
||||
void setDestination(int32 destination);
|
||||
|
||||
Token::Type getType() const;
|
||||
|
||||
bool isConditional() const {
|
||||
Token::Type type = getType();
|
||||
return type == Token::Type::BITNOTEQ || type == Token::Type::VARNOTEQ || \
|
||||
type == Token::Type::IFGTEQ || type == Token::Type::IFLTEQ || \
|
||||
type == Token::Type::VAREQ || _type == Token::Type::INVISQ;
|
||||
}
|
||||
|
||||
void setBranches(FCLInstructionVector *thenBranch, FCLInstructionVector *elseBranch);
|
||||
|
||||
FCLInstruction duplicate();
|
||||
|
||||
int32 _source;
|
||||
int32 _additional;
|
||||
int32 _destination;
|
||||
|
||||
FCLInstructionVector *_thenInstructions;
|
||||
FCLInstructionVector *_elseInstructions;
|
||||
|
||||
private:
|
||||
enum Token::Type _type;
|
||||
};
|
||||
|
||||
} // End of namespace Freescape
|
||||
|
||||
#endif // FREESCAPE_INSTRUCTION_H
|
||||
121
engines/freescape/language/token.h
Normal file
121
engines/freescape/language/token.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on Phantasma code by Thomas Harte (2013),
|
||||
// available at https://github.com/TomHarte/Phantasma/ (MIT)
|
||||
|
||||
#ifndef FREESCAPE_TOKEN_H
|
||||
#define FREESCAPE_TOKEN_H
|
||||
|
||||
namespace Freescape {
|
||||
|
||||
struct Token {
|
||||
public:
|
||||
enum Type {
|
||||
ADDVAR,
|
||||
AGAIN,
|
||||
AND,
|
||||
ANDV,
|
||||
CONDITIONAL,
|
||||
DELAY,
|
||||
DESTROY,
|
||||
DESTROYEDQ,
|
||||
ELSE,
|
||||
END,
|
||||
ENDGAME,
|
||||
ENDIF,
|
||||
EXECUTE,
|
||||
GOTO,
|
||||
IF,
|
||||
INVIS,
|
||||
INVISQ,
|
||||
INCLUDE,
|
||||
LOOP,
|
||||
MODE,
|
||||
MOVE,
|
||||
MOVETO,
|
||||
NOTV,
|
||||
NOP,
|
||||
OR,
|
||||
ORV,
|
||||
GETXPOS,
|
||||
GETYPOS,
|
||||
GETZPOS,
|
||||
PRINT,
|
||||
RESTART,
|
||||
REDRAW,
|
||||
REMOVE,
|
||||
SCREEN,
|
||||
SOUND,
|
||||
SETVAR,
|
||||
SETFLAGS,
|
||||
START,
|
||||
STARTANIM,
|
||||
STOPANIM,
|
||||
SPFX,
|
||||
SUBVAR,
|
||||
SYNCSND,
|
||||
THEN,
|
||||
TOGVIS,
|
||||
TRIGANIM,
|
||||
UPDATEI,
|
||||
VAREQ,
|
||||
IFGTEQ,
|
||||
IFLTEQ,
|
||||
VISQ,
|
||||
VIS,
|
||||
WAIT,
|
||||
WAITTRIG,
|
||||
COMMA,
|
||||
OPENBRACKET,
|
||||
CLOSEBRACKET,
|
||||
CONSTANT,
|
||||
VARIABLE,
|
||||
STRINGLITERAL,
|
||||
UNKNOWN,
|
||||
ENDOFFILE,
|
||||
SETBIT,
|
||||
CLEARBIT,
|
||||
TOGGLEBIT,
|
||||
SWAPJET,
|
||||
BITNOTEQ,
|
||||
VARNOTEQ
|
||||
};
|
||||
|
||||
Type getType();
|
||||
|
||||
Token() {
|
||||
_type = UNKNOWN;
|
||||
_value = 0;
|
||||
}
|
||||
Token(Type type_) {
|
||||
_type = type_;
|
||||
_value = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Type _type;
|
||||
int32 _value;
|
||||
};
|
||||
|
||||
} // End of namespace Freescape
|
||||
|
||||
#endif // FREESCAPE_TOKEN_H
|
||||
Reference in New Issue
Block a user