Initial commit

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

View File

@@ -0,0 +1,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

View 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

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine immortal "The Immortal" no "" "" ""

View File

@@ -0,0 +1,3 @@
begin_section("Immortal");
add_person("Michael Hayman", "Quote58", "");
end_section();

120
engines/immortal/cycle.cpp Normal file
View File

@@ -0,0 +1,120 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* [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

View 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

View 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);

View 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

View 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
View 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

View 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

View 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

View 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
View File

@@ -0,0 +1,775 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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

File diff suppressed because it is too large Load Diff

156
engines/immortal/level.cpp Normal file
View 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
View 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

View 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

View 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
View 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

View 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
View 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
View 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

View 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

View 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
View 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
View 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
View 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

View 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

View 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