Initial commit
This commit is contained in:
32
engines/immortal/bullet.cpp
Normal file
32
engines/immortal/bullet.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* [Alternate Name: Projectile Processing]
|
||||
* --- What is a Bullet ---
|
||||
*/
|
||||
|
||||
#include "immortal/room.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
|
||||
|
||||
} // namespace Immortal
|
||||
320
engines/immortal/compression.cpp
Normal file
320
engines/immortal/compression.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
/* 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 "immortal/immortal.h"
|
||||
|
||||
/* Decompression:
|
||||
* This decompression algorithm follows the source assembly very closely,
|
||||
* which is itself a modification to LZW (a derivative of LZ78).
|
||||
* In: Source data as File, size of data
|
||||
* Out: Pointer to uncompressed data as SeekableReadStream
|
||||
*/
|
||||
namespace Immortal {
|
||||
|
||||
enum codeMask {
|
||||
kMaskMSBS = 0xF000, // Code link is Most significant bits
|
||||
kMaskLSBS = 0xFF00, // K link is Least significant bits
|
||||
kMaskCode = 0x0FFF // Code is 12 bit
|
||||
};
|
||||
|
||||
Common::SeekableReadStream *ImmortalEngine::unCompress(Common::File *source, int lSource) {
|
||||
/* Note: this function does not seek() in the file, which means
|
||||
* that if there is a header on the data, the expectation is that
|
||||
* seek() was already used to move past the header before this function.
|
||||
*/
|
||||
|
||||
/* Other notes:
|
||||
* Tk is k in (w,k)
|
||||
* Link is spread out between code and tk, where code has the most significant 4 bits, and tk has the least significant 8
|
||||
* Codes contains the keys (plus link codes) for the substring values of the dictionary and can be up to 12 bits (4096 total entries) in size
|
||||
* Tk contains byte values from the compressed data (plus link codes)
|
||||
* Stack contains the currently being recreated string before it gets sent to the output
|
||||
*/
|
||||
|
||||
// If the source data has no length, we certainly do not want to decompress it
|
||||
if (lSource == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// In the source, the data allocated here is a pointer passed to the function, but it's only used by this anyway
|
||||
uint16 *pCodes = (uint16 *)malloc(k8K); // The Codes stack has 8 * 1024 bytes allocated
|
||||
uint16 *pTk = (uint16 *)malloc(k8K); // The Tk has 8 * 1024 bytes allocated
|
||||
uint16 pStack[k8K]; // In the source, the stack has the rest of the 20K. That's way more than it needs though, so we're just giving it 8k for now
|
||||
|
||||
uint16 oldCode = 0;
|
||||
uint16 finChar = 0;
|
||||
uint16 topStack = 0;
|
||||
uint16 evenOdd = 0;
|
||||
uint16 myCode = 0;
|
||||
uint16 inCode = 0;
|
||||
uint16 findEmpty = 0;
|
||||
uint16 index = 0;
|
||||
|
||||
/* This will be the dynamically re-allocated writeable memory stream.
|
||||
* We do not want it to be deleted from scope, as this location is where
|
||||
* the readstream being returned will point to.
|
||||
*/
|
||||
Common::MemoryWriteStreamDynamic dest(DisposeAfterUse::NO);
|
||||
|
||||
/* In the source we save a backup of the starting pointer to the destination, which is increased
|
||||
* as more data is added to it, so that the final length can be dest - destBkp. However in
|
||||
* our case, the MemoryReadStream already has a size associated with it.
|
||||
*/
|
||||
|
||||
// Clear the dictionary
|
||||
setUpDictionary(pCodes, pTk, findEmpty);
|
||||
evenOdd = 0;
|
||||
topStack = 0;
|
||||
|
||||
// Get the initial input (always 0?)
|
||||
inputCode(finChar, lSource, source, evenOdd);
|
||||
oldCode = finChar;
|
||||
myCode = oldCode;
|
||||
|
||||
// (byte) is basically the same as the SEP #$20 : STA : REP #$20
|
||||
dest.writeByte((byte)myCode);
|
||||
|
||||
// Loops until it gets no more input codes (ie. length of source is 0)
|
||||
while (inputCode(inCode, lSource, source, evenOdd) == 0) {
|
||||
myCode = inCode;
|
||||
|
||||
// The source uses the Y register for this
|
||||
// We can rearrange this a little to avoid using an extra variable, but for now we're pretending index is the register
|
||||
index = inCode;
|
||||
|
||||
/* Check if the code is defined (has links for the linked list).
|
||||
* We do this by grabbing the link portion from the code,
|
||||
* then adding the Tk, and grabbing just the link portion.
|
||||
* This way, if either of the link codes exists, we know it's defined,
|
||||
* otherwise you just get zeros.
|
||||
* This special case is for a string which is the same as the last string,
|
||||
* but with the first char duplicated and added to the end (how common can that possibly be??)
|
||||
*/
|
||||
if ((((pCodes[index] & kMaskMSBS) | pTk[index]) & kMaskLSBS) == 0) {
|
||||
// Push the last char of this string, which is the same as the first of the previous one
|
||||
pStack[topStack] = finChar;
|
||||
topStack++;
|
||||
myCode = oldCode;
|
||||
}
|
||||
|
||||
// The code is defined, but it could be either a single char or a multi char
|
||||
// If the index into the dictionary is above 100, it's a multi character substring
|
||||
while ((myCode) >= 0x100) {
|
||||
index = myCode;
|
||||
myCode = pCodes[index] & kMaskCode;
|
||||
pStack[topStack] = pTk[index] & kMaskLow;
|
||||
topStack++;
|
||||
}
|
||||
|
||||
// Otherwise, it's a single char
|
||||
finChar = myCode;
|
||||
|
||||
// which we write to the output
|
||||
dest.writeByte((byte)myCode);
|
||||
|
||||
// Dump the stack
|
||||
index = topStack;
|
||||
index--;
|
||||
while (index < 0x8000) {
|
||||
dest.writeByte((byte)pStack[index]);
|
||||
index--;
|
||||
}
|
||||
topStack = 0;
|
||||
|
||||
// Hash the old code with the current char, if it isn't in the dictionary, append it
|
||||
member(oldCode, finChar, pCodes, pTk, findEmpty, index);
|
||||
|
||||
// Set up the current code as the old code for the next code
|
||||
oldCode = inCode;
|
||||
}
|
||||
|
||||
free(pCodes);
|
||||
free(pTk);
|
||||
|
||||
/* Return a readstream with a pointer to the data in the write stream.
|
||||
* This one we do want to dispose after using, because it will be in the scope of the engine itself
|
||||
*/
|
||||
return new Common::MemoryReadStream(dest.getData(), dest.size(), DisposeAfterUse::YES);
|
||||
|
||||
}
|
||||
|
||||
/* Clear the tables and mark the first 256 bytes of the char table as used */
|
||||
void ImmortalEngine::setUpDictionary(uint16 *pCodes, uint16 *pTk, uint16 &findEmpty) {
|
||||
// Clear the tables completely (4095 entries, same as the mask for codes)
|
||||
for (int i = kMaskCode; i >= 0; i -= 1) {
|
||||
pCodes[i] = 0;
|
||||
pTk[i] = 0;
|
||||
}
|
||||
|
||||
// Mark the first 0x100 as used for uncompress
|
||||
for (int i = 0xFF; i >= 0; i -= 1) {
|
||||
pTk[i] = 0x100;
|
||||
}
|
||||
|
||||
// findEmpty is a pointer for finding empty slots, so it starts at the end of the data (data is 2 bytes wide, so it's 4k instead of 8k)
|
||||
findEmpty = k4K;
|
||||
}
|
||||
|
||||
/* Get a code from the input stream. 1 = no more codes, 0 = got code
|
||||
* On even iterations, we grab the first word.
|
||||
* On odd iterations, we grab the word starting from the second byte of the previous word
|
||||
*/
|
||||
int ImmortalEngine::inputCode(uint16 &outCode, int &lSource, Common::File *source, uint16 &evenOdd) {
|
||||
// If length is 0, we're done getting codes
|
||||
if (lSource == 0) {
|
||||
// No more codes
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Even
|
||||
if (evenOdd == 0) {
|
||||
lSource -= 2; // Even number of bytes, decrease by 2
|
||||
evenOdd++; // Next alignment will be odd
|
||||
|
||||
/* The codes are stored in 12 bits, so 3 bytes = 2 codes
|
||||
* nnnn nnnn [nnnn cccc cccc cccc] & 0x0FFF
|
||||
* nnnn nnnn [0000 cccc cccc cccc]
|
||||
*/
|
||||
outCode = source->readUint16LE() & kMaskCode;
|
||||
source->seek(-1, SEEK_CUR);
|
||||
|
||||
// Odd
|
||||
} else {
|
||||
lSource--;
|
||||
evenOdd--;
|
||||
/* This grabs the next code which is made up of the previous code's second byte
|
||||
* plus the current code's byte + the next 2 byte value
|
||||
* [nnnn nnnn nnnn cccc] cccc cccc >> 3
|
||||
* [000n nnnn nnnn nnnc] cccc cccc & 0xFFFE <- this is done so the Y register has code * 2
|
||||
* [000n nnnn nnnn nnn0] cccc cccc >> 1 <- in our case, we could have just done code >> 4
|
||||
* [0000 nnnn nnnn nnnn]
|
||||
*/
|
||||
outCode = ((source->readUint16LE() >> 3) & 0xFFFE) >> 1;
|
||||
}
|
||||
|
||||
// We have a good code, no error
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ImmortalEngine::member(uint16 &codeW, uint16 &k, uint16 *pCodes, uint16 *pTk, uint16 &findEmpty, uint16 &index) {
|
||||
// Step one is to make a hash value out of W (oldCode) and k (finChar)
|
||||
index = ((((((k << 3) ^ k) << 1) ^ k) ^ codeW));
|
||||
|
||||
// The hash value has to be larger than 200 because that's where the single chars are
|
||||
if (index < 0x100) {
|
||||
index += 0x100;
|
||||
}
|
||||
|
||||
if ((((pCodes[index] & kMaskMSBS) | pTk[index]) & kMaskLSBS) == 0) {
|
||||
// There was no link, so we insert the key, mark the table as used, with no link
|
||||
pCodes[index] = codeW;
|
||||
pTk[index] = k | 0x100;
|
||||
|
||||
// Code not found, return error
|
||||
return 1;
|
||||
}
|
||||
|
||||
// There is a link, so it's not empty
|
||||
// This is a bad loop, because there is no safe way out if the data isn't right, but it's the source logic
|
||||
// If there is anything corrupted in the data, the game will get stuck forever
|
||||
while (true) {
|
||||
uint16 tmp = 0;
|
||||
|
||||
// If the code matches
|
||||
if ((pCodes[index] & kMaskCode) == codeW) {
|
||||
// And k also matches
|
||||
if ((pTk[index] & kMaskLow) == k) {
|
||||
// Then entry is found, return no error
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Entry was used, but it is not holding the desired key
|
||||
// Follow link to next entry, if there is no next entry, append to the list
|
||||
if ((pCodes[index] & kMaskMSBS) == 0) {
|
||||
// Find an empty entry and link it to the last entry in the chain, then put the data in the new entry
|
||||
uint16 prev = index;
|
||||
if (findEmpty >= 0x100) {
|
||||
// Table is not full, keep looking
|
||||
do {
|
||||
findEmpty--;
|
||||
// This is slightly more redundant than the source, but I trust the compiler to add a branch here
|
||||
if (findEmpty < 0x100) {
|
||||
setUpDictionary(pCodes, pTk, findEmpty);
|
||||
return 1;
|
||||
}
|
||||
// We decrease the index and check the entry until we find an empty entry or the end of the table
|
||||
} while ((((pCodes[findEmpty] & kMaskMSBS) | pTk[findEmpty]) & kMaskLSBS) != 0);
|
||||
|
||||
// The link is zero, therefor we have found an empty entry
|
||||
|
||||
pCodes[findEmpty] = codeW;
|
||||
pTk[findEmpty] = k | 0x100; // Marked as used, but still no link because this is the end of the list
|
||||
|
||||
// Now we attach a link to this entry from the previous one in the list
|
||||
uint16 link = findEmpty;
|
||||
|
||||
// Get the link of this entry
|
||||
/* 0000 llll llll llll xba
|
||||
* llll llll 0000 llll & kMaskLSBS
|
||||
* llll llll 0000 0000
|
||||
*/
|
||||
tmp = xba(link) & kMaskLSBS;
|
||||
|
||||
// On the previous entry, take out the K and add the new link onto it
|
||||
/* xxxx xxxx xxxx xxxx & kMaskLow
|
||||
* 0000 0000 xxxx xxxx | tmp
|
||||
* llll llll xxxx xxxx
|
||||
*/
|
||||
pTk[prev] = (pTk[prev] & kMaskLow) | tmp;
|
||||
|
||||
// And now the code gets it's half of the link written in
|
||||
/* 0000 llll llll llll << 4
|
||||
* llll llll llll llll & kMaskMSBS
|
||||
* llll 0000 0000 0000 | pCodes[Prev]
|
||||
* llll xxxx xxxx xxxx
|
||||
*/
|
||||
pCodes[prev] = ((link << 4) & kMaskMSBS) | pCodes[prev];
|
||||
|
||||
// Done
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
// Table is full, reset dictionary
|
||||
setUpDictionary(pCodes, pTk, findEmpty);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Put the link code together by combining the MSBS of the code and the LSBS of k
|
||||
/* code = l000 >> 4
|
||||
* = 0l00
|
||||
* k = ll00 xba
|
||||
* = 00ll
|
||||
* k | code = 0lll
|
||||
*/
|
||||
tmp = (pCodes[index] & kMaskMSBS) >> 4;
|
||||
index = ((xba(pTk[index]) & kMaskLow) | tmp);// << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
3
engines/immortal/configure.engine
Normal file
3
engines/immortal/configure.engine
Normal 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 immortal "The Immortal" no "" "" ""
|
||||
3
engines/immortal/credits.pl
Normal file
3
engines/immortal/credits.pl
Normal file
@@ -0,0 +1,3 @@
|
||||
begin_section("Immortal");
|
||||
add_person("Michael Hayman", "Quote58", "");
|
||||
end_section();
|
||||
120
engines/immortal/cycle.cpp
Normal file
120
engines/immortal/cycle.cpp
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* [Alternate Name: Sprite Animation Processing]
|
||||
* --- What is a Cycle ---
|
||||
* Strictly speaking, a Cycle is an instruction list, but more specifically:
|
||||
* A 'Cyc' is defined through Story as a static ROM entry of the format:
|
||||
* file, num, numcycles, cycles[]
|
||||
* However file + num = datasprite, and numcycles is actually a boolean for repeat animation.
|
||||
* So really it's data, repeat, cycles[]
|
||||
* This ROM entry is pointed to by a list of pointers, indexed by a byte pointer, which is
|
||||
* referenced lexigraphically through the dynamic story compilation system. This pointer is then
|
||||
* turned into a 'cycList' pointer, and stored in the RAM Cyc, which is of the format:
|
||||
* index, cycList
|
||||
* Where index is the index into the *instruction list*, not the frame array. However it's
|
||||
* Usually used as an index into the frame array by subtracting the frame enum first.
|
||||
* Here's the movement of data from ROM to RAM:
|
||||
* list of Cycles on heap (cyc)
|
||||
* |
|
||||
* list of word pointers in wram to Cycles (cycPtrs)
|
||||
* |
|
||||
* list of lexical pointers as byte indexes into word pointers (cycID -> cyclist)
|
||||
*/
|
||||
|
||||
#include "immortal/room.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
int Room::cycleNew(CycID id) {
|
||||
// An 'available' cyc is identified by the index being -1
|
||||
for (int i = 0; i < kMaxCycles; i++) {
|
||||
if (g_immortal->_cycles[i]._index == -1) {
|
||||
g_immortal->_cycles[i]._index = 0;
|
||||
g_immortal->_cycles[i]._cycList = id;
|
||||
//debug("made cyc, = %d", i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
debug("Null Cyc, can not be created");
|
||||
return kMaxCycles - 1;
|
||||
}
|
||||
|
||||
void Room::cycleFree(int c) {
|
||||
g_immortal->_cycles[c]._index = -1;
|
||||
}
|
||||
|
||||
bool Room::cycleAdvance(int c) {
|
||||
/* If we have reached the end, check if repeat == true, and set back to 0 if so
|
||||
* Otherwise, set to the last used index */
|
||||
g_immortal->_cycles[c]._index++;
|
||||
if (g_immortal->_cycPtrs[g_immortal->_cycles[c]._cycList]._frames[g_immortal->_cycles[c]._index] == -1) {
|
||||
if (g_immortal->_cycPtrs[g_immortal->_cycles[c]._cycList]._repeat == true) {
|
||||
g_immortal->_cycles[c]._index = 0;
|
||||
} else {
|
||||
g_immortal->_cycles[c]._index--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int Room::cycleGetFrame(int c) {
|
||||
/* The source version of this is fascinating. It is basically:
|
||||
* in: cycList, Index
|
||||
* index -> tmp
|
||||
* Load the value of cycPtrs + cycList (returns address of start of cyc)
|
||||
* Add index (returns address of frame in cyc)
|
||||
* Store to the position of the next label
|
||||
* Load a single byte from the value at the address in the label (returns frame value within cyc)
|
||||
* This is essentially self-modifying code, and it saves 2 bytes of DP memory over the traditional
|
||||
* STA DP : LDA (DP)
|
||||
*/
|
||||
return g_immortal->_cycPtrs[g_immortal->_cycles[c]._cycList]._frames[g_immortal->_cycles[c]._index];
|
||||
}
|
||||
|
||||
int Room::cycleGetNumFrames(int c) {
|
||||
// For whatever reason, this is not a property of the cycle, so it has to be re-calculated each time
|
||||
int index = 0;
|
||||
while (g_immortal->_cycPtrs[g_immortal->_cycles[c]._cycList]._frames[index] != -1) {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
DataSprite *Room::cycleGetDataSprite(int c) {
|
||||
return &g_immortal->_dataSprites[g_immortal->_cycPtrs[g_immortal->_cycles[c]._cycList]._sName];
|
||||
}
|
||||
|
||||
CycID Room::getCycList(int c) {
|
||||
return g_immortal->_cycles[c]._cycList;
|
||||
}
|
||||
|
||||
int Room::cycleGetIndex(int c) {
|
||||
return g_immortal->_cycles[c]._index;
|
||||
}
|
||||
|
||||
void Room::cycleSetIndex(int c, int f) {
|
||||
g_immortal->_cycles[c]._index = f;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Immortal
|
||||
240
engines/immortal/definitions.h
Normal file
240
engines/immortal/definitions.h
Normal file
@@ -0,0 +1,240 @@
|
||||
/* 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 IMMORTAL_DEFINITIONS_H
|
||||
#define IMMORTAL_DEFINITIONS_H
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
enum CycID {
|
||||
kCycNone,
|
||||
kCycBubble1 = 0,
|
||||
kCycBubble2,
|
||||
kCycSparkLBlood,
|
||||
kCycSparkRBlood,
|
||||
kCycSparkLWizBlood,
|
||||
kCycSparkRWizBlood,
|
||||
kCycBurst,
|
||||
kCycPipe0,
|
||||
kCycPipe1,
|
||||
kCycPipe2,
|
||||
kCycPipe3,
|
||||
kCycAnaDisappears,
|
||||
kCycAnaGlimpse,
|
||||
kCycKnife,
|
||||
kCycFireball0,
|
||||
kCycArrow,
|
||||
kCycMiniBall,
|
||||
kCycBigBurst,
|
||||
kCycFFlicker0,
|
||||
kCycFFlicker1,
|
||||
kCycFFlicker2,
|
||||
kCycFNormal0,
|
||||
kCycFNormal1,
|
||||
kCycFNormal2,
|
||||
kCycFOff,
|
||||
kCycFCandleFlicker,
|
||||
kCycFCandleLeap,
|
||||
kCycFCandleJump,
|
||||
kCycFCandleSway,
|
||||
kCycFCandleBurst,
|
||||
kCycSink,
|
||||
kCycNorDown
|
||||
|
||||
// Level 0
|
||||
// Level 1
|
||||
// Level 2
|
||||
// Level 3
|
||||
// Level 4
|
||||
// Level 5
|
||||
// Level 6
|
||||
// Level 7
|
||||
};
|
||||
|
||||
enum Motive { // This will likely be moved to a monster ai specific file later
|
||||
kMotiveRoomCombat,
|
||||
kMotiveShadeFind,
|
||||
kMotiveShadeLoose,
|
||||
kMotiveEngage,
|
||||
kMotiveUpdateGoal,
|
||||
kMotiveFollow,
|
||||
kMotiveShadeHesitate,
|
||||
kMotiveEasyRoomCombat,
|
||||
kMotiveFind8,
|
||||
kMotiveLoose4,
|
||||
kMotiveDefensiveCombat,
|
||||
kMotiveUlinTalk,
|
||||
kMotiveGive,
|
||||
kMotiveUseUpMonster,
|
||||
kMotiveAliveRoomCombat,
|
||||
kMotiveFindAlways,
|
||||
kMotivePlayerCombat,
|
||||
kMotiveJoystick,
|
||||
kMotivePlayerDoor,
|
||||
kMotivewaittalk2,
|
||||
kMotiveGetDisturbed,
|
||||
kMotiveLoose32,
|
||||
kMotiveIfNot1Skip1,
|
||||
};
|
||||
|
||||
enum Str {
|
||||
kStrSword,
|
||||
kStrSwordDesc,
|
||||
kStrBonesText1,
|
||||
kStrBonesText2,
|
||||
kStrBonesText3,
|
||||
kStrComp,
|
||||
kStrCompDesc,
|
||||
kStrOpenBag,
|
||||
kStrThrowComp,
|
||||
kStrSmithText1,
|
||||
kStrSmithText2,
|
||||
kStrCarpet,
|
||||
kStrBomb,
|
||||
kStrBombDesc,
|
||||
kStrPickItUp,
|
||||
kStrYesNo,
|
||||
kStrOther,
|
||||
kStrChestKey,
|
||||
kStrDoorKey,
|
||||
kStrChestKeyDesc,
|
||||
kStrOpenChestDesc,
|
||||
kStrPutItOn,
|
||||
kStrDropItThen,
|
||||
kStrChestDesc,
|
||||
kStrGoodChestDesc,
|
||||
kStrBadChestDesc,
|
||||
kStrComboLock,
|
||||
kStrGold,
|
||||
kStrFindGold,
|
||||
kStrNull,
|
||||
kStrNotHere,
|
||||
kStrUnlockDoor,
|
||||
kStrWeak1,
|
||||
kStrDummyWater,
|
||||
kStrBadWizard,
|
||||
kStrDiesAnyway,
|
||||
kStrDoorKeyDesc,
|
||||
kStrNoteDesc,
|
||||
kStrNote,
|
||||
kStrLootBodyDesc,
|
||||
kStrNotEnough,
|
||||
kStrGameOver,
|
||||
kStrYouWin,
|
||||
kStrWormFoodDesc,
|
||||
kStrWormFood,
|
||||
kStrStoneDesc,
|
||||
kStrStone,
|
||||
kStrGemDesc,
|
||||
kStrGem,
|
||||
kStrFireBallDesc,
|
||||
kStrFireBall,
|
||||
kStrDeathMapDesc,
|
||||
kStrDeathMap,
|
||||
kStrBoots,
|
||||
kStrUseBoots,
|
||||
kStrWowCharmDesc,
|
||||
kStrWowCharm,
|
||||
kStrUseWowCharm,
|
||||
kStrWaterOpen,
|
||||
kStrDrinkIt,
|
||||
kStrItWorks,
|
||||
kStrSBOpen,
|
||||
kStrUsesFire,
|
||||
kStrMuscleDesc,
|
||||
kStrMuscle,
|
||||
kStrSBDesc,
|
||||
kStrSB,
|
||||
kStrFace,
|
||||
kStrFaceDesc,
|
||||
kStrTRNDesc,
|
||||
kStrTRN,
|
||||
kStrInvisDesc,
|
||||
kStrGoodLuckDesc,
|
||||
kStrAnaRing,
|
||||
kStrInvis,
|
||||
kStrGoesAway,
|
||||
kStrGiveHerRing,
|
||||
kStrGive2,
|
||||
kStrMadKingText,
|
||||
kStrMadKing3Text,
|
||||
kStrMadKing2Text,
|
||||
kStrDream1,
|
||||
kStrDream1P2,
|
||||
kStrDream1P3,
|
||||
kStrHowToGetOut,
|
||||
kStrSpore,
|
||||
kStrSporeDesc,
|
||||
kStrRequestPlayDisc,
|
||||
kStrOldGame,
|
||||
kStrEnterCertificate,
|
||||
kStrBadCertificate,
|
||||
kStrCert,
|
||||
kStrCert2,
|
||||
kStrTitle0,
|
||||
kStrTitle4,
|
||||
kStrMDesc,
|
||||
kStrM3Desc,
|
||||
kStrMapText1,
|
||||
kStrMapText2,
|
||||
|
||||
// Level 0 str
|
||||
|
||||
// Level 1 str
|
||||
|
||||
// Level 2 str
|
||||
|
||||
// Level 3 str
|
||||
|
||||
// Level 4 str
|
||||
|
||||
// Level 5 str
|
||||
|
||||
// Level 6 str
|
||||
|
||||
// Level 7 str
|
||||
|
||||
kCantUnlockDoor = kStrBadChestDesc
|
||||
};
|
||||
|
||||
enum SObjType {
|
||||
kTypeTrap,
|
||||
kTypeCoin,
|
||||
kTypeWowCharm,
|
||||
kTypeDead,
|
||||
kTypeFireBall,
|
||||
kTypeDunRing,
|
||||
kTypeChest,
|
||||
kTypeDeathMap,
|
||||
kTypeWater,
|
||||
kTypeSpores,
|
||||
kTypeWormFood,
|
||||
kTypeChestKey,
|
||||
kTypePhant,
|
||||
kTypeGold,
|
||||
kTypeHay,
|
||||
kTypeBeam
|
||||
};
|
||||
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
#endif
|
||||
32
engines/immortal/detection.cpp
Normal file
32
engines/immortal/detection.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/* 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 "engines/advancedDetector.h"
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
#include "immortal/detection_tables.h"
|
||||
|
||||
ImmortalMetaEngineDetection::ImmortalMetaEngineDetection() : AdvancedMetaEngineDetection(Immortal::gameDescriptions,
|
||||
Immortal::immortalGames) {
|
||||
}
|
||||
|
||||
REGISTER_PLUGIN_STATIC(IMMORTAL_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, ImmortalMetaEngineDetection);
|
||||
55
engines/immortal/detection.h
Normal file
55
engines/immortal/detection.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* 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 IMMORTAL_DETECTION_H
|
||||
#define IMMORTAL_DETECTION_H
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
extern const PlainGameDescriptor immortalGames[];
|
||||
extern const ADGameDescription gameDescriptions[];
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
class ImmortalMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
|
||||
static const DebugChannelDef debugFlagList[];
|
||||
|
||||
public:
|
||||
ImmortalMetaEngineDetection();
|
||||
~ImmortalMetaEngineDetection() override {}
|
||||
|
||||
const char *getName() const override {
|
||||
return "immortal";
|
||||
}
|
||||
|
||||
const char *getEngineName() const override {
|
||||
return "The Immortal";
|
||||
}
|
||||
|
||||
const char *getOriginalCopyright() const override {
|
||||
return "(c)1990 Will Harvey & Electronic Arts";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
43
engines/immortal/detection_tables.h
Normal file
43
engines/immortal/detection_tables.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
const PlainGameDescriptor immortalGames[] = {
|
||||
{ "immortal", "The Immortal" },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
const ADGameDescription gameDescriptions[] = {
|
||||
{
|
||||
"immortal",
|
||||
nullptr,
|
||||
AD_ENTRY1s("IMMORTAL.dsk", "094941fd71e6d2cdaa96c9664d32329e", 819200),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2GS,
|
||||
ADGF_UNSTABLE,
|
||||
GUIO1(GUIO_NONE)
|
||||
},
|
||||
|
||||
AD_TABLE_END_MARKER
|
||||
};
|
||||
|
||||
} // namespace Immortal
|
||||
130
engines/immortal/door.cpp
Normal file
130
engines/immortal/door.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* [Alternate Name: Door Processing]
|
||||
*/
|
||||
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
enum DoorMask {
|
||||
kDoorXMask = 0x1f, // Only relevant for extracting the data from the compressed bytes in the story record
|
||||
kDoorYMask = 0x1f,
|
||||
kDoorFullMask = 0x40,
|
||||
kDoorOnMask = 0x60
|
||||
};
|
||||
|
||||
enum DoorIs {
|
||||
kDoorIsRight = 0x80,
|
||||
kDoorIsBusy = 0x40,
|
||||
kDoorIsLocked = 0x20
|
||||
};
|
||||
|
||||
enum DoorSide {
|
||||
kDoorTopPriority = 64,
|
||||
kDoorPriority = 85 - kDoorTopPriority,
|
||||
kDoorLeftTop = 24, // To left of this enters door
|
||||
kDoorRightTop = 8, // To right of this enters door
|
||||
kDoorLeftBottom = 10,
|
||||
kDoorRightBottom = 22,
|
||||
kDoorTopBottom = 20
|
||||
};
|
||||
|
||||
void ImmortalEngine::doorNew(SDoor door) {
|
||||
Door d;
|
||||
d._x = door._x;
|
||||
d._y = door._y;
|
||||
d._fromRoom = door._fromRoom;
|
||||
d._toRoom = door._toRoom;
|
||||
d._busyOnRight = door._dir | door._x;
|
||||
d._on = door._y & kDoorYMask;
|
||||
_doors.push_back(d);
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
int ImmortalEngine::findDoorTop(int x, int y) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
int ImmortalEngine::findDoor(int x, int y) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
bool ImmortalEngine::doLockStuff(int d, MonsterID m, int top) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
bool ImmortalEngine::inDoorTop(int x, int y, MonsterID m) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
bool ImmortalEngine::inDoor(int x, int y, MonsterID m) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
int ImmortalEngine::doorDoStep(MonsterID m, int d, int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
int ImmortalEngine::doorSetOn(int d) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* Not implemented yet
|
||||
*/
|
||||
int ImmortalEngine::doorComeOut(MonsterID m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* These functions are not yet implemented
|
||||
*/
|
||||
void ImmortalEngine::doorSetLadders(MonsterID m) {}
|
||||
void ImmortalEngine::doorDrawAll() {}
|
||||
void ImmortalEngine::doorOnDoorMat() {}
|
||||
void ImmortalEngine::doorOpenSecret() {}
|
||||
void ImmortalEngine::doorCloseSecret() {}
|
||||
void ImmortalEngine::doorInit() {}
|
||||
void ImmortalEngine::doorClrLock() {}
|
||||
|
||||
} // namespace Immortal
|
||||
365
engines/immortal/drawChr.cpp
Normal file
365
engines/immortal/drawChr.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
/* 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 "immortal/immortal.h"
|
||||
|
||||
/* -- How does tile image construction work --
|
||||
* Tiles in the source are quite complex, and the way they are drawn
|
||||
* to the screen makes use of some very low level opcode trickery.
|
||||
* To that end, here is the data structure in the source:
|
||||
* Files: .CNM (Character Number Map), .UNV (Universe)
|
||||
* .CNM = Logical CNM, uncompressed
|
||||
* .UNV = Universe parameters, uncompressed + CNM/CBM, compressed
|
||||
* Logical CNM = Draw type info about the CNM (indexes into a table used when constructing the complete CNM)
|
||||
* Universe parameters = Bounds, animations (unused), Columns, Rows, Chrs, Cells
|
||||
* CNM = Map of all cells in the level, with each cell being an index to the Chr in the CBM
|
||||
* CBM = All gfx data for the level, stored in 'Chrs' which are 4x2 tiles, with each tile being 2x2 blocks of 8x8 pixel gfx data,
|
||||
* stored in Linear Reversed Chunk gfx, *not* bitplane data.
|
||||
*
|
||||
* Data Structures: CNM, Solid, Left, Right, Draw
|
||||
* CNM = Unchanged from the uncompressed file data
|
||||
* Solid/Left/Right = [2 bytes] index for screen clipping, [2 bytes] index to position in Draw where the chr gfx start
|
||||
* Draw = Series of microroutines that use PEA to move pixel data directly to screen buffer. For Left/Right versions,
|
||||
* the routine draws only half of the tile data, divided by a diagonal in one of ULHC/LLHC/URHC/LRHC
|
||||
*
|
||||
* Tile Structures:
|
||||
* Chr = This term is used for multiple things throughout the source, but in this context it is the entire 8x4 set of 8x8 pixel gfx data
|
||||
* Block = Not a term used in the source, but is what describes the 8x8 pixel gfx data
|
||||
*
|
||||
* So the way this works is that after uncompressing the CNM and CBM data,
|
||||
* the game converts the pixel data into the microroutines (mungeX()) and builds the Solid/Left/Right routines.
|
||||
* Then, when tile drawing needs to happen, the drawing routines (drawX()) will take the chr as an index into Solid/Left/Right,
|
||||
* and use the clip data to find a starting scanline, which it can use to determine where the stack will point to.
|
||||
* With the stack pointing to the screen buffer, the game will jump to the microroutine (called Linear Coded Chr Routines in the source)
|
||||
* and execute the code, moving the pixel data to the screen.
|
||||
*
|
||||
* So how does it change for this port?
|
||||
* Well we can't replicate that PEA trick, so we'll have to ditch the microroutines. However,
|
||||
* we will keep the same general structure. Instead of converting the pixel data to routines,
|
||||
* we instead convert it into the type of pixel data needed by the ScummVM screen buffer,
|
||||
* ie. change 1 pixel per nyble to 1 pixel per byte. However, it gets more complicated in how
|
||||
* it has to be stored. In the source, reading the pixel data doesn't have to account for how
|
||||
* many pixels to read per scanline, because it is executing code which will end the scanline
|
||||
* whenever it has been written to do so. In our case, we must instead rely on the pixel reading
|
||||
* function to read the same number of pixels per scanline that we put into the data structure
|
||||
* when converting it from the raw pixel data.
|
||||
* So now we have:
|
||||
* Draw: A vector of Chrs
|
||||
* Chr: A set of 32 scan lines
|
||||
* Scanline: A byte buffer containing anywhere from 1 to 32 bytes of pixel data
|
||||
*/
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
void ImmortalEngine::drawSolid(int chr, int x, int y) {
|
||||
/* Okay, this routine is quite different here compared to
|
||||
* the source, so I'll explain:
|
||||
* At this point in the source, you had an array of linear
|
||||
* coded chr routines that draw pixels by directly storing
|
||||
* byte values to the screen buffer. These routines would
|
||||
* get executed from these drawX() routines. It didn't need
|
||||
* to worry about how many pixels per line for example,
|
||||
* because the code would simply stop sending pixels to the
|
||||
* screen when it reached the end of the scanline (this is
|
||||
* important for the other drawX() routines). In our case,
|
||||
* we instead have a table of structs that contain an array
|
||||
* of scan lines, each with 1 - 32 bytes of pixel data
|
||||
* that is already formatted for use by ScummVM's screen buffer.
|
||||
* These drawX() functions must now draw the pixels from that
|
||||
* data.
|
||||
*/
|
||||
|
||||
// This will need clipping later
|
||||
int index = _Solid[chr];
|
||||
int point = (y * kResH) + x;
|
||||
|
||||
// For every scanline, draw the pixels and move down by the size of the screen
|
||||
for (int cy = 0; cy < 32; cy++, point += kResH) {
|
||||
|
||||
// For solid, we always have 64 pixels in each scanline
|
||||
for (int cx = 0; cx < 64; cx++) {
|
||||
_screenBuff[point + cx] = _Draw[index]._scanlines[cy][cx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::drawLRHC(int chr, int x, int y) {
|
||||
// This will need clipping later
|
||||
int index = _Right[chr];
|
||||
int point = (y * kResH) + x;
|
||||
|
||||
for (int cy = 0; cy < 32; cy++, point += kResH) {
|
||||
|
||||
// We only want to draw the amount of pixels based on the number of lines down the tile
|
||||
for (int cx = 0; cx < (2 * (cy + 1)); cx++) {
|
||||
_screenBuff[point + cx + (64 - (2 * (cy + 1)))] = _Draw[index]._scanlines[cy][cx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::drawLLHC(int chr, int x, int y) {
|
||||
// This will need clipping later
|
||||
int index = _Left[chr];
|
||||
int point = (y * kResH) + x;
|
||||
|
||||
for (int cy = 0; cy < 32; cy++, point += kResH) {
|
||||
for (int cx = 0; cx < (2 * (cy + 1)); cx++) {
|
||||
_screenBuff[point + cx] = _Draw[index]._scanlines[cy][cx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::drawULHC(int chr, int x, int y) {
|
||||
// This will need clipping later
|
||||
int index = _Right[chr];
|
||||
int point = (y * kResH) + x;
|
||||
|
||||
for (int cy = 0; cy < 32; cy++, point += kResH) {
|
||||
for (int cx = 0; cx < (64 - (cy * 2)); cx++) {
|
||||
_screenBuff[point + cx] = _Draw[index]._scanlines[cy][cx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::drawURHC(int chr, int x, int y) {
|
||||
// This will need clipping later
|
||||
int index = _Left[chr];
|
||||
int point = (y * kResH) + x;
|
||||
|
||||
for (int cy = 0; cy < 32; cy++, point += kResH) {
|
||||
for (int cx = 0; cx < (64 - (cy * 2)); cx++) {
|
||||
_screenBuff[point + cx + (cy * 2)] = _Draw[index]._scanlines[cy][cx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ImmortalEngine::mungeCBM(uint16 num2Chrs) {
|
||||
const uint16 kTBlisterCorners[60] = {7, 1, 1, 1, 1, 1, 5, 3, 1, 1, 1, 1, 1, 3, 5, 3, 5, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 16, 16, 16, 16, 8,
|
||||
8, 8, 8, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
};
|
||||
|
||||
// Is this missing an entry? There should be 20 columns, and this is only 19...
|
||||
const uint16 kTLogicalCorners[19] = {1, 1, 1, 1, 16, 8, 1, 8,
|
||||
16, 1, 1, 8, 1, 16, 8, 16,
|
||||
1, 16, 8
|
||||
};
|
||||
|
||||
// Each tile is 1024 bytes, so the oldCBM is 1024 * number of tiles
|
||||
int lCBM = k1K * _univ->_numChrs;
|
||||
_oldCBM = (byte *)malloc(lCBM);
|
||||
|
||||
//debug("Length of CBM: %d", lCBM);
|
||||
|
||||
// Now we get the CBM from the file
|
||||
_dataBuffer->seek(_univ->_num2Cells);
|
||||
_dataBuffer->read(_oldCBM, lCBM);
|
||||
|
||||
// And now we need to set up the data structures that will be used to expand the CNM/CBM
|
||||
// Each Chr needs a 'solid' function, but some also need a 'left' and 'right' function as well
|
||||
// So each one needs to be the size of as many Chrs as you have
|
||||
_Solid = (uint16 *)malloc(num2Chrs);
|
||||
_Right = (uint16 *)malloc(num2Chrs);
|
||||
_Left = (uint16 *)malloc(num2Chrs);
|
||||
|
||||
// _Draw is actually going to be a length that depends on the CNM, so we need it to be a vector
|
||||
|
||||
// In the source, this does all 3 at once, but we have them in separate variables
|
||||
uint16 *lists[3] = {_Solid, _Right, _Left};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < _univ->_numChrs; j++) {
|
||||
lists[i][j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint16 cell = 0;
|
||||
uint16 chr = 0;
|
||||
uint16 corners = 0;
|
||||
int oldChr = 0;
|
||||
uint16 drawIndex = 0;
|
||||
|
||||
// Loop over every cell in the CNM, and set up the draw routines for each draw type needed
|
||||
do {
|
||||
// The CNM has the chr number in each cell
|
||||
chr = _CNM[cell];
|
||||
|
||||
// Have we already done it?
|
||||
if (_Solid[chr] == 0) {
|
||||
|
||||
// Mark it as done even if not solid presumably for future checks on the solid chr list?
|
||||
_Solid[chr] = 1;
|
||||
|
||||
// If cell is within the first 3 rows, and cell 17 is 0
|
||||
if ((cell >= (_univ->_numCols)) && (cell < (_univ->_numCols * 3)) && (_CNM[17] != 0)) {
|
||||
// Then we get it from a table because this is the water level
|
||||
corners = kTBlisterCorners[cell];
|
||||
|
||||
} else {
|
||||
// Otherwise we get it from a table indexed by the entry in the logical CNM
|
||||
corners = kTLogicalCorners[_logicalCNM[cell]];
|
||||
}
|
||||
|
||||
// In the source this is actually asl 5 : asl + rol 5, but we can just use an int instead of 2 uint16s
|
||||
oldChr = chr * 1024;
|
||||
|
||||
// Corners determines whether we create a _Draw entry for just solid, or diagonals as well
|
||||
if ((corners & 1) != 0) {
|
||||
storeAddr(_Solid, chr, drawIndex);
|
||||
mungeSolid(oldChr, drawIndex);
|
||||
}
|
||||
|
||||
if ((corners & 2) != 0) {
|
||||
storeAddr(_Left, chr, drawIndex);
|
||||
mungeLLHC(oldChr, drawIndex);
|
||||
}
|
||||
|
||||
if ((corners & 4) != 0) {
|
||||
storeAddr(_Right, chr, drawIndex);
|
||||
mungeLRHC(oldChr, drawIndex);
|
||||
}
|
||||
|
||||
if ((corners & 8) != 0) {
|
||||
storeAddr(_Left, chr, drawIndex);
|
||||
mungeURHC(oldChr, drawIndex);
|
||||
}
|
||||
|
||||
if ((corners & 16) != 0) {
|
||||
storeAddr(_Right, chr, drawIndex);
|
||||
mungeULHC(oldChr, drawIndex);
|
||||
}
|
||||
}
|
||||
|
||||
cell++;
|
||||
} while (cell != (_univ->_num2Cells / 2));
|
||||
|
||||
// Finally just return the size of the draw table, which is essentially the expanded CBM
|
||||
return _Draw.size();
|
||||
}
|
||||
|
||||
void ImmortalEngine::storeAddr(uint16 *drawType, uint16 chr, uint16 drawIndex) {
|
||||
// The entry at chr2 is the index into the draw table
|
||||
// In the source this required checking bank boundries, luckily that's not relevant here
|
||||
drawType[chr] = drawIndex;
|
||||
}
|
||||
|
||||
void ImmortalEngine::mungeSolid(int oldChr, uint16 &drawIndex) {
|
||||
// We need a Chr for the entry in the draw table
|
||||
Chr chrData;
|
||||
|
||||
// This is a little different from the source, because the source creates the linear coded chr routines
|
||||
// So here we are just grabbing the pixel data in the normal way
|
||||
|
||||
// For every line of pixels in the chr
|
||||
for (int py = 0; py < 32; py++) {
|
||||
// Each scanline needs a byte buffer
|
||||
byte *scanline = (byte *)malloc(64);
|
||||
|
||||
// For every pixel in the line, we extract the data from the oldCBM
|
||||
for (int px = 0; px < 64; px += 2) {
|
||||
|
||||
/* Pixels are stored in Linear Reversed Chunk format
|
||||
* Which is 2 pixels per byte, stored in reverse order.
|
||||
*/
|
||||
scanline[px] = (_oldCBM[oldChr] & kMask8High) >> 4;
|
||||
scanline[px + 1] = (_oldCBM[oldChr] & kMask8Low);
|
||||
oldChr++;
|
||||
}
|
||||
// Now we add the byte buffer into the chrData
|
||||
chrData._scanlines[py] = scanline;
|
||||
}
|
||||
// And we add the chrData into the draw table
|
||||
_Draw.push_back(chrData);
|
||||
drawIndex++;
|
||||
}
|
||||
|
||||
void ImmortalEngine::mungeLRHC(int oldChr, uint16 &drawIndex) {
|
||||
Chr chrData;
|
||||
|
||||
for (int py = 0; py < 32; py++) {
|
||||
byte *scanline = (byte *)malloc(2 * (py + 1));
|
||||
oldChr += (32 - (py + 1));
|
||||
|
||||
for (int px = 0; px < (2 * (py + 1)); px += 2) {
|
||||
scanline[px] = (_oldCBM[oldChr] & kMask8High) >> 4;
|
||||
scanline[px + 1] = (_oldCBM[oldChr] & kMask8Low);
|
||||
oldChr++;
|
||||
}
|
||||
chrData._scanlines[py] = scanline;
|
||||
}
|
||||
_Draw.push_back(chrData);
|
||||
drawIndex++;
|
||||
}
|
||||
|
||||
void ImmortalEngine::mungeLLHC(int oldChr, uint16 &drawIndex) {
|
||||
Chr chrData;
|
||||
|
||||
for (int py = 0; py < 32; py++) {
|
||||
byte *scanline = (byte *)malloc(2 * (py + 1));
|
||||
|
||||
for (int px = 0; px < (2 * (py + 1)); px += 2) {
|
||||
scanline[px] = (_oldCBM[oldChr] & kMask8High) >> 4;
|
||||
scanline[px + 1] = (_oldCBM[oldChr] & kMask8Low);
|
||||
oldChr++;
|
||||
}
|
||||
oldChr += (32 - (py + 1));
|
||||
chrData._scanlines[py] = scanline;
|
||||
}
|
||||
_Draw.push_back(chrData);
|
||||
drawIndex++;
|
||||
}
|
||||
|
||||
void ImmortalEngine::mungeULHC(int oldChr, uint16 &drawIndex) {
|
||||
Chr chrData;
|
||||
|
||||
for (int py = 0; py < 32; py++) {
|
||||
byte *scanline = (byte *)malloc(64 - ((py + 1) * 2));
|
||||
|
||||
for (int px = 0; px < (64 - ((py + 1) * 2)); px += 2) {
|
||||
scanline[px] = (_oldCBM[oldChr] & kMask8High) >> 4;
|
||||
scanline[px + 1] = (_oldCBM[oldChr] & kMask8Low);
|
||||
oldChr++;
|
||||
}
|
||||
oldChr += (py + 1);
|
||||
chrData._scanlines[py] = scanline;
|
||||
}
|
||||
_Draw.push_back(chrData);
|
||||
drawIndex++;
|
||||
}
|
||||
|
||||
void ImmortalEngine::mungeURHC(int oldChr, uint16 &drawIndex) {
|
||||
Chr chrData;
|
||||
|
||||
for (int py = 0; py < 32; py++) {
|
||||
byte *scanline = (byte *)malloc(64 - (py * 2));
|
||||
|
||||
for (int px = 0; px < (64 - (py * 2)); px += 2) {
|
||||
scanline[px] = (_oldCBM[oldChr] & kMask8High) >> 4;
|
||||
scanline[px + 1] = (_oldCBM[oldChr] & kMask8Low);
|
||||
oldChr++;
|
||||
}
|
||||
oldChr += (py + 1);
|
||||
chrData._scanlines[py] = scanline;
|
||||
}
|
||||
_Draw.push_back(chrData);
|
||||
drawIndex++;
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
151
engines/immortal/flameSet.cpp
Normal file
151
engines/immortal/flameSet.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* [Alternate Name: Torch Processing]
|
||||
* --- What is a FlameSet ---
|
||||
* A FlameSet is a list of torches in a given room.
|
||||
* The level has X amount of flames total, and each
|
||||
* room has N amount of torches, making up a FlameSet.
|
||||
* There are 3 instances of torches in the game.
|
||||
* First is in the story record, second is in the level torches,
|
||||
* and lastly is the in-room torch. This is done so that
|
||||
* the in-room torch can be lit up by a fireball and then
|
||||
* stay permanantly lit for the next time the player enters the room.
|
||||
*/
|
||||
|
||||
#include "immortal/room.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
void Room::flameInit() {
|
||||
flameFreeAll();
|
||||
_candleTmp = 0;
|
||||
}
|
||||
|
||||
void Room::flameFreeAll() {
|
||||
_numFlames = 0;
|
||||
_numInRoom = 0;
|
||||
}
|
||||
|
||||
void Room::flameDrawAll(uint16 vX, uint16 vY) {
|
||||
for (uint i = 0; i < _fset.size(); i++) {
|
||||
// For every flame in the room, add the sprite to the sprite table
|
||||
univAddSprite(vX, vY, _fset[i]._x, _fset[i]._y, g_immortal->_cycPtrs[g_immortal->_cycles[_fset[i]._c]._cycList]._sName, cycleGetFrame(_fset[i]._c), 0);
|
||||
if (cycleAdvance(_fset[i]._c) == true) {
|
||||
cycleFree(_fset[i]._c);
|
||||
_fset[i]._c = flameGetCyc(&_fset[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Room::roomLighted() {
|
||||
// For testing purposes, we say it's always lit
|
||||
return true;
|
||||
|
||||
// Very simple, just checks every torch and if any of them are lit, we say the room is lit
|
||||
for (uint i = 0; i < _fset.size(); i++) {
|
||||
if (_fset[i]._p != kFlameOff) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Room::lightTorch(uint8 x, uint8 y) {
|
||||
/* Checks every torch to see if it is:
|
||||
* pattern == off, and inside the point x,y
|
||||
* which is the fireball position. This is a
|
||||
* little bit clever, because it saves cycles
|
||||
* over checking x,y first, since you need to
|
||||
* check both x,y and flame pattern. Neato.
|
||||
*/
|
||||
|
||||
for (uint i = 0; i < _fset.size(); i++) {
|
||||
if (_fset[i]._p == kFlameOff) {
|
||||
if (Utilities::inside(kLightTorchX, x, y, _fset[i]._x + 16, _fset[i]._y + 8)) {
|
||||
_fset[i]._p = kFlameNormal;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Room::flameSetRoom(Common::Array<SFlame> &allFlames) {
|
||||
for (uint i = 0; i < allFlames.size(); i++) {
|
||||
Flame f;
|
||||
f._p = allFlames[i]._p;
|
||||
f._x = allFlames[i]._x;
|
||||
f._y = allFlames[i]._y;
|
||||
f._c = flameGetCyc(&f, (0 | _candleTmp));
|
||||
//debug("made flame, cyc = %d", f._c);
|
||||
_fset.push_back(f);
|
||||
}
|
||||
_candleTmp = 1;
|
||||
}
|
||||
|
||||
int Room::flameGetCyc(Flame *f, int first) {
|
||||
/* I must say, although this is clever, it is the most
|
||||
* convoluted way to do this I could imagine. Here's what it does:
|
||||
* Get a random number between 0 and 255. Now reduce this number by the length
|
||||
* of the array for the particular flame pattern until we are at less than 0.
|
||||
* Now add back that same length. We are now at the length of the array
|
||||
* minus a random amount between 0 and the length of the array.
|
||||
* This gives us a random entry within the array to start at.
|
||||
*/
|
||||
CycID flamePatA[] = {kCycFNormal0, kCycFNormal1, kCycFNormal2,
|
||||
kCycFNormal0, kCycFNormal1, kCycFNormal2,
|
||||
kCycFNormal0, kCycFNormal1, kCycFNormal2,
|
||||
kCycFNormal0, kCycFNormal1, kCycFNormal2
|
||||
};
|
||||
CycID flamePatB[] = {kCycFCandleBurst, kCycFCandleSway, kCycFCandleJump,
|
||||
kCycFCandleLeap, kCycFCandleFlicker,
|
||||
kCycFCandleFlicker, kCycFCandleFlicker, kCycFCandleFlicker
|
||||
};
|
||||
CycID flamePatC[] = {kCycFOff};
|
||||
CycID flamePatD[] = {kCycFFlicker0, kCycFFlicker1, kCycFFlicker2};
|
||||
|
||||
int numFlameCycs[] = {12, 8, 1, 3};
|
||||
|
||||
int r = getRandomNumber(255) & (kMaxFlameCycs - 1);
|
||||
|
||||
do {
|
||||
r -= numFlameCycs[(int) f->_p];
|
||||
} while (r >= 0);
|
||||
|
||||
r += numFlameCycs[(int) f->_p];
|
||||
|
||||
// Why is this not indexed further? ie. LDA patternTable,x : STA $00 : LDA ($00),y instead of a branch tree?
|
||||
// Pretty sure CPX 3 times is more than a single LDA (dp),y
|
||||
switch (f->_p) {
|
||||
case 0:
|
||||
return cycleNew(flamePatA[r]);
|
||||
case 1:
|
||||
return cycleNew(flamePatB[r]);
|
||||
case 2:
|
||||
return cycleNew(flamePatC[r]);
|
||||
case 3:
|
||||
return cycleNew(flamePatD[r]);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
232
engines/immortal/immortal.cpp
Normal file
232
engines/immortal/immortal.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// common/config-manager is needed for the search manager
|
||||
#include "common/config-manager.h"
|
||||
|
||||
// engines/util is needed for initGraphics()
|
||||
#include "engines/util.h"
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
ImmortalEngine *g_immortal;
|
||||
|
||||
ImmortalEngine::ImmortalEngine(OSystem *syst, const ADGameDescription *gameDesc)
|
||||
: Engine(syst)
|
||||
, _gameDescription(gameDesc)
|
||||
, _randomSource("Immortal") {
|
||||
g_immortal = this;
|
||||
|
||||
// Add the game folder to the search manager path variable
|
||||
const Common::FSNode gameDataDir(ConfMan.getPath("path"));
|
||||
SearchMan.addSubDirectoryMatching(gameDataDir, "game");
|
||||
|
||||
// Confirm that the engine was created
|
||||
debug("ImmortalEngine::ImmortalEngine");
|
||||
}
|
||||
|
||||
ImmortalEngine::~ImmortalEngine() {
|
||||
// Confirm that the engine was destroyed
|
||||
debug("ImmortalEngine::~ImmortalEngine");
|
||||
}
|
||||
|
||||
// --- Functions to make things a little more simple ---
|
||||
|
||||
uint16 ImmortalEngine::xba(uint16 ab) {
|
||||
/* XBA in 65816 swaps the low and high bits of a given word in A.
|
||||
* This is used very frequently, so this function just makes
|
||||
* initial translation a little more straightforward. Eventually,
|
||||
* code should be refactored to not require this.
|
||||
*/
|
||||
return ((ab & kMaskLow) << 8) | ((ab & kMaskHigh) >> 8);
|
||||
}
|
||||
|
||||
uint16 ImmortalEngine::rol(uint16 ab, int n) {
|
||||
/* This just replicates bit rotation, and it
|
||||
* assumes a 16 bit unsigned int because that's all
|
||||
* we need for the 65816.
|
||||
*/
|
||||
return (ab << n) | (ab >> (-n & 15));
|
||||
}
|
||||
|
||||
uint16 ImmortalEngine::ror(uint16 ab, int n) {
|
||||
/* The way this works is very straightforward. We start by
|
||||
* performing the bit shift like normal: 0001 -> 0000
|
||||
* Then we need an easy way to apply the bit whether it fell
|
||||
* off the end or not, so we bit shift the opposite direction
|
||||
* for the length of the word minus the size of the shift.
|
||||
* This way, the bit that we shifted normally, gets
|
||||
* separately moved to the other end: 0001 -> 1000
|
||||
* In other words, the space C uses to evaluate the second
|
||||
* part of the expression, is simulating the register for the
|
||||
* carry flag. To avoid branching in case of a 0, we get the
|
||||
* shift size by using the negative of the number as an
|
||||
* implicit subtraction and grabbing just the relevant bits.
|
||||
*/
|
||||
return (ab >> n) | (ab << (-n & 15));
|
||||
}
|
||||
|
||||
uint16 ImmortalEngine::mult16(uint16 a, uint16 b) {
|
||||
/* We aren't using the game's multiplication function (mult16), but we do want
|
||||
* to retain the ability to drop the second word, without doing (uint16) every time
|
||||
*/
|
||||
return (uint16)(a * b);
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
|
||||
uint32 ImmortalEngine::getFeatures() const {
|
||||
// No specific features currently
|
||||
return _gameDescription->flags;
|
||||
}
|
||||
|
||||
Common::String ImmortalEngine::getGameId() const {
|
||||
// No game ID currently
|
||||
return _gameDescription->gameId;
|
||||
}
|
||||
|
||||
Common::ErrorCode ImmortalEngine::initDisks() {
|
||||
// Check for the boot disk
|
||||
if (SearchMan.hasFile("IMMORTAL.dsk")) {
|
||||
|
||||
// Instantiate the disk as an object. The disk will then open and parse itself
|
||||
Common::ProDOSDisk *diskBoot = new Common::ProDOSDisk("IMMORTAL.dsk");
|
||||
if (diskBoot) {
|
||||
|
||||
// With the disk successfully parsed, it can be added to the search manager
|
||||
debug("Boot disk found");
|
||||
SearchMan.add("IMMORTAL.dsk", diskBoot, 0, true);
|
||||
}
|
||||
} else {
|
||||
debug("Please insert Boot disk...");
|
||||
return Common::kPathDoesNotExist;
|
||||
}
|
||||
|
||||
// Check for the gfx disk
|
||||
if (SearchMan.hasFile("IMMORTAL_GFX.dsk")) {
|
||||
Common::ProDOSDisk *diskGFX = new Common::ProDOSDisk("IMMORTAL_GFX.dsk");
|
||||
if (diskGFX) {
|
||||
debug("Gfx disk found");
|
||||
SearchMan.add("IMMORTAL_GFX.dsk", diskGFX, 0, true);
|
||||
}
|
||||
} else {
|
||||
debug("Please insert GFX disk...");
|
||||
return Common::kPathDoesNotExist;
|
||||
}
|
||||
|
||||
// All good, return with no error
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
Common::Error ImmortalEngine::run() {
|
||||
initGraphics(kResH, kResV);
|
||||
|
||||
_mainSurface = new Graphics::Surface();
|
||||
_mainSurface->create(kResH, kResV, Graphics::PixelFormat::createFormatCLUT8());
|
||||
|
||||
_screenBuff = new byte[kScreenSize];
|
||||
|
||||
if (initDisks() != Common::kNoError) {
|
||||
debug("Some kind of disc error!");
|
||||
return Common::kPathDoesNotExist;
|
||||
}
|
||||
|
||||
// Main:
|
||||
_zero = 0;
|
||||
_draw = 1;
|
||||
_usingNormal = 1;
|
||||
_penY = 7;
|
||||
_penX = 1;
|
||||
|
||||
initStoryStatic(); // Init the arrays of static story elements (done at compile time in the source)
|
||||
loadPalette(); // We need to grab the palette from the disk first
|
||||
|
||||
// This is the equivalent of Main->InitGraphics->MyClearScreen in Driver
|
||||
useNormal(); // The first palette will be the default
|
||||
|
||||
loadFont(); // Load the font sprites
|
||||
loadWindow(); // Load the window background
|
||||
loadSingles("Song A"); // Music
|
||||
loadSprites(); // Get all the sprite data into memory
|
||||
|
||||
_playing = kSongNothing;
|
||||
_themePaused = 0;
|
||||
|
||||
clearSprites(); // Clear the sprites before we start
|
||||
// This is where the request play disk would happen, but that's not needed here
|
||||
logicInit(); // Init the game logic
|
||||
|
||||
_err = Common::kNoError;
|
||||
|
||||
while (!shouldQuit()) {
|
||||
/* The game loop runs at 60fps, which is 16 milliseconds per frame.
|
||||
* This loop keeps that time by getting the time in milliseconds at the start of the loop,
|
||||
* then again at the end, and the difference between them is the remainder
|
||||
* of the frame budget. If that remainder is within the 16 millisecond budget,
|
||||
* then it delays ScummVM for the remainder. If it is 0 or negative, then it continues.
|
||||
*/
|
||||
int64 loopStart = g_system->getMillis();
|
||||
|
||||
// Main
|
||||
Common::Event event;
|
||||
g_system->getEventManager()->pollEvent(event);
|
||||
|
||||
userIO();
|
||||
noNetwork();
|
||||
pollKeys();
|
||||
logic();
|
||||
pollKeys();
|
||||
if (logicFreeze() == 0) {
|
||||
drawUniv();
|
||||
pollKeys();
|
||||
fixColors();
|
||||
copyToScreen();
|
||||
pollKeys();
|
||||
}
|
||||
|
||||
if (_err != Common::kNoError) {
|
||||
debug("To err is human, to really screw up you need an Apple IIGS!");
|
||||
return Common::kUnknownError;
|
||||
}
|
||||
|
||||
int64 loopEnd = 16 - (g_system->getMillis() - loopStart);
|
||||
if (loopEnd > 0) {
|
||||
//debug("remaining budget: %d", loopEnd);
|
||||
g_system->delayMillis(loopEnd);
|
||||
}
|
||||
}
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
Common::Error ImmortalEngine::syncGame(Common::Serializer &s) {
|
||||
/* The Serializer has methods isLoading() and isSaving()
|
||||
* if you need to specific steps; for example setting
|
||||
* an array size after reading it's length, whereas
|
||||
* for saving it would write the existing array's length
|
||||
*/
|
||||
int dummy = 0;
|
||||
s.syncAsUint32LE(dummy);
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
775
engines/immortal/immortal.h
Normal file
775
engines/immortal/immortal.h
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IMMORTAL_H
|
||||
#define IMMORTAL_H
|
||||
|
||||
// Audio is only handled in kernel, therefore it is only needed here
|
||||
#include "audio/mixer.h"
|
||||
|
||||
// Immortal.h is the engine, so it needs the engine headers
|
||||
#include "engines/engine.h"
|
||||
#include "engines/savestate.h"
|
||||
|
||||
// Theorectically, all graphics should be handled through driver, which is part of kernel, which is in immortal.h
|
||||
#include "graphics/screen.h"
|
||||
#include "graphics/surface.h"
|
||||
|
||||
// Detection is only needed by the main engine
|
||||
#include "immortal/detection.h"
|
||||
|
||||
#include "common/formats/prodos.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/events.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/system.h"
|
||||
#include "common/fs.h"
|
||||
#include "common/hash-str.h"
|
||||
#include "common/random.h"
|
||||
#include "common/serializer.h"
|
||||
#include "common/util.h"
|
||||
#include "common/platform.h"
|
||||
|
||||
// Utilities.h contains many things used by all objects, not just immortal
|
||||
#include "immortal/utilities.h"
|
||||
|
||||
// Room also includes story.h
|
||||
#include "immortal/room.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
// Needed by kernel for input
|
||||
enum InputAction {
|
||||
kActionNothing,
|
||||
kActionKey,
|
||||
kActionRestart, // Key "R" <-- Debug?
|
||||
kActionSound,
|
||||
kActionFire,
|
||||
kActionButton, // Does this just refer to whatever is not the fire button?
|
||||
kActionDBGStep // Debug key for moving engine forward one frame at a time
|
||||
};
|
||||
|
||||
enum ButtonHeldMask {
|
||||
kButton0Held = 2,
|
||||
kButton1Held = 4
|
||||
};
|
||||
|
||||
enum InputDirection {
|
||||
kDirectionUp,
|
||||
kDirectionLeft,
|
||||
kDirectionDown,
|
||||
kDirectionRight
|
||||
};
|
||||
|
||||
// Needed by kernel for text
|
||||
enum FadeType {
|
||||
kTextFadeIn,
|
||||
kTextDontFadeIn
|
||||
};
|
||||
|
||||
// Needed by kernel for music
|
||||
enum Song {
|
||||
kSongNothing,
|
||||
kSongMaze,
|
||||
kSongCombat,
|
||||
kSongText
|
||||
};
|
||||
|
||||
enum Sound {
|
||||
kSoundSwish = 6,
|
||||
kSoundAwe,
|
||||
kSoundHuh,
|
||||
kSoundClank,
|
||||
kSoundFireBall,
|
||||
kSoundDoor
|
||||
};
|
||||
|
||||
// Needed by logic for various things
|
||||
enum MonsterID {
|
||||
kPlayerID
|
||||
};
|
||||
|
||||
// Needed by logic for certificate processing
|
||||
enum CertificateIndex : uint8 {
|
||||
kCertHits,
|
||||
kCertLevel,
|
||||
kCertLoGameFlags,
|
||||
kCertHiGameFlags,
|
||||
kCertQuickness,
|
||||
kCertInvLo,
|
||||
kCertInvHi,
|
||||
kCertGoldLo,
|
||||
kCertGoldHi
|
||||
};
|
||||
|
||||
// Needed by logic for various things
|
||||
enum GameFlags : uint8 {
|
||||
kSavedNone,
|
||||
kSavedKing,
|
||||
kSavedAna
|
||||
};
|
||||
|
||||
// Needed by level (maybe?)
|
||||
enum LevelType {
|
||||
kRoomType,
|
||||
kMonsterType,
|
||||
kObjectType
|
||||
};
|
||||
|
||||
// Basically the equivalent of the explosion from a projectile in other games I think
|
||||
struct Spark {
|
||||
};
|
||||
|
||||
// Generic sprites can be used anywhere, just sort of misc sprites
|
||||
struct GenericSprite {
|
||||
};
|
||||
|
||||
// Doors are a property of the level, not the room, they define the connections between rooms
|
||||
struct Door {
|
||||
uint8 _x = 0;
|
||||
uint8 _y = 0;
|
||||
uint8 _fromRoom = 0;
|
||||
uint8 _toRoom = 0;
|
||||
uint8 _busyOnRight = 0;
|
||||
uint8 _on = 0;
|
||||
};
|
||||
|
||||
// Universe is a set of properties for the entire level, nor just the room
|
||||
struct Univ {
|
||||
uint16 _rectX = 0;
|
||||
uint16 _rectY = 0;
|
||||
uint16 _numAnims = 0;
|
||||
uint16 _numCols = 0;
|
||||
uint16 _numRows = 0;
|
||||
uint16 _numChrs = 0;
|
||||
uint16 _num2Cols = 0;
|
||||
uint16 _num2Rows = 0;
|
||||
uint16 _num2Cells = 0;
|
||||
uint16 _num2Chrs = 0;
|
||||
};
|
||||
|
||||
struct Chr {
|
||||
byte *_scanlines[32];
|
||||
};
|
||||
|
||||
struct ImmortalGameDescription;
|
||||
|
||||
// Forward declaration because we will need the Disk and Room classes
|
||||
class ProDosDisk;
|
||||
class Room;
|
||||
|
||||
class ImmortalEngine : public Engine {
|
||||
private:
|
||||
Common::RandomSource _randomSource;
|
||||
|
||||
protected:
|
||||
// Engine APIs
|
||||
Common::Error run() override;
|
||||
|
||||
public:
|
||||
ImmortalEngine(OSystem *syst, const ADGameDescription *gameDesc);
|
||||
~ImmortalEngine() override;
|
||||
|
||||
const ADGameDescription *_gameDescription;
|
||||
|
||||
/* Terrible functions because C doesn't like
|
||||
* bit manipulation enough
|
||||
*/
|
||||
uint16 xba(uint16 ab); // This just replicates the XBA command from the 65816, because flipping the byte order is somehow not a common library function???
|
||||
uint16 rol(uint16 ab, int n); // Rotate bits left by n
|
||||
uint16 ror(uint16 ab, int n); // Rotate bits right by n
|
||||
uint16 mult16(uint16 a, uint16 b); // Just avoids using (uint16) everywhere, and is slightly closer to the original
|
||||
|
||||
/*
|
||||
* --- Members ---
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
|
||||
// Misc constants
|
||||
const int kNumLengths = 21;
|
||||
const int kNiceTime = 36;
|
||||
const int kMaxCertificate = 16;
|
||||
|
||||
// Screen constants
|
||||
const int kScreenW__ = 128; // ??? labeled in source as SCREENWIDTH
|
||||
const int kScreenH__ = 128; // ???
|
||||
const int kViewPortW = 256;
|
||||
const int kViewPortH = 128;
|
||||
const int kScreenSize = (kResH * kResV) * 2; // The size of the screen buffer is (320x200) * 2 byte words
|
||||
const uint16 kScreenLeft = 32;
|
||||
const uint16 kScreenTop = 20;
|
||||
const uint8 kTextLeft = 8;
|
||||
const uint8 kTextTop = 4;
|
||||
const uint8 kGaugeX = 0;
|
||||
const uint8 kGaugeY = static_cast<uint8>((-13) & 0xff); // ???
|
||||
const uint16 kScreenBMW = 160; // Screen BitMap Width?
|
||||
const uint16 kChrW = 64;
|
||||
const uint16 kChrH = 32;
|
||||
const uint16 kChrH2 = kChrH * 2;
|
||||
const uint16 kChrH3 = kChrH * 3;
|
||||
const uint16 kChrLen = (kChrW / 2) * kChrH;
|
||||
const uint16 kChrBMW = kChrW / 2;
|
||||
const uint16 kLCutaway = 4;
|
||||
const uint16 kLDrawSolid = 32 * ((3 * 16) + 5);
|
||||
|
||||
const uint16 kChrDy[19] = {kChr0, kChrH, kChrH2, kChrH, kChrH2,
|
||||
kChrH2, kChrH, kChrH2, kChrH2, kChr0,
|
||||
kChr0, kChrH2, kChrH, kChrH2, kChrH2,
|
||||
kChrH2, kChrH, kChrH2, kChrH2
|
||||
};
|
||||
|
||||
const uint16 kChrMask[19] = {kChr0, kChr0, kChr0, kChr0,
|
||||
kChrR, kChrL, kChr0, kChrL,
|
||||
kChrR, kChr0, kChr0, kChrLD,
|
||||
kChr0, kChrR, kChrLD, kChrRD,
|
||||
kChr0, kChrRD, kChrL
|
||||
};
|
||||
|
||||
const uint16 kIsBackground[36] = {1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
// Disk offsets
|
||||
const int kPaletteOffset = 21205; // This is the byte position of the palette data in the disk
|
||||
|
||||
// Sprite constants
|
||||
const uint16 kMaxSpriteW = 64;
|
||||
const uint16 kMaxSpriteH = 64;
|
||||
const uint16 kSpriteDY = 32;
|
||||
const uint16 kVSX = kMaxSpriteW;
|
||||
const uint16 kVSY = kSpriteDY;
|
||||
const uint16 kVSBMW = (kViewPortW + kMaxSpriteW) / 2;
|
||||
const uint16 kVSLen = kVSBMW * (kViewPortH + kMaxSpriteH);
|
||||
const uint16 kVSDY = 32; // difference from top of screen to top of viewport in the virtual screen buffer
|
||||
const uint16 kMySuperBottom = kVSDY + kViewPortH;
|
||||
const uint16 kSuperBottom = 200;
|
||||
const uint16 kMySuperTop = kVSDY;
|
||||
const uint16 kSuperTop = 0;
|
||||
const uint16 kViewPortSpX = 32;
|
||||
const uint16 kViewPortSpY = 0;
|
||||
const uint16 kWizardX = 28; // Common sprite center for some reason
|
||||
const uint16 kWizardY = 37;
|
||||
const uint16 kObjectY = 24;
|
||||
const uint16 kObjectX = 32;
|
||||
const uint16 kObjectHeight = 48;
|
||||
const uint16 kObjectWidth = 64;
|
||||
|
||||
// Text constants
|
||||
const uint8 kMaxRows = 5;
|
||||
const uint8 kMaxCollumns = 26;
|
||||
|
||||
const uint16 kYesNoY = 88;
|
||||
const uint16 kYesNoX1 = 8;
|
||||
const uint16 kYesNoX2 = 182;
|
||||
|
||||
// Asset constants
|
||||
const char kGaugeOn = 1; // On uses the sprite at index 1 of the font spriteset
|
||||
const char kGaugeOff = 0; // Off uses the sprite at index 0 of the font spriteset
|
||||
const char kGaugeStop = 1; // Literally just means the final kGaugeOn char to draw
|
||||
const char kGaugeStart = 1; // First kGaugeOn char to draw
|
||||
|
||||
// Level constants
|
||||
const int kStoryNull = 5;
|
||||
const int kMaxFilesPerLevel = 16;
|
||||
const int kMaxPartInstances = 4;
|
||||
const int kLevelToMaze[8] = {0, 0, 1, 1, 2, 2, 2, 3};
|
||||
|
||||
/*
|
||||
* 'global' members
|
||||
*/
|
||||
|
||||
// Misc
|
||||
Common::ErrorCode _err; // If this is not kNoError at any point, the engine will stop
|
||||
uint8 _certificate[16]; // The certificate (password) is basically the inventory/equipment array
|
||||
uint8 _lastCertLen = 0;
|
||||
bool _draw = 0; // Whether the screen should draw this frame
|
||||
int _zero = 0; // No idea what this is yet
|
||||
bool _gameOverFlag = false;
|
||||
uint8 _gameFlags = 0; // Bitflag array of event flags, but only two were used (saving ana and saving the king) <-- why is gameOverFlag not in this? Lol
|
||||
bool _themePaused = false; // In the source, this is actually considered a bit flag array of 2 bits (b0 and b1). However, it only ever checks for non-zero, so it's effectively only 1 bit.
|
||||
int _titlesShown = 0;
|
||||
int _time = 0;
|
||||
int _promoting = 0; // I think promoting means the title stuff
|
||||
bool _restart = false;
|
||||
|
||||
// Story members
|
||||
Story _stories[8];
|
||||
|
||||
// Level members
|
||||
int _maxLevels = 0; // This is determined when loading in story files
|
||||
int _level = 0;
|
||||
bool _levelOver = false;
|
||||
int _count = 0;
|
||||
int _lastLevelLoaded = 0;
|
||||
int _lastSongLoaded = 0;
|
||||
int _storyLevel = 0;
|
||||
int _storyX = 0;
|
||||
int _loadA = 0;
|
||||
int _loadY = 0;
|
||||
uint16 _initialX = 0;
|
||||
uint16 _initialY = 0;
|
||||
int _initialBX = 0;
|
||||
int _initialBY = 0;
|
||||
int _dRoomNum = 0;
|
||||
int _initialRoom = 0;
|
||||
int _currentRoom = 0;
|
||||
int _lastType = 0;
|
||||
int _roomCellX = 0;
|
||||
int _roomCellY = 0;
|
||||
Room *_rooms[kMaxRooms]; // Rooms within the level
|
||||
Common::Array<SFlame> _allFlames[kMaxRooms]; // The level needs it's own set of flames so that the flames can be turned on/off permanently. This is technically more like a hashmap in the source, but it could also be seen as a 2d array, just hashed together in the source
|
||||
|
||||
// Door members
|
||||
Common::Array<Door> _doors;
|
||||
uint8 _numDoors = 0;
|
||||
uint8 _doorRoom = 0;
|
||||
uint8 _doorToNextLevel = 0;
|
||||
uint8 _doorCameInFrom = 0;
|
||||
uint8 _ladders = 0;
|
||||
uint8 _numLadders = 0;
|
||||
uint8 _ladderInUse = 0;
|
||||
uint8 _secretLadder = 0;
|
||||
uint8 _secretCount = 0;
|
||||
uint8 _secretDelta = 0;
|
||||
|
||||
// Debug members
|
||||
bool _singleStep = false; // Flag for _singleStep mode
|
||||
|
||||
// Input members
|
||||
int _pressedAction = 0;
|
||||
int _heldAction = 0;
|
||||
int _pressedDirection = 0;
|
||||
int _heldDirection = 0;
|
||||
|
||||
// Text printing members
|
||||
uint8 _slowText = 0;
|
||||
uint8 _formatted = 0;
|
||||
uint8 _collumn = 0;
|
||||
uint8 _row = 0;
|
||||
uint8 _myButton = 0;
|
||||
uint8 _lastYes = 0;
|
||||
|
||||
// Music members
|
||||
Song _playing; // Currently playing song
|
||||
int _themeID = 0; // Not sure yet tbh
|
||||
int _combatID = 0;
|
||||
|
||||
// Asset members
|
||||
int _numSprites = 0; // This is more accurately actually the index within the sprite array, so _numSprites + 1 is the current number of sprites
|
||||
DataSprite _dataSprites[kFont + 1]; // All the sprite data, indexed by SpriteName
|
||||
Sprite _sprites[kMaxSprites]; // All the sprites shown on screen
|
||||
Cycle _cycles[kMaxCycles];
|
||||
Common::Array<Common::String> _strPtrs; // Str should really be a char array, but inserting frame values will be stupid so it's just a string instead
|
||||
Common::Array<Motive> _motivePtrs;
|
||||
Common::Array<Damage> _damagePtrs;
|
||||
Common::Array<Use> _usePtrs;
|
||||
Common::Array<Pickup> _pickupPtrs;
|
||||
Common::Array<SCycle> _cycPtrs; // This is not actually a set of pointers, but it is serving the function of what was called cycPtrs in the source
|
||||
CArray2D<Motive> _programPtrs;
|
||||
Common::Array<ObjType> _objTypePtrs;
|
||||
|
||||
// Universe members
|
||||
Univ *_univ; // Pointer to the struct that contains the universe properties
|
||||
uint16 *_logicalCNM; // Draw-type data for the CNM (indexes into )
|
||||
uint16 *_CNM; // Stands for CHARACTER NUMBER MAP, but really it should be TILE NUMBER MAP, because it points to tiles, which are made of characters
|
||||
byte *_oldCBM; // Stands for CHARACTER BIT MAP, but should probably be called like, TILE CHARACTER MAP, because it is the full gfx data for all tiles
|
||||
Common::Array<Chr> _Draw; // In the source this contained the Linear Coded Chr Routines, but here it just contains the expanded pixel data
|
||||
uint16 *_Solid;
|
||||
uint16 *_Right;
|
||||
uint16 *_Left;
|
||||
Common::SeekableReadStream *_dataBuffer; // This contains the uncompressed CNM + CBM
|
||||
|
||||
uint16 *_modCNM;
|
||||
uint16 *_modLogicalCNM;
|
||||
|
||||
uint16 _myCNM[(kViewPortCW + 1)][(kViewPortCH + 1)];
|
||||
uint16 _myModCNM[(kViewPortCW + 1)][(kViewPortCH + 1)];
|
||||
uint16 _myModLCNM[(kViewPortCW + 1)][(kViewPortCH + 1)];
|
||||
|
||||
// Screen members
|
||||
byte *_screenBuff; // The final buffer that will transfer to the screen
|
||||
Graphics::Surface *_mainSurface; // The ScummVM Surface
|
||||
|
||||
uint16 _columnX[kViewPortCW + 1];
|
||||
uint16 _columnTop[kViewPortCW + 1];
|
||||
uint16 _columnIndex[kViewPortCW + 1]; // Why the heck is this an entire array, when it's just an index that gets zeroed before it gets used anyway...
|
||||
uint16 _tIndex[kMaxDrawItems];
|
||||
uint16 _tPriority[kMaxDrawItems];
|
||||
uint16 _viewPortX = 0;
|
||||
uint16 _viewPortY = 0;
|
||||
uint16 _myViewPortX = 0; // Probably mirror of viewportX
|
||||
uint16 _myViewPortY = 0;
|
||||
int _lastGauge = 0; // Mirror for player health, used to update health gauge display
|
||||
uint16 _lastBMW = 0; // Mirrors used to determine where bitmap width needs to be re-calculated
|
||||
uint16 _lastY = 0;
|
||||
uint16 _lastPoint = 0;
|
||||
uint16 _penX = 0; // Basically where in the screen we are currently drawing
|
||||
uint16 _penY = 0;
|
||||
uint16 _myUnivPointX = 0;
|
||||
uint16 _myUnivPointY = 0;
|
||||
int _num2DrawItems = 0;
|
||||
GenericSprite _genSprites[6];
|
||||
|
||||
// Palette members
|
||||
int _dontResetColors = 0; // Not sure yet
|
||||
bool _usingNormal = 0; // Whether the palette is using normal
|
||||
bool _dim = 0; // Whether the palette is dim
|
||||
uint16 _palUniv[16];
|
||||
uint16 _palDefault[16];
|
||||
uint16 _palWhite[16];
|
||||
uint16 _palBlack[16];
|
||||
uint16 _palDim[16];
|
||||
byte _palRGB[48]; // Palette that ScummVM actually uses, which is an RGB conversion of the original
|
||||
|
||||
|
||||
/*
|
||||
* --- Functions ---
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* [Kernel.cpp] Functions from Kernel.gs and Driver.gs
|
||||
*/
|
||||
|
||||
// Screen
|
||||
void clearScreen(); // Draws a black rectangle on the screen buffer but only inside the frame
|
||||
void whiteScreen(); // Draws a white rectanlge on the screen buffer (but does not do anything with resetColors)
|
||||
void rect(int x, int y, int w, int h); // Draws a solid rectangle at x,y with size w,h. Also shadows for blit?
|
||||
void backspace(); // Moves draw position back and draws empty rect in place of char
|
||||
void printByte(int b);
|
||||
void printChr(char c);
|
||||
void loadWindow(); // Gets the window.bm file
|
||||
void drawUniv(); // Draw the background, add the sprites, determine draw order, draw the sprites
|
||||
void copyToScreen(); // If draw is 0, just check input, otherwise also copy the screen buffer to the scummvm surface and update screen
|
||||
void mungeBM(); // Put together final bitmap?
|
||||
void blit(); // Will probably want this to be it's own function
|
||||
void blit40(); // Uses macro blit 40 times
|
||||
void sBlit();
|
||||
void scroll();
|
||||
void makeMyCNM(); // ?
|
||||
void drawBGRND(); // Draw floor parts of leftmask rightmask and maskers
|
||||
void addRows(); // Add rows to drawitem array
|
||||
void addSprite(uint16 vpX, uint16 vpY, SpriteName s, int img, uint16 x, uint16 y, uint16 p);
|
||||
void addSprites(); // Add all active sprites that are in the viewport, into a list that will be sorted by priority
|
||||
void sortDrawItems(); // Sort said items
|
||||
void drawItems(); // Draw the items over the background
|
||||
void drawIcon(int img);
|
||||
void setPen(uint16 penX, uint16 penY); // Sets the 'pen' x and y positions, including making y negative if above a certain point
|
||||
void center();
|
||||
void carriageReturn();
|
||||
|
||||
// Music
|
||||
void toggleSound(); // Actually pauses the sound, doesn't just turn it off/mute
|
||||
void fixPause();
|
||||
Song getPlaying();
|
||||
void playMazeSong();
|
||||
void playCombatSong();
|
||||
void playTextSong();
|
||||
void doGroan();
|
||||
void stopMusic();
|
||||
void musicPause(int sID);
|
||||
void musicUnPause(int sID);
|
||||
void loadSingles(Common::String songName); // Loads and then parse the maze song
|
||||
void standardBeep();
|
||||
|
||||
// Palette
|
||||
void loadPalette(); // Get the static palette data from the disk
|
||||
void setColors(uint16 pal[]); // Applies the current palette to the ScummVM surface palette
|
||||
void fixColors(); // Determine whether the screen should be dim or normal
|
||||
void useNormal();
|
||||
void useDim();
|
||||
void useBlack();
|
||||
void useWhite();
|
||||
void pump(); // Alternates between white and black with delays in between (flashes screen)
|
||||
void fadePal(uint16 pal[], int count, uint16 target[]); // Fades the palette except the frame
|
||||
void fade(uint16 pal[], int dir, int delay); // Calls fadePal() by a given delay each iteration
|
||||
void fadeOut(int j); // Calls Fade with a delay of j jiffies and direction 1
|
||||
void fadeIn(int j); // || and direction 0
|
||||
void normalFadeOut();
|
||||
void slowFadeOut();
|
||||
void normalFadeIn();
|
||||
|
||||
// Assets
|
||||
Common::SeekableReadStream *loadIFF(Common::String fileName); // Loads a file and uncompresses if it is compressed
|
||||
void initStoryStatic(); // Sets up all of the global static story elements
|
||||
int loadUniv(char mazeNum); // Unpacks the .CNM and .UNV files into all the CNM stuff, returns the total length of everything
|
||||
void loadMazeGraphics(int m); // Creates a universe with a maze
|
||||
void makeBlisters(int povX, int povY); // Turns the unmodified CNM/CBM/LCNM etc into the modified ones to actually be used for drawing the game
|
||||
void loadFont(); // Gets the font.spr file, and centers the sprite
|
||||
void clearSprites(); // Clears all sprites before drawing the current frame
|
||||
void loadSprites(); // Loads all the sprite files and centers their sprites (in spritelist, but called from kernel)
|
||||
|
||||
// Input
|
||||
void userIO(); // Get input
|
||||
void pollKeys(); // Buffer input
|
||||
void noNetwork(); // Setup input mirrors
|
||||
void waitKey(); // Waits until a key is pressed (until getInput() returns true)
|
||||
void waitClick(); // Waits until one of the two buttons is pressed
|
||||
void blit8(); // This is actually just input, but it is called blit because it does a 'paddle blit' 8 times
|
||||
|
||||
// These will replace the myriad of hardware input handling from the source
|
||||
bool getInput(); // True if there was input, false if not
|
||||
void addKeyBuffer();
|
||||
void clearKeyBuff();
|
||||
|
||||
|
||||
/*
|
||||
* [DrawChr.cpp] Functions from DrawChr.cpp
|
||||
*/
|
||||
|
||||
// Main
|
||||
int mungeCBM(uint16 num2Chrs);
|
||||
void storeAddr(uint16 *drawType, uint16 chr2, uint16 drawIndex);
|
||||
void mungeSolid(int oldChr, uint16 &drawIndex);
|
||||
void mungeLRHC(int oldChr, uint16 &drawIndex);
|
||||
void mungeLLHC(int oldChr, uint16 &drawIndex);
|
||||
void mungeULHC(int oldChr, uint16 &drawIndex);
|
||||
void mungeURHC(int oldChr, uint16 &drawIndex);
|
||||
void drawSolid(int chr, int x, int y);
|
||||
void drawULHC(int chr, int x, int y);
|
||||
void drawURHC(int chr, int x, int y);
|
||||
void drawLLHC(int chr, int x, int y);
|
||||
void drawLRHC(int chr, int x, int y);
|
||||
|
||||
|
||||
/*
|
||||
* [Logic.cpp] Functions from Logic.GS
|
||||
*/
|
||||
|
||||
// Debug
|
||||
void doSingleStep(); // Let the user advance the engine one frame at a time
|
||||
|
||||
// Main
|
||||
void trapKeys(); // Poorly named, this checks if the player wants to restart/pause music/use debug step
|
||||
int keyOrButton(); // Returns value based on whether it was a keyboard key or a button press
|
||||
void logicInit();
|
||||
void logic(); // Keeps time, handles win and lose conditions, then general logic
|
||||
void restartLogic(); // This is the actual logic init
|
||||
int logicFreeze(); // Overcomplicated way to check if game over or level over
|
||||
void updateHitGauge();
|
||||
void drawGauge(int h);
|
||||
void makeCertificate();
|
||||
void calcCheckSum(int l, uint8 checksum[]); // Checksum is one word, but the source called it CheckSum
|
||||
bool getCertificate();
|
||||
void printCertificate();
|
||||
|
||||
// Misc
|
||||
bool printAnd(Str s);
|
||||
bool fromOldGame();
|
||||
void setGameFlags(uint16 f);
|
||||
uint16 getGameFlags();
|
||||
void setSavedKing();
|
||||
bool isSavedKing();
|
||||
void setSavedAna();
|
||||
bool isSavedAna();
|
||||
int getLevel(); // Literally just return _level...
|
||||
void gameOverDisplay();
|
||||
void gameOver();
|
||||
void levelOver();
|
||||
|
||||
|
||||
/*
|
||||
* [Misc.cpp] Functions from Misc
|
||||
*/
|
||||
|
||||
// Misc
|
||||
void miscInit();
|
||||
void setRandomSeed();
|
||||
void getRandom();
|
||||
|
||||
// Input related
|
||||
bool buttonPressed();
|
||||
bool firePressed();
|
||||
|
||||
// Text printing
|
||||
void myFadeOut();
|
||||
void myFadeIn();
|
||||
bool textPrint(Str s, int n);
|
||||
bool textBeginning(Str s, int n);
|
||||
bool textSub(Str s, FadeType f, int n);
|
||||
void textEnd(Str s, int n);
|
||||
void textMiddle(Str s, int n);
|
||||
|
||||
void textCR();
|
||||
void textPageBreak(Common::String s, int &index);
|
||||
void textAutoPageBreak();
|
||||
void textDoSpace(Common::String s, int index);
|
||||
void textBounceDelay();
|
||||
|
||||
bool yesNo();
|
||||
void noOn();
|
||||
void yesOn();
|
||||
|
||||
void myDelay(int j);
|
||||
|
||||
|
||||
/*
|
||||
* [Level.cpp] Functions from level.GS
|
||||
* < All functions implemented (in some capacity)! >
|
||||
*/
|
||||
// Init
|
||||
void levelInitAtStartOfGameOnly();
|
||||
void levelInit();
|
||||
//void levelGetCount <-- lda count
|
||||
|
||||
// Main
|
||||
void levelStory(int l);
|
||||
void levelLoadFile(int l);
|
||||
void levelNew(int l);
|
||||
void levelDrawAll();
|
||||
void levelShowRoom(int r, int bX, int bY);
|
||||
bool levelIsShowRoom(int r);
|
||||
bool levelIsLoaded(int l);
|
||||
void univAtNew(int l);
|
||||
//void getLastType <-- lda lastType
|
||||
//void setLastType <-- sta lastType
|
||||
//void getShowRoom <-- lda currentRoom
|
||||
|
||||
/*
|
||||
* [Cycle.cpp] Functions from Cyc
|
||||
*/
|
||||
|
||||
// Misc
|
||||
void cycleFreeAll(); // Delete all cycles
|
||||
|
||||
|
||||
/*
|
||||
* [Story.cpp] Functions related to Story.GS
|
||||
*/
|
||||
|
||||
// Init
|
||||
void initStoryDynamic();
|
||||
|
||||
/*
|
||||
* [Sprites.cpp] Functions from Sprites.GS and spriteList.GS
|
||||
*/
|
||||
|
||||
// Init
|
||||
void initDataSprite(Common::SeekableReadStream *f, DataSprite *d, int index, uint16 cenX, uint16 cenY); // Initializes the data sprite
|
||||
|
||||
// Main
|
||||
void superSprite(DataSprite *dSprite, uint16 x, uint16 y, int img, uint16 bmw, byte *dst, uint16 superTop, uint16 superBottom);
|
||||
bool clipSprite(uint16 &height, uint16 &pointIndex, uint16 &skipY, DataSprite *dSprite, uint16 &pointX, uint16 &pointY, int img, uint16 bmw, uint16 superTop, uint16 superBottom);
|
||||
void spriteAligned(DataSprite *dSprite, Image &img, uint16 &skipY, uint16 &pointIndex, uint16 &height, uint16 bmw, byte *dst);
|
||||
|
||||
/*
|
||||
* [Compression.cpp] Functions from Compression.GS
|
||||
*/
|
||||
|
||||
// Main routines
|
||||
Common::SeekableReadStream *unCompress(Common::File *source, int lSource);
|
||||
|
||||
// Subroutines called by unCompress
|
||||
void setUpDictionary(uint16 *pCodes, uint16 *pTk, uint16 &findEmpty);
|
||||
int inputCode(uint16 &outCode, int &lSource, Common::File *source, uint16 &evenOdd);
|
||||
int member(uint16 &codeW, uint16 &k, uint16 *pCodes, uint16 *pTk, uint16 &findEmpty, uint16 &index);
|
||||
|
||||
/*
|
||||
* [door.cpp] Functions from Door.GS
|
||||
*/
|
||||
|
||||
void roomTransfer(int r, int x, int y); // Transfers the player from the current room to a new room at x,y
|
||||
void doorOpenSecret();
|
||||
void doorCloseSecret();
|
||||
//void doorToNextLevel();
|
||||
void doorInit();
|
||||
void doorClrLock();
|
||||
void doorNew(SDoor door);
|
||||
void doorDrawAll();
|
||||
void doorOnDoorMat();
|
||||
//void doorEnter(); // <-- this is actually a method of Player Monster, should probably move it there later
|
||||
int findDoorTop(int x, int y);
|
||||
int findDoor(int x, int y);
|
||||
bool doLockStuff(int d, MonsterID m, int top);
|
||||
bool inDoorTop(int x, int y, MonsterID m);
|
||||
bool inDoor(int x, int y, MonsterID m);
|
||||
int doorDoStep(MonsterID m, int d, int index);
|
||||
int doorSetOn(int d);
|
||||
int doorComeOut(MonsterID m);
|
||||
void doorSetLadders(MonsterID m);
|
||||
|
||||
|
||||
/*
|
||||
* [Music.cpp] Functions from music.GS and sound.GS
|
||||
*/
|
||||
|
||||
// Misc
|
||||
|
||||
|
||||
/*
|
||||
* --- ScummVM general engine Functions ---
|
||||
*
|
||||
*/
|
||||
|
||||
Common::ErrorCode initDisks(); // Opens and parses IMMORTAL.dsk and IMMORTAL_GFX.dsk
|
||||
uint32 getFeatures() const; // Returns the game description flags
|
||||
Common::String getGameId() const; // Returns the game Id
|
||||
|
||||
/* Gets a random number
|
||||
*/
|
||||
uint32 getRandomNumber(uint maxNum) {
|
||||
return _randomSource.getRandomNumber(maxNum);
|
||||
}
|
||||
|
||||
bool hasFeature(EngineFeature f) const override {
|
||||
return
|
||||
(f == kSupportsLoadingDuringRuntime) ||
|
||||
(f == kSupportsSavingDuringRuntime) ||
|
||||
(f == kSupportsReturnToLauncher);
|
||||
};
|
||||
|
||||
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
|
||||
return true;
|
||||
}
|
||||
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Uses a serializer to allow implementing savegame
|
||||
* loading and saving using a single method
|
||||
*/
|
||||
Common::Error syncGame(Common::Serializer &s);
|
||||
|
||||
/* Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) {
|
||||
Common::Serializer s(nullptr, stream);
|
||||
return syncGame(s);
|
||||
}
|
||||
|
||||
Common::Error loadGameStream(Common::SeekableReadStream *stream) {
|
||||
Common::Serializer s(stream, nullptr);
|
||||
return syncGame(s);
|
||||
} */
|
||||
};
|
||||
|
||||
extern ImmortalEngine *g_immortal;
|
||||
#define SHOULD_QUIT ::Immortal::g_immortal->shouldQuit()
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
#endif
|
||||
1112
engines/immortal/kernal.cpp
Normal file
1112
engines/immortal/kernal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
156
engines/immortal/level.cpp
Normal file
156
engines/immortal/level.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
/* 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 "immortal/room.h"
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
void ImmortalEngine::levelInitAtStartOfGameOnly() {
|
||||
initStoryDynamic();
|
||||
_lastLevelLoaded = -1;
|
||||
_lastSongLoaded = -1;
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelInit() {
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelNew(int l) {
|
||||
stopMusic();
|
||||
clearScreen();
|
||||
|
||||
levelStory(l);
|
||||
if (kLevelToMaze[l] != _lastLevelLoaded) {
|
||||
_lastLevelLoaded = kLevelToMaze[l];
|
||||
loadMazeGraphics(l);
|
||||
}
|
||||
|
||||
if (_level != _lastSongLoaded) {
|
||||
//loadSong(l);
|
||||
}
|
||||
|
||||
//startMusic();
|
||||
//monstSetXY -> _rooms[_currentRoom].monsters[kPlayerID].setXY(_initialBX, _initialBY);
|
||||
|
||||
//univSetXY(_initialX << 3, _initialY << 3);
|
||||
|
||||
levelShowRoom(_initialRoom, _initialBX, _initialBY);
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelStory(int l) {
|
||||
levelLoadFile(l);
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelLoadFile(int l) {
|
||||
/* This was originally a large branching tree that checked the identifier of each entry and
|
||||
* Processed them all for the story. Once again, this would have been better as an indexed
|
||||
* JSR instead of a set of comparisons and branches. Regardless, we instead use the information
|
||||
* in the story struct to create the rooms and then populate them.
|
||||
*/
|
||||
|
||||
// Create the rooms and doors, then populate the rooms with their objects and actors
|
||||
|
||||
for (uint d = 0; d < _stories[l]._doors.size(); d++) {
|
||||
doorNew(_stories[l]._doors[d]);
|
||||
}
|
||||
|
||||
for (uint r = 0; r < _stories[l]._rooms.size(); r++) {
|
||||
_rooms[r] = new Room(_stories[l]._rooms[r]._x, _stories[l]._rooms[r]._y, _stories[l]._rooms[r]._flags);
|
||||
|
||||
Common::Array<SFlame> allFlames(_stories[l]._flames[r].size());
|
||||
if (_stories[l]._flames[r].size() > 0) {
|
||||
for (uint f = 0; f < _stories[l]._flames[r].size(); f++) {
|
||||
SFlame sf;
|
||||
sf._p = _stories[l]._flames[r][f]._p;
|
||||
sf._x = _stories[l]._flames[r][f]._x;
|
||||
sf._y = _stories[l]._flames[r][f]._y;
|
||||
allFlames[f] = sf;
|
||||
}
|
||||
}
|
||||
_allFlames[r] = allFlames;
|
||||
|
||||
if (_stories[l]._objects[r].size() > 0) {
|
||||
for (uint o = 0; o < _stories[l]._objects[r].size(); o++) {
|
||||
//objNew(_stories[l]._objects[r][o]);
|
||||
}
|
||||
}
|
||||
|
||||
if (_stories[l]._monsters[r].size() > 0) {
|
||||
for (uint m = 0; m < _stories[l]._monsters[r].size(); m++) {
|
||||
//monstNew(_stories[l]._monsters[r][m]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the _initial variables for the engine scope
|
||||
univAtNew(l);
|
||||
}
|
||||
|
||||
void ImmortalEngine::univAtNew(int l) {
|
||||
_initialRoom = _dRoomNum;
|
||||
_initialX = _stories[l]._initialUnivX;
|
||||
_initialY = _stories[l]._initialUnivY;
|
||||
_initialBX = _stories[l]._playerPointX;
|
||||
_initialBY = _stories[l]._playerPointY;
|
||||
|
||||
//doorToNextLevel(_stories[l]._doorToNextLevel, _initialBX, _initialBY);
|
||||
//doorSetLadders(_stories[l]._doorSetLadders);
|
||||
//roomSetHole(_stories[l]._setHole, _stories[l]._setHoleX, _stories[l]._setHoleY);
|
||||
//monstRepos(kPlayerID);
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelDrawAll() {
|
||||
_count++;
|
||||
//univAutoCenter();
|
||||
clearSprites();
|
||||
_rooms[_currentRoom]->drawContents(_viewPortX, _viewPortY);
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelShowRoom(int r, int bX, int bY) {
|
||||
_currentRoom = r;
|
||||
cycleFreeAll(); // This may not be needed, or it may need to be changed slightly
|
||||
_rooms[_currentRoom]->flameSetRoom(_allFlames[r]);
|
||||
//univSetRoom(r, bX, bY);
|
||||
//fset, spark, bullet, and door get set to the current room
|
||||
//roomGetCell(r, bX, bY);
|
||||
//x, y <- roomGetXY(r, bX, bY);
|
||||
//x += bX;
|
||||
//y += bY;
|
||||
//x <<= 1;
|
||||
//blister();
|
||||
}
|
||||
|
||||
bool ImmortalEngine::levelIsLoaded(int l) {
|
||||
if (l == _storyLevel) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImmortalEngine::levelIsShowRoom(int r) {
|
||||
if (r == _currentRoom) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
664
engines/immortal/logic.cpp
Normal file
664
engines/immortal/logic.cpp
Normal file
@@ -0,0 +1,664 @@
|
||||
/* 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 "immortal/room.h"
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
int ImmortalEngine::logicFreeze() {
|
||||
// Very silly way of checking if the level is over and/or the game is over
|
||||
int g = _gameOverFlag | _levelOver;
|
||||
return (g ^ 1) >> 1;
|
||||
}
|
||||
|
||||
void ImmortalEngine::logicInit() {
|
||||
_titlesShown = 0;
|
||||
_time = 0;
|
||||
_promoting = 0;
|
||||
_restart = true;
|
||||
levelInitAtStartOfGameOnly();
|
||||
_lastCertLen = 0;
|
||||
}
|
||||
|
||||
void ImmortalEngine::restartLogic() {
|
||||
_singleStep = false;
|
||||
_levelOver = false;
|
||||
_gameFlags = kSavedNone;
|
||||
|
||||
// Here's where the majority of the game actually gets initialized
|
||||
miscInit();
|
||||
cycleFreeAll();
|
||||
levelInit();
|
||||
//roomInit(); <-- will be run in constructor of room
|
||||
//monstInit(); <-- room.initMonsters() -|
|
||||
//objectInit(); <-- room.initObjects() |
|
||||
//doorInit(); <-- room.initDoors() |- probably all get run from room constructor
|
||||
//sparkInit(); <-- room.initSparks() |
|
||||
//bulletInit(); <-- room.initProjectiles() -|
|
||||
//objectInit(); <-- again? Odd...
|
||||
//genericSpriteInit(); <-- room.initGenSprites()
|
||||
|
||||
if (fromOldGame() == false) {
|
||||
_level = 0;
|
||||
levelNew(_level);
|
||||
}
|
||||
|
||||
_rooms[_currentRoom]->flameInit();
|
||||
|
||||
if (_level != 7) {
|
||||
_themePaused = true; // and #-1-2 = set both flags for themePaused
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::logic() {
|
||||
trapKeys(); // First thing in any gameloop is to check if we should restart/toggle sound
|
||||
_time += 1;
|
||||
|
||||
/* This is actually the main game state loop. I think the best way to translate it
|
||||
* is as a do-while loop. As in, check if the gamestate says we need to restart,
|
||||
* and if so, restart the logic and check again
|
||||
* Personally, I think this should have been a jump table for the different
|
||||
* game state routines, indexed by a single game state variable.
|
||||
* Ie. LDX _gameState : JMP (gameStates),X
|
||||
* Much cleaner I think. Regardless, this will probably be a switch statement eventually.
|
||||
*/
|
||||
do {
|
||||
|
||||
if (_restart == true) {
|
||||
restartLogic();
|
||||
_restart = false;
|
||||
}
|
||||
|
||||
// This is the original logic, but I think it makes more sense if this were an else if statement
|
||||
if (_gameOverFlag == true) {
|
||||
gameOver();
|
||||
_gameOverFlag = false;
|
||||
_restart = true;
|
||||
|
||||
} else if (_levelOver == true) {
|
||||
_themePaused = true;
|
||||
_levelOver = false;
|
||||
|
||||
if (_level == (_maxLevels - 1)) {
|
||||
textPrint(kStrYouWin, 0);
|
||||
|
||||
} else {
|
||||
makeCertificate();
|
||||
printCertificate();
|
||||
_promoting = 1;
|
||||
}
|
||||
_restart = true;
|
||||
|
||||
} else {
|
||||
|
||||
// Here's where the gameplay sequence actually happens!
|
||||
doSingleStep(); // Debug step function
|
||||
//monstRunAll();
|
||||
//objectRunAll();
|
||||
//doInfiniteHallways();
|
||||
levelDrawAll();
|
||||
updateHitGauge();
|
||||
|
||||
_dim = false;
|
||||
if ((_level == 0) && (/*_currentLevel.getShowRoom()*/0 == 0) && (_rooms[_currentRoom]->roomLighted() == false) && (/*getNumBullets()*/ 0 == 0)) {
|
||||
_dim = true;
|
||||
}
|
||||
|
||||
if (_level == 7) {
|
||||
doGroan();
|
||||
}
|
||||
|
||||
if (/*monstIsCombat(kPlayerID)*/false == true) {
|
||||
if (getPlaying() != kSongCombat) {
|
||||
playCombatSong();
|
||||
}
|
||||
} else {
|
||||
if (getPlaying() != kSongMaze) {
|
||||
playMazeSong();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while (_restart == true);
|
||||
}
|
||||
|
||||
void ImmortalEngine::trapKeys() {
|
||||
/* Weird name for a normal routine. It simply checks for the
|
||||
* restart key (or button on the nes), or the sound toggle,
|
||||
* (if debug mode is active it also checks for the
|
||||
* _singleStep key), and then performs a high level action
|
||||
* (on the NES it only checks restart, and it opens a dialog to do it)
|
||||
*/
|
||||
getInput();
|
||||
switch (_pressedAction) {
|
||||
case kActionDBGStep:
|
||||
_singleStep = true;
|
||||
break;
|
||||
case kActionRestart:
|
||||
gameOver();
|
||||
break;
|
||||
case kActionSound:
|
||||
toggleSound();
|
||||
// fall through
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int ImmortalEngine::keyOrButton() {
|
||||
// Returns a key if a key was pressed, or 13 if a button was pressed
|
||||
|
||||
int button = 0;
|
||||
while (button == 0) {
|
||||
getInput();
|
||||
switch (_pressedAction) {
|
||||
case kActionKey:
|
||||
button = _pressedAction;
|
||||
break;
|
||||
case kActionFire:
|
||||
// fall through
|
||||
case kActionButton:
|
||||
button = 13;
|
||||
// fall through
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
void ImmortalEngine::doSingleStep() {
|
||||
/* This is a very cool debug routine. If you press the _singleStep key,
|
||||
* the engine enters this debug mode where it waits for another key press.
|
||||
* If the key is anything other than the _singleStep key, it will advance
|
||||
* the engine one frame (or rather, until we hit this routine again, which
|
||||
* should be one frame). If you hit the _singleStep key, it will exit the mode
|
||||
* and advance normally again.
|
||||
*/
|
||||
if (_singleStep == true) {
|
||||
// If singleStep mode is active, stop the engine until we get input
|
||||
waitKey();
|
||||
// If the input is anything other than DGBStep, advance one frame
|
||||
if (_pressedAction == kActionDBGStep) {
|
||||
// Otherwise, we want to exit the mode
|
||||
_singleStep = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::updateHitGauge() {
|
||||
/* This HUD (essentially) drawing routine is a bit weird because
|
||||
* the game was originally meant to have multiple player characters
|
||||
* in the room at once. So the engine sees the player as a 'monster'
|
||||
* in the same way it sees enemies (and presumably would have seen other players).
|
||||
* As such, this routine asks the room to ask the monster called player,
|
||||
* what their health is. If the game considered the player unique, this would
|
||||
* probably just check a global 'health' variable instead.
|
||||
*/
|
||||
//int hits = _rooms[_currentRoom]._monsters[kPlayerID]._getHits();
|
||||
int hits = 15;
|
||||
if (hits != _lastGauge) {
|
||||
// Update the mirror value if the health has changed since last frame
|
||||
_lastGauge = hits;
|
||||
drawGauge(hits);
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::drawGauge(int h) {
|
||||
/* Draw the health bar:
|
||||
* We have two variables, the current health (number of hits remaining),
|
||||
* and the difference betweeen the current health and max health (16).
|
||||
* We then do some silly branching logic that is functionally the same
|
||||
* as a for loop for the available health, and then another for unavailable health.
|
||||
* But we could also write it much more efficiently like this:
|
||||
* sta tmp : lda #$16 : tay : dey : sub tmp : tax
|
||||
* -
|
||||
* txa : beq +
|
||||
* lda #$1 : dex
|
||||
* +
|
||||
* jsr draw
|
||||
* dey : bpl -
|
||||
* Ie. Loop over the entire bar, and once you run out of one icon to draw, that 0 becomes
|
||||
* the index of the chr for the other icons.
|
||||
*/
|
||||
int r = 16 - h;
|
||||
setPen(kGaugeX, kGaugeY);
|
||||
h--;
|
||||
if (h >= 0) {
|
||||
// This could be written as a regular for loop, but the game thinks of start/stop as different from on
|
||||
printChr(kGaugeStart);
|
||||
h--;
|
||||
for (; h >= 0; h--) {
|
||||
if (h == 0) {
|
||||
// Redundant code is redundant
|
||||
printChr(kGaugeStop);
|
||||
} else {
|
||||
printChr(kGaugeOn);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Oh hey, this one is indeed a normal for loop
|
||||
for (; r >= 0; r--) {
|
||||
printChr(kGaugeOff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ImmortalEngine::printAnd(Str s) {
|
||||
// Only ever used by fromOldGame()
|
||||
// Just prints what it's given and then checks for input
|
||||
textPrint(s, 0);
|
||||
getInput();
|
||||
if (_heldAction != kActionNothing) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImmortalEngine::fromOldGame() {
|
||||
/* This is the basic load game routine (and also title display).
|
||||
* It lets the user enter a password, or start a new game.
|
||||
* Either way it sets up the inventory for the level, and also
|
||||
* various object related things for the specific level.
|
||||
*/
|
||||
if (_titlesShown == 0) {
|
||||
_titlesShown++;
|
||||
_dontResetColors = 1;
|
||||
printAnd(kStrTitle0);
|
||||
printAnd(kStrTitle4);
|
||||
getInput();
|
||||
return false;
|
||||
}
|
||||
|
||||
_dontResetColors = 0;
|
||||
if (_promoting == 1) {
|
||||
_promoting = 0;
|
||||
|
||||
} else {
|
||||
|
||||
do {
|
||||
if (!textPrint(kStrOldGame, 0)) {
|
||||
// They choose not to load an old game
|
||||
return false;
|
||||
}
|
||||
} while (getCertificate() == true);
|
||||
|
||||
if (_lastCertLen == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set game flags
|
||||
_level = _certificate[kCertLevel];
|
||||
|
||||
setGameFlags((_certificate[kCertHiGameFlags] << 4) | _certificate[kCertLoGameFlags]);
|
||||
|
||||
// Create the player
|
||||
|
||||
//uint8 hits = _certificate[kCertHits];
|
||||
//uint8 quick = _certificate[kCertQuickness];
|
||||
//uint8 gold = (_certificate[kCertGoldHi] << 4) | _certificate[kCertGoldLo];
|
||||
// monstMakePlayer(hits, quick, gold); <- will become room.makePlayer();
|
||||
|
||||
// Create the inventory
|
||||
// room.makeObject(3, kObjIsRunning, 0, goldType);
|
||||
|
||||
// Hi bits of inventory
|
||||
int certInv = _certificate[kCertInvHi];
|
||||
|
||||
if ((certInv & 1) != 0) {
|
||||
if (_level < 2) {
|
||||
//room.makeObject(3, 0, 0, waterType);
|
||||
}
|
||||
}
|
||||
|
||||
if ((certInv & 2) != 0) {
|
||||
//room.makeObject(3, 0, kRingFrame, dunRingType);
|
||||
}
|
||||
|
||||
if ((certInv & 4) != 0) {
|
||||
if (_level < 6) {
|
||||
//room.makeObject(3, 0, kSporesFrame, wormFoodType);
|
||||
}
|
||||
}
|
||||
|
||||
if ((certInv & 8) != 0) {
|
||||
//room.makeObject(3, 0, 0 (?), coinType);
|
||||
}
|
||||
|
||||
|
||||
// Low bits of inventory
|
||||
certInv = _certificate[kCertInvLo];
|
||||
|
||||
// This would have been much more clean as a set of tables instead of a long branching tree
|
||||
switch (_certificate[kCertLevel]) {
|
||||
case 1:
|
||||
if ((certInv & 2) != 0) {
|
||||
//room.makeObject(3, 0, kSporesFrame, sporesType);
|
||||
}
|
||||
|
||||
if ((certInv & 4) != 0) {
|
||||
//room.makeObject(3, 0, kSporesFrame, wowCharmType);
|
||||
}
|
||||
|
||||
break;
|
||||
case 4:
|
||||
if ((certInv & 2) != 0) {
|
||||
//room.makeObject(3, kIsInvisible, kSporesFrame, coffeeType);
|
||||
}
|
||||
|
||||
break;
|
||||
case 3:
|
||||
if ((certInv & 1) != 0) {
|
||||
//room.makeObject(3, kIsRunning, kRingFrame, faceRingType);
|
||||
}
|
||||
|
||||
break;
|
||||
case 7:
|
||||
if ((certInv & 1) != 0) {
|
||||
//room.makeObject(6, kUsesFireButton, kSporesFrame, bronzeType);
|
||||
}
|
||||
|
||||
if ((certInv & 2) != 0) {
|
||||
//room.makeObject(3, 0, kSporesFrame, tractorType);
|
||||
}
|
||||
|
||||
if ((certInv & 4) != 0) {
|
||||
//room.makeObject(3, 0, kSporesFrame, antiType);
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
break;
|
||||
}
|
||||
levelNew(_level);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImmortalEngine::makeCertificate() {
|
||||
/* The code for this bit doesn't really make sense,
|
||||
* so I will write it as it is, but I am noting here
|
||||
* that it should be:
|
||||
* jsr monst_getGold : ... sta certificate+certgoldhi
|
||||
* jsr monst_getQuickness : sta certificate+certquickness
|
||||
* instead of getquickness : get gold : sta gold : sta quickness
|
||||
* also no need to ldx 0 since this is player only ram right?
|
||||
*/
|
||||
|
||||
//uint8 q = room._playerQuickness
|
||||
//uint16 g = room._playerGold
|
||||
uint16 g = 0;
|
||||
|
||||
_certificate[kCertGoldLo] = g & 0xf;
|
||||
_certificate[kCertGoldHi] = g >> 4;
|
||||
_certificate[kCertQuickness] = g >> 4; // Should actually be = q, but this is what the game does
|
||||
|
||||
_certificate[kCertHits] = 0; //room._playerHits
|
||||
_certificate[kCertLoGameFlags] = getGameFlags() & 0xf;
|
||||
_certificate[kCertLoGameFlags] = getGameFlags() >> 4;
|
||||
|
||||
_certificate[kCertLevel] = _level + 1;
|
||||
_certificate[kCertInvLo] = 0;
|
||||
_certificate[kCertInvHi] = 0;
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(waterType)*/) {
|
||||
_certificate[kCertInvHi] |= 1;
|
||||
}
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(dunRingType)*/) {
|
||||
_certificate[kCertInvHi] |= 2;
|
||||
}
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(wormFoodType)*/) {
|
||||
_certificate[kCertInvHi] |= 4;
|
||||
}
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(coinType)*/) {
|
||||
_certificate[kCertInvHi] |= 8;
|
||||
}
|
||||
|
||||
// The lo byte of the inventory is used for items that only exist on a specific level, and are removed after
|
||||
switch (_certificate[kCertLevel]) {
|
||||
case 1:
|
||||
if (true/*room.monster[kPlayerID].hasObject(sporesType)*/) {
|
||||
_certificate[kCertInvLo] |= 2;
|
||||
}
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(wowCharmType)*/) {
|
||||
_certificate[kCertInvLo] |= 4;
|
||||
}
|
||||
// fall through
|
||||
case 3:
|
||||
if (true/*room.monster[kPlayerID].hasObject(faceRingType)*/) {
|
||||
_certificate[kCertInvLo] |= 1;
|
||||
}
|
||||
// fall through
|
||||
case 4:
|
||||
if (true/*room.monster[kPlayerID].hasObject(coffeeType)*/) {
|
||||
_certificate[kCertInvLo] |= 2;
|
||||
}
|
||||
// fall through
|
||||
case 7:
|
||||
if (true/*room.monster[kPlayerID].hasObject(bronzeType)*/) {
|
||||
_certificate[kCertInvLo] |= 1;
|
||||
}
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(tractorType)*/) {
|
||||
_certificate[kCertInvLo] |= 2;
|
||||
}
|
||||
|
||||
if (true/*room.monster[kPlayerID].hasObject(antiType)*/) {
|
||||
_certificate[kCertInvLo] |= 4;
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
_lastCertLen = 13;
|
||||
uint8 checksum[4];
|
||||
calcCheckSum(_lastCertLen, checksum);
|
||||
_certificate[0] = checksum[0];
|
||||
_certificate[1] = checksum[1];
|
||||
_certificate[2] = checksum[2];
|
||||
_certificate[3] = checksum[3];
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::calcCheckSum(int l, uint8 checksum[]) {
|
||||
checksum[0] = 4;
|
||||
checksum[1] = 0xa5;
|
||||
|
||||
/* The game logic seems to allow a len of 4 (cmp 4 : bcc),
|
||||
* but the checksum iny before it checks if the sizes are the same,
|
||||
* so shouldn't a cert of len 4 cause it to loop 0xfffc times?
|
||||
*/
|
||||
for (int i = 4; i <= l; i++) {
|
||||
checksum[0] = (_certificate[i] + checksum[0]) ^ checksum[1];
|
||||
checksum[1] = (_certificate[i] << 1) + checksum[1];
|
||||
}
|
||||
|
||||
checksum[3] = checksum[1] >> 4;
|
||||
checksum[2] = checksum[1] & 0xf;
|
||||
checksum[1] = checksum[0] >> 4;
|
||||
checksum[0] = checksum[0] & 0xf;
|
||||
}
|
||||
|
||||
bool ImmortalEngine::getCertificate() {
|
||||
textPrint(kStrCert, 0);
|
||||
int certLen = 0;
|
||||
bool entered = false;
|
||||
int k = 0;
|
||||
|
||||
// My goodness the logic for this is a mess.
|
||||
while (entered == false) {
|
||||
k = keyOrButton();
|
||||
if (k == 13) {
|
||||
entered = true;
|
||||
|
||||
} else if (k == 0x7f) {
|
||||
// The input was a backspace
|
||||
if (certLen != 0) {
|
||||
certLen--; // Length is one smaller now
|
||||
backspace(); // move the drawing position back and reprint the '-' char
|
||||
backspace();
|
||||
printChr('-');
|
||||
}
|
||||
|
||||
} else {
|
||||
// The input was a key
|
||||
if (certLen != kMaxCertificate) {
|
||||
if ((k >= 'a') && (k < '{')) {
|
||||
k -= 0x20;
|
||||
}
|
||||
|
||||
if (k >= '0') {
|
||||
if (k < ('9' + 1)) {
|
||||
k -= '0';
|
||||
}
|
||||
|
||||
else {
|
||||
if (k < 'A') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (k < ('F' + 1)) {
|
||||
k -= ('A' - 10);
|
||||
}
|
||||
}
|
||||
|
||||
int certK = k;
|
||||
if ((k < ('Z' + 1)) && (k >= 'A')) {
|
||||
k += ('a' - 'A');
|
||||
}
|
||||
backspace();
|
||||
printChr(k);
|
||||
printChr('-');
|
||||
_certificate[certLen] = certK;
|
||||
certLen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input of certificate is finished
|
||||
if (certLen == 0) {
|
||||
certLen = _lastCertLen;
|
||||
}
|
||||
if (certLen != 0) {
|
||||
if (certLen < 4) {
|
||||
textPrint(kStrBadCertificate, 0);
|
||||
return false;
|
||||
}
|
||||
uint8 checksum[4];
|
||||
calcCheckSum(certLen, checksum);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (checksum[i] != _certificate[i]) {
|
||||
textPrint(kStrBadCertificate, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cert is good
|
||||
_lastCertLen = certLen;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImmortalEngine::printCertificate() {
|
||||
/* In contrast to the other certificate routines,
|
||||
* this one is nice and simple. You could also
|
||||
* just add the appropriate offset for the letters,
|
||||
* but grabbing it from a table is faster and doesn't
|
||||
* use a lot of space (especially if it's used anywhere else)
|
||||
*/
|
||||
char toHex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
textBeginning(kStrCert, 0);
|
||||
for (int i = 0; i < _lastCertLen; i++) {
|
||||
printChr(toHex[_certificate[i]]);
|
||||
}
|
||||
textEnd(kStrCert2, 0);
|
||||
}
|
||||
|
||||
bool ImmortalEngine::isSavedKing() {
|
||||
if ((_gameFlags & kSavedKing) == kSavedKing) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ImmortalEngine::isSavedAna() {
|
||||
if ((_gameFlags & kSavedAna) == kSavedAna) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* These functions don't really need to be functions
|
||||
*/
|
||||
|
||||
void ImmortalEngine::setGameFlags(uint16 f) {
|
||||
_gameFlags = f;
|
||||
}
|
||||
|
||||
uint16 ImmortalEngine::getGameFlags() {
|
||||
return _gameFlags;
|
||||
}
|
||||
|
||||
int ImmortalEngine::getLevel() {
|
||||
return _level;
|
||||
}
|
||||
|
||||
void ImmortalEngine::gameOverDisplay() {
|
||||
_themePaused = true;
|
||||
textPrint(kStrGameOver, 0);
|
||||
}
|
||||
|
||||
void ImmortalEngine::gameOver() {
|
||||
_gameOverFlag = 1;
|
||||
}
|
||||
|
||||
void ImmortalEngine::levelOver() {
|
||||
_levelOver = 1;
|
||||
}
|
||||
|
||||
void ImmortalEngine::setSavedKing() {
|
||||
_gameFlags |= kSavedKing;
|
||||
}
|
||||
|
||||
void ImmortalEngine::setSavedAna() {
|
||||
_gameFlags |= kSavedAna;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Not relevant yet (music)
|
||||
*/
|
||||
|
||||
void ImmortalEngine::doGroan() {
|
||||
//getRandom();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Immortal
|
||||
49
engines/immortal/metaengine.cpp
Normal file
49
engines/immortal/metaengine.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 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 "immortal/metaengine.h"
|
||||
#include "immortal/detection.h"
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
const char *ImmortalMetaEngine::getName() const {
|
||||
return "immortal";
|
||||
}
|
||||
|
||||
Common::Error ImmortalMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
|
||||
*engine = new Immortal::ImmortalEngine(syst, desc);
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
bool ImmortalMetaEngine::hasFeature(MetaEngineFeature f) const {
|
||||
return (f == kSavesUseExtendedFormat) ||
|
||||
(f == kSimpleSavesNames) ||
|
||||
(f == kSupportsListSaves) ||
|
||||
(f == kSupportsDeleteSave) ||
|
||||
(f == kSavesSupportMetaInfo) ||
|
||||
(f == kSavesSupportThumbnail) ||
|
||||
(f == kSupportsLoadingDuringStartup);
|
||||
}
|
||||
|
||||
#if PLUGIN_ENABLED_DYNAMIC(IMMORTAL)
|
||||
REGISTER_PLUGIN_DYNAMIC(IMMORTAL, PLUGIN_TYPE_ENGINE, ImmortalMetaEngine);
|
||||
#else
|
||||
REGISTER_PLUGIN_STATIC(IMMORTAL, PLUGIN_TYPE_ENGINE, ImmortalMetaEngine);
|
||||
#endif
|
||||
43
engines/immortal/metaengine.h
Normal file
43
engines/immortal/metaengine.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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 IMMORTAL_METAENGINE_H
|
||||
#define IMMORTAL_METAENGINE_H
|
||||
|
||||
#include "base/plugins.h"
|
||||
#include "engines/achievements.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
class ImmortalMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
|
||||
public:
|
||||
const char *getName() const override;
|
||||
|
||||
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
|
||||
|
||||
/**
|
||||
* Determine whether the engine supports the specified MetaEngine feature.
|
||||
*
|
||||
* Used by e.g. the launcher to determine whether to enable the Load button.
|
||||
*/
|
||||
bool hasFeature(MetaEngineFeature f) const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
472
engines/immortal/misc.cpp
Normal file
472
engines/immortal/misc.cpp
Normal file
@@ -0,0 +1,472 @@
|
||||
/* 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 "immortal/immortal.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- Main Functions -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
void ImmortalEngine::miscInit() {
|
||||
// In the source, this is where the seed for the rng is set, but we don't need to do that as we used _randomSource
|
||||
_lastGauge = 0;
|
||||
}
|
||||
|
||||
void ImmortalEngine::setRandomSeed() {}
|
||||
void ImmortalEngine::getRandom() {}
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- Text Printing -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
// myFadeOut and myFadeIn are just RTS in the source, but they are called quite a lot
|
||||
void ImmortalEngine::myFadeOut() {
|
||||
return;
|
||||
}
|
||||
|
||||
void ImmortalEngine::myFadeIn() {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ImmortalEngine::textPrint(Str s, int n) {
|
||||
_slowText = 0;
|
||||
_formatted = 0;
|
||||
_collumn = 0;
|
||||
_row = 0;
|
||||
playTextSong();
|
||||
clearScreen();
|
||||
return textSub(s, kTextFadeIn, n);
|
||||
}
|
||||
|
||||
bool ImmortalEngine::textBeginning(Str s, int n) {
|
||||
_slowText = 0;
|
||||
_formatted = 0;
|
||||
_collumn = 0;
|
||||
_row = 0;
|
||||
playTextSong();
|
||||
clearScreen();
|
||||
return textSub(s, kTextDontFadeIn, n);
|
||||
}
|
||||
|
||||
void ImmortalEngine::textEnd(Str s, int n) {
|
||||
textSub(s, kTextFadeIn, n);
|
||||
}
|
||||
|
||||
void ImmortalEngine::textMiddle(Str s, int n) {
|
||||
textSub(s, kTextDontFadeIn, n);
|
||||
}
|
||||
|
||||
bool ImmortalEngine::textSub(Str s, FadeType f, int n) {
|
||||
bool done = false;
|
||||
|
||||
char chr = 0;
|
||||
int index = 0;
|
||||
Common::String text = _strPtrs[s];
|
||||
|
||||
while (done == false) {
|
||||
switch (text[index]) {
|
||||
case '@':
|
||||
case '=':
|
||||
case char(0):
|
||||
done = true;
|
||||
// This is so the while loop can be a little cleaner
|
||||
index--;
|
||||
break;
|
||||
case '&':
|
||||
textCR();
|
||||
break;
|
||||
case '$':
|
||||
printByte(n);
|
||||
copyToScreen();
|
||||
break;
|
||||
case '_':
|
||||
myFadeIn();
|
||||
_slowText = 1;
|
||||
break;
|
||||
case '<':
|
||||
_slowText = 0;
|
||||
break;
|
||||
case '>':
|
||||
_formatted = 0;
|
||||
break;
|
||||
case '\\':
|
||||
normalFadeOut();
|
||||
break;
|
||||
case '/':
|
||||
slowFadeOut();
|
||||
break;
|
||||
case '|':
|
||||
normalFadeIn();
|
||||
break;
|
||||
case '}':
|
||||
_formatted = 1;
|
||||
break;
|
||||
case ']':
|
||||
myDelay(40);
|
||||
break;
|
||||
case '{':
|
||||
index++;
|
||||
myDelay(text[index]);
|
||||
break;
|
||||
case '*':
|
||||
textPageBreak(text, index);
|
||||
break;
|
||||
case '[':
|
||||
textAutoPageBreak();
|
||||
break;
|
||||
case '#':
|
||||
index++;
|
||||
drawIcon(text[index]);
|
||||
break;
|
||||
case '~':
|
||||
text = _strPtrs[(int)text[index + 1]];
|
||||
index = -1;
|
||||
break;
|
||||
case '^':
|
||||
center();
|
||||
break;
|
||||
case '%':
|
||||
return yesNo();
|
||||
case '+':
|
||||
chr = 0x27;
|
||||
break;
|
||||
case '(':
|
||||
chr = 0x60;
|
||||
break;
|
||||
default:
|
||||
chr = text[index];
|
||||
_collumn++;
|
||||
if (chr == ' ') {
|
||||
if (text[index + 1] == '~') {
|
||||
text = _strPtrs[(int)text[index + 2]];
|
||||
index = -1;
|
||||
}
|
||||
textDoSpace(text, index);
|
||||
|
||||
} else {
|
||||
printChr(chr);
|
||||
// We need this to show up now, not when the frame ends, so we have to update the screen here
|
||||
copyToScreen();
|
||||
if (_slowText != 0) {
|
||||
myDelay(5);
|
||||
switch (chr) {
|
||||
case '?':
|
||||
// fall through
|
||||
case ':':
|
||||
myDelay(13);
|
||||
// fall through
|
||||
case '.':
|
||||
myDelay(13);
|
||||
// fall through
|
||||
case ',':
|
||||
myDelay(13);
|
||||
// fall through
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (index == 0xFF) {
|
||||
debug("String too long!");
|
||||
return false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
chr = text[index];
|
||||
|
||||
if (f != kTextFadeIn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we need to display an 'OK' message
|
||||
if (chr != '=') {
|
||||
setPen(_penX, kYesNoY);
|
||||
center();
|
||||
drawIcon(kOkayFrame);
|
||||
copyToScreen();
|
||||
if (_slowText == 0) {
|
||||
myFadeIn();
|
||||
}
|
||||
waitClick();
|
||||
standardBeep();
|
||||
textBounceDelay();
|
||||
|
||||
} else if (_slowText == 0) {
|
||||
myFadeIn();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImmortalEngine::textCR() {
|
||||
carriageReturn();
|
||||
_row++;
|
||||
_collumn = 0;
|
||||
}
|
||||
|
||||
void ImmortalEngine::textPageBreak(Common::String s, int &index) {
|
||||
_collumn = 0;
|
||||
_row = 0;
|
||||
if (_slowText == 0) {
|
||||
myFadeIn();
|
||||
}
|
||||
|
||||
index++;
|
||||
myDelay((int) s[index]);
|
||||
myFadeOut();
|
||||
clearScreen();
|
||||
|
||||
if (_slowText != 0) {
|
||||
myFadeIn();
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::textAutoPageBreak() {
|
||||
_collumn = 0;
|
||||
_row = 0;
|
||||
if (_slowText == 0) {
|
||||
myFadeIn();
|
||||
}
|
||||
|
||||
myDelay(140);
|
||||
myFadeOut();
|
||||
clearScreen();
|
||||
|
||||
if (_slowText != 0) {
|
||||
myFadeIn();
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::textDoSpace(Common::String s, int index) {
|
||||
// If text is formatted, then check if the space between here and the end of the string will fit, if not, use a newline or pagebreak
|
||||
if (_formatted != 0) {
|
||||
bool foundEnd = false;
|
||||
int start = index;
|
||||
while (foundEnd == false) {
|
||||
index++;
|
||||
switch (s[index]) {
|
||||
case '=':
|
||||
// fall through
|
||||
case '@':
|
||||
// fall through
|
||||
case '%':
|
||||
// fall through
|
||||
case '[':
|
||||
// fall through
|
||||
case ' ':
|
||||
foundEnd = true;
|
||||
// fall through
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (((index - start) + _collumn) >= kMaxCollumns) {
|
||||
if (_row < kMaxRows) {
|
||||
textCR();
|
||||
|
||||
} else {
|
||||
textAutoPageBreak();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
printChr(' ');
|
||||
}
|
||||
|
||||
void ImmortalEngine::textBounceDelay() {
|
||||
Utilities::delay(7);
|
||||
}
|
||||
|
||||
bool ImmortalEngine::yesNo() {
|
||||
uint8 tyes[9] = {0, 1, 1, 1, 0, 0, 0, 0, 0};
|
||||
|
||||
getInput();
|
||||
|
||||
if (tyes[_heldDirection] == 0) {
|
||||
noOn();
|
||||
_lastYes = 0;
|
||||
|
||||
} else {
|
||||
yesOn();
|
||||
_lastYes = 1;
|
||||
}
|
||||
|
||||
while (buttonPressed() || firePressed()) {
|
||||
// If they have not pressed a button yet, we get the input after a delay
|
||||
Utilities::delay(4);
|
||||
getInput();
|
||||
|
||||
// And then if they have changed direction, we play a sound and update the direction and button gfx
|
||||
if (tyes[_heldDirection] != _lastYes) {
|
||||
_lastYes = tyes[_heldDirection];
|
||||
standardBeep();
|
||||
if (_lastYes == 0) {
|
||||
noOn();
|
||||
|
||||
} else {
|
||||
yesOn();
|
||||
}
|
||||
// Since we need this to show up right during the text sequence, we need to update the screen
|
||||
copyToScreen();
|
||||
}
|
||||
}
|
||||
|
||||
standardBeep();
|
||||
textBounceDelay();
|
||||
|
||||
// In source this is done weirdly so that it can use a result in A, except it never uses that result, so it's just weird.
|
||||
return (!(bool) _lastYes);
|
||||
}
|
||||
|
||||
void ImmortalEngine::noOn() {
|
||||
// Draw the No icon as on, and the Yes icon as off
|
||||
setPen(kYesNoX1, kYesNoY);
|
||||
drawIcon(kNoIconOn);
|
||||
setPen(kYesNoX2, kYesNoY);
|
||||
drawIcon(kYesIconOff);
|
||||
}
|
||||
|
||||
void ImmortalEngine::yesOn() {
|
||||
// Draw the No icon as off, and the Yes icon as on
|
||||
setPen(kYesNoX1, kYesNoY);
|
||||
drawIcon(kNoIconOff);
|
||||
setPen(kYesNoX2, kYesNoY);
|
||||
drawIcon(kYesIconOn);
|
||||
}
|
||||
|
||||
void ImmortalEngine::myDelay(int j) {
|
||||
int type = 0;
|
||||
|
||||
// Update input
|
||||
getInput();
|
||||
|
||||
// 0 = neither button held, 1 = one held, 2 = both held
|
||||
if (_heldAction & kActionButton) {
|
||||
type++;
|
||||
}
|
||||
|
||||
if (_heldAction & kActionFire) {
|
||||
type++;
|
||||
}
|
||||
|
||||
do {
|
||||
// If the button was *pressed* and not held, then skip any delay
|
||||
if (!buttonPressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firePressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we delay by different amounts based on what's held down
|
||||
switch (type) {
|
||||
case 1:
|
||||
Utilities::delay4(1);
|
||||
break;
|
||||
case 0:
|
||||
Utilities::delay(1);
|
||||
// fall through
|
||||
case 2:
|
||||
// fall through
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
j--;
|
||||
} while (j != 0);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- Input Related -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
bool ImmortalEngine::buttonPressed() {
|
||||
// Returns false if the button was pressed, but not held or up
|
||||
getInput();
|
||||
|
||||
if (_heldAction == kActionButton) {
|
||||
// Zero just the button0held bit
|
||||
_myButton &= (0xFF - kButton0Held);
|
||||
|
||||
} else if ((_myButton & kButton0Held) == 0) {
|
||||
_myButton |= kButton0Held;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImmortalEngine::firePressed() {
|
||||
// Returns false if the button was pressed, but not held or up
|
||||
getInput();
|
||||
|
||||
if (_heldAction == kActionFire) {
|
||||
_myButton &= (0xFF - kButton1Held);
|
||||
|
||||
} else if ((_myButton & kButton1Held) == 0) {
|
||||
_myButton |= kButton1Held;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- Screen Related -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- Sound Related -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
void ImmortalEngine::standardBeep() {
|
||||
//playNote(4, 5, 0x4C);
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
35
engines/immortal/module.mk
Normal file
35
engines/immortal/module.mk
Normal file
@@ -0,0 +1,35 @@
|
||||
MODULE := engines/immortal
|
||||
|
||||
MODULE_OBJS = \
|
||||
bullet.o \
|
||||
compression.o \
|
||||
cycle.o \
|
||||
door.o \
|
||||
drawChr.o \
|
||||
flameSet.o \
|
||||
immortal.o \
|
||||
kernal.o \
|
||||
level.o \
|
||||
logic.o \
|
||||
metaengine.o \
|
||||
misc.o \
|
||||
room.o \
|
||||
sprites.o \
|
||||
story.o \
|
||||
utilities.o \
|
||||
univ.o
|
||||
|
||||
# object.o \
|
||||
# monster.o \
|
||||
# motives.o
|
||||
|
||||
# This module can be built as a plugin
|
||||
ifeq ($(ENABLE_IMMORTAL), DYNAMIC_PLUGIN)
|
||||
PLUGIN := 1
|
||||
endif
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
||||
|
||||
# Detection objects
|
||||
DETECT_OBJS += $(MODULE)/detection.o
|
||||
88
engines/immortal/room.cpp
Normal file
88
engines/immortal/room.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/* 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 "immortal/room.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
Room::Room(uint8 x, uint8 y, RoomFlag f)
|
||||
: _xPos(x)
|
||||
, _yPos(y)
|
||||
, _flags(f)
|
||||
, _candleTmp(0)
|
||||
, _randomSource("Immortal") {
|
||||
}
|
||||
|
||||
void Room::addMonster() {
|
||||
//_monsters->push_back(new Monster());
|
||||
}
|
||||
|
||||
void Room::removeMonster() {
|
||||
//_monsters->pop_back();
|
||||
}
|
||||
|
||||
void Room::addObject() {
|
||||
//_objects->push_back(new Object());
|
||||
}
|
||||
|
||||
void Room::removeObject() {
|
||||
//_objects->pop_back();
|
||||
}
|
||||
|
||||
Common::Array<Monster> Room::getMonsterList() {
|
||||
return _monsters;
|
||||
}
|
||||
|
||||
Common::Array<Object> Room::getObjectList() {
|
||||
return _objects;
|
||||
}
|
||||
|
||||
void Room::getXY(uint16 &x, uint16 &y) {
|
||||
x <<= 2;
|
||||
y <<= 2;
|
||||
}
|
||||
|
||||
void Room::getCell(uint16 &x, uint16 &y) {
|
||||
x >>= 3;
|
||||
y >>= 3;
|
||||
}
|
||||
|
||||
void Room::setHole() {}
|
||||
|
||||
void Room::drawContents(uint16 vX, uint16 vY) {
|
||||
flameDrawAll(vX, vY);
|
||||
//sparkDrawAll();
|
||||
//bulletDrawAll();
|
||||
//genSpriteDrawAll();
|
||||
//loop over monsters and draw each
|
||||
//loop over objects and draw each
|
||||
//doorDrawAll();
|
||||
}
|
||||
|
||||
bool Room::getWideWallNormal(uint8 x, uint8 y, uint8 xPrev, uint8 yPrev, int id, int spacing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Room::getWallNormal(uint8 x, uint8 y, uint8 xPrev, uint8 yPrev, int id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
216
engines/immortal/room.h
Normal file
216
engines/immortal/room.h
Normal file
@@ -0,0 +1,216 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* --- What is a Room ---
|
||||
*
|
||||
*/
|
||||
|
||||
// Common/system includes basic things like Array
|
||||
#include "common/system.h"
|
||||
|
||||
// Story is needed by both immortal.h and room.h
|
||||
#include "immortal/story.h"
|
||||
|
||||
// Utilities.h contains many things used by all objects, not just immortal
|
||||
#include "immortal/utilities.h"
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
#ifndef IMMORTAL_ROOM_H
|
||||
#define IMMORTAL_ROOM_H
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
enum RoomTile : uint8 {
|
||||
kTileFloor,
|
||||
kTileUpper5,
|
||||
kTileUpper3,
|
||||
kTileCeiling,
|
||||
kTileTop1,
|
||||
kTileTop7,
|
||||
kTileWallFace,
|
||||
kTileTopLower13,
|
||||
kTileTopLower75,
|
||||
kTileLower3,
|
||||
kTileLower5,
|
||||
kTileCeilingTile = 2 // This duplicate is intentional
|
||||
};
|
||||
|
||||
/* Quick note:
|
||||
* This looks entirely redundant and silly, I agree. However
|
||||
* this is because the source does more or less the same thing.
|
||||
* At compile time, it creates and stores in memory what are the
|
||||
* equivalent of structs (or maybe tuples), and then at run time
|
||||
* when creating a room, it makes room specific versions that can
|
||||
* be changed. So essentially it creates two RAM structs and then
|
||||
* treats the first as ROM. As such, that's what I'm doing here.
|
||||
* The 'Story' structs are ROM, the 'Room' structs are RAM. There
|
||||
* are also slight differences, like how the room Flame has a reference
|
||||
* to the Cyc it is using. Although again the Story ones are ram
|
||||
* and could do this too.
|
||||
*/
|
||||
|
||||
// Temp
|
||||
struct Object {
|
||||
};
|
||||
|
||||
// Temp
|
||||
struct Monster {
|
||||
};
|
||||
|
||||
struct Flame {
|
||||
FPattern _p = kFlameOff;
|
||||
uint8 _x = 0;
|
||||
uint8 _y = 0;
|
||||
int _c = 0;
|
||||
};
|
||||
|
||||
struct Chest {
|
||||
};
|
||||
|
||||
struct Bullet {
|
||||
};
|
||||
|
||||
class Room {
|
||||
private:
|
||||
Common::RandomSource _randomSource;
|
||||
|
||||
public:
|
||||
Room(uint8 x, uint8 y, RoomFlag f);
|
||||
~Room() {}
|
||||
|
||||
/*
|
||||
* --- Data ---
|
||||
*
|
||||
*/
|
||||
|
||||
// Constants
|
||||
const uint8 kLightTorchX = 10;
|
||||
const uint8 kMaxFlameCycs = 16;
|
||||
|
||||
Common::Array<SCycle> _cycPtrs;
|
||||
Common::Array<Flame> _fset;
|
||||
Common::Array<Monster> _monsters;
|
||||
Common::Array<Object> _objects;
|
||||
|
||||
RoomFlag _flags;
|
||||
|
||||
uint8 _xPos = 0;
|
||||
uint8 _yPos = 0;
|
||||
uint8 _holeRoom = 0;
|
||||
uint8 _holeCellX = 0;
|
||||
uint8 _holeCellY = 0;
|
||||
uint8 _candleTmp = 0; // Special case for candle in maze 0
|
||||
uint8 _numFlames = 0;
|
||||
uint8 _numInRoom = 0;
|
||||
|
||||
/*
|
||||
* --- Methods ---
|
||||
*
|
||||
*/
|
||||
|
||||
uint32 getRandomNumber(uint maxNum) {
|
||||
return _randomSource.getRandomNumber(maxNum);
|
||||
}
|
||||
|
||||
/*
|
||||
* [room.cpp] Functions from Room.GS
|
||||
*/
|
||||
|
||||
//void init();
|
||||
//void inRoomNew();
|
||||
//void getTilePair(uint8 x, uint8 y); // Modifies a struct of the tile number, aboveTile number, and the cell coordinates of the tile
|
||||
|
||||
void setHole();
|
||||
void drawContents(uint16 vX, uint16 vY);
|
||||
bool getTilePair(uint8 x, uint8 y, int id);
|
||||
bool getWideWallNormal(uint8 x, uint8 y, uint8 xPrev, uint8 yPrev, int id, int spacing);
|
||||
bool getWallNormal(uint8 x, uint8 y, uint8 xPrev, uint8 yPrev, int id);
|
||||
void addMonster();
|
||||
void addObject();
|
||||
void removeObject();
|
||||
void removeMonster();
|
||||
Common::Array<Monster> getMonsterList();
|
||||
Common::Array<Object> getObjectList();
|
||||
void getXY(uint16 &x, uint16 &y);
|
||||
void getCell(uint16 &x, uint16 &y);
|
||||
|
||||
|
||||
/*
|
||||
* [Cycle.cpp] Functions from Cyc
|
||||
*/
|
||||
|
||||
// Init
|
||||
int cycleNew(CycID id); // Adds a cycle to the current list
|
||||
void cycleFree(int c);
|
||||
|
||||
// Getters
|
||||
DataSprite *cycleGetDataSprite(int c); // This takes the place of getFile + getNum
|
||||
int cycleGetIndex(int c);
|
||||
int cycleGetFrame(int c);
|
||||
int cycleGetNumFrames(int c);
|
||||
|
||||
// Setters
|
||||
void cycleSetIndex(int c, int f);
|
||||
|
||||
// Misc
|
||||
bool cycleAdvance(int c);
|
||||
CycID getCycList(int c);
|
||||
|
||||
/* Unnecessary cycle functions
|
||||
void cycleInit();
|
||||
void cycleFree();
|
||||
void cycleGetNumFrames();
|
||||
void cycleGetList();*/
|
||||
|
||||
/*
|
||||
* [flameSet.cpp] Functions from flameSet.GS
|
||||
*/
|
||||
|
||||
//void flameNew() does not need to exist, because we create the duplicate SFlame in Level, and the array in immortal.h is not accessible from here
|
||||
void flameInit();
|
||||
void flameDrawAll(uint16 vX, uint16 vY);
|
||||
bool roomLighted();
|
||||
void lightTorch(uint8 x, uint8 y);
|
||||
void flameFreeAll();
|
||||
void flameSetRoom(Common::Array<SFlame> &allFlames);
|
||||
int flameGetCyc(Flame *f, int first);
|
||||
|
||||
/*
|
||||
* [bullet.cpp] Functions from Bullet.GS
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* [object.cpp] Functions from Object.GS
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* [Univ.cpp] Functions from Univ.GS
|
||||
*/
|
||||
|
||||
void univAddSprite(uint16 vX, uint16 vY, uint16 x, uint16 y, SpriteName s, int img, uint16 p);
|
||||
};
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
#endif
|
||||
299
engines/immortal/sprite_list.h
Normal file
299
engines/immortal/sprite_list.h
Normal file
@@ -0,0 +1,299 @@
|
||||
/* 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 IMMORTAL_SPRITE_LIST_H
|
||||
#define IMMORTAL_SPRITE_LIST_H
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
// We need a few two-dimensional vectors, and writing them out in full each time is tedious
|
||||
template<class T> using CArray2D = Common::Array<Common::Array<T>>;
|
||||
|
||||
struct Image {
|
||||
uint16 _deltaX;
|
||||
uint16 _deltaY;
|
||||
uint16 _rectW;
|
||||
uint16 _rectH;
|
||||
Common::Array<uint16> _scanWidth;
|
||||
Common::Array<uint16> _deltaPos;
|
||||
CArray2D<byte> _bitmap;
|
||||
};
|
||||
|
||||
struct DataSprite {
|
||||
uint16 _cenX; // These are the base center positions
|
||||
uint16 _cenY;
|
||||
uint16 _numImages;
|
||||
Common::Array<Image> _images;
|
||||
};
|
||||
|
||||
struct Sprite {
|
||||
int _image; // Index of _dSprite._images[]
|
||||
uint16 _x;
|
||||
uint16 _y;
|
||||
uint16 _on; // 1 = active
|
||||
uint16 _priority;
|
||||
DataSprite *_dSprite;
|
||||
};
|
||||
|
||||
enum SpriteFrame {
|
||||
// Null
|
||||
kNoFrame,
|
||||
|
||||
// Chest frames
|
||||
kChest0Frame = 0,
|
||||
kOpenChestFrame,
|
||||
kRingFrame,
|
||||
kKnifeFrame,
|
||||
kDeadGoblinFrame,
|
||||
|
||||
// Normal frames
|
||||
kSwordFrame,
|
||||
kKeyFrame,
|
||||
kYesIconOff,
|
||||
kYesIconOn,
|
||||
kNoIconOff,
|
||||
kNoIconOn,
|
||||
kChoiceFrame,
|
||||
kEraseChoseFrame,
|
||||
kSwordBigFrame,
|
||||
kVaseBigFrame,
|
||||
kVaseFrame,
|
||||
kBrokenFrame,
|
||||
kKeyBigFrame,
|
||||
kBagFrame,
|
||||
kBagBigFrame,
|
||||
kBookBigFrame,
|
||||
kBookFrame,
|
||||
kScrollFrame,
|
||||
kScrollBigFrame,
|
||||
kOkayFrame,
|
||||
kAltarFrame,
|
||||
kGoldBigFrame,
|
||||
kMapBigFrame,
|
||||
kSemblanceFrame,
|
||||
kTrapDoorFrame,
|
||||
kBonesFrame,
|
||||
kSackBigFrame,
|
||||
kSporesFrame,
|
||||
kGemGlintFrame,
|
||||
kStoneFrame,
|
||||
kGreenStoneFrame,
|
||||
kGemBigFrame,
|
||||
kStoneBigFrame,
|
||||
kPileFrame,
|
||||
kNoteBigFrame,
|
||||
|
||||
// 45 - 48 are Merchant frames
|
||||
kMerchantFrame,
|
||||
|
||||
// Remaining frames
|
||||
kCoinBigFrame = 49,
|
||||
kPileBigFrame,
|
||||
kKingFrame,
|
||||
kDeadKingFrame,
|
||||
kBombBigFrame,
|
||||
kRingBigFrame,
|
||||
kKnifeBigFrame,
|
||||
kCarpetBigFrame,
|
||||
kAnaInHoleFrame,
|
||||
kAnaNotInHoleFrame,
|
||||
kInvisRingFrame
|
||||
};
|
||||
|
||||
enum SpriteName {
|
||||
// Moresprites 10
|
||||
kCandle,
|
||||
kWow,
|
||||
kAnaVanish,
|
||||
kSink,
|
||||
kTrapdoor,
|
||||
kWizPhant,
|
||||
kVanish,
|
||||
kShadow,
|
||||
kSlime,
|
||||
kSlimeDeath,
|
||||
|
||||
// Norlac 5
|
||||
kBridge,
|
||||
kVortex,
|
||||
kBubble,
|
||||
kNorlac,
|
||||
kNolac2,
|
||||
|
||||
// Powwow 7
|
||||
kPlanners,
|
||||
kUgh,
|
||||
kIsDevoured,
|
||||
kIsBadCrawl,
|
||||
kIsGoodCrawl,
|
||||
kLeg,
|
||||
kIsWebbed,
|
||||
|
||||
// Turrets 10
|
||||
kSleep,
|
||||
kShrink,
|
||||
kLocksmith,
|
||||
kAnaGlimpse,
|
||||
kMadKing,
|
||||
kTorch,
|
||||
kPipe,
|
||||
kProjectile,
|
||||
kKnife,
|
||||
kAnaHug,
|
||||
|
||||
// Worm 4
|
||||
kWorm0,
|
||||
kWorm1,
|
||||
kSpike,
|
||||
kIsSpiked,
|
||||
|
||||
// Iansprites 6
|
||||
kMurder,
|
||||
kWizCrawlUp,
|
||||
kWizLight,
|
||||
kWizBattle,
|
||||
kDown,
|
||||
kNorlacDown,
|
||||
|
||||
// Lastsprites 3
|
||||
kWaterLadder,
|
||||
kPulledDown,
|
||||
kSpill,
|
||||
|
||||
// Doorsprites 10
|
||||
kDoor,
|
||||
kTele,
|
||||
kBomb,
|
||||
kTorched,
|
||||
kLadderTop,
|
||||
kSecret,
|
||||
kLadderBottom,
|
||||
kSlipped,
|
||||
kGoblinSlipped,
|
||||
kFlame,
|
||||
|
||||
// General 5
|
||||
kArrow,
|
||||
kSpark,
|
||||
kObject,
|
||||
kBigBurst,
|
||||
kBeam,
|
||||
|
||||
// Mordamir 3
|
||||
kLight,
|
||||
kMord,
|
||||
kDragMask,
|
||||
|
||||
// Dragon2 2
|
||||
kDFlames,
|
||||
kThroat,
|
||||
|
||||
// Dragon 1
|
||||
kDragon,
|
||||
|
||||
// Rope 3
|
||||
kChop,
|
||||
kHead,
|
||||
kNurse,
|
||||
|
||||
// Rescue 2
|
||||
kRescue1,
|
||||
kRescue2,
|
||||
|
||||
// Troll 9 (8 directions + ?)
|
||||
kTroll0,
|
||||
kTroll1,
|
||||
kTroll2,
|
||||
kTroll3,
|
||||
kTroll4,
|
||||
kTroll5,
|
||||
kTroll6,
|
||||
kTroll7,
|
||||
kTroll8,
|
||||
|
||||
// Goblin 10 (8 directions + ?)
|
||||
kGoblin0,
|
||||
kGoblin1,
|
||||
kGoblin2,
|
||||
kGoblin3,
|
||||
kGoblin4,
|
||||
kGoblin5,
|
||||
kGoblin6,
|
||||
kGoblin7,
|
||||
kGoblin8,
|
||||
kGoblin9,
|
||||
|
||||
// Wizard A 8 (8 directions)
|
||||
kWizard0,
|
||||
kWizard1,
|
||||
kWizard2,
|
||||
kWizard3,
|
||||
kWizard4,
|
||||
kWizard5,
|
||||
kWizard6,
|
||||
kWizard7,
|
||||
|
||||
// Wizard B 3 (3 ?)
|
||||
kWizard8,
|
||||
kWizard9,
|
||||
kWizard10,
|
||||
|
||||
// Ulindor 9 (8 directions + ?)
|
||||
kUlindor0,
|
||||
kUlindor1,
|
||||
kUlindor2,
|
||||
kUlindor3,
|
||||
kUlindor4,
|
||||
kUlindor5,
|
||||
kUlindor6,
|
||||
kUlindor7,
|
||||
kUlindor8,
|
||||
|
||||
// Spider 10 (probably not directions)
|
||||
kSpider0,
|
||||
kSpider1,
|
||||
kSpider2,
|
||||
kSpider3,
|
||||
kSpider4,
|
||||
kSpider5,
|
||||
kSpider6,
|
||||
kSpider7,
|
||||
kSpider8,
|
||||
kSpider9,
|
||||
|
||||
// Drag 9 (probably not directions)
|
||||
kDrag0,
|
||||
kDrag1,
|
||||
kDrag2,
|
||||
kDrag3,
|
||||
kDrag4,
|
||||
kDrag5,
|
||||
kDrag6,
|
||||
kDrag7,
|
||||
kDrag8,
|
||||
|
||||
// Font
|
||||
kFont
|
||||
};
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
#endif
|
||||
247
engines/immortal/sprites.cpp
Normal file
247
engines/immortal/sprites.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
/* 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 "immortal/immortal.h"
|
||||
|
||||
/* -- How does image construction work --
|
||||
* One thing to note about this translation, is that the source
|
||||
* has a lot of address related stuff mixed in to it. This is
|
||||
* because 'Super Sprites' could use a screen buffer and sprite
|
||||
* data from anywhere in memory, including locations that cross
|
||||
* bank boundaries. This means that you don't just have
|
||||
* position -> relative position -> screen, you have
|
||||
* position -> relative position -> relative *address* position -> screen
|
||||
* With that out of the way, here's what a sprite is:
|
||||
* A 'Super Sprite' is several layers of structures combined.
|
||||
* This is both more and less complex in the source. It is structurally
|
||||
* less complicated, only being seen as a sprite + frame, and a cycle.
|
||||
* But in reality that comes with complicated indexing and routines
|
||||
* designed just to get relative indexes that are already in memory.
|
||||
* Instead, I have chosen to clean up the structure a little bit,
|
||||
* which in turns makes it slightly more complicated on a top level.
|
||||
* What we end up with, basically looks like this:
|
||||
* Cycle (ram/rom)
|
||||
* |
|
||||
* Sprite (ram)
|
||||
* |
|
||||
* DataSprite (rom)
|
||||
* |
|
||||
* Frame (rom)
|
||||
* |
|
||||
* Scanline (rom)
|
||||
* |
|
||||
* Bitmap (rom)
|
||||
*/
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- Main Functions -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
// This function is basically setSpriteCenter + getSpriteInfo from the source
|
||||
void ImmortalEngine::initDataSprite(Common::SeekableReadStream *f, DataSprite *d, int index, uint16 cenX, uint16 cenY) {
|
||||
// We set the center X and Y
|
||||
d->_cenX = cenX;
|
||||
d->_cenY = cenY;
|
||||
|
||||
// But now we need to get the rest of the meta data for each frame
|
||||
// index is the index of the sprite within the file (not the same as the sprite name enum)
|
||||
index *= 8;
|
||||
f->seek(index);
|
||||
|
||||
index = f->readUint16LE();
|
||||
uint16 numImages = f->readUint16LE();
|
||||
|
||||
d->_numImages = numImages;
|
||||
|
||||
// Only here for dragon, but just in case, it's a high number so it should catch others
|
||||
if (numImages >= 0x0200) {
|
||||
//debug("** Crazy large value, this isn't a frame number **");
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Array<Image> images;
|
||||
|
||||
for (int i = 0; i < numImages; i++) {
|
||||
Image newImage;
|
||||
f->seek(index + (i * 2));
|
||||
int ptrFrame = f->readUint16LE();
|
||||
f->seek(ptrFrame);
|
||||
newImage._deltaX = f->readUint16LE() << 1; // This member does not seem to be used in the actual game, and it is not clear whether it needs the << 1 or if that was fixed before release
|
||||
newImage._deltaY = f->readUint16LE();
|
||||
newImage._rectW = f->readUint16LE();
|
||||
newImage._rectH = f->readUint16LE();
|
||||
uint16 next = 0;
|
||||
for (int j = 0; j < newImage._rectH; j++) {
|
||||
next = f->readUint16LE();
|
||||
newImage._deltaPos.push_back(next);
|
||||
next = f->readUint16LE();
|
||||
newImage._scanWidth.push_back(next);
|
||||
Common::Array<byte> b;
|
||||
b.resize(newImage._scanWidth[j]);
|
||||
for (int k = 0; k < newImage._scanWidth[j]; k++) {
|
||||
b[k] = f->readByte();
|
||||
}
|
||||
newImage._bitmap.push_back(b);
|
||||
}
|
||||
images.push_back(newImage);
|
||||
}
|
||||
|
||||
d->_images = images;
|
||||
}
|
||||
|
||||
bool ImmortalEngine::clipSprite(uint16 &height, uint16 &pointIndex, uint16 &skipY, DataSprite *dSprite, uint16 &pointX, uint16 &pointY, int img, uint16 bmw, uint16 superTop, uint16 superBottom) {
|
||||
/* Something important to note here:
|
||||
* In the source, bmw is not *2, and pointX is /2. However, the source
|
||||
* was using a buffer of 2 pixels per byte. In ScummVM, the screen buffer
|
||||
* is 1 pixel per byte. This means some calculations are slightly different.
|
||||
*/
|
||||
|
||||
// This bit is to get the base index into the screen buffer, unless that's already been done, which is _lastPoint
|
||||
if ((pointY != _lastY) || (bmw != _lastBMW)) {
|
||||
_lastBMW = bmw;
|
||||
_lastY = pointY;
|
||||
if (pointY < kMaskNeg) {
|
||||
// For the Apple IIGS, pointY in pixels needed to be converted to bytes. For us, it's the other way around, we need bmw in pixels
|
||||
// This should probably use mult16() instead of *
|
||||
_lastPoint = pointY * (bmw);
|
||||
} else {
|
||||
// Screen wrapping?
|
||||
uint16 temp = (0 - pointY) + 1;
|
||||
_lastPoint = temp * bmw;
|
||||
_lastPoint = 0 - _lastPoint;
|
||||
}
|
||||
}
|
||||
|
||||
pointIndex = _lastPoint;
|
||||
|
||||
// Now we begin clipping, starting with totally offscreen
|
||||
if (pointY > superBottom) {
|
||||
return true;
|
||||
|
||||
} else if ((pointY + height) < superTop) {
|
||||
return true;
|
||||
|
||||
/* The actual clipping is pretty simple:
|
||||
* Lower height = stop drawing the sprite early. Higher SkipY = start drawing the sprite late
|
||||
* So we just determine the delta for each based on superTop and superBottom
|
||||
*/
|
||||
} else {
|
||||
|
||||
// Starting with checking if any of the sprite is under the bottom of the screen
|
||||
if ((pointY + height) >= superBottom) {
|
||||
height = superBottom - pointY;
|
||||
}
|
||||
|
||||
// Next we get the difference of overlap from the sprite if it is above the top
|
||||
if (uint16((superTop - pointY)) < kMaskNeg) {
|
||||
skipY = (superTop - pointY);
|
||||
}
|
||||
|
||||
// The image is clipped, time to move the index to the sprite's first scanline base position
|
||||
pointIndex += pointX;// + dSprite->_images[img]._rectW;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImmortalEngine::spriteAligned(DataSprite *dSprite, Image &img, uint16 &skipY, uint16 &pointIndex, uint16 &height, uint16 bmw, byte *dst) {
|
||||
/* This is an approximation of the sprite drawing system in the source.
|
||||
* It is an approximation because the source needed to do some things
|
||||
* that aren't relevant anymore, and it had some....creative solutions.
|
||||
* For example, transparency was handled with a 256 byte table of masks
|
||||
* that was indexed by the pixel itself, and used to find what nyble needed
|
||||
* to be masked. However we are using a slightly different kind of screen buffer,
|
||||
* and so I chose a more traditional method. Likewise, alignement was
|
||||
* relevant for the source, but is not relevant here, and the sprite drawing
|
||||
* is not accomplished by indexed a set of code blocks.
|
||||
*/
|
||||
byte pixel1 = 0;
|
||||
byte pixel2 = 0;
|
||||
|
||||
// For every scanline before height
|
||||
for (int y = 0; y < height; y++, pointIndex += (bmw)) {
|
||||
|
||||
// We increase the position by one screen width
|
||||
if (img._deltaPos[y] < kMaskNeg) {
|
||||
pointIndex += (img._deltaPos[y] * 2);
|
||||
}
|
||||
|
||||
// And if the delta X for the line is positive, we add it. If negative we subtract
|
||||
else {
|
||||
pointIndex -= ((0 - img._deltaPos[y]) * 2);
|
||||
}
|
||||
|
||||
// For every pixel in the scanline
|
||||
for (int x = 0; x < img._scanWidth[y]; x++, pointIndex += 2) {
|
||||
// SkipY defines the lines we don't draw because they are clipped
|
||||
if (y >= skipY) {
|
||||
|
||||
// For handling transparency, I chose to simply check if the pixel is 0,
|
||||
// as that is the transparent colour
|
||||
pixel1 = (img._bitmap[y][x] & kMask8High) >> 4;
|
||||
pixel2 = (img._bitmap[y][x] & kMask8Low);
|
||||
|
||||
if (pixel1 != 0) {
|
||||
_screenBuff[pointIndex] = pixel1;
|
||||
}
|
||||
|
||||
if (pixel2 != 0) {
|
||||
_screenBuff[pointIndex + 1] = pixel2;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImmortalEngine::superSprite(DataSprite *dSprite, uint16 pointX, uint16 pointY, int img, uint16 bmw, byte *dst, uint16 superTop, uint16 superBottom) {
|
||||
// Main sprite image construction routine
|
||||
|
||||
// For the Apple IIGS, the bmw is in bytes, but for us it needs to be the reverse, in pixels
|
||||
bmw <<= 1;
|
||||
|
||||
uint16 cenX = dSprite->_cenX;
|
||||
uint16 cenY = dSprite->_cenY;
|
||||
uint16 dY = dSprite->_images[img]._deltaY;
|
||||
uint16 height = dSprite->_images[img]._rectH;
|
||||
|
||||
uint16 skipY = 0;
|
||||
uint16 pointIndex = 0; // This is 'screen' and 'screen + 2' in the source
|
||||
|
||||
pointX -= cenX;
|
||||
pointY -= cenY;
|
||||
pointY += dY;
|
||||
|
||||
// Normally I would just make the return from clip be reversed, but the idea is that the return would be 'offscreen == true'
|
||||
if (!(clipSprite(height, pointIndex, skipY, dSprite, pointX, pointY, img, bmw, superTop, superBottom))) {
|
||||
|
||||
// Alignment was a factor in the assembly because it was essentially 2 pixels per byte. However ScummVM is 1 pixel per byte
|
||||
spriteAligned(dSprite, dSprite->_images[img], skipY, pointIndex, height, bmw, dst);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
359
engines/immortal/story.cpp
Normal file
359
engines/immortal/story.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* [Alternate Name: Level Subsystem/Script Data]
|
||||
* --- Story File ---
|
||||
* A story file (as defined in story.h) is a set of ROM
|
||||
* data that describes the properties of a level. This includes
|
||||
* the coordinates for each room, the doors in the level,
|
||||
* the torches, objects, monsters, etc. It also included the string
|
||||
* data in the source code, but technically it was writing those
|
||||
* strings to a separate string bank, so they weren't contiguous
|
||||
* with the story files (sometimes?). These story files are read in when loading
|
||||
* a new level, and are used to construct the room object, the monster
|
||||
* objects, and everything in the rooms.
|
||||
*/
|
||||
|
||||
/* These are the UNIVAT for each Story entry
|
||||
UNIVAT 1024,480, 1152, 464, \-1, -1, zip,level1Ladders, rooma, 704/64, 544/32\
|
||||
UNIVAT 304, 448, 472+32, 500+16, \-1, -1, zip,level12Ladders, -1, 0, 0\
|
||||
UNIVAT 600, 450, 560, 598, \-1, r2.b+(16*r2.a), zip,level3Ladders, r2.b, 640/64, 576/32\
|
||||
UNIVAT 120, 540, 188, 584, \-1, -1, zip,level4Ladders, -1, 0, 0\
|
||||
UNIVAT 64, 128, 128, 128+32, \-1, -1, zip,level5Ladders, -1, 1088/64, 928/32\
|
||||
UNIVAT 768, 224, 896, 288-16, \-1, -1, zip,level5Ladders, -1, 1088/64, 928/32\
|
||||
UNIVAT 896, 672+64, 960, 832-16, \-1, -1, zip,level6Ladders, -1, 0, 0\
|
||||
UNIVAT 688, 800, 912-64, 888-32, \-1, -1, zip,level7Ladders, -1, 1088/64, 928/32\
|
||||
UNIVAT 64, 704, 64+96, 704+64, \-1, -1, zip,level8Ladders, -1, 0, 0\
|
||||
*/
|
||||
|
||||
#include "immortal/immortal.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
void ImmortalEngine::initStoryStatic() {
|
||||
Common::Array<Common::String> s{"#" + Common::String(kSwordBigFrame) + "sword@",
|
||||
"You find an Elven sword of&agility. Take it?@",
|
||||
"Search the bones?%",
|
||||
"}The sword permanently endows you with Elven agility and quickness in combat.@",
|
||||
"}You notice something that looks wet and green under the pile. Search further?%",
|
||||
"#" + Common::String(kBagBigFrame) + " dust@",
|
||||
"}You find a bag containing Dust of Complaisance.&@",
|
||||
"}Drop the bait on the ground here?%",
|
||||
"}To use this dust, you throw it in the air. Do that here?%",
|
||||
"_}Don+t bother me, I+m cutting a gem. Yes, you need it. No, you can+t have it. I wouldn+t give it to anyone, least of all you. Go away. ]]]]=",
|
||||
"_}Let me help you. Please take this gem. No, really, I insist. Take it and go with my blessings. Good luck. ]]]]=",
|
||||
"#" + Common::String(kCarpetBigFrame) + "carpet@",
|
||||
"#" + Common::String(kBombBigFrame) + " bomb@",
|
||||
"A gas bomb that goblins&use to paralyze trolls.&@",
|
||||
"Take it?<>@",
|
||||
"%",
|
||||
" other@",
|
||||
"#" + Common::String(kKeyBigFrame) + " key@",
|
||||
"#" + Common::String(kKeyBigFrame) + " key@",
|
||||
"A key to a chest.&@",
|
||||
"The chest is open. Examine&contents?%",
|
||||
"Put it on?%",
|
||||
"Drop it?%",
|
||||
"It+s unlocked. Open it?%",
|
||||
"It+s locked but you have&the key. Open it?%",
|
||||
"It+s locked and you don+t&have the key.@",
|
||||
"The lock, triggered by a&complicated set of latches,&is unfamiliar to you.@",
|
||||
"#" + Common::String(kGoldBigFrame) + "$0 gold@",
|
||||
"You find $0 gold pieces.&&^#" + Common::String(kPileFrame) + "@",
|
||||
"@",
|
||||
"You can+t plant them on&stone tiles.@",
|
||||
"It+s locked but you are&able to unlock it with&the key.@",
|
||||
"_}The king is not dead, but the poison is taking effect. When he sees you, he attempts to speak:[(Give me water... the fountain... I give you... information... peace...+[Give him water?%",
|
||||
"_}You dont have any water to give him. He mumbles something. Then silence... You find a key on his body.]]]]=",
|
||||
"_}He mumbles something. Then silence... You find a key on his body.]]]]=",
|
||||
"_}I+ll tell you how to... next level... past slime... three jewels... slime... rock becomes... floor... right, left, center of the... [Then silence. His hand opens, releasing a key.]]]]=",
|
||||
"You find a door key.&@",
|
||||
"You find a note.&@",
|
||||
"#" + Common::String(kNoteBigFrame) + "note@",
|
||||
"He+s dead.&Look for possessions?%",
|
||||
"You don+t have it. Check&your inventory.@",
|
||||
"Game Over&&Play again?@",
|
||||
"Congratulations!&&Play again?@",
|
||||
"You find a bag of bait.&@",
|
||||
"#" + Common::String(kBagBigFrame) + " bait@",
|
||||
"You find a stone. @",
|
||||
"#" + Common::String(kStoneBigFrame) + " stone@",
|
||||
"You find a red gem.&@",
|
||||
"#" + Common::String(kGemBigFrame) + " gem@",
|
||||
"You find a scroll with&fireball spells.&@",
|
||||
"#" + Common::String(kScrollBigFrame) + "$ shots@",
|
||||
"You find a map warning&you about pit traps.&@",
|
||||
"#" + Common::String(kMapBigFrame) + " map@",
|
||||
"#" + Common::String(kVaseBigFrame) + " oil@",
|
||||
"You apply the oil but notice&as you walk that the leather&is drying out quickly.@",
|
||||
"}You discover a scroll with a charm spell to use on will o+ the wisps.&@",
|
||||
"#" + Common::String(kScrollBigFrame) + " charm@",
|
||||
"}This charms the will o+ the wisps to follow you. Read the spell again to turn them against your enemies.@",
|
||||
"}It looks like water. Drink it?%",
|
||||
"Drink it?%",
|
||||
"}It works! You are much stronger.]]]=",
|
||||
"}It looks like it has green stuff inside. Open it?%",
|
||||
"Now this will take&effect when you press the&fire button.@",
|
||||
"You find a potion,&Magic Muscle.&@",
|
||||
"#" + Common::String(kVaseBigFrame) + " potion@",
|
||||
"You find a bottle.&@",
|
||||
"#" + Common::String(kVaseBigFrame) + " bottle@",
|
||||
"#" + Common::String(kRingBigFrame) + "Protean@",
|
||||
"You find a Protean Ring.&@",
|
||||
"You find a troll ritual knife,&used to declare a fight to&the death. @",
|
||||
"#" + Common::String(kKnifeBigFrame) + " knife@",
|
||||
"_}It is a fine woman+s garment. Folded inside is a ring with the words,[`To Ana, so harm will never find you. -Your loving father, Dunric.+&@",
|
||||
"You find a small, well&crafted ring. @",
|
||||
"#" + Common::String(kRingBigFrame) + " gift@",
|
||||
"#" + Common::String(kRingBigFrame) + " Ana+s@",
|
||||
"_}She is hurt and upset when she finds you don+t have her ring or won+t give it to her. She scurries back into the hole. The hole is too small for you to follow.&@",
|
||||
"_}`Sir, can you help me,+ the girl pleads. `I was kidnapped and dragged down here. All the man would say is `Mordamir+s orders.+[I ~" + Common::String(kStrGive2),
|
||||
"escaped using a ring my father gave me, but now I+ve lost it. Did you find it?+%",
|
||||
"_}We have met before, old man. Do you remember? Because you helped me, you may pass. But I warn you, we are at war with the trolls.[Over this ladder, across the spikes, is troll territory. Very dangerous.@",
|
||||
"_}You are an impostor!]]]]=",
|
||||
"_}Old man, do you remember me? I am king of the goblins. You didn+t give me the water. You left me to die after you took the key from me. Now you will pay.]]]]=",
|
||||
"_}You quickly fall into a deep, healing sleep...[Vivid images of a beautiful enchanted city pass by. All the city people are young and glowing. Fountains fill the city, and the splash and ~" + Common::String(kStrDream1P2),
|
||||
"sparkle of water is everywhere...[Suddenly the images go black. A face appears... Mordamir!]][ ~" + Common::String(kStrDream1P3),
|
||||
"He is different from how you remember him. His gentle features are now withered. His kind eyes, now cold and sunken, seem to look through you with a dark, penetrating stare. You wake rejuvenated, but disturbed.]]]]]=",
|
||||
"_}Here, take this ring in return. [I don+t know if it will help, but I heard the unpleasant little dwarf say, (Clockwise, three rings around the triangle.+[Could that be a clue to his exit puzzle? I must go. Goodbye.]]]]=",
|
||||
"#" + Common::String(kSackBigFrame) + " spores@",
|
||||
"You find a sack of bad&smelling spores.&@",
|
||||
"Please insert play disk.@",
|
||||
"New game?%",
|
||||
"Enter certificate:&-=",
|
||||
"Invalid certificate.@",
|
||||
"End of level!&Here is your certificate:&&=",
|
||||
"&@",
|
||||
"\\ Electronic Arts presents&& The Immortal&&&& 1990 Will Harvey|]]]]]]]]\\]=",
|
||||
" written by&& Will Harvey& Ian Gooding& Michael Marcantel& Brett G. Durrett& Douglas Fulton|]]]]]]]/=",
|
||||
"_}Greetings, friend! Come, I+ve got something you need. These parts are plagued with slime.[You can+t venture safely without my slime oil for boots, a bargain at only 80 gold pieces.%",
|
||||
"_}All right, 60 gold pieces for my oil. Rub it on your boots and slime won+t touch you. 60, friend.%",
|
||||
"This room doesn+t resemble&any part of the map.@",
|
||||
"This room resembles part&of the map.@"};
|
||||
_strPtrs = s;
|
||||
|
||||
Common::Array<int> cyc0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, -1};
|
||||
Common::Array<int> cyc1{15, 16, 17, 18, 19, 20, 21, 22, -1};
|
||||
Common::Array<int> cyc2{0, 1, 2, -1};
|
||||
Common::Array<int> cyc3{3, 4, 5, -1};
|
||||
Common::Array<int> cyc4{6, 7, 8, 9, 10, -1};
|
||||
Common::Array<int> cyc5{11, 12, 13, 14, 15, -1};
|
||||
Common::Array<int> cyc6{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, -1};
|
||||
Common::Array<int> cyc7{0, 1, 2, 3, 4, -1};
|
||||
Common::Array<int> cyc8{5, 1 + 5, 2 + 5, 3 + 5, 4 + 5, -1};
|
||||
Common::Array<int> cyc9{10, 1 + 10, 2 + 10, 3 + 10, 4 + 10, -1};
|
||||
Common::Array<int> cyc10{15, 1 + 15, 2 + 15, 3 + 15, 4 + 15, -1};
|
||||
Common::Array<int> cyc11{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, -1};
|
||||
Common::Array<int> cyc12{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1};
|
||||
Common::Array<int> cyc13{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, -1};
|
||||
Common::Array<int> cyc14{31, 32, 33, 32, 34, 35, 36, 35, 37, 38, 39, 38, 40, 41, 42, 41, 43, 44, 45, 44, 46, 47, 48, 47, 49, 50, 51, 50, 52, 53, 54, 53, -1};
|
||||
Common::Array<int> cyc15{55, -1};
|
||||
Common::Array<int> cyc16{63, 64, 65, 66, 63, 64, 65, 66, 63, 64, 65, 66, 63, 64, 65, 66, 63, 64, 65, 66, 63, 64, 65, 66, 63, 64, 65, 66, 63, 64, 65, 66, -1};
|
||||
Common::Array<int> cyc17{0, 1, 0, -1};
|
||||
Common::Array<int> cyc18{0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 2, 1, -1};
|
||||
Common::Array<int> cyc19{0, 0, 1, 2, 13, 14, 15, 16, 4, 2, 3, -1};
|
||||
Common::Array<int> cyc20{0, 1, 2, 3, 20, 21, 22, 23, 24, 25, 26, 27, 5, 4, 3, -1};
|
||||
Common::Array<int> cyc21{0, 1, 2, 3, -1};
|
||||
Common::Array<int> cyc22{0, 17, 18, 19, 3, -1};
|
||||
Common::Array<int> cyc23{0, 1, -1};
|
||||
Common::Array<int> cyc24{28, 28, 28, 28, -1};
|
||||
Common::Array<int> cyc25{15, 16, 15, 16, 15, 1 + 15, 1 + 15, -1};
|
||||
Common::Array<int> cyc26{10 + 15, 11 + 15, 12 + 15, 13 + 15, 14 + 15, 15 + 15, 16 + 15, -1};
|
||||
Common::Array<int> cyc27{2 + 15, 3 + 15, 4 + 15, 5 + 15, -1};
|
||||
Common::Array<int> cyc28{6 + 15, 7 + 15, 8 + 15, 9 + 15, -1};
|
||||
Common::Array<int> cyc29{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, -1};
|
||||
Common::Array<int> cyc30{0, 1, 2, 3, 3, 3, 3, 4, 5, 6, -1};
|
||||
Common::Array<int> cyc31{0, 1, 2, 3, 4, 5, 6, 7, 8, -1};
|
||||
|
||||
Common::Array<SCycle> c{SCycle(kBubble, false, cyc0), SCycle(kBubble, false, cyc1),
|
||||
SCycle(kSpark, false, cyc2), SCycle(kSpark, false, cyc3),
|
||||
SCycle(kSpark, false, cyc4), SCycle(kSpark, false, cyc5), SCycle(kSpark, false, cyc6),
|
||||
SCycle(kPipe, false, cyc7), SCycle(kPipe, false, cyc8),
|
||||
SCycle(kPipe, false, cyc9), SCycle(kPipe, false, cyc10),
|
||||
SCycle(kAnaVanish, false, cyc11), SCycle(kAnaGlimpse, false, cyc12),
|
||||
SCycle(kKnife, true, cyc13),
|
||||
SCycle(kSpark, true, cyc14), SCycle(kSpark, true, cyc15), SCycle(kSpark, true, cyc16),
|
||||
SCycle(kBigBurst, false, cyc17),
|
||||
SCycle(kFlame, false, cyc18), SCycle(kFlame, false, cyc19), SCycle(kFlame, false, cyc20),
|
||||
SCycle(kFlame, false, cyc21), SCycle(kFlame, false, cyc22), SCycle(kFlame, false, cyc23),
|
||||
SCycle(kFlame, false, cyc24),
|
||||
SCycle(kCandle, false, cyc25), SCycle(kCandle, false, cyc26), SCycle(kCandle, false, cyc27),
|
||||
SCycle(kCandle, false, cyc28), SCycle(kCandle, false, cyc29),
|
||||
SCycle(kSink, false, cyc30),
|
||||
SCycle(kNorlacDown, false, cyc31)};
|
||||
_cycPtrs = c;
|
||||
|
||||
Common::Array<Motive> m{};
|
||||
_motivePtrs = m;
|
||||
|
||||
Common::Array<Damage> d{};
|
||||
_damagePtrs = d;
|
||||
|
||||
Common::Array<Use> u{};
|
||||
_usePtrs = u;
|
||||
|
||||
Common::Array<Pickup> p{};
|
||||
_pickupPtrs = p;
|
||||
|
||||
CArray2D<Motive> pr{};
|
||||
_programPtrs = pr;
|
||||
|
||||
Common::Array<ObjType> o{};
|
||||
_objTypePtrs = o;
|
||||
|
||||
}
|
||||
|
||||
void ImmortalEngine::initStoryDynamic() {
|
||||
/* There is one major difference between the source logic and this method.
|
||||
* It doesn't change the game logic, but it does change the logic of storing
|
||||
* the initial rom data. In the source, because there are no language based
|
||||
* arrays available (the array/qarray have overhead and are not designed for this),
|
||||
* the story entries are written out dynamically to ensure everything links together
|
||||
* (in quite a clever way, but does require a lot of untangling to see).
|
||||
* On the game end however, this means that to populate a level with it's objects,
|
||||
* rooms, etc. It has to look at every single entry individually, and check the 'recordop'.
|
||||
* This tells the game what kind of entry it is, and therefor which routine to call.
|
||||
* But, the catch is that making sure the right entry goes with the right room is tricky.
|
||||
* In certain cases, there are references to the rooms. In most however it relies on
|
||||
* INROOM, which is a macro that basically sets the dynamic variable keeping track of what
|
||||
* room the current entry is using for x/y coordinates. This doesn't serve any purpose
|
||||
* for us though, because we can use real arrays and structs for the stories, which is what
|
||||
* I believe the source would have used (though even the DOS version did it this way so
|
||||
* who knows). All of this to say, instead of INROOM, the equivlent here is basically
|
||||
* checking for nullptr within arrays that are always the size of the number of rooms.
|
||||
*/
|
||||
|
||||
// *NOTE* the data types Trap and Program will be in the static Story area, and referenced by an enum
|
||||
|
||||
const uint16 kZip = 5;
|
||||
|
||||
/*
|
||||
* ::: Level 0: Intro 1 :::
|
||||
*/
|
||||
|
||||
/* Universe related properties
|
||||
* including spawn point and entry/exit points
|
||||
*/
|
||||
int univRoom = 4; // The room the player starts in when beginning this level
|
||||
uint16 univRoomX = 512;
|
||||
uint16 univRoomY = 416;
|
||||
|
||||
_stories[0]._level = 0;
|
||||
_stories[0]._part = 1;
|
||||
_stories[0]._initialUnivX = 1024 / 8;
|
||||
_stories[0]._initialUnivY = 480 / 8;
|
||||
_stories[0]._playerPointX = (1152 - univRoomX) / 2;
|
||||
_stories[0]._playerPointY = 464 - univRoomY;
|
||||
|
||||
Common::Array<int> ladders{-1, -1, kStoryNull, 2, 0, univRoom, (704 / 64), (544 / 32)};
|
||||
_stories[0]._ladders = ladders;
|
||||
|
||||
/* All of the rooms
|
||||
*/
|
||||
Common::Array<SRoom> rooms{SRoom(384, 256, kRoomFlag0), SRoom(512, 64, kRoomFlag0),
|
||||
SRoom(640, 160, kRoomFlag0), SRoom(768, 224, kRoomFlag0),
|
||||
SRoom(univRoomX, univRoomY, kRoomFlag0), SRoom(960, 512, kRoomFlag0),
|
||||
SRoom(1024, 352, kRoomFlag0), SRoom(896, 64, kRoomFlag0)};
|
||||
_stories[0]._rooms = rooms;
|
||||
|
||||
/* All of the doors
|
||||
*/
|
||||
Common::Array<SDoor> doors{SDoor(0, 704, 224, 0, 2, false), SDoor(1, 576, 352, 4, 0, true),
|
||||
SDoor(1, 704, 96, 2, 1, false), SDoor(1, 960, 128, 7, 2, false),
|
||||
SDoor(1, 1088, 160, 3, 7, false), SDoor(1, 1088, 320, 6, 3, false),
|
||||
SDoor(1, 896, 416, 4, 3, false)};
|
||||
_stories[0]._doors = doors;
|
||||
|
||||
/* All of the flames
|
||||
* Macro for flames is (x - roomx), (y - roomy), pattern number
|
||||
*/
|
||||
Common::Array<SFlame> f5{SFlame(512 - 384, (240 + 32) - 256, kFlameOff), SFlame(672 - 384, (240 + 32) - 256, kFlameOff)};
|
||||
Common::Array<SFlame> f7{SFlame(576 - 384, (112 + 32) - 256, kFlameNormal), SFlame(576 - 384, (112 + 32) - 256, kFlameNormal),
|
||||
SFlame(928 - 384, (48 + 32) - 256, kFlameNormal)};
|
||||
Common::Array<SFlame> f8{SFlame(800 - 640, (144 + 32) - 160, kFlameNormal)};
|
||||
Common::Array<SFlame> f9{SFlame(768 - 768, (304 + 32) - 224, kFlameNormal), SFlame((928 - 768), (304 + 32) - 224, kFlameNormal),
|
||||
SFlame(1024 - 768, (240 + 32) - 224, kFlameNormal)};
|
||||
Common::Array<SFlame> fA{SFlame(672 - 512, (400 + 32) - 416, kFlameNormal), SFlame((800 - 64) - 512, (496 - 32) - 416, kFlameNormal),
|
||||
SFlame(576 - 512, (528 + 32) - 416, kFlameNormal)};
|
||||
Common::Array<SFlame> fD{SFlame(1024 - 960, (496 + 32) - 512, kFlameNormal)};
|
||||
Common::Array<SFlame> fE{SFlame(1184 - 1024, 432 - 352, kFlameCandle)};
|
||||
Common::Array<SFlame> fF{SFlame(1024 - 896, (144 + 32) - 64, kFlameNormal)};
|
||||
CArray2D<SFlame> flames{f5, f7, f8, f9, fA, fD, fE, fF};
|
||||
_stories[0]._flames = flames;
|
||||
|
||||
/* All of the objects
|
||||
* Macro for traps is arrowType,freq,#sinkTraps,#1(going toward 5),#3,#5,#7,#trapdoors
|
||||
*/
|
||||
Common::Array<uint8> noTraps{};
|
||||
Common::Array<uint8> o5Traps{0, 0x80, 0, 0, 0, 0, 0, 5};
|
||||
Common::Array<uint8> o7Traps{0, 0x80, 15, 5, 3, 0, 0, 0};
|
||||
Common::Array<uint8> o8Traps{0, 0x80, 0, 0, 0, 0, 0, 3};
|
||||
|
||||
Common::Array<SObj> noObj{};
|
||||
Common::Array<SObj> o5{SObj(kZip, kZip, kTypeTrap, kNoFrame, kObjIsRunning + kObjIsInvisible, o5Traps),
|
||||
SObj(459, 379, kTypeCoin, kRingFrame, kObjNone, noTraps),
|
||||
SObj(446, 327, kTypeWowCharm, kScrollFrame, kObjNone, noTraps)};
|
||||
Common::Array<SObj> o7{SObj(145, 138, kTypeTrap, kNoFrame, kObjIsRunning + kObjIsInvisible, o7Traps)};
|
||||
Common::Array<SObj> o8{SObj(kZip, kZip, kTypeTrap, kNoFrame, kObjIsRunning + kObjIsInvisible, o8Traps)};
|
||||
Common::Array<SObj> o9{SObj(1052, 309, kTypeDead, kDeadGoblinFrame, kObjIsChest + kObjIsOnGround, noTraps),
|
||||
SObj(kZip, kZip, kTypeFireBall, kScrollFrame, kObjUsesFireButton, noTraps),
|
||||
SObj(128, 464, kTypeDunRing, kRingFrame, 0, noTraps),
|
||||
SObj(837, 421, kTypeChest, kChest0Frame, kObjIsChest, noTraps),
|
||||
SObj(kZip, kZip, kTypeDeathMap, kScrollFrame, 0, noTraps),
|
||||
SObj(597, 457, kTypeWater, kVaseFrame, 0, noTraps),
|
||||
SObj(kZip, kZip, kTypeSpores, kSporesFrame, 0, noTraps),
|
||||
SObj(kZip, kZip, kTypeWormFood, kNoFrame, 0, noTraps),
|
||||
SObj(205, 158, kTypeChestKey, kKeyFrame, 0, noTraps)};
|
||||
Common::Array<SObj> oE{SObj(1184, 426, kTypePhant, kAltarFrame, 0, noTraps),
|
||||
SObj(145, 138, kTypeGold, kNoFrame, kObjIsRunning, noTraps),
|
||||
SObj(671, 461, kTypeHay, kNoFrame, kObjIsRunning + kObjIsInvisible, noTraps),
|
||||
SObj(780, 508, kTypeBeam, kNoFrame, kObjIsRunning + kObjIsInvisible, noTraps)};
|
||||
CArray2D<SObj> objects{o5, o7, o8, o9, noObj, noObj, oE, noObj};
|
||||
_stories[0]._objects = objects;
|
||||
|
||||
/* All of the monsters
|
||||
* A 'Program' is just an array of pointers to 'Motives'
|
||||
*/
|
||||
Common::Array<Motive> progShade{kMotiveRoomCombat, kMotiveShadeFind, kMotiveShadeLoose, kMotiveEngage, kMotiveUpdateGoal, kMotiveFollow, kMotiveShadeHesitate};
|
||||
Common::Array<Motive> progEasy{kMotiveEasyRoomCombat, kMotiveFind8, kMotiveLoose4, kMotiveEngage, kMotiveUpdateGoal, kMotiveFollow};
|
||||
Common::Array<Motive> progUlindor{kMotiveDefensiveCombat, kMotiveEngage, kMotiveUlinTalk, kMotiveGive, kMotiveUseUpMonster};
|
||||
Common::Array<Motive> progGoblin5{kMotiveAliveRoomCombat, kMotiveFindAlways, kMotiveLoose4, kMotiveEngage, kMotiveUpdateGoal, kMotiveFollow};
|
||||
Common::Array<Motive> progPlayer{kMotivePlayerCombat, kMotiveJoystick, kMotivePlayerDoor};
|
||||
Common::Array<Motive> progWill2{kMotiveRoomCombat, kMotivewaittalk2, kMotiveFindAlways, kMotiveGetDisturbed, kMotiveLoose32, kMotiveUpdateGoal, kMotiveIfNot1Skip1, kMotiveFollow, kMotiveEngage};
|
||||
|
||||
Common::Array<SMonster> noMonst{};
|
||||
Common::Array<SMonster> m5{SMonster(448, 344, 12, kMonstPlayer, kMonstA + kMonstIsEngage + kMonstIsTough, progShade, kShadow),
|
||||
SMonster(590, 381, 12, kMonstPlayer, kMonstA + kMonstIsEngage + kMonstIsTough, progShade, kShadow)};
|
||||
Common::Array<SMonster> m9{SMonster(1106, 258, 3, kMonstPlayer, kMonstA + kMonstIsEngage, progEasy, kGoblin0),
|
||||
SMonster(832, 364, 10, kMonstA, kMonstB + kMonstIsPoss, progUlindor, kUlindor3),
|
||||
SMonster(838, 370, 15, kMonstPlayer, kMonstA + kMonstIsEngage, progGoblin5, kGoblin7)};
|
||||
Common::Array<SMonster> mE{SMonster(1136, 464, 15, kMonstMonster, kMonstPlayer + kMonstIsEngage, progPlayer, kWizard0)};
|
||||
Common::Array<SMonster> mF{SMonster(1182, 116, 5, kMonstPlayer, kMonstA + kMonstIsEngage, progWill2, kGoblin5)};
|
||||
CArray2D<SMonster> monsters{m5, noMonst, noMonst, m9, noMonst, noMonst, mE, mF};
|
||||
_stories[0]._monsters = monsters;
|
||||
|
||||
/*
|
||||
* ::: Level 0: Intro 2 :::
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
259
engines/immortal/story.h
Normal file
259
engines/immortal/story.h
Normal file
@@ -0,0 +1,259 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Definitions are the enum for the set of global definitions in Story.GS
|
||||
#include "immortal/definitions.h"
|
||||
|
||||
// Sprite List is a list of all sprite definitions (could be included in definitions.h, but sprite_list.gs was a separate source file and is sprite specific)
|
||||
#include "immortal/sprite_list.h"
|
||||
|
||||
#ifndef IMMORTAL_STORY_H
|
||||
#define IMMORTAL_STORY_H
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
// These maximum numbers aren't really needed, because most of these are vectors and have .size()
|
||||
enum StoryMaxes {
|
||||
kMaxRooms = 16,
|
||||
kMaxDoors = 10,
|
||||
kMaxFlames = 32,
|
||||
kMaxFlamesInRoom = 5,
|
||||
kMaxObjects = 42,
|
||||
kMaxMonsters = 20,
|
||||
kMaxGenSprites = 6,
|
||||
kMaxCycles = 32
|
||||
};
|
||||
|
||||
// These are flags that are relevant to their specific story data structures
|
||||
enum RoomFlag : uint8 { // Generic properties available to each room
|
||||
kRoomFlag0 = 0x01,
|
||||
kRoomFlag1 = 0x02,
|
||||
kRoomFlag2 = 0x04,
|
||||
kRoomFlag3 = 0x08
|
||||
};
|
||||
|
||||
enum ObjFlag : uint8 { // Properties of the object essentially
|
||||
kObjUsesFireButton = 0x40,
|
||||
kObjIsInvisible = 0x20,
|
||||
kObjIsRunning = 0x10,
|
||||
kObjIsChest = 0x08,
|
||||
kObjIsOnGround = 0x04,
|
||||
kObjIsF1 = 0x02,
|
||||
kObjIsF2 = 0x01,
|
||||
kObjNone = 0x0
|
||||
};
|
||||
|
||||
enum IsA : uint8 { // To be completely honest, I'm not really sure what this is. It seems to be more object flags, but they act a little strangely
|
||||
kIsAF1 = 0x20,
|
||||
kIsAF2 = 0x40,
|
||||
kIsANone = 0x0,
|
||||
};
|
||||
|
||||
enum MonsterFlag : uint8 { // Mostly properties of the AI for a given monster, *including the player*
|
||||
kMonstIsNone = 0x00,
|
||||
kMonstIsTough = 0x10,
|
||||
kMonstIsDead = 0x20,
|
||||
kMonstIsPoss = 0x40,
|
||||
kMonstIsBaby = 0x40,
|
||||
kMonstIsEngage = 0x80,
|
||||
kMonstPlayer = 0x00,
|
||||
kMonstMonster = 0x01,
|
||||
kMonstAnybody = 0x02,
|
||||
kMonstNobody = 0x03,
|
||||
kMonstA = 0x04,
|
||||
kMonstB = 0x05,
|
||||
kMonstC = 0x06,
|
||||
kMonstD = 0x07
|
||||
};
|
||||
|
||||
// Flame pattern is used by the story data, in-room data, *and* the level based total flame data. So it needs to be in story.h to be used by immortal.h and room.h
|
||||
enum FPattern : uint8 { // This defines which Cyc animation it uses
|
||||
kFlameNormal,
|
||||
kFlameCandle,
|
||||
kFlameOff,
|
||||
kFlameGusty
|
||||
};
|
||||
|
||||
// Object Pickup defines how an object can be picked up by the player, with different functions
|
||||
enum SObjPickup {
|
||||
};
|
||||
|
||||
struct Pickup {
|
||||
int _param;
|
||||
// This will be a pointer to function
|
||||
};
|
||||
|
||||
// Damage is used by object types as well as enemy types
|
||||
enum SDamage {
|
||||
};
|
||||
|
||||
struct Damage {
|
||||
};
|
||||
|
||||
// Use defines the function and parameters for using an object
|
||||
enum SObjUse {
|
||||
};
|
||||
|
||||
struct Use {
|
||||
int _param;
|
||||
// This will be a pointer to function
|
||||
};
|
||||
|
||||
struct ObjType {
|
||||
Str _str = kStrNull;
|
||||
Str _desc = kStrNull;
|
||||
int _size = 0;
|
||||
Use _use;
|
||||
Use _run;
|
||||
Pickup _pickup;
|
||||
};
|
||||
|
||||
// Cycles define the animation of sprites within a level. There is a fixed total of cycles available, and they are not room dependant
|
||||
struct Cycle {
|
||||
int _index; // In source this is actually the position within the *instruction list*, but since cycle's are structs, it's just the index of frames now
|
||||
CycID _cycList;
|
||||
};
|
||||
|
||||
/* Strictly speaking, many of these structs (which were rom data written dynamically
|
||||
* with compiler macros) combine multiple properties into single bytes (ex. room uses
|
||||
* bits 0-2 of X to also hold the roomOP, and bits 0-2 of Y to hold flags). However
|
||||
* for the moment there's no need to replicate this particular bit of space saving.
|
||||
*/
|
||||
struct SCycle {
|
||||
SpriteName _sName;
|
||||
Common::Array<int> _frames;
|
||||
bool _repeat;
|
||||
SCycle() {}
|
||||
SCycle(SpriteName s, bool r, Common::Array<int> f) {
|
||||
_sName = s;
|
||||
_repeat = r;
|
||||
_frames = f;
|
||||
}
|
||||
};
|
||||
|
||||
struct SRoom {
|
||||
uint16 _x = 0;
|
||||
uint16 _y = 0;
|
||||
|
||||
RoomFlag _flags = kRoomFlag0;
|
||||
SRoom() {}
|
||||
SRoom(uint16 x, uint16 y, RoomFlag f) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
_flags = f;
|
||||
}
|
||||
};
|
||||
|
||||
struct SDoor {
|
||||
uint8 _dir = 0;
|
||||
uint16 _x = 0;
|
||||
uint16 _y = 0;
|
||||
|
||||
uint16 _fromRoom = 0;
|
||||
uint16 _toRoom = 0;
|
||||
|
||||
bool _isLocked = false;
|
||||
SDoor() {}
|
||||
SDoor(uint8 d, uint16 x, uint16 y, uint16 f, uint16 t, bool l) {
|
||||
_dir = d;
|
||||
_x = x;
|
||||
_y = y;
|
||||
|
||||
_fromRoom = f;
|
||||
_toRoom = t;
|
||||
|
||||
_isLocked = l;
|
||||
}
|
||||
};
|
||||
|
||||
struct SFlame {
|
||||
uint16 _x = 0;
|
||||
uint16 _y = 0;
|
||||
|
||||
FPattern _p = kFlameOff;
|
||||
SFlame() {}
|
||||
SFlame(uint16 x, uint16 y, FPattern p) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
_p = p;
|
||||
}
|
||||
};
|
||||
|
||||
struct SObj {
|
||||
uint16 _x = 0;
|
||||
uint16 _y = 0;
|
||||
uint8 _flags = 0;
|
||||
|
||||
SObjType _type = kTypeTrap;
|
||||
SpriteFrame _frame = kNoFrame;
|
||||
Common::Array<uint8> _traps;
|
||||
SObj() {}
|
||||
SObj(uint16 x, uint16 y, SObjType t, SpriteFrame s, uint8 f, Common::Array<uint8> traps) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
_type = t;
|
||||
_flags = f;
|
||||
_traps = traps;
|
||||
_frame = s;
|
||||
}
|
||||
};
|
||||
|
||||
struct SMonster {
|
||||
uint16 _x = 0;
|
||||
uint16 _y = 0;
|
||||
uint16 _hits = 0;
|
||||
uint8 _flags = 0;
|
||||
|
||||
MonsterFlag _madAt = kMonstIsNone;
|
||||
SpriteName _sprite = kCandle;
|
||||
Common::Array<Motive> _program;
|
||||
SMonster() {}
|
||||
SMonster(uint16 x, uint16 y, uint16 h, MonsterFlag m, uint8 f, Common::Array<Motive> p, SpriteName s) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
_hits = h;
|
||||
_madAt = m;
|
||||
_flags = f;
|
||||
_program = p;
|
||||
_sprite = s;
|
||||
}
|
||||
};
|
||||
|
||||
struct Story {
|
||||
int _level = 0;
|
||||
int _part = 1;
|
||||
|
||||
uint16 _initialUnivX = 0;
|
||||
uint16 _initialUnivY = 0;
|
||||
uint16 _playerPointX = 0;
|
||||
uint16 _playerPointY = 0;
|
||||
|
||||
Common::Array<int> _ladders;
|
||||
Common::Array<SRoom> _rooms;
|
||||
Common::Array<SDoor> _doors;
|
||||
CArray2D<SFlame> _flames;
|
||||
CArray2D<SObj> _objects;
|
||||
CArray2D<SMonster> _monsters;
|
||||
};
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
#endif
|
||||
31
engines/immortal/univ.cpp
Normal file
31
engines/immortal/univ.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/* 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 "immortal/room.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
void Room::univAddSprite(uint16 vX, uint16 vY, uint16 x, uint16 y, SpriteName s, int img, uint16 p) {
|
||||
//debug("%d %d %d", *_numSprites, n, img);
|
||||
//g_immortal->addSprite(vX, vY, s, img, x, y, p);
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
111
engines/immortal/utilities.cpp
Normal file
111
engines/immortal/utilities.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
/* 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 "immortal/utilities.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
/*
|
||||
*
|
||||
* ----- -----
|
||||
* ----- General Use -----
|
||||
* ----- -----
|
||||
*
|
||||
*/
|
||||
|
||||
void Utilities::delay(int j) { // Delay is measured in jiffies, which are 56.17ms
|
||||
g_system->delayMillis(j * 56);
|
||||
}
|
||||
|
||||
void Utilities::delay4(int j) { // Named in source quarterClock for some reason, 1/4 jiffies are 14.04ms
|
||||
g_system->delayMillis(j * 14);
|
||||
}
|
||||
|
||||
void Utilities::delay8(int j) { // 1/8 jiffies are 7.02ms
|
||||
g_system->delayMillis(j * 7);
|
||||
}
|
||||
|
||||
bool Utilities::inside(uint8 dist, uint8 cenX, uint8 cenY, uint8 pointX, uint8 pointY) {
|
||||
// you can't be within 0 distance of something
|
||||
if (dist == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we want the negative distance because this is a rectangle all the way around a point
|
||||
uint8 negDist = ((dist ^ 0xFF) + 1) + 1;
|
||||
|
||||
// First is the X, so we get delta X from the points
|
||||
uint8 dX = cenX - pointX;
|
||||
if (dX < 0x80) {
|
||||
// Our point is beyond the other point
|
||||
if (dX >= dist) {
|
||||
// And it is further than point + distance, so it's not useable
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (dX >= negDist) {
|
||||
// If the negative delta X is *greater* than the negative distance, that means we're not far *enough* in the X
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exact same system here but with the Y positions instead
|
||||
uint8 dY = cenY - pointY;
|
||||
if (dY < 0x80) {
|
||||
if (dY >= dist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (dY >= negDist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If all conditions are met, we are within the distance of the point
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utilities::insideRect(uint8 rectX, uint8 rectY, uint8 w, uint8 h, uint8 pointX, uint8 pointY) {
|
||||
/* Very simple comapred to inside, we simply check
|
||||
* first if width and height are >0, to make sure
|
||||
* the rectangle has a size, and then we see if
|
||||
* the point is between the point X,Y and the
|
||||
* point X,Y + the width,height of the rect.
|
||||
* This is done by grabbing the delta X,Y and
|
||||
* making sure it is not negative.
|
||||
*/
|
||||
|
||||
// The source specifically checks only for w *and* h being 0, so you could give it a rect with a width or height or 0, just not both
|
||||
if ((w == 0) && (h == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8 dX = pointX - rectX;
|
||||
uint8 dY = pointY - rectY;
|
||||
|
||||
if ((dX < 0x80) && (dX < w)) {
|
||||
if ((dY < 0x80) && (dY < h)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Immortal
|
||||
97
engines/immortal/utilities.h
Normal file
97
engines/immortal/utilities.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* 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 IMMORTAL_UTIL_H
|
||||
#define IMMORTAL_UTIL_H
|
||||
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Immortal {
|
||||
|
||||
// The source uses nK many times throughout, which seems to be a compiler macro for n * 1024, ie. Kb
|
||||
enum Kilobyte {
|
||||
k1K = 0x400, // 1024
|
||||
k2K = 0x800, // 2048
|
||||
k3K = 0xC00, // 3072
|
||||
k4K = 0x1000, // 4096
|
||||
k6K = 0x1800, // 6144
|
||||
k8K = 0x2000, // 8192
|
||||
k10K = 0x2800, // 10240
|
||||
k16K = 0x4000 // 16384
|
||||
};
|
||||
|
||||
enum BitMask16 : uint16 {
|
||||
kMaskLow = 0x00FF,
|
||||
kMaskHigh = 0xFF00,
|
||||
kMaskLast = 0xF000,
|
||||
kMaskFirst = 0x000F,
|
||||
kMaskHLow = 0x0F00,
|
||||
kMaskLHigh = 0x00F0,
|
||||
kMaskNeg = 0x8000,
|
||||
};
|
||||
|
||||
enum BitMask8 : uint8 {
|
||||
kMaskASCII = 0x7F, // The non-extended ASCII table uses 7 bits, this makes a couple of things easier
|
||||
kMask8High = 0xF0,
|
||||
kMask8Low = 0x0F
|
||||
};
|
||||
|
||||
enum ColourBitMask : uint16 {
|
||||
kMaskRed = 0x0F00,
|
||||
kMaskGreen = 0x00F0,
|
||||
kMaskBlue = 0x000F
|
||||
};
|
||||
|
||||
enum ChrMask : uint16 {
|
||||
kChr0 = 0x0000,
|
||||
kChrL = 0x0001,
|
||||
kChrR = 0xFFFF,
|
||||
kChrLD = 0x0002,
|
||||
kChrRD = 0xFFFE
|
||||
};
|
||||
|
||||
enum Screen { // These are constants that are used for defining screen related arrays
|
||||
kResH = 320,
|
||||
kResV = 200,
|
||||
kMaxSprites = 32, // Number of sprites allowed at once
|
||||
kViewPortCW = 256 / 64,
|
||||
kViewPortCH = 128 / kMaxSprites,
|
||||
kMaxDrawItems = kViewPortCH + 1 + kMaxSprites,
|
||||
kMaxSpriteAbove = 48, // Maximum sprite extents from center
|
||||
kMaxSpriteBelow = 16,
|
||||
kMaxSpriteLeft = 16,
|
||||
kMaxSpriteRight = 16
|
||||
};
|
||||
|
||||
namespace Utilities {
|
||||
|
||||
// Other
|
||||
void delay(int j); // Delay engine by j jiffies (from driver originally, but makes more sense grouped with misc)
|
||||
void delay4(int j); // || /4
|
||||
void delay8(int j); // || /8
|
||||
bool inside(uint8 dist, uint8 centX, uint8 centY, uint8 pointX, uint8 pointY);
|
||||
bool insideRect(uint8 rectX, uint8 rectY, uint8 w, uint8 h, uint8 pointX, uint8 pointY);
|
||||
|
||||
} // namespace Utilities
|
||||
|
||||
} // namespace Immortal
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user